Render the progress bar for file receives

This commit is contained in:
Kovid Goyal 2021-11-11 16:32:06 +05:30
parent 09742af92b
commit a2533e9a46
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C

View File

@ -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: