From 86f81a6122fd9fc141d89121fb9d42a41ecaee5b Mon Sep 17 00:00:00 2001 From: Luke Murphy Date: Tue, 13 Oct 2020 12:23:53 +0200 Subject: [PATCH] Workable pending transfer logic implementation! With logging. --- dropship/constant.py | 7 ++++++ dropship/dropship.py | 51 ++++++++++++++++++++++------------------ dropship/transfer.py | 35 +++++++++++++++++++++++++++ dropship/ui_templates.py | 46 ++++++++++++++++++++++-------------- dropship/wormhole.py | 37 +++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 41 deletions(-) create mode 100644 dropship/constant.py create mode 100644 dropship/transfer.py create mode 100644 dropship/wormhole.py diff --git a/dropship/constant.py b/dropship/constant.py new file mode 100644 index 0000000..b98a4af --- /dev/null +++ b/dropship/constant.py @@ -0,0 +1,7 @@ +from pathlib import Path + +_CWD = Path(__file__).absolute().parent + +UI_DIR = f"{_CWD}/ui" + +DEFAULT_DROP_LABEL = "Drag a file to send" diff --git a/dropship/dropship.py b/dropship/dropship.py index f69bafd..9ad2860 100644 --- a/dropship/dropship.py +++ b/dropship/dropship.py @@ -1,9 +1,8 @@ from os.path import basename from pathlib import Path -from subprocess import PIPE from gi import require_version -from trio import open_process, run_process +from trio import CancelScope, open_process, run_process from dropship import log @@ -12,9 +11,11 @@ require_version("Gdk", "3.0") from gi.repository import Gdk, GLib, Gtk +from dropship import log +from dropship.constant import UI_DIR +from dropship.transfer import PendingTransfer from dropship.ui_templates import PendingTransferRow - -CWD = Path(__file__).absolute().parent +from dropship.wormhole import wormhole_recv, wormhole_send class DropShip: @@ -22,8 +23,8 @@ class DropShip: def __init__(self, nursery): """Object initialisation.""" - self.GLADE_FILE = f"{CWD}/ui/dropship.ui" - self.CSS_FILE = f"{CWD}/ui/dropship.css" + self.GLADE_FILE = f"{UI_DIR}/dropship.ui" + self.CSS_FILE = f"{UI_DIR}/dropship.css" self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) self.nursery = nursery @@ -95,20 +96,20 @@ class DropShip: self.files_to_send = files if len(files) == 1: fpath = files[0].replace("file://", "") - self.nursery.start_soon(self.wormhole_send, fpath) + self.nursery.start_soon(self.send, fpath) else: log.info("Multiple file sending coming soon ™") def on_recv(self, entry): """Handler for receiving transfers.""" - self.nursery.start_soon(self.wormhole_recv, entry.get_text()) + self.nursery.start_soon(self.receive, entry.get_text()) def add_files(self, widget, event): """Handler for adding files with system interface""" response = self.file_chooser.run() if response == Gtk.ResponseType.OK: fpath = self.file_chooser.get_filenames()[0] - self.nursery.start_soon(self.wormhole_send, fpath) + self.nursery.start_soon(self.send, fpath) self.file_chooser.hide() def _send_spinner_on(self): @@ -128,23 +129,27 @@ class DropShip: self.drop_spinner.set_vexpand(False) self.drop_spinner.set_visible(False) - def _create_pending_transfer(self, fpath, code): + def _create_pending_transfer(self, fpath, code, scope): """Create a new pending transfer.""" - pending = PendingTransferRow(basename(fpath), code) - self.pending_transfers_list.insert(pending, -1) + transfer = PendingTransfer(fpath, code, scope) + template = PendingTransferRow(transfer, self) + self.pending_transfers_list.insert(template, -1) + + def _remove_pending_transfer(self, code): + """Remove pending transfer.""" + for pending_transfer in self.pending_transfers_list: + if pending_transfer.code == code: + pending_transfer.cancel() - async def wormhole_send(self, fpath): - """Run `wormhole send` on a local file path.""" + async def send(self, fpath): self._send_spinner_on() - process = await open_process(["wormhole", "send", fpath], stderr=PIPE) - output = await process.stderr.receive_some() - code = output.decode().split()[-1] - self._create_pending_transfer(fpath, code) + code, scope = await self.nursery.start(wormhole_send, fpath) + self._create_pending_transfer(fpath, code, scope) self.clipboard.set_text(code, -1) self._send_spinner_off(code) - await process.wait() + log.info(f"send: successfully initiated transfer send ({code})") - async def wormhole_recv(self, code): - """Run `wormhole receive` with a pending transfer code.""" - command = ["wormhole", "receive", "--accept-file", code] - await run_process(command, stderr=PIPE) + async def receive(self, code): + await self.nursery.start(wormhole_recv, code) + self._remove_pending_transfer(code) + log.info(f"receive: successfully received transfer ({code})") diff --git a/dropship/transfer.py b/dropship/transfer.py new file mode 100644 index 0000000..a782f0a --- /dev/null +++ b/dropship/transfer.py @@ -0,0 +1,35 @@ +from os.path import basename, getsize + + +class PendingTransfer: + """A pending transfer context and config.""" + + def __init__(self, fpath, code, scope): + self.fpath = fpath + self.code = code + self.scope = scope + + @property + def fname(self): + """Filename being transferred.""" + return basename(self.fpath) + + @property + def size(self): + """Filename size.""" + return self._human_readable_size(getsize(self.fpath)) + + def cancel(self): + """Cancel the transfer.""" + self.scope.cancel() + + def _human_readable_size(self, num, suffix="B"): + """Convert file size to human readable format. + + Thanks Fred. See https://stackoverflow.com/a/1094933. + """ + for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]: + if abs(num) < 1024.0: + return "%3.1f%s%s" % (num, unit, suffix) + num /= 1024.0 + return "%.1f%s%s" % (num, "Yi", suffix) diff --git a/dropship/ui_templates.py b/dropship/ui_templates.py index 1c93ec1..f9e1579 100644 --- a/dropship/ui_templates.py +++ b/dropship/ui_templates.py @@ -6,41 +6,51 @@ gi.require_version("Gtk", "3.0") from gi.repository import Gdk, GLib, Gtk -CWD = Path(__file__).absolute().parent +from dropship import log +from dropship.constant import DEFAULT_DROP_LABEL, UI_DIR -@Gtk.Template.from_file(f"{CWD}/ui/pendingTransferRow.ui") +@Gtk.Template.from_file(f"{UI_DIR}/pendingTransferRow.ui") class PendingTransferRow(Gtk.ListBoxRow): - # Note(decentral1se): must match ui/pendingTransferRow.ui name __gtype_name__ = "pendingTransferRow" fileNameLabel = Gtk.Template.Child() fileNameMetadata = Gtk.Template.Child() transferCodeButton = Gtk.Template.Child() cancelTransfer = Gtk.Template.Child() + pendingTransferRow = Gtk.Template.Child() - def __init__(self, fileName, transferCode, *args, **kwargs): + def __init__(self, transfer, parent, *args, **kwargs): Gtk.ListBoxRow.__init__(self, *args, **kwargs) - self.fileNameLabel.set_text(fileName) - self.transferCodeButton.set_label(transferCode) + self.transfer = transfer + self.parent = parent + self.fileNameLabel.set_text(self.transfer.fname) + self.fileNameMetadata.set_text(self.transfer.size) + self.transferCodeButton.set_label(self.transfer.code) + + @property + def code(self): + """Retrieve code of underyling transfer.""" + return self.transfer.code @Gtk.Template.Callback("copy_transfer_code") def copy_transfer_code(self, widget): - """ - what to do when we press the button: - copy the code again to clipboard - """ - print("click") + """Copy transfer code to clipboard.""" code = widget.get_label() - clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) clipboard.set_text(code, -1) # -1 is auto-size @Gtk.Template.Callback("cancel_transfer") def cancel_transfer(self, widget): - """ - cancel the transfer - destroy thread - remove the object from the list - """ - print("poof!") + """Callback entrypoint for transfer cancellation.""" + self.cancel() + + def cancel(self): + """Internal programmatic API for transfer cancellation.""" + self.transfer.cancel() + self.pendingTransferRow.destroy() + self.parent.drop_label.set_text(DEFAULT_DROP_LABEL) + self.parent.drop_label.set_selectable(False) + log.info( + f"PendingTransferRow: successfully cancelled transfer ({self.transfer.code})" + ) diff --git a/dropship/wormhole.py b/dropship/wormhole.py new file mode 100644 index 0000000..5c0d4f2 --- /dev/null +++ b/dropship/wormhole.py @@ -0,0 +1,37 @@ +from subprocess import PIPE + +from trio import TASK_STATUS_IGNORED, CancelScope, open_process, run_process + +from dropship import log + + +async def wormhole_send(fpath, task_status=TASK_STATUS_IGNORED): + """Run `wormhole send` on a local file path.""" + with CancelScope() as scope: + command = ["wormhole", "send", fpath] + process = await open_process(command, stderr=PIPE) + output = await process.stderr.receive_some() + code = output.decode().split()[-1] + task_status.started((code, scope,)) + log.info(f"wormhole_send: now waiting for other side ({code})") + await process.wait() + log.info(f"wormhole_send: succesfully transfered ({code})") + + if scope.cancel_called: + process.terminate() + log.info(f"wormhole_send: succesfully terminated process ({code})") + + +async def wormhole_recv(code, task_status=TASK_STATUS_IGNORED): + """Run `wormhole receive` on a pending transfer code.""" + with CancelScope() as scope: + command = ["wormhole", "receive", "--accept-file", code] + process = await open_process(command, stderr=PIPE) + task_status.started((scope,)) + log.info(f"wormhole_recv: now starting receiving process ({code})") + await process.wait() + log.info(f"wormhole_recv: succesfully received ({code})") + + if scope.cancel_called: + process.terminate() + log.info(f"wormhole_recv: succesfully terminated process ({code})")