comparison sat_frontends/jp/cmd_file.py @ 3094:c3cb18236bdf

jp (file): new `get` command + encryption with upload: - new file/get command let download a file from URL. It handles `aesgcm:` scheme by thanks to backend decryption on the fly. - new `--encrypt` option for upload. When used, the file will be encrypted using AES-GCM algorithm, and the `aesgcm:` URL will be returned - for both commands, the XMLUI note is displayed in case of error
author Goffi <goffi@goffi.org>
date Fri, 20 Dec 2019 12:28:04 +0100
parents e75024e41f81
children 040ca99e25fe
comparison
equal deleted inserted replaced
3093:d909473a76cc 3094:c3cb18236bdf
17 # You should have received a copy of the GNU Affero General Public License 17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 20
21 from . import base 21 from . import base
22 from . import xmlui_manager
22 import sys 23 import sys
23 import os 24 import os
24 import os.path 25 import os.path
25 import tarfile 26 import tarfile
26 from sat.core.i18n import _ 27 from sat.core.i18n import _
27 from sat.tools.common import data_format 28 from sat.tools.common import data_format
28 from sat_frontends.jp.constants import Const as C 29 from sat_frontends.jp.constants import Const as C
29 from sat_frontends.jp import common 30 from sat_frontends.jp import common
30 from sat_frontends.tools import jid 31 from sat_frontends.tools import jid
31 from sat.tools.common.ansi import ANSI as A 32 from sat.tools.common.ansi import ANSI as A
33 from urllib.parse import urlparse
34 from pathlib import Path
32 import tempfile 35 import tempfile
33 import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI 36 import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI
34 import json 37 import json
35 38
36 __commands__ = ["File"] 39 __commands__ = ["File"]
252 path = os.path.join(path, self.filename) 255 path = os.path.join(path, self.filename)
253 else: 256 else:
254 path = os.path.abspath(self.filename) 257 path = os.path.abspath(self.filename)
255 258
256 if os.path.exists(path) and not self.args.force: 259 if os.path.exists(path) and not self.args.force:
257 message = _(f"File {path} already exists! Do you want to overwrite?") 260 message = _("File {path} already exists! Do you want to overwrite?").format(
261 path = path)
258 await self.host.confirmOrQuit(message, _("file request cancelled")) 262 await self.host.confirmOrQuit(message, _("file request cancelled"))
259 263
260 self.full_dest_jid = await self.host.get_full_jid(self.args.jid) 264 self.full_dest_jid = await self.host.get_full_jid(self.args.jid)
261 extra = {} 265 extra = {}
262 if self.args.path: 266 if self.args.path:
411 self.host.quit_on_progress_end = False 415 self.host.quit_on_progress_end = False
412 self.disp(_("waiting for incoming file request"), 2) 416 self.disp(_("waiting for incoming file request"), 2)
413 await self.start_answering() 417 await self.start_answering()
414 418
415 419
420 class Get(base.CommandBase):
421
422 def __init__(self, host):
423 super(Get, self).__init__(
424 host, "get", use_progress=True, use_verbose=True,
425 help=_("download a file from URI")
426 )
427
428 def add_parser_options(self):
429 self.parser.add_argument(
430 '-o', '--dest_file', type=str, default='',
431 help=_("destination file (DEFAULT: filename from URL)")
432 )
433 self.parser.add_argument(
434 "-f",
435 "--force",
436 action="store_true",
437 help=_("overwrite existing file without confirmation"),
438 )
439 self.parser.add_argument("uri", type=str, help=_("URI of the file to retrieve"))
440
441 async def onProgressStarted(self, metadata):
442 self.disp(_("File download started"), 2)
443
444 async def onProgressFinished(self, metadata):
445 self.disp(_("File downloaded successfully"), 2)
446
447 async def onProgressError(self, error_msg):
448 self.disp(_("Error while downloading file: {}").format(error_msg), error=True)
449
450 async def gotId(self, data):
451 """Called when a progress id has been received"""
452 try:
453 await self.set_progress_id(data["progress"])
454 except KeyError:
455 if 'xmlui' in data:
456 ui = xmlui_manager.create(self.host, data['xmlui'])
457 await ui.show()
458 else:
459 self.disp(_("Can't download file"), error=True)
460 self.host.quit(C.EXIT_ERROR)
461
462 async def start(self):
463 uri = self.args.uri
464 dest_file = self.args.dest_file
465 if not dest_file:
466 parsed_uri = urlparse(uri)
467 dest_file = Path(parsed_uri.path).name.strip() or "downloaded_file"
468
469 dest_file = Path(dest_file).expanduser().resolve()
470 if dest_file.exists() and not self.args.force:
471 message = _("File {path} already exists! Do you want to overwrite?").format(
472 path = dest_file)
473 await self.host.confirmOrQuit(message, _("file download cancelled"))
474
475 options = {}
476
477 try:
478 download_data = await self.host.bridge.fileDownload(
479 uri,
480 str(dest_file),
481 data_format.serialise(options),
482 self.profile,
483 )
484 except Exception as e:
485 self.disp(f"error while trying to download a file: {e}", error=True)
486 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
487 else:
488 await self.gotId(download_data)
489
490
416 class Upload(base.CommandBase): 491 class Upload(base.CommandBase):
417 def __init__(self, host): 492 def __init__(self, host):
418 super(Upload, self).__init__( 493 super(Upload, self).__init__(
419 host, "upload", use_progress=True, use_verbose=True, help=_("upload a file") 494 host, "upload", use_progress=True, use_verbose=True, help=_("upload a file")
420 ) 495 )
421 496
422 def add_parser_options(self): 497 def add_parser_options(self):
498 self.parser.add_argument(
499 "-e",
500 "--encrypt",
501 action="store_true",
502 help=_("encrypt file using AES-GCM"),
503 )
423 self.parser.add_argument("file", type=str, help=_("file to upload")) 504 self.parser.add_argument("file", type=str, help=_("file to upload"))
424 self.parser.add_argument( 505 self.parser.add_argument(
425 "jid", 506 "jid",
426 nargs="?", 507 nargs="?",
427 help=_("jid of upload component (nothing to autodetect)"), 508 help=_("jid of upload component (nothing to autodetect)"),
428 ) 509 )
429 self.parser.add_argument( 510 self.parser.add_argument(
430 "--ignore-tls-errors", 511 "--ignore-tls-errors",
431 action="store_true", 512 action="store_true",
432 help=_("ignore invalide TLS certificate"), 513 help=_(r"ignore invalide TLS certificate (/!\ Dangerous /!\)"),
433 ) 514 )
434 515
435 async def onProgressStarted(self, metadata): 516 async def onProgressStarted(self, metadata):
436 self.disp(_("File upload started"), 2) 517 self.disp(_("File upload started"), 2)
437 518
441 url = metadata["url"] 522 url = metadata["url"]
442 except KeyError: 523 except KeyError:
443 self.disp("download URL not found in metadata") 524 self.disp("download URL not found in metadata")
444 else: 525 else:
445 self.disp(_("URL to retrieve the file:"), 1) 526 self.disp(_("URL to retrieve the file:"), 1)
446 # XXX: url is display alone on a line to make parsing easier 527 # XXX: url is displayed alone on a line to make parsing easier
447 self.disp(url) 528 self.disp(url)
448 529
449 async def onProgressError(self, error_msg): 530 async def onProgressError(self, error_msg):
450 self.disp(_("Error while uploading file: {}").format(error_msg), error=True) 531 self.disp(_("Error while uploading file: {}").format(error_msg), error=True)
451 532
456 @param file_(str): file path 537 @param file_(str): file path
457 """ 538 """
458 try: 539 try:
459 await self.set_progress_id(data["progress"]) 540 await self.set_progress_id(data["progress"])
460 except KeyError: 541 except KeyError:
461 # TODO: if 'xmlui' key is present, manage xmlui message display 542 if 'xmlui' in data:
462 self.disp(_("Can't upload file"), error=True) 543 ui = xmlui_manager.create(self.host, data['xmlui'])
544 await ui.show()
545 else:
546 self.disp(_("Can't upload file"), error=True)
463 self.host.quit(C.EXIT_ERROR) 547 self.host.quit(C.EXIT_ERROR)
464 548
465 async def start(self): 549 async def start(self):
466 file_ = self.args.file 550 file_ = self.args.file
467 if not os.path.exists(file_): 551 if not os.path.exists(file_):
477 self.full_dest_jid = await self.host.get_full_jid(self.args.jid) 561 self.full_dest_jid = await self.host.get_full_jid(self.args.jid)
478 562
479 options = {} 563 options = {}
480 if self.args.ignore_tls_errors: 564 if self.args.ignore_tls_errors:
481 options["ignore_tls_errors"] = True 565 options["ignore_tls_errors"] = True
566 if self.args.encrypt:
567 options["encryption"] = C.ENC_AES_GCM
482 568
483 path = os.path.abspath(file_) 569 path = os.path.abspath(file_)
484 try: 570 try:
485 upload_data = await self.host.bridge.fileUpload( 571 upload_data = await self.host.bridge.fileUpload(
486 path, 572 path,
488 self.full_dest_jid, 574 self.full_dest_jid,
489 data_format.serialise(options), 575 data_format.serialise(options),
490 self.profile, 576 self.profile,
491 ) 577 )
492 except Exception as e: 578 except Exception as e:
493 self.disp(f"can't while trying to upload a file: {e}", error=True) 579 self.disp(f"error while trying to upload a file: {e}", error=True)
494 self.host.quit(C.EXIT_BRIDGE_ERRBACK) 580 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
495 else: 581 else:
496 await self.gotId(upload_data, file_) 582 await self.gotId(upload_data, file_)
497 583
498 584
725 host, "share", use_profile=False, help=_("files sharing management") 811 host, "share", use_profile=False, help=_("files sharing management")
726 ) 812 )
727 813
728 814
729 class File(base.CommandBase): 815 class File(base.CommandBase):
730 subcommands = (Send, Request, Receive, Upload, Share) 816 subcommands = (Send, Request, Receive, Get, Upload, Share)
731 817
732 def __init__(self, host): 818 def __init__(self, host):
733 super(File, self).__init__( 819 super(File, self).__init__(
734 host, "file", use_profile=False, help=_("files sending/receiving/management") 820 host, "file", use_profile=False, help=_("files sending/receiving/management")
735 ) 821 )