dropship/dropship.py

203 lines
6.2 KiB
Python
Raw Normal View History

2020-07-22 16:55:56 +02:00
"""Magic-Wormhole bundled up inside a PyGtk GUI."""
import asyncio
2020-07-22 17:26:59 +02:00
import logging
import os
2020-07-22 19:25:53 +02:00
import subprocess
from pathlib import Path
2020-07-23 12:36:28 +02:00
from signal import SIGINT, SIGTERM
2020-07-22 16:55:56 +02:00
import asyncio_glib
2020-07-21 14:48:34 +02:00
import gi
gi.require_version("Gtk", "3.0")
2020-07-22 16:03:55 +02:00
gi.require_version("Gdk", "3.0")
2020-07-22 16:37:30 +02:00
from gi.repository import Gdk as gdk
from gi.repository import GLib as glib
from gi.repository import Gtk as gtk
2020-07-22 16:03:55 +02:00
2020-07-22 16:55:56 +02:00
asyncio.set_event_loop_policy(asyncio_glib.GLibEventLoopPolicy())
2020-07-22 17:26:59 +02:00
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
log = logging.getLogger("dropship")
2020-07-22 18:56:26 +02:00
loop = asyncio.get_event_loop()
2020-07-22 16:55:56 +02:00
2020-07-23 14:44:14 +02:00
AUTO_CLIP_COPY_SIZE = -1
2020-07-22 16:55:56 +02:00
2020-07-23 12:24:56 +02:00
class PendingTransfer:
"""A wormhole send waiting for a wormhole receive."""
def __init__(self, code):
"""Object initialisation."""
self.code = code
2020-07-22 16:55:56 +02:00
class DropShip:
"""Drag it, drop it, ship it."""
2020-07-21 14:48:34 +02:00
def __init__(self):
2020-07-23 12:24:50 +02:00
"""Object initialisation."""
2020-07-22 16:55:56 +02:00
self.GLADE_FILE = "dropship.glade"
self.CSS_FILE = "dropship.css"
2020-07-23 12:40:50 +02:00
self.DOWNLOAD_DIR = os.path.expanduser("~")
2020-07-23 14:44:14 +02:00
self.clipboard = gtk.Clipboard.get(gdk.SELECTION_CLIPBOARD)
2020-07-22 18:56:26 +02:00
self._running = loop.create_future()
2020-07-23 12:24:56 +02:00
self._pending = []
2020-07-22 18:56:26 +02:00
2020-07-22 16:55:56 +02:00
self.init_glade()
self.init_css()
self.init_ui_elements()
self.init_window()
2020-07-22 16:55:56 +02:00
def init_glade(self):
"""Initialise the GUI from Glade file."""
2020-07-22 16:37:30 +02:00
self.builder = gtk.Builder()
2020-07-22 16:55:56 +02:00
self.builder.add_from_file(self.GLADE_FILE)
2020-07-21 14:48:34 +02:00
self.builder.connect_signals(self)
2020-07-22 16:55:56 +02:00
def init_css(self):
"""Initialise CSS injection."""
self.screen = gdk.Screen.get_default()
self.provider = gtk.CssProvider()
self.provider.load_from_path(self.CSS_FILE)
2020-07-22 16:37:30 +02:00
gtk.StyleContext.add_provider_for_screen(
self.screen, self.provider, gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
2020-07-22 16:03:55 +02:00
)
def init_window(self):
"""Initialise the Main GUI window."""
self.main_window_id = "mainWindow"
self.window = self.builder.get_object(self.main_window_id)
2020-07-22 18:56:26 +02:00
self.window.connect("delete-event", self.on_quit)
self.window.show()
2020-07-21 14:48:34 +02:00
def init_ui_elements(self):
"""Initialize the UI elements."""
# Send UI
# Drag & Drop Box
self.files_to_send = ""
self.enforce_target = gtk.TargetEntry.new("text/uri-list", gtk.TargetFlags(4), 129)
self.drop_box = self.builder.get_object('dropBox')
self.drop_box.drag_dest_set(
2020-07-22 16:55:56 +02:00
gtk.DestDefaults.ALL, [self.enforce_target], gdk.DragAction.COPY
2020-07-22 16:03:55 +02:00
)
self.drop_box.connect("drag-data-received", self.on_drop)
self.drop_label = self.builder.get_object('dropLabel')
2020-07-21 17:37:09 +02:00
# File chooser
self.file_chooser = self.builder.get_object('filePicker')
self.file_chooser.add_buttons(
'Cancel', gtk.ResponseType.CANCEL, "Add", gtk.ResponseType.OK
)
# Receive UI
# Code entry box
self.recv_box = self.builder.get_object('receiveBoxCodeEntry')
2020-07-23 13:06:56 +02:00
self.recv_box.connect("activate", self.on_recv)
2020-07-22 16:55:56 +02:00
def on_drop(self, widget, drag_context, x, y, data, info, time):
2020-07-23 13:06:56 +02:00
"""Handler for file dropping."""
files = data.get_uris()
2020-07-22 16:03:55 +02:00
self.files_to_send = files
2020-07-22 19:00:00 +02:00
if len(files) == 1:
2020-07-22 19:25:53 +02:00
fpath = Path(files[0].replace("file://", ""))
self.schedule(self.wormhole_send(self, fpath))
2020-07-22 19:00:00 +02:00
self.drop_label.set_text("Sending..")
else:
log.info("Multiple file sending coming soon ™")
2020-07-21 17:37:09 +02:00
def add_files(self,widget, event):
"""Handler for adding files with system interface"""
response = self.file_chooser.run()
if response == gtk.ResponseType.OK:
self.schedule(self.wormhole_send(self, self.file_chooser.get_filenames()[0]))
elif response == gtk.ResponseType.CANCEL:
#TODO(roel) something isn't right here.. maybe we need to initialize it every time we run it.
print("Cancel clicked")
self.file_chooser.destroy()
2020-07-23 13:06:56 +02:00
def on_recv(self, entry):
"""Handler for receiving transfers."""
code = entry.get_text()
self.schedule(self.wormhole_recv(self, code))
2020-07-22 18:56:26 +02:00
def on_quit(self, *args, **kwargs):
2020-07-22 19:10:29 +02:00
"""Quit the program."""
2020-07-22 18:56:26 +02:00
self.window.close()
self._running.set_result(None)
2020-07-22 19:17:32 +02:00
def schedule(self, function):
2020-07-22 19:21:18 +02:00
"""Schedule a task on the event loop."""
2020-07-22 19:17:32 +02:00
loop.call_soon_threadsafe(asyncio.ensure_future, function)
2020-07-23 09:29:52 +02:00
async def read_lines(self, stream, pattern):
"""Read stdout from a command and match lines."""
# TODO(decentral1se): if pattern doesnt match, trapped forever
while True:
line = await stream.readline()
decoded = line.decode("utf-8").strip()
if pattern in decoded:
return decoded
2020-07-22 19:17:32 +02:00
async def wormhole_send(self, widget, fpath):
2020-07-22 19:10:38 +02:00
"""Run `wormhole send` on a local file path."""
2020-07-22 19:25:53 +02:00
process = await asyncio.create_subprocess_exec(
2020-07-23 09:29:52 +02:00
"wormhole",
"send",
2020-07-23 13:06:56 +02:00
"--hide-progress",
2020-07-23 09:29:52 +02:00
fpath,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
2020-07-22 19:25:53 +02:00
)
2020-07-23 09:53:58 +02:00
2020-07-23 09:29:52 +02:00
line = await self.read_lines(process.stderr, "wormhole receive")
code = line.split()[-1]
2020-07-23 14:44:14 +02:00
self.drop_label.set_selectable(True)
2020-07-23 09:53:58 +02:00
self.drop_label.set_text(code)
2020-07-22 19:25:53 +02:00
2020-07-23 14:44:14 +02:00
self.clipboard.set_text(code, AUTO_CLIP_COPY_SIZE)
2020-07-23 12:24:56 +02:00
self._pending.append(PendingTransfer(code))
2020-07-23 12:40:50 +02:00
await process.wait()
async def wormhole_recv(self, widget, code):
"""Run `wormhole receive` with a pending transfer code."""
process = await asyncio.create_subprocess_exec(
"wormhole",
"receive",
"--accept-file",
"--hide-progress",
code,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
2020-07-23 09:29:52 +02:00
await process.wait()
2020-07-22 19:10:38 +02:00
2020-07-21 14:48:34 +02:00
2020-07-22 16:55:56 +02:00
async def main():
"""The application entrypoint."""
2020-07-23 12:36:28 +02:00
try:
dropship = DropShip()
await dropship._running
except asyncio.CancelledError:
pass
2020-07-22 16:55:56 +02:00
2020-07-22 16:03:55 +02:00
if __name__ == "__main__":
2020-07-22 19:00:09 +02:00
try:
2020-07-23 12:36:28 +02:00
main_task = asyncio.ensure_future(main())
loop.add_signal_handler(SIGINT, main_task.cancel)
loop.add_signal_handler(SIGTERM, main_task.cancel)
loop.run_until_complete(main_task)
2020-07-22 19:00:09 +02:00
finally:
loop.close()