Mercurial > libervia-backend
diff sat_frontends/jp/cmd_file.py @ 3040:fee60f17ebac
jp: jp asyncio port:
/!\ this commit is huge. Jp is temporarily not working with `dbus` bridge /!\
This patch implements the port of jp to asyncio, so it is now correctly using the bridge
asynchronously, and it can be used with bridges like `pb`. This also simplify the code,
notably for things which were previously implemented with many callbacks (like pagination
with RSM).
During the process, some behaviours have been modified/fixed, in jp and backends, check
diff for details.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 25 Sep 2019 08:56:41 +0200 |
parents | ab2696e34d29 |
children | e189ceca7e8b |
line wrap: on
line diff
--- a/sat_frontends/jp/cmd_file.py Wed Sep 25 08:53:38 2019 +0200 +++ b/sat_frontends/jp/cmd_file.py Wed Sep 25 08:56:41 2019 +0200 @@ -31,7 +31,6 @@ from sat.tools.common.ansi import ANSI as A import tempfile import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI -from functools import partial import json __commands__ = ["File"] @@ -46,7 +45,6 @@ use_verbose=True, help=_("send a file to a contact"), ) - self.need_loop = True def add_parser_options(self): self.parser.add_argument( @@ -75,23 +73,19 @@ help=("name to use (DEFAULT: use source file name)"), ) - def start(self): - """Send files to jabber contact""" - self.send_files() - - def onProgressStarted(self, metadata): + async def onProgressStarted(self, metadata): self.disp(_("File copy started"), 2) - def onProgressFinished(self, metadata): + async def onProgressFinished(self, metadata): self.disp(_("File sent successfully"), 2) - def onProgressError(self, error_msg): + async def onProgressError(self, error_msg): if error_msg == C.PROGRESS_ERROR_DECLINED: self.disp(_("The file has been refused by your contact")) else: self.disp(_("Error while sending file: {}").format(error_msg), error=True) - def gotId(self, data, file_): + async def gotId(self, data, file_): """Called when a progress id has been received @param pid(unicode): progress id @@ -100,7 +94,7 @@ # FIXME: this show progress only for last progress_id self.disp(_("File request sent to {jid}".format(jid=self.full_dest_jid)), 1) try: - self.progress_id = data["progress"] + await self.set_progress_id(data["progress"]) except KeyError: # TODO: if 'xmlui' key is present, manage xmlui message display self.disp( @@ -108,27 +102,18 @@ ) self.host.quit(2) - def error(self, failure): - self.disp( - _("Error while trying to send a file: {reason}").format(reason=failure), - error=True, - ) - self.host.quit(1) - - def send_files(self): + async def start(self): for file_ in self.args.files: if not os.path.exists(file_): - self.disp(_("file [{}] doesn't exist !").format(file_), error=True) - self.host.quit(1) + self.disp(_(f"file {file_!r} doesn't exist!"), error=True) + self.host.quit(C.EXIT_BAD_ARG) if not self.args.bz2 and os.path.isdir(file_): self.disp( - _( - "[{}] is a dir ! Please send files inside or use compression" - ).format(file_) + _(f"{file_!r} is a dir! Please send files inside or use compression") ) - self.host.quit(1) + self.host.quit(C.EXIT_BAD_ARG) - self.full_dest_jid = self.host.get_full_jid(self.args.jid) + self.full_dest_jid = await self.host.get_full_jid(self.args.jid) extra = {} if self.args.path: extra["path"] = self.args.path @@ -152,29 +137,37 @@ bz2.close() self.disp(_("Done !"), 1) - self.host.bridge.fileSend( - self.full_dest_jid, - buf.name, - self.args.name or archive_name, - "", - extra, - self.profile, - callback=lambda pid, file_=buf.name: self.gotId(pid, file_), - errback=self.error, - ) + try: + send_data = await self.host.bridge.fileSend( + self.full_dest_jid, + buf.name, + self.args.name or archive_name, + "", + extra, + self.profile, + ) + except Exception as e: + self.disp(f"can't send file: {e}", error=True) + self.host.quit(C.EXIT_BRIDGE_ERRBACK) + else: + await self.gotId(send_data, file_) else: for file_ in self.args.files: path = os.path.abspath(file_) - self.host.bridge.fileSend( - self.full_dest_jid, - path, - self.args.name, - "", - extra, - self.profile, - callback=lambda pid, file_=file_: self.gotId(pid, file_), - errback=self.error, - ) + try: + send_data = await self.host.bridge.fileSend( + self.full_dest_jid, + path, + self.args.name, + "", + extra, + self.profile, + ) + except Exception as e: + self.disp(f"can't send file {file_!r}: {e}", error=True) + self.host.quit(C.EXIT_BRIDGE_ERRBACK) + else: + await self.gotId(send_data, file_) class Request(base.CommandBase): @@ -186,7 +179,6 @@ use_verbose=True, help=_("request a file from a contact"), ) - self.need_loop = True @property def filename(self): @@ -200,7 +192,8 @@ "-D", "--dest", help=_( - "destination path where the file will be saved (default: [current_dir]/[name|hash])" + "destination path where the file will be saved (default: " + "[current_dir]/[name|hash])" ), ) self.parser.add_argument( @@ -238,36 +231,21 @@ help=_("overwrite existing file without confirmation"), ) - def onProgressStarted(self, metadata): + async def onProgressStarted(self, metadata): self.disp(_("File copy started"), 2) - def onProgressFinished(self, metadata): + async def onProgressFinished(self, metadata): self.disp(_("File received successfully"), 2) - def onProgressError(self, error_msg): + async def onProgressError(self, error_msg): if error_msg == C.PROGRESS_ERROR_DECLINED: self.disp(_("The file request has been refused")) else: self.disp(_("Error while requesting file: {}").format(error_msg), error=True) - def gotId(self, progress_id): - """Called when a progress id has been received - - @param progress_id(unicode): progress id - """ - self.progress_id = progress_id - - def error(self, failure): - self.disp( - _("Error while trying to send a file: {reason}").format(reason=failure), - error=True, - ) - self.host.quit(1) - - def start(self): + async def start(self): if not self.args.name and not self.args.hash: self.parser.error(_("at least one of --name or --hash must be provided")) - # extra = dict(self.args.extra) if self.args.dest: path = os.path.abspath(os.path.expanduser(self.args.dest)) if os.path.isdir(path): @@ -276,35 +254,30 @@ path = os.path.abspath(self.filename) if os.path.exists(path) and not self.args.force: - message = _("File {path} already exists! Do you want to overwrite?").format( - path=path - ) - confirm = input("{} (y/N) ".format(message).encode("utf-8")) - if confirm not in ("y", "Y"): - self.disp(_("file request cancelled")) - self.host.quit(2) + message = _(f"File {path} already exists! Do you want to overwrite?") + await self.host.confirmOrQuit(message, _("file request cancelled")) - self.full_dest_jid = self.host.get_full_jid(self.args.jid) + self.full_dest_jid = await self.host.get_full_jid(self.args.jid) extra = {} if self.args.path: extra["path"] = self.args.path if self.args.namespace: extra["namespace"] = self.args.namespace - self.host.bridge.fileJingleRequest( - self.full_dest_jid, - path, - self.args.name, - self.args.hash, - self.args.hash_algo if self.args.hash else "", - extra, - self.profile, - callback=self.gotId, - errback=partial( - self.errback, - msg=_("can't request file: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK, - ), - ) + try: + progress_id = await self.host.bridge.fileJingleRequest( + self.full_dest_jid, + path, + self.args.name, + self.args.hash, + self.args.hash_algo if self.args.hash else "", + extra, + self.profile, + ) + except Exception as e: + self.disp(msg=_(f"can't request file: {e}"), error=True) + self.host.quit(C.EXIT_BRIDGE_ERRBACK) + else: + await self.set_progress_id(progress_id) class Receive(base.CommandAnswering): @@ -322,89 +295,6 @@ C.META_TYPE_OVERWRITE: self.onOverwriteAction, } - def onProgressStarted(self, metadata): - self.disp(_("File copy started"), 2) - - def onProgressFinished(self, metadata): - self.disp(_("File received successfully"), 2) - if metadata.get("hash_verified", False): - try: - self.disp( - _("hash checked: {algo}:{checksum}").format( - algo=metadata["hash_algo"], checksum=metadata["hash"] - ), - 1, - ) - except KeyError: - self.disp(_("hash is checked but hash value is missing", 1), error=True) - else: - self.disp(_("hash can't be verified"), 1) - - def onProgressError(self, error_msg): - self.disp(_("Error while receiving file: {}").format(error_msg), error=True) - - def getXmluiId(self, action_data): - # FIXME: we temporarily use ElementTree, but a real XMLUI managing module - # should be available in the futur - # TODO: XMLUI module - try: - xml_ui = action_data["xmlui"] - except KeyError: - self.disp(_("Action has no XMLUI"), 1) - else: - ui = ET.fromstring(xml_ui.encode("utf-8")) - xmlui_id = ui.get("submit") - if not xmlui_id: - self.disp(_("Invalid XMLUI received"), error=True) - return xmlui_id - - def onFileAction(self, action_data, action_id, security_limit, profile): - xmlui_id = self.getXmluiId(action_data) - if xmlui_id is None: - return self.host.quitFromSignal(1) - try: - from_jid = jid.JID(action_data["meta_from_jid"]) - except KeyError: - self.disp(_("Ignoring action without from_jid data"), 1) - return - try: - progress_id = action_data["meta_progress_id"] - except KeyError: - self.disp(_("ignoring action without progress id"), 1) - return - - if not self.bare_jids or from_jid.bare in self.bare_jids: - if self._overwrite_refused: - self.disp(_("File refused because overwrite is needed"), error=True) - self.host.bridge.launchAction( - xmlui_id, {"cancelled": C.BOOL_TRUE}, profile_key=profile - ) - return self.host.quitFromSignal(2) - self.progress_id = progress_id - xmlui_data = {"path": self.path} - self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile) - - def onOverwriteAction(self, action_data, action_id, security_limit, profile): - xmlui_id = self.getXmluiId(action_data) - if xmlui_id is None: - return self.host.quitFromSignal(1) - try: - progress_id = action_data["meta_progress_id"] - except KeyError: - self.disp(_("ignoring action without progress id"), 1) - return - self.disp(_("Overwriting needed"), 1) - - if progress_id == self.progress_id: - if self.args.force: - self.disp(_("Overwrite accepted"), 2) - else: - self.disp(_("Refused to overwrite"), 2) - self._overwrite_refused = True - - xmlui_data = {"answer": C.boolConst(self.args.force)} - self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile) - def add_parser_options(self): self.parser.add_argument( "jids", @@ -432,15 +322,95 @@ help=_("destination path (default: working directory)"), ) - def start(self): + async def onProgressStarted(self, metadata): + self.disp(_("File copy started"), 2) + + async def onProgressFinished(self, metadata): + self.disp(_("File received successfully"), 2) + if metadata.get("hash_verified", False): + try: + self.disp(_( + f"hash checked: {metadata['hash_algo']}:{metadata['hash']}"), 1) + except KeyError: + self.disp(_("hash is checked but hash value is missing", 1), error=True) + else: + self.disp(_("hash can't be verified"), 1) + + async def onProgressError(self, e): + self.disp(_(f"Error while receiving file: {e}"), error=True) + + def getXmluiId(self, action_data): + # FIXME: we temporarily use ElementTree, but a real XMLUI managing module + # should be available in the futur + # TODO: XMLUI module + try: + xml_ui = action_data["xmlui"] + except KeyError: + self.disp(_("Action has no XMLUI"), 1) + else: + ui = ET.fromstring(xml_ui.encode("utf-8")) + xmlui_id = ui.get("submit") + if not xmlui_id: + self.disp(_("Invalid XMLUI received"), error=True) + return xmlui_id + + async def onFileAction(self, action_data, action_id, security_limit, profile): + xmlui_id = self.getXmluiId(action_data) + if xmlui_id is None: + return self.host.quitFromSignal(1) + try: + from_jid = jid.JID(action_data["meta_from_jid"]) + except KeyError: + self.disp(_("Ignoring action without from_jid data"), 1) + return + try: + progress_id = action_data["meta_progress_id"] + except KeyError: + self.disp(_("ignoring action without progress id"), 1) + return + + if not self.bare_jids or from_jid.bare in self.bare_jids: + if self._overwrite_refused: + self.disp(_("File refused because overwrite is needed"), error=True) + await self.host.bridge.launchAction( + xmlui_id, {"cancelled": C.BOOL_TRUE}, profile_key=profile + ) + return self.host.quitFromSignal(2) + await self.set_progress_id(progress_id) + xmlui_data = {"path": self.path} + await self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile) + + async def onOverwriteAction(self, action_data, action_id, security_limit, profile): + xmlui_id = self.getXmluiId(action_data) + if xmlui_id is None: + return self.host.quitFromSignal(1) + try: + progress_id = action_data["meta_progress_id"] + except KeyError: + self.disp(_("ignoring action without progress id"), 1) + return + self.disp(_("Overwriting needed"), 1) + + if progress_id == self.progress_id: + if self.args.force: + self.disp(_("Overwrite accepted"), 2) + else: + self.disp(_("Refused to overwrite"), 2) + self._overwrite_refused = True + + xmlui_data = {"answer": C.boolConst(self.args.force)} + await self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile) + + async def start(self): self.bare_jids = [jid.JID(jid_).bare for jid_ in self.args.jids] self.path = os.path.abspath(self.args.path) if not os.path.isdir(self.path): self.disp(_("Given path is not a directory !", error=True)) - self.host.quit(2) + self.host.quit(C.EXIT_BAD_ARG) if self.args.multiple: self.host.quit_on_progress_end = False self.disp(_("waiting for incoming file request"), 2) + await self.start_answering() class Upload(base.CommandBase): @@ -448,7 +418,6 @@ super(Upload, self).__init__( host, "upload", use_progress=True, use_verbose=True, help=_("upload a file") ) - self.need_loop = True def add_parser_options(self): self.parser.add_argument("file", type=str, help=_("file to upload")) @@ -463,10 +432,10 @@ help=_("ignore invalide TLS certificate"), ) - def onProgressStarted(self, metadata): + async def onProgressStarted(self, metadata): self.disp(_("File upload started"), 2) - def onProgressFinished(self, metadata): + async def onProgressFinished(self, metadata): self.disp(_("File uploaded successfully"), 2) try: url = metadata["url"] @@ -477,55 +446,54 @@ # XXX: url is display alone on a line to make parsing easier self.disp(url) - def onProgressError(self, error_msg): + async def onProgressError(self, error_msg): self.disp(_("Error while uploading file: {}").format(error_msg), error=True) - def gotId(self, data, file_): + async def gotId(self, data, file_): """Called when a progress id has been received @param pid(unicode): progress id @param file_(str): file path """ try: - self.progress_id = data["progress"] + await self.set_progress_id(data["progress"]) except KeyError: # TODO: if 'xmlui' key is present, manage xmlui message display self.disp(_("Can't upload file"), error=True) - self.host.quit(2) + self.host.quit(C.EXIT_ERROR) - def error(self, failure): - self.disp( - _("Error while trying to upload a file: {reason}").format(reason=failure), - error=True, - ) - self.host.quit(1) - - def start(self): + async def start(self): file_ = self.args.file if not os.path.exists(file_): - self.disp(_("file [{}] doesn't exist !").format(file_), error=True) - self.host.quit(1) + self.disp(_(f"file {file_!r} doesn't exist !"), error=True) + self.host.quit(C.EXIT_BAD_ARG) if os.path.isdir(file_): - self.disp(_("[{}] is a dir! Can't upload a dir").format(file_)) - self.host.quit(1) + self.disp(_(f"{file_!r} is a dir! Can't upload a dir")) + self.host.quit(C.EXIT_BAD_ARG) - self.full_dest_jid = ( - self.host.get_full_jid(self.args.jid) if self.args.jid is not None else "" - ) + if self.args.jid is None: + self.full_dest_jid = None + else: + self.full_dest_jid = await self.host.get_full_jid(self.args.jid) + options = {} if self.args.ignore_tls_errors: options["ignore_tls_errors"] = C.BOOL_TRUE path = os.path.abspath(file_) - self.host.bridge.fileUpload( - path, - "", - self.full_dest_jid, - options, - self.profile, - callback=lambda pid, file_=file_: self.gotId(pid, file_), - errback=self.error, - ) + try: + upload_data = await self.host.bridge.fileUpload( + path, + "", + self.full_dest_jid, + options, + self.profile, + ) + except Exception as e: + self.disp(f"can't while trying to upload a file: {e}", error=True) + self.host.quit(C.EXIT_BRIDGE_ERRBACK) + else: + await self.gotId(upload_data, file_) class ShareList(base.CommandBase): @@ -539,7 +507,6 @@ help=_("retrieve files shared by an entity"), use_verbose=True, ) - self.need_loop = True def add_parser_options(self): self.parser.add_argument( @@ -555,12 +522,6 @@ help=_("jid of sharing entity (nothing to check our own jid)"), ) - def file_gen(self, files_data): - for file_data in files_data: - yield file_data["name"] - yield file_data.get("size", "") - yield file_data.get("hash", "") - def _name_filter(self, name, row): if row.type == C.FILE_TYPE_DIRECTORY: return A.color(C.A_DIRECTORY, name) @@ -588,38 +549,37 @@ files_data.sort(key=lambda d: d["name"].lower()) show_header = False if self.verbosity == 0: - headers = ("name", "type") + keys = headers = ("name", "type") elif self.verbosity == 1: - headers = ("name", "type", "size") + keys = headers = ("name", "type", "size") elif self.verbosity > 1: show_header = True + keys = ("name", "type", "size", "file_hash") headers = ("name", "type", "size", "hash") table = common.Table.fromDict( self.host, files_data, - headers, + keys=keys, + headers=headers, filters={"name": self._name_filter, "size": self._size_filter}, - defaults={"size": "", "hash": ""}, + defaults={"size": "", "file_hash": ""}, ) table.display_blank(show_header=show_header, hide_cols=["type"]) - def _FISListCb(self, files_data): - self.output(files_data) - self.host.quit() + async def start(self): + try: + files_data = await self.host.bridge.FISList( + self.args.jid, + self.args.path, + {}, + self.profile, + ) + except Exception as e: + self.disp(f"can't retrieve shared files: {e}", error=True) + self.host.quit(C.EXIT_BRIDGE_ERRBACK) - def start(self): - self.host.bridge.FISList( - self.args.jid, - self.args.path, - {}, - self.profile, - callback=self._FISListCb, - errback=partial( - self.errback, - msg=_("can't retrieve shared files: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK, - ), - ) + await self.output(files_data) + self.host.quit() class SharePath(base.CommandBase): @@ -627,7 +587,6 @@ super(SharePath, self).__init__( host, "path", help=_("share a file or directory"), use_verbose=True ) - self.need_loop = True def add_parser_options(self): self.parser.add_argument( @@ -640,6 +599,7 @@ perm_group.add_argument( "-j", "--jid", + metavar="JID", action="append", dest="jids", default=[], @@ -649,7 +609,8 @@ "--public", action="store_true", help=_( - "share publicly the file(s) (/!\\ *everybody* will be able to access them)" + r"share publicly the file(s) (/!\ *everybody* will be able to access " + r"them)" ), ) self.parser.add_argument( @@ -657,13 +618,7 @@ help=_("path to a file or directory to share"), ) - def _FISSharePathCb(self, name): - self.disp( - _('{path} shared under the name "{name}"').format(path=self.path, name=name) - ) - self.host.quit() - - def start(self): + async def start(self): self.path = os.path.abspath(self.args.path) if self.args.public: access = {"read": {"type": "public"}} @@ -673,18 +628,19 @@ access = {"read": {"type": "whitelist", "jids": jids}} else: access = {} - self.host.bridge.FISSharePath( - self.args.name, - self.path, - json.dumps(access, ensure_ascii=False), - self.profile, - callback=self._FISSharePathCb, - errback=partial( - self.errback, - msg=_("can't share path: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK, - ), - ) + try: + name = await self.host.bridge.FISSharePath( + self.args.name, + self.path, + json.dumps(access, ensure_ascii=False), + self.profile, + ) + except Exception as e: + self.disp(f"can't share path: {e}", error=True) + self.host.quit(C.EXIT_BRIDGE_ERRBACK) + else: + self.disp(_(f'{self.path} shared under the name "{name}"')) + self.host.quit() class ShareInvite(base.CommandBase): @@ -692,7 +648,6 @@ super(ShareInvite, self).__init__( host, "invite", help=_("send invitation for a shared repository") ) - self.need_loop = True def add_parser_options(self): self.parser.add_argument( @@ -733,13 +688,7 @@ help=_("jid of the person to invite"), ) - def _FISInviteCb(self): - self.disp( - _('invitation sent to {entity}').format(entity=self.args.jid) - ) - self.host.quit() - - def start(self): + async def start(self): self.path = os.path.normpath(self.args.path) if self.args.path else "" extra = {} if self.args.thumbnail is not None: @@ -747,22 +696,25 @@ self.parser.error(_("only http(s) links are allowed with --thumbnail")) else: extra['thumb_url'] = self.args.thumbnail - self.host.bridge.FISInvite( - self.args.jid, - self.args.service, - self.args.type, - self.args.namespace, - self.path, - self.args.name, - data_format.serialise(extra), - self.profile, - callback=self._FISInviteCb, - errback=partial( - self.errback, - msg=_("can't send invitation: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK, - ), - ) + try: + await self.host.bridge.FISInvite( + self.args.jid, + self.args.service, + self.args.type, + self.args.namespace, + self.path, + self.args.name, + data_format.serialise(extra), + self.profile, + ) + except Exception as e: + self.disp(f"can't send invitation: {e}", error=True) + self.host.quit(C.EXIT_BRIDGE_ERRBACK) + else: + self.disp( + _(f'invitation sent to {self.args.jid}') + ) + self.host.quit() class Share(base.CommandBase):