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