Mercurial > libervia-backend
comparison 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 |
comparison
equal
deleted
inserted
replaced
3039:a1bc34f90fa5 | 3040:fee60f17ebac |
---|---|
29 from sat_frontends.jp import common | 29 from sat_frontends.jp import common |
30 from sat_frontends.tools import jid | 30 from sat_frontends.tools import jid |
31 from sat.tools.common.ansi import ANSI as A | 31 from sat.tools.common.ansi import ANSI as A |
32 import tempfile | 32 import tempfile |
33 import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI | 33 import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI |
34 from functools import partial | |
35 import json | 34 import json |
36 | 35 |
37 __commands__ = ["File"] | 36 __commands__ = ["File"] |
38 | 37 |
39 | 38 |
44 "send", | 43 "send", |
45 use_progress=True, | 44 use_progress=True, |
46 use_verbose=True, | 45 use_verbose=True, |
47 help=_("send a file to a contact"), | 46 help=_("send a file to a contact"), |
48 ) | 47 ) |
49 self.need_loop = True | |
50 | 48 |
51 def add_parser_options(self): | 49 def add_parser_options(self): |
52 self.parser.add_argument( | 50 self.parser.add_argument( |
53 "files", type=str, nargs="+", metavar="file", help=_("a list of file") | 51 "files", type=str, nargs="+", metavar="file", help=_("a list of file") |
54 ) | 52 ) |
73 "--name", | 71 "--name", |
74 default="", | 72 default="", |
75 help=("name to use (DEFAULT: use source file name)"), | 73 help=("name to use (DEFAULT: use source file name)"), |
76 ) | 74 ) |
77 | 75 |
78 def start(self): | 76 async def onProgressStarted(self, metadata): |
79 """Send files to jabber contact""" | |
80 self.send_files() | |
81 | |
82 def onProgressStarted(self, metadata): | |
83 self.disp(_("File copy started"), 2) | 77 self.disp(_("File copy started"), 2) |
84 | 78 |
85 def onProgressFinished(self, metadata): | 79 async def onProgressFinished(self, metadata): |
86 self.disp(_("File sent successfully"), 2) | 80 self.disp(_("File sent successfully"), 2) |
87 | 81 |
88 def onProgressError(self, error_msg): | 82 async def onProgressError(self, error_msg): |
89 if error_msg == C.PROGRESS_ERROR_DECLINED: | 83 if error_msg == C.PROGRESS_ERROR_DECLINED: |
90 self.disp(_("The file has been refused by your contact")) | 84 self.disp(_("The file has been refused by your contact")) |
91 else: | 85 else: |
92 self.disp(_("Error while sending file: {}").format(error_msg), error=True) | 86 self.disp(_("Error while sending file: {}").format(error_msg), error=True) |
93 | 87 |
94 def gotId(self, data, file_): | 88 async def gotId(self, data, file_): |
95 """Called when a progress id has been received | 89 """Called when a progress id has been received |
96 | 90 |
97 @param pid(unicode): progress id | 91 @param pid(unicode): progress id |
98 @param file_(str): file path | 92 @param file_(str): file path |
99 """ | 93 """ |
100 # FIXME: this show progress only for last progress_id | 94 # FIXME: this show progress only for last progress_id |
101 self.disp(_("File request sent to {jid}".format(jid=self.full_dest_jid)), 1) | 95 self.disp(_("File request sent to {jid}".format(jid=self.full_dest_jid)), 1) |
102 try: | 96 try: |
103 self.progress_id = data["progress"] | 97 await self.set_progress_id(data["progress"]) |
104 except KeyError: | 98 except KeyError: |
105 # TODO: if 'xmlui' key is present, manage xmlui message display | 99 # TODO: if 'xmlui' key is present, manage xmlui message display |
106 self.disp( | 100 self.disp( |
107 _("Can't send file to {jid}".format(jid=self.full_dest_jid)), error=True | 101 _("Can't send file to {jid}".format(jid=self.full_dest_jid)), error=True |
108 ) | 102 ) |
109 self.host.quit(2) | 103 self.host.quit(2) |
110 | 104 |
111 def error(self, failure): | 105 async def start(self): |
112 self.disp( | |
113 _("Error while trying to send a file: {reason}").format(reason=failure), | |
114 error=True, | |
115 ) | |
116 self.host.quit(1) | |
117 | |
118 def send_files(self): | |
119 for file_ in self.args.files: | 106 for file_ in self.args.files: |
120 if not os.path.exists(file_): | 107 if not os.path.exists(file_): |
121 self.disp(_("file [{}] doesn't exist !").format(file_), error=True) | 108 self.disp(_(f"file {file_!r} doesn't exist!"), error=True) |
122 self.host.quit(1) | 109 self.host.quit(C.EXIT_BAD_ARG) |
123 if not self.args.bz2 and os.path.isdir(file_): | 110 if not self.args.bz2 and os.path.isdir(file_): |
124 self.disp( | 111 self.disp( |
125 _( | 112 _(f"{file_!r} is a dir! Please send files inside or use compression") |
126 "[{}] is a dir ! Please send files inside or use compression" | |
127 ).format(file_) | |
128 ) | 113 ) |
129 self.host.quit(1) | 114 self.host.quit(C.EXIT_BAD_ARG) |
130 | 115 |
131 self.full_dest_jid = self.host.get_full_jid(self.args.jid) | 116 self.full_dest_jid = await self.host.get_full_jid(self.args.jid) |
132 extra = {} | 117 extra = {} |
133 if self.args.path: | 118 if self.args.path: |
134 extra["path"] = self.args.path | 119 extra["path"] = self.args.path |
135 if self.args.namespace: | 120 if self.args.namespace: |
136 extra["namespace"] = self.args.namespace | 121 extra["namespace"] = self.args.namespace |
150 self.disp(_("Adding {}").format(file_), 1) | 135 self.disp(_("Adding {}").format(file_), 1) |
151 bz2.add(file_) | 136 bz2.add(file_) |
152 bz2.close() | 137 bz2.close() |
153 self.disp(_("Done !"), 1) | 138 self.disp(_("Done !"), 1) |
154 | 139 |
155 self.host.bridge.fileSend( | 140 try: |
156 self.full_dest_jid, | 141 send_data = await self.host.bridge.fileSend( |
157 buf.name, | 142 self.full_dest_jid, |
158 self.args.name or archive_name, | 143 buf.name, |
159 "", | 144 self.args.name or archive_name, |
160 extra, | 145 "", |
161 self.profile, | 146 extra, |
162 callback=lambda pid, file_=buf.name: self.gotId(pid, file_), | 147 self.profile, |
163 errback=self.error, | 148 ) |
164 ) | 149 except Exception as e: |
150 self.disp(f"can't send file: {e}", error=True) | |
151 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | |
152 else: | |
153 await self.gotId(send_data, file_) | |
165 else: | 154 else: |
166 for file_ in self.args.files: | 155 for file_ in self.args.files: |
167 path = os.path.abspath(file_) | 156 path = os.path.abspath(file_) |
168 self.host.bridge.fileSend( | 157 try: |
169 self.full_dest_jid, | 158 send_data = await self.host.bridge.fileSend( |
170 path, | 159 self.full_dest_jid, |
171 self.args.name, | 160 path, |
172 "", | 161 self.args.name, |
173 extra, | 162 "", |
174 self.profile, | 163 extra, |
175 callback=lambda pid, file_=file_: self.gotId(pid, file_), | 164 self.profile, |
176 errback=self.error, | 165 ) |
177 ) | 166 except Exception as e: |
167 self.disp(f"can't send file {file_!r}: {e}", error=True) | |
168 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | |
169 else: | |
170 await self.gotId(send_data, file_) | |
178 | 171 |
179 | 172 |
180 class Request(base.CommandBase): | 173 class Request(base.CommandBase): |
181 def __init__(self, host): | 174 def __init__(self, host): |
182 super(Request, self).__init__( | 175 super(Request, self).__init__( |
184 "request", | 177 "request", |
185 use_progress=True, | 178 use_progress=True, |
186 use_verbose=True, | 179 use_verbose=True, |
187 help=_("request a file from a contact"), | 180 help=_("request a file from a contact"), |
188 ) | 181 ) |
189 self.need_loop = True | |
190 | 182 |
191 @property | 183 @property |
192 def filename(self): | 184 def filename(self): |
193 return self.args.name or self.args.hash or "output" | 185 return self.args.name or self.args.hash or "output" |
194 | 186 |
198 ) | 190 ) |
199 self.parser.add_argument( | 191 self.parser.add_argument( |
200 "-D", | 192 "-D", |
201 "--dest", | 193 "--dest", |
202 help=_( | 194 help=_( |
203 "destination path where the file will be saved (default: [current_dir]/[name|hash])" | 195 "destination path where the file will be saved (default: " |
196 "[current_dir]/[name|hash])" | |
204 ), | 197 ), |
205 ) | 198 ) |
206 self.parser.add_argument( | 199 self.parser.add_argument( |
207 "-n", | 200 "-n", |
208 "--name", | 201 "--name", |
236 "--force", | 229 "--force", |
237 action="store_true", | 230 action="store_true", |
238 help=_("overwrite existing file without confirmation"), | 231 help=_("overwrite existing file without confirmation"), |
239 ) | 232 ) |
240 | 233 |
241 def onProgressStarted(self, metadata): | 234 async def onProgressStarted(self, metadata): |
242 self.disp(_("File copy started"), 2) | 235 self.disp(_("File copy started"), 2) |
243 | 236 |
244 def onProgressFinished(self, metadata): | 237 async def onProgressFinished(self, metadata): |
245 self.disp(_("File received successfully"), 2) | 238 self.disp(_("File received successfully"), 2) |
246 | 239 |
247 def onProgressError(self, error_msg): | 240 async def onProgressError(self, error_msg): |
248 if error_msg == C.PROGRESS_ERROR_DECLINED: | 241 if error_msg == C.PROGRESS_ERROR_DECLINED: |
249 self.disp(_("The file request has been refused")) | 242 self.disp(_("The file request has been refused")) |
250 else: | 243 else: |
251 self.disp(_("Error while requesting file: {}").format(error_msg), error=True) | 244 self.disp(_("Error while requesting file: {}").format(error_msg), error=True) |
252 | 245 |
253 def gotId(self, progress_id): | 246 async def start(self): |
254 """Called when a progress id has been received | |
255 | |
256 @param progress_id(unicode): progress id | |
257 """ | |
258 self.progress_id = progress_id | |
259 | |
260 def error(self, failure): | |
261 self.disp( | |
262 _("Error while trying to send a file: {reason}").format(reason=failure), | |
263 error=True, | |
264 ) | |
265 self.host.quit(1) | |
266 | |
267 def start(self): | |
268 if not self.args.name and not self.args.hash: | 247 if not self.args.name and not self.args.hash: |
269 self.parser.error(_("at least one of --name or --hash must be provided")) | 248 self.parser.error(_("at least one of --name or --hash must be provided")) |
270 # extra = dict(self.args.extra) | |
271 if self.args.dest: | 249 if self.args.dest: |
272 path = os.path.abspath(os.path.expanduser(self.args.dest)) | 250 path = os.path.abspath(os.path.expanduser(self.args.dest)) |
273 if os.path.isdir(path): | 251 if os.path.isdir(path): |
274 path = os.path.join(path, self.filename) | 252 path = os.path.join(path, self.filename) |
275 else: | 253 else: |
276 path = os.path.abspath(self.filename) | 254 path = os.path.abspath(self.filename) |
277 | 255 |
278 if os.path.exists(path) and not self.args.force: | 256 if os.path.exists(path) and not self.args.force: |
279 message = _("File {path} already exists! Do you want to overwrite?").format( | 257 message = _(f"File {path} already exists! Do you want to overwrite?") |
280 path=path | 258 await self.host.confirmOrQuit(message, _("file request cancelled")) |
281 ) | 259 |
282 confirm = input("{} (y/N) ".format(message).encode("utf-8")) | 260 self.full_dest_jid = await self.host.get_full_jid(self.args.jid) |
283 if confirm not in ("y", "Y"): | |
284 self.disp(_("file request cancelled")) | |
285 self.host.quit(2) | |
286 | |
287 self.full_dest_jid = self.host.get_full_jid(self.args.jid) | |
288 extra = {} | 261 extra = {} |
289 if self.args.path: | 262 if self.args.path: |
290 extra["path"] = self.args.path | 263 extra["path"] = self.args.path |
291 if self.args.namespace: | 264 if self.args.namespace: |
292 extra["namespace"] = self.args.namespace | 265 extra["namespace"] = self.args.namespace |
293 self.host.bridge.fileJingleRequest( | 266 try: |
294 self.full_dest_jid, | 267 progress_id = await self.host.bridge.fileJingleRequest( |
295 path, | 268 self.full_dest_jid, |
296 self.args.name, | 269 path, |
297 self.args.hash, | 270 self.args.name, |
298 self.args.hash_algo if self.args.hash else "", | 271 self.args.hash, |
299 extra, | 272 self.args.hash_algo if self.args.hash else "", |
300 self.profile, | 273 extra, |
301 callback=self.gotId, | 274 self.profile, |
302 errback=partial( | 275 ) |
303 self.errback, | 276 except Exception as e: |
304 msg=_("can't request file: {}"), | 277 self.disp(msg=_(f"can't request file: {e}"), error=True) |
305 exit_code=C.EXIT_BRIDGE_ERRBACK, | 278 self.host.quit(C.EXIT_BRIDGE_ERRBACK) |
306 ), | 279 else: |
307 ) | 280 await self.set_progress_id(progress_id) |
308 | 281 |
309 | 282 |
310 class Receive(base.CommandAnswering): | 283 class Receive(base.CommandAnswering): |
311 def __init__(self, host): | 284 def __init__(self, host): |
312 super(Receive, self).__init__( | 285 super(Receive, self).__init__( |
320 self.action_callbacks = { | 293 self.action_callbacks = { |
321 C.META_TYPE_FILE: self.onFileAction, | 294 C.META_TYPE_FILE: self.onFileAction, |
322 C.META_TYPE_OVERWRITE: self.onOverwriteAction, | 295 C.META_TYPE_OVERWRITE: self.onOverwriteAction, |
323 } | 296 } |
324 | 297 |
325 def onProgressStarted(self, metadata): | 298 def add_parser_options(self): |
299 self.parser.add_argument( | |
300 "jids", | |
301 nargs="*", | |
302 help=_("jids accepted (accept everything if none is specified)"), | |
303 ) | |
304 self.parser.add_argument( | |
305 "-m", | |
306 "--multiple", | |
307 action="store_true", | |
308 help=_("accept multiple files (you'll have to stop manually)"), | |
309 ) | |
310 self.parser.add_argument( | |
311 "-f", | |
312 "--force", | |
313 action="store_true", | |
314 help=_( | |
315 "force overwritting of existing files (/!\\ name is choosed by sender)" | |
316 ), | |
317 ) | |
318 self.parser.add_argument( | |
319 "--path", | |
320 default=".", | |
321 metavar="DIR", | |
322 help=_("destination path (default: working directory)"), | |
323 ) | |
324 | |
325 async def onProgressStarted(self, metadata): | |
326 self.disp(_("File copy started"), 2) | 326 self.disp(_("File copy started"), 2) |
327 | 327 |
328 def onProgressFinished(self, metadata): | 328 async def onProgressFinished(self, metadata): |
329 self.disp(_("File received successfully"), 2) | 329 self.disp(_("File received successfully"), 2) |
330 if metadata.get("hash_verified", False): | 330 if metadata.get("hash_verified", False): |
331 try: | 331 try: |
332 self.disp( | 332 self.disp(_( |
333 _("hash checked: {algo}:{checksum}").format( | 333 f"hash checked: {metadata['hash_algo']}:{metadata['hash']}"), 1) |
334 algo=metadata["hash_algo"], checksum=metadata["hash"] | |
335 ), | |
336 1, | |
337 ) | |
338 except KeyError: | 334 except KeyError: |
339 self.disp(_("hash is checked but hash value is missing", 1), error=True) | 335 self.disp(_("hash is checked but hash value is missing", 1), error=True) |
340 else: | 336 else: |
341 self.disp(_("hash can't be verified"), 1) | 337 self.disp(_("hash can't be verified"), 1) |
342 | 338 |
343 def onProgressError(self, error_msg): | 339 async def onProgressError(self, e): |
344 self.disp(_("Error while receiving file: {}").format(error_msg), error=True) | 340 self.disp(_(f"Error while receiving file: {e}"), error=True) |
345 | 341 |
346 def getXmluiId(self, action_data): | 342 def getXmluiId(self, action_data): |
347 # FIXME: we temporarily use ElementTree, but a real XMLUI managing module | 343 # FIXME: we temporarily use ElementTree, but a real XMLUI managing module |
348 # should be available in the futur | 344 # should be available in the futur |
349 # TODO: XMLUI module | 345 # TODO: XMLUI module |
356 xmlui_id = ui.get("submit") | 352 xmlui_id = ui.get("submit") |
357 if not xmlui_id: | 353 if not xmlui_id: |
358 self.disp(_("Invalid XMLUI received"), error=True) | 354 self.disp(_("Invalid XMLUI received"), error=True) |
359 return xmlui_id | 355 return xmlui_id |
360 | 356 |
361 def onFileAction(self, action_data, action_id, security_limit, profile): | 357 async def onFileAction(self, action_data, action_id, security_limit, profile): |
362 xmlui_id = self.getXmluiId(action_data) | 358 xmlui_id = self.getXmluiId(action_data) |
363 if xmlui_id is None: | 359 if xmlui_id is None: |
364 return self.host.quitFromSignal(1) | 360 return self.host.quitFromSignal(1) |
365 try: | 361 try: |
366 from_jid = jid.JID(action_data["meta_from_jid"]) | 362 from_jid = jid.JID(action_data["meta_from_jid"]) |
374 return | 370 return |
375 | 371 |
376 if not self.bare_jids or from_jid.bare in self.bare_jids: | 372 if not self.bare_jids or from_jid.bare in self.bare_jids: |
377 if self._overwrite_refused: | 373 if self._overwrite_refused: |
378 self.disp(_("File refused because overwrite is needed"), error=True) | 374 self.disp(_("File refused because overwrite is needed"), error=True) |
379 self.host.bridge.launchAction( | 375 await self.host.bridge.launchAction( |
380 xmlui_id, {"cancelled": C.BOOL_TRUE}, profile_key=profile | 376 xmlui_id, {"cancelled": C.BOOL_TRUE}, profile_key=profile |
381 ) | 377 ) |
382 return self.host.quitFromSignal(2) | 378 return self.host.quitFromSignal(2) |
383 self.progress_id = progress_id | 379 await self.set_progress_id(progress_id) |
384 xmlui_data = {"path": self.path} | 380 xmlui_data = {"path": self.path} |
385 self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile) | 381 await self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile) |
386 | 382 |
387 def onOverwriteAction(self, action_data, action_id, security_limit, profile): | 383 async def onOverwriteAction(self, action_data, action_id, security_limit, profile): |
388 xmlui_id = self.getXmluiId(action_data) | 384 xmlui_id = self.getXmluiId(action_data) |
389 if xmlui_id is None: | 385 if xmlui_id is None: |
390 return self.host.quitFromSignal(1) | 386 return self.host.quitFromSignal(1) |
391 try: | 387 try: |
392 progress_id = action_data["meta_progress_id"] | 388 progress_id = action_data["meta_progress_id"] |
401 else: | 397 else: |
402 self.disp(_("Refused to overwrite"), 2) | 398 self.disp(_("Refused to overwrite"), 2) |
403 self._overwrite_refused = True | 399 self._overwrite_refused = True |
404 | 400 |
405 xmlui_data = {"answer": C.boolConst(self.args.force)} | 401 xmlui_data = {"answer": C.boolConst(self.args.force)} |
406 self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile) | 402 await self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile) |
407 | 403 |
408 def add_parser_options(self): | 404 async def start(self): |
409 self.parser.add_argument( | |
410 "jids", | |
411 nargs="*", | |
412 help=_("jids accepted (accept everything if none is specified)"), | |
413 ) | |
414 self.parser.add_argument( | |
415 "-m", | |
416 "--multiple", | |
417 action="store_true", | |
418 help=_("accept multiple files (you'll have to stop manually)"), | |
419 ) | |
420 self.parser.add_argument( | |
421 "-f", | |
422 "--force", | |
423 action="store_true", | |
424 help=_( | |
425 "force overwritting of existing files (/!\\ name is choosed by sender)" | |
426 ), | |
427 ) | |
428 self.parser.add_argument( | |
429 "--path", | |
430 default=".", | |
431 metavar="DIR", | |
432 help=_("destination path (default: working directory)"), | |
433 ) | |
434 | |
435 def start(self): | |
436 self.bare_jids = [jid.JID(jid_).bare for jid_ in self.args.jids] | 405 self.bare_jids = [jid.JID(jid_).bare for jid_ in self.args.jids] |
437 self.path = os.path.abspath(self.args.path) | 406 self.path = os.path.abspath(self.args.path) |
438 if not os.path.isdir(self.path): | 407 if not os.path.isdir(self.path): |
439 self.disp(_("Given path is not a directory !", error=True)) | 408 self.disp(_("Given path is not a directory !", error=True)) |
440 self.host.quit(2) | 409 self.host.quit(C.EXIT_BAD_ARG) |
441 if self.args.multiple: | 410 if self.args.multiple: |
442 self.host.quit_on_progress_end = False | 411 self.host.quit_on_progress_end = False |
443 self.disp(_("waiting for incoming file request"), 2) | 412 self.disp(_("waiting for incoming file request"), 2) |
413 await self.start_answering() | |
444 | 414 |
445 | 415 |
446 class Upload(base.CommandBase): | 416 class Upload(base.CommandBase): |
447 def __init__(self, host): | 417 def __init__(self, host): |
448 super(Upload, self).__init__( | 418 super(Upload, self).__init__( |
449 host, "upload", use_progress=True, use_verbose=True, help=_("upload a file") | 419 host, "upload", use_progress=True, use_verbose=True, help=_("upload a file") |
450 ) | 420 ) |
451 self.need_loop = True | |
452 | 421 |
453 def add_parser_options(self): | 422 def add_parser_options(self): |
454 self.parser.add_argument("file", type=str, help=_("file to upload")) | 423 self.parser.add_argument("file", type=str, help=_("file to upload")) |
455 self.parser.add_argument( | 424 self.parser.add_argument( |
456 "jid", | 425 "jid", |
461 "--ignore-tls-errors", | 430 "--ignore-tls-errors", |
462 action="store_true", | 431 action="store_true", |
463 help=_("ignore invalide TLS certificate"), | 432 help=_("ignore invalide TLS certificate"), |
464 ) | 433 ) |
465 | 434 |
466 def onProgressStarted(self, metadata): | 435 async def onProgressStarted(self, metadata): |
467 self.disp(_("File upload started"), 2) | 436 self.disp(_("File upload started"), 2) |
468 | 437 |
469 def onProgressFinished(self, metadata): | 438 async def onProgressFinished(self, metadata): |
470 self.disp(_("File uploaded successfully"), 2) | 439 self.disp(_("File uploaded successfully"), 2) |
471 try: | 440 try: |
472 url = metadata["url"] | 441 url = metadata["url"] |
473 except KeyError: | 442 except KeyError: |
474 self.disp("download URL not found in metadata") | 443 self.disp("download URL not found in metadata") |
475 else: | 444 else: |
476 self.disp(_("URL to retrieve the file:"), 1) | 445 self.disp(_("URL to retrieve the file:"), 1) |
477 # XXX: url is display alone on a line to make parsing easier | 446 # XXX: url is display alone on a line to make parsing easier |
478 self.disp(url) | 447 self.disp(url) |
479 | 448 |
480 def onProgressError(self, error_msg): | 449 async def onProgressError(self, error_msg): |
481 self.disp(_("Error while uploading file: {}").format(error_msg), error=True) | 450 self.disp(_("Error while uploading file: {}").format(error_msg), error=True) |
482 | 451 |
483 def gotId(self, data, file_): | 452 async def gotId(self, data, file_): |
484 """Called when a progress id has been received | 453 """Called when a progress id has been received |
485 | 454 |
486 @param pid(unicode): progress id | 455 @param pid(unicode): progress id |
487 @param file_(str): file path | 456 @param file_(str): file path |
488 """ | 457 """ |
489 try: | 458 try: |
490 self.progress_id = data["progress"] | 459 await self.set_progress_id(data["progress"]) |
491 except KeyError: | 460 except KeyError: |
492 # TODO: if 'xmlui' key is present, manage xmlui message display | 461 # TODO: if 'xmlui' key is present, manage xmlui message display |
493 self.disp(_("Can't upload file"), error=True) | 462 self.disp(_("Can't upload file"), error=True) |
494 self.host.quit(2) | 463 self.host.quit(C.EXIT_ERROR) |
495 | 464 |
496 def error(self, failure): | 465 async def start(self): |
497 self.disp( | |
498 _("Error while trying to upload a file: {reason}").format(reason=failure), | |
499 error=True, | |
500 ) | |
501 self.host.quit(1) | |
502 | |
503 def start(self): | |
504 file_ = self.args.file | 466 file_ = self.args.file |
505 if not os.path.exists(file_): | 467 if not os.path.exists(file_): |
506 self.disp(_("file [{}] doesn't exist !").format(file_), error=True) | 468 self.disp(_(f"file {file_!r} doesn't exist !"), error=True) |
507 self.host.quit(1) | 469 self.host.quit(C.EXIT_BAD_ARG) |
508 if os.path.isdir(file_): | 470 if os.path.isdir(file_): |
509 self.disp(_("[{}] is a dir! Can't upload a dir").format(file_)) | 471 self.disp(_(f"{file_!r} is a dir! Can't upload a dir")) |
510 self.host.quit(1) | 472 self.host.quit(C.EXIT_BAD_ARG) |
511 | 473 |
512 self.full_dest_jid = ( | 474 if self.args.jid is None: |
513 self.host.get_full_jid(self.args.jid) if self.args.jid is not None else "" | 475 self.full_dest_jid = None |
514 ) | 476 else: |
477 self.full_dest_jid = await self.host.get_full_jid(self.args.jid) | |
478 | |
515 options = {} | 479 options = {} |
516 if self.args.ignore_tls_errors: | 480 if self.args.ignore_tls_errors: |
517 options["ignore_tls_errors"] = C.BOOL_TRUE | 481 options["ignore_tls_errors"] = C.BOOL_TRUE |
518 | 482 |
519 path = os.path.abspath(file_) | 483 path = os.path.abspath(file_) |
520 self.host.bridge.fileUpload( | 484 try: |
521 path, | 485 upload_data = await self.host.bridge.fileUpload( |
522 "", | 486 path, |
523 self.full_dest_jid, | 487 "", |
524 options, | 488 self.full_dest_jid, |
525 self.profile, | 489 options, |
526 callback=lambda pid, file_=file_: self.gotId(pid, file_), | 490 self.profile, |
527 errback=self.error, | 491 ) |
528 ) | 492 except Exception as e: |
493 self.disp(f"can't while trying to upload a file: {e}", error=True) | |
494 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | |
495 else: | |
496 await self.gotId(upload_data, file_) | |
529 | 497 |
530 | 498 |
531 class ShareList(base.CommandBase): | 499 class ShareList(base.CommandBase): |
532 def __init__(self, host): | 500 def __init__(self, host): |
533 extra_outputs = {"default": self.default_output} | 501 extra_outputs = {"default": self.default_output} |
537 use_output=C.OUTPUT_LIST_DICT, | 505 use_output=C.OUTPUT_LIST_DICT, |
538 extra_outputs=extra_outputs, | 506 extra_outputs=extra_outputs, |
539 help=_("retrieve files shared by an entity"), | 507 help=_("retrieve files shared by an entity"), |
540 use_verbose=True, | 508 use_verbose=True, |
541 ) | 509 ) |
542 self.need_loop = True | |
543 | 510 |
544 def add_parser_options(self): | 511 def add_parser_options(self): |
545 self.parser.add_argument( | 512 self.parser.add_argument( |
546 "-d", | 513 "-d", |
547 "--path", | 514 "--path", |
552 "jid", | 519 "jid", |
553 nargs="?", | 520 nargs="?", |
554 default="", | 521 default="", |
555 help=_("jid of sharing entity (nothing to check our own jid)"), | 522 help=_("jid of sharing entity (nothing to check our own jid)"), |
556 ) | 523 ) |
557 | |
558 def file_gen(self, files_data): | |
559 for file_data in files_data: | |
560 yield file_data["name"] | |
561 yield file_data.get("size", "") | |
562 yield file_data.get("hash", "") | |
563 | 524 |
564 def _name_filter(self, name, row): | 525 def _name_filter(self, name, row): |
565 if row.type == C.FILE_TYPE_DIRECTORY: | 526 if row.type == C.FILE_TYPE_DIRECTORY: |
566 return A.color(C.A_DIRECTORY, name) | 527 return A.color(C.A_DIRECTORY, name) |
567 elif row.type == C.FILE_TYPE_FILE: | 528 elif row.type == C.FILE_TYPE_FILE: |
586 def default_output(self, files_data): | 547 def default_output(self, files_data): |
587 """display files a way similar to ls""" | 548 """display files a way similar to ls""" |
588 files_data.sort(key=lambda d: d["name"].lower()) | 549 files_data.sort(key=lambda d: d["name"].lower()) |
589 show_header = False | 550 show_header = False |
590 if self.verbosity == 0: | 551 if self.verbosity == 0: |
591 headers = ("name", "type") | 552 keys = headers = ("name", "type") |
592 elif self.verbosity == 1: | 553 elif self.verbosity == 1: |
593 headers = ("name", "type", "size") | 554 keys = headers = ("name", "type", "size") |
594 elif self.verbosity > 1: | 555 elif self.verbosity > 1: |
595 show_header = True | 556 show_header = True |
557 keys = ("name", "type", "size", "file_hash") | |
596 headers = ("name", "type", "size", "hash") | 558 headers = ("name", "type", "size", "hash") |
597 table = common.Table.fromDict( | 559 table = common.Table.fromDict( |
598 self.host, | 560 self.host, |
599 files_data, | 561 files_data, |
600 headers, | 562 keys=keys, |
563 headers=headers, | |
601 filters={"name": self._name_filter, "size": self._size_filter}, | 564 filters={"name": self._name_filter, "size": self._size_filter}, |
602 defaults={"size": "", "hash": ""}, | 565 defaults={"size": "", "file_hash": ""}, |
603 ) | 566 ) |
604 table.display_blank(show_header=show_header, hide_cols=["type"]) | 567 table.display_blank(show_header=show_header, hide_cols=["type"]) |
605 | 568 |
606 def _FISListCb(self, files_data): | 569 async def start(self): |
607 self.output(files_data) | 570 try: |
571 files_data = await self.host.bridge.FISList( | |
572 self.args.jid, | |
573 self.args.path, | |
574 {}, | |
575 self.profile, | |
576 ) | |
577 except Exception as e: | |
578 self.disp(f"can't retrieve shared files: {e}", error=True) | |
579 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | |
580 | |
581 await self.output(files_data) | |
608 self.host.quit() | 582 self.host.quit() |
609 | |
610 def start(self): | |
611 self.host.bridge.FISList( | |
612 self.args.jid, | |
613 self.args.path, | |
614 {}, | |
615 self.profile, | |
616 callback=self._FISListCb, | |
617 errback=partial( | |
618 self.errback, | |
619 msg=_("can't retrieve shared files: {}"), | |
620 exit_code=C.EXIT_BRIDGE_ERRBACK, | |
621 ), | |
622 ) | |
623 | 583 |
624 | 584 |
625 class SharePath(base.CommandBase): | 585 class SharePath(base.CommandBase): |
626 def __init__(self, host): | 586 def __init__(self, host): |
627 super(SharePath, self).__init__( | 587 super(SharePath, self).__init__( |
628 host, "path", help=_("share a file or directory"), use_verbose=True | 588 host, "path", help=_("share a file or directory"), use_verbose=True |
629 ) | 589 ) |
630 self.need_loop = True | |
631 | 590 |
632 def add_parser_options(self): | 591 def add_parser_options(self): |
633 self.parser.add_argument( | 592 self.parser.add_argument( |
634 "-n", | 593 "-n", |
635 "--name", | 594 "--name", |
638 ) | 597 ) |
639 perm_group = self.parser.add_mutually_exclusive_group() | 598 perm_group = self.parser.add_mutually_exclusive_group() |
640 perm_group.add_argument( | 599 perm_group.add_argument( |
641 "-j", | 600 "-j", |
642 "--jid", | 601 "--jid", |
602 metavar="JID", | |
643 action="append", | 603 action="append", |
644 dest="jids", | 604 dest="jids", |
645 default=[], | 605 default=[], |
646 help=_("jid of contacts allowed to retrieve the files"), | 606 help=_("jid of contacts allowed to retrieve the files"), |
647 ) | 607 ) |
648 perm_group.add_argument( | 608 perm_group.add_argument( |
649 "--public", | 609 "--public", |
650 action="store_true", | 610 action="store_true", |
651 help=_( | 611 help=_( |
652 "share publicly the file(s) (/!\\ *everybody* will be able to access them)" | 612 r"share publicly the file(s) (/!\ *everybody* will be able to access " |
613 r"them)" | |
653 ), | 614 ), |
654 ) | 615 ) |
655 self.parser.add_argument( | 616 self.parser.add_argument( |
656 "path", | 617 "path", |
657 help=_("path to a file or directory to share"), | 618 help=_("path to a file or directory to share"), |
658 ) | 619 ) |
659 | 620 |
660 def _FISSharePathCb(self, name): | 621 async def start(self): |
661 self.disp( | |
662 _('{path} shared under the name "{name}"').format(path=self.path, name=name) | |
663 ) | |
664 self.host.quit() | |
665 | |
666 def start(self): | |
667 self.path = os.path.abspath(self.args.path) | 622 self.path = os.path.abspath(self.args.path) |
668 if self.args.public: | 623 if self.args.public: |
669 access = {"read": {"type": "public"}} | 624 access = {"read": {"type": "public"}} |
670 else: | 625 else: |
671 jids = self.args.jids | 626 jids = self.args.jids |
672 if jids: | 627 if jids: |
673 access = {"read": {"type": "whitelist", "jids": jids}} | 628 access = {"read": {"type": "whitelist", "jids": jids}} |
674 else: | 629 else: |
675 access = {} | 630 access = {} |
676 self.host.bridge.FISSharePath( | 631 try: |
677 self.args.name, | 632 name = await self.host.bridge.FISSharePath( |
678 self.path, | 633 self.args.name, |
679 json.dumps(access, ensure_ascii=False), | 634 self.path, |
680 self.profile, | 635 json.dumps(access, ensure_ascii=False), |
681 callback=self._FISSharePathCb, | 636 self.profile, |
682 errback=partial( | 637 ) |
683 self.errback, | 638 except Exception as e: |
684 msg=_("can't share path: {}"), | 639 self.disp(f"can't share path: {e}", error=True) |
685 exit_code=C.EXIT_BRIDGE_ERRBACK, | 640 self.host.quit(C.EXIT_BRIDGE_ERRBACK) |
686 ), | 641 else: |
687 ) | 642 self.disp(_(f'{self.path} shared under the name "{name}"')) |
643 self.host.quit() | |
688 | 644 |
689 | 645 |
690 class ShareInvite(base.CommandBase): | 646 class ShareInvite(base.CommandBase): |
691 def __init__(self, host): | 647 def __init__(self, host): |
692 super(ShareInvite, self).__init__( | 648 super(ShareInvite, self).__init__( |
693 host, "invite", help=_("send invitation for a shared repository") | 649 host, "invite", help=_("send invitation for a shared repository") |
694 ) | 650 ) |
695 self.need_loop = True | |
696 | 651 |
697 def add_parser_options(self): | 652 def add_parser_options(self): |
698 self.parser.add_argument( | 653 self.parser.add_argument( |
699 "-n", | 654 "-n", |
700 "--name", | 655 "--name", |
731 self.parser.add_argument( | 686 self.parser.add_argument( |
732 "jid", | 687 "jid", |
733 help=_("jid of the person to invite"), | 688 help=_("jid of the person to invite"), |
734 ) | 689 ) |
735 | 690 |
736 def _FISInviteCb(self): | 691 async def start(self): |
737 self.disp( | |
738 _('invitation sent to {entity}').format(entity=self.args.jid) | |
739 ) | |
740 self.host.quit() | |
741 | |
742 def start(self): | |
743 self.path = os.path.normpath(self.args.path) if self.args.path else "" | 692 self.path = os.path.normpath(self.args.path) if self.args.path else "" |
744 extra = {} | 693 extra = {} |
745 if self.args.thumbnail is not None: | 694 if self.args.thumbnail is not None: |
746 if not self.args.thumbnail.startswith('http'): | 695 if not self.args.thumbnail.startswith('http'): |
747 self.parser.error(_("only http(s) links are allowed with --thumbnail")) | 696 self.parser.error(_("only http(s) links are allowed with --thumbnail")) |
748 else: | 697 else: |
749 extra['thumb_url'] = self.args.thumbnail | 698 extra['thumb_url'] = self.args.thumbnail |
750 self.host.bridge.FISInvite( | 699 try: |
751 self.args.jid, | 700 await self.host.bridge.FISInvite( |
752 self.args.service, | 701 self.args.jid, |
753 self.args.type, | 702 self.args.service, |
754 self.args.namespace, | 703 self.args.type, |
755 self.path, | 704 self.args.namespace, |
756 self.args.name, | 705 self.path, |
757 data_format.serialise(extra), | 706 self.args.name, |
758 self.profile, | 707 data_format.serialise(extra), |
759 callback=self._FISInviteCb, | 708 self.profile, |
760 errback=partial( | 709 ) |
761 self.errback, | 710 except Exception as e: |
762 msg=_("can't send invitation: {}"), | 711 self.disp(f"can't send invitation: {e}", error=True) |
763 exit_code=C.EXIT_BRIDGE_ERRBACK, | 712 self.host.quit(C.EXIT_BRIDGE_ERRBACK) |
764 ), | 713 else: |
765 ) | 714 self.disp( |
715 _(f'invitation sent to {self.args.jid}') | |
716 ) | |
717 self.host.quit() | |
766 | 718 |
767 | 719 |
768 class Share(base.CommandBase): | 720 class Share(base.CommandBase): |
769 subcommands = (ShareList, SharePath, ShareInvite) | 721 subcommands = (ShareList, SharePath, ShareInvite) |
770 | 722 |