Render the progress bar for file receives
This commit is contained in:
parent
09742af92b
commit
a2533e9a46
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import posixpath
|
import posixpath
|
||||||
|
from asyncio import TimerHandle
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from enum import auto
|
from enum import auto
|
||||||
@ -11,7 +12,7 @@ from time import monotonic
|
|||||||
from typing import Deque, Dict, Iterator, List, Optional
|
from typing import Deque, Dict, Iterator, List, Optional
|
||||||
|
|
||||||
from kitty.cli_stub import TransferCLIOptions
|
from kitty.cli_stub import TransferCLIOptions
|
||||||
from kitty.fast_data_types import FILE_TRANSFER_CODE
|
from kitty.fast_data_types import FILE_TRANSFER_CODE, wcswidth
|
||||||
from kitty.file_transmission import (
|
from kitty.file_transmission import (
|
||||||
Action, Compression, FileTransmissionCommand, FileType, NameReprEnum,
|
Action, Compression, FileTransmissionCommand, FileType, NameReprEnum,
|
||||||
encode_bypass
|
encode_bypass
|
||||||
@ -22,9 +23,13 @@ from kitty.utils import sanitize_control_codes
|
|||||||
from ..tui.handler import Handler
|
from ..tui.handler import Handler
|
||||||
from ..tui.loop import Loop, debug
|
from ..tui.loop import Loop, debug
|
||||||
from ..tui.operations import styled, without_line_wrap
|
from ..tui.operations import styled, without_line_wrap
|
||||||
|
from ..tui.spinners import Spinner
|
||||||
from ..tui.utils import human_size
|
from ..tui.utils import human_size
|
||||||
from .send import Transfer
|
from .send import Transfer
|
||||||
from .utils import expand_home, random_id, should_be_compressed
|
from .utils import (
|
||||||
|
expand_home, random_id, render_progress_in_width, safe_divide,
|
||||||
|
should_be_compressed
|
||||||
|
)
|
||||||
|
|
||||||
debug
|
debug
|
||||||
file_counter = count(1)
|
file_counter = count(1)
|
||||||
@ -172,6 +177,7 @@ class ProgressTracker:
|
|||||||
self.transfered_stats_interval = 0.
|
self.transfered_stats_interval = 0.
|
||||||
self.started_at = 0.
|
self.started_at = 0.
|
||||||
self.signature_bytes = 0
|
self.signature_bytes = 0
|
||||||
|
self.done_files: List[File] = []
|
||||||
|
|
||||||
def change_active_file(self, nf: File) -> None:
|
def change_active_file(self, nf: File) -> None:
|
||||||
now = monotonic()
|
now = monotonic()
|
||||||
@ -183,7 +189,8 @@ class ProgressTracker:
|
|||||||
self.started_at = monotonic()
|
self.started_at = monotonic()
|
||||||
|
|
||||||
def file_written(self, af: File, amt: int, is_done: bool) -> None:
|
def file_written(self, af: File, amt: int, is_done: bool) -> None:
|
||||||
self.active_file = af
|
if self.active_file is not af:
|
||||||
|
self.change_active_file(af)
|
||||||
af.transmitted_bytes += amt
|
af.transmitted_bytes += amt
|
||||||
self.total_transferred += amt
|
self.total_transferred += amt
|
||||||
self.transfers.append(Transfer(amt))
|
self.transfers.append(Transfer(amt))
|
||||||
@ -194,6 +201,7 @@ class ProgressTracker:
|
|||||||
self.transfered_stats_amt = sum(t.amt for t in self.transfers)
|
self.transfered_stats_amt = sum(t.amt for t in self.transfers)
|
||||||
if is_done:
|
if is_done:
|
||||||
af.done_at = monotonic()
|
af.done_at = monotonic()
|
||||||
|
self.done_files.append(af)
|
||||||
|
|
||||||
|
|
||||||
class Manager:
|
class Manager:
|
||||||
@ -334,6 +342,10 @@ class Receive(Handler):
|
|||||||
self.quit_after_write_code: Optional[int] = None
|
self.quit_after_write_code: Optional[int] = None
|
||||||
self.check_paths_printed = False
|
self.check_paths_printed = False
|
||||||
self.transmit_started = False
|
self.transmit_started = False
|
||||||
|
self.max_name_length = 0
|
||||||
|
self.spinner = Spinner()
|
||||||
|
self.progress_update_call: Optional[TimerHandle] = None
|
||||||
|
self.progress_drawn = False
|
||||||
|
|
||||||
def send_payload(self, payload: str) -> None:
|
def send_payload(self, payload: str) -> None:
|
||||||
self.write(self.manager.prefix)
|
self.write(self.manager.prefix)
|
||||||
@ -382,10 +394,10 @@ class Receive(Handler):
|
|||||||
else:
|
else:
|
||||||
self.start_transfer()
|
self.start_transfer()
|
||||||
if self.manager.transfer_done:
|
if self.manager.transfer_done:
|
||||||
self.exit_after_completion()
|
self.quit_after_write_code = 0
|
||||||
|
self.refresh_progress()
|
||||||
def exit_after_completion(self) -> None:
|
elif self.transmit_started:
|
||||||
self.quit_loop(0)
|
self.refresh_progress()
|
||||||
|
|
||||||
def confirm_paths(self) -> None:
|
def confirm_paths(self) -> None:
|
||||||
self.print_check_paths()
|
self.print_check_paths()
|
||||||
@ -438,6 +450,8 @@ class Receive(Handler):
|
|||||||
self.print(f'Queueing transfer of {len(self.manager.files)} files(s)')
|
self.print(f'Queueing transfer of {len(self.manager.files)} files(s)')
|
||||||
for x in self.manager.request_files():
|
for x in self.manager.request_files():
|
||||||
self.send_payload(x)
|
self.send_payload(x)
|
||||||
|
names = (f.display_name for f in self.manager.files)
|
||||||
|
self.max_name_length = max(6, max(map(wcswidth, names)))
|
||||||
|
|
||||||
def print_err(self, msg: str) -> None:
|
def print_err(self, msg: str) -> None:
|
||||||
self.cmd.styled(msg, fg='red')
|
self.cmd.styled(msg, fg='red')
|
||||||
@ -463,12 +477,89 @@ class Receive(Handler):
|
|||||||
self.manager.state = State.canceled
|
self.manager.state = State.canceled
|
||||||
self.asyncio_loop.call_later(delay, self.quit_loop, 1)
|
self.asyncio_loop.call_later(delay, self.quit_loop, 1)
|
||||||
|
|
||||||
|
def render_progress(
|
||||||
|
self, name: str, spinner_char: str = ' ', bytes_so_far: int = 0, total_bytes: int = 0,
|
||||||
|
secs_so_far: float = 0., bytes_per_sec: float = 0., is_complete: bool = False
|
||||||
|
) -> None:
|
||||||
|
if is_complete:
|
||||||
|
bytes_so_far = total_bytes
|
||||||
|
self.write(render_progress_in_width(
|
||||||
|
name, width=self.screen_size.cols, max_path_length=self.max_name_length, spinner_char=spinner_char,
|
||||||
|
bytes_so_far=bytes_so_far, total_bytes=total_bytes, secs_so_far=secs_so_far,
|
||||||
|
bytes_per_sec=bytes_per_sec, is_complete=is_complete
|
||||||
|
))
|
||||||
|
|
||||||
|
def draw_progress_for_current_file(self, af: File, spinner_char: str = ' ', is_complete: bool = False) -> None:
|
||||||
|
p = self.manager.progress_tracker
|
||||||
|
now = monotonic()
|
||||||
|
self.render_progress(
|
||||||
|
af.display_name, spinner_char=spinner_char, is_complete=is_complete,
|
||||||
|
bytes_so_far=af.transmitted_bytes, total_bytes=af.expected_size,
|
||||||
|
secs_so_far=(af.done_at or now) - af.transmit_started_at,
|
||||||
|
bytes_per_sec=safe_divide(p.transfered_stats_amt, p.transfered_stats_interval)
|
||||||
|
)
|
||||||
|
|
||||||
|
def erase_progress(self) -> None:
|
||||||
|
if self.progress_drawn:
|
||||||
|
self.cmd.move_cursor_by(2, 'up')
|
||||||
|
self.write('\r')
|
||||||
|
self.cmd.clear_to_end_of_screen()
|
||||||
|
self.progress_drawn = False
|
||||||
|
|
||||||
|
def refresh_progress(self) -> None:
|
||||||
|
self.erase_progress()
|
||||||
|
self.draw_progress()
|
||||||
|
|
||||||
|
def schedule_progress_update(self, delay: float = 0.1) -> None:
|
||||||
|
if self.progress_update_call is None:
|
||||||
|
self.progress_update_call = self.asyncio_loop.call_later(delay, self.refresh_progress)
|
||||||
|
elif self.asyncio_loop.time() + delay < self.progress_update_call.when():
|
||||||
|
self.progress_update_call.cancel()
|
||||||
|
self.progress_update_call = self.asyncio_loop.call_later(delay, self.refresh_progress)
|
||||||
|
|
||||||
@Handler.atomic_update
|
@Handler.atomic_update
|
||||||
def draw_progress(self) -> None:
|
def draw_progress(self) -> None:
|
||||||
if self.manager.state is State.canceled:
|
if self.manager.state is State.canceled:
|
||||||
return
|
return
|
||||||
with without_line_wrap(self.write):
|
with without_line_wrap(self.write):
|
||||||
pass
|
for df in self.manager.progress_tracker.done_files:
|
||||||
|
sc = styled('✔', fg='green')
|
||||||
|
if df.ftype is FileType.regular:
|
||||||
|
self.draw_progress_for_current_file(df, spinner_char=sc, is_complete=True)
|
||||||
|
else:
|
||||||
|
self.write(f'{sc} {df.display_name} {styled(df.ftype.name, dim=True, italic=True)}')
|
||||||
|
self.print()
|
||||||
|
del self.manager.progress_tracker.done_files[:]
|
||||||
|
is_complete = self.quit_after_write_code is not None
|
||||||
|
if is_complete:
|
||||||
|
sc = styled('✔', fg='green') if self.quit_after_write_code == 0 else styled('✘', fg='red')
|
||||||
|
else:
|
||||||
|
sc = self.spinner()
|
||||||
|
p = self.manager.progress_tracker
|
||||||
|
now = monotonic()
|
||||||
|
if is_complete:
|
||||||
|
self.cmd.repeat('─', self.screen_size.width)
|
||||||
|
else:
|
||||||
|
af = p.active_file
|
||||||
|
if af is not None:
|
||||||
|
self.draw_progress_for_current_file(af, spinner_char=sc)
|
||||||
|
self.print()
|
||||||
|
if p.total_transferred > 0:
|
||||||
|
self.render_progress(
|
||||||
|
'Total', spinner_char=sc,
|
||||||
|
bytes_so_far=p.total_transferred, total_bytes=p.total_bytes_to_transfer,
|
||||||
|
secs_so_far=now - p.started_at, is_complete=is_complete,
|
||||||
|
bytes_per_sec=safe_divide(p.transfered_stats_amt, p.transfered_stats_interval)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.print('File data transfer has not yet started', end='')
|
||||||
|
self.print()
|
||||||
|
self.schedule_progress_update(self.spinner.interval)
|
||||||
|
self.progress_drawn = True
|
||||||
|
|
||||||
|
def on_writing_finished(self) -> None:
|
||||||
|
if self.quit_after_write_code is not None:
|
||||||
|
self.quit_loop(self.quit_after_write_code)
|
||||||
|
|
||||||
|
|
||||||
def receive_main(cli_opts: TransferCLIOptions, args: List[str]) -> None:
|
def receive_main(cli_opts: TransferCLIOptions, args: List[str]) -> None:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user