From 162b0c686d167a3eb1dbc5e491eab6797b7c03b8 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 9 Jul 2020 20:04:50 +0200 Subject: Make /upload work in Flatpak MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When /upload isn’t given an argument, it will instead open a file chooser and block poezio until the user selected a file. This will make poezio timeout from all rooms until the user is done choosing a file, but I didn’t find a good way to integrate GLib’s main loop with asyncio for now, and this can be fixed in a latter commit. --- plugins/upload.py | 105 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 96 insertions(+), 9 deletions(-) diff --git a/plugins/upload.py b/plugins/upload.py index 6926c075..311c9987 100644 --- a/plugins/upload.py +++ b/plugins/upload.py @@ -9,11 +9,14 @@ This plugin adds a command to the chat tabs. .. glossary:: /upload - **Usage:** ``/upload `` + **Usage:** ``/upload [filename]`` Uploads the file to the preferred HTTP File Upload service (see XEP-0363) and fill the input with its URL. + If isn’t specified, use the FileChooser from + xdg-desktop-portal to ask the user which file to upload. + """ @@ -23,6 +26,7 @@ import asyncio import traceback from os.path import expanduser from glob import glob +from concurrent.futures import ThreadPoolExecutor from slixmpp.plugins.xep_0363.http_upload import FileTooBig, HTTPError, UploadServiceNotFound @@ -31,6 +35,12 @@ from poezio.core.structs import Completion from poezio.decorators import command_args_parser from poezio import tabs +try: + from gi.repository import Gio, GLib + from urllib.parse import urlparse, unquote + HAVE_GLIB = True +except ImportError: + HAVE_GLIB = False class Plugin(BasePlugin): dependencies = {'embed'} @@ -56,7 +66,13 @@ class Plugin(BasePlugin): short='Upload a file', completion=self.completion_filename) - async def upload(self, filename, encrypted=False) -> Optional[str]: + async def upload(self, filename: Optional[str], encrypted=False) -> Optional[str]: + if filename is None: + with ThreadPoolExecutor() as pool: + loop = asyncio.get_running_loop() + filename = await loop.run_in_executor(pool, self.open_file_xdg_desktop_portal) + if filename is None: + return None try: upload_file = self.core.xmpp['xep_0363'].upload_file if encrypted: @@ -75,18 +91,17 @@ class Plugin(BasePlugin): return None return url - async def send_upload(self, filename, tab, encrypted=False): + async def send_upload(self, filename: Optional[str], tab, encrypted=False): url = await self.upload(filename, encrypted) if url is not None: self.embed.embed_image_url(url, tab) - @command_args_parser.quoted(1) + @command_args_parser.quoted(0, 1) def command_upload(self, args): - if args is None: - self.core.command.help('upload') - return - filename, = args - filename = expanduser(filename) + if args: + filename = expanduser(args[0]) + else: + filename = None tab = self.api.current_tab() encrypted = self.core.xmpp['xep_0454'] and tab.e2e_encryption is not None asyncio.create_task(self.send_upload(filename, tab, encrypted)) @@ -96,3 +111,75 @@ class Plugin(BasePlugin): txt = expanduser(the_input.get_text()[8:]) files = glob(txt + '*') return Completion(the_input.auto_completion, files, quotify=False) + + def open_file_xdg_desktop_portal(self): + ''' + Use org.freedesktop.portal.FileChooser from xdg-desktop-portal to open a + file chooser dialog. + + This method uses GDBus from GLib, and specifically runs its mainloop which + will block the entirety of poezio until it is done, which might cause us to + drop from rooms and such if the user isn’t quick enough at choosing the + file… + + See https://flatpak.github.io/xdg-desktop-portal/portal-docs.html + ''' + if not HAVE_GLIB: + self.api.information('GLib or Gio not available.', 'Error') + return None + + def get_file(connection, + sender, + path, + interface, + signal, + params): + nonlocal return_path + # TODO: figure out how to raise an exception to the outside of the GLib + # loop. + if not isinstance(params, GLib.Variant): + loop.quit() + return + response_code, results = params.unpack() + if response_code != 0: + loop.quit() + return + uris = results['uris'] + if len(uris) != 1: + loop.quit() + return + parsed_uri = urlparse(uris[0]) + if parsed_uri.scheme != "file": + loop.quit() + return + return_path = unquote(parsed_uri.path) + loop.quit() + + return_path = None + proxy = Gio.DBusProxy.new_for_bus_sync(Gio.BusType.SESSION, + Gio.DBusProxyFlags.NONE, + None, + 'org.freedesktop.portal.Desktop', + '/org/freedesktop/portal/desktop', + 'org.freedesktop.portal.FileChooser', + None) + + try: + handle = proxy.OpenFile('(ssa{sv})', '', 'poezio', { + 'accept_label': GLib.Variant('s', '_Upload'), + }) + except GLib.Error as err: + self.api.information('Failed to query file selection portal: %s' % err, 'Error') + return None + conn = proxy.get_connection() + conn.signal_subscribe('org.freedesktop.portal.Desktop', + 'org.freedesktop.portal.Request', + 'Response', + handle, + None, + Gio.DBusSignalFlags.NO_MATCH_RULE, + get_file) + + loop = GLib.MainLoop() + loop.run() + return return_path -- cgit v1.2.3