diff frontends/src/jp/cmd_file.py @ 1606:de785fcf9a7b

jp (base, file): file command and progress fixes and adaptation to new API: - progress use new API, and signals to monitor progression and avoid use of progressGet before progress is actually started - progress behaviour on event is managed by callbacks which are Jp attributes - progress with unknown or 0 size don't show the progress bar - better handling of errors - CommandAnswering is update to manage actions instead of the deprecated askConfirmation - managed actions use callback easy to associate with an action type. - file command is updated to manage these changes and the recent changes in backend behaviour - verbosity is used to display more or less message when sending/receiving a file - destination path can be specified in file receive - file receive doesn't stop yet, still need some work in the backend
author Goffi <goffi@goffi.org>
date Sun, 15 Nov 2015 23:42:21 +0100
parents 069ad98b360d
children a17a91531fbe
line wrap: on
line diff
--- a/frontends/src/jp/cmd_file.py	Sun Nov 15 23:25:58 2015 +0100
+++ b/frontends/src/jp/cmd_file.py	Sun Nov 15 23:42:21 2015 +0100
@@ -17,7 +17,6 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from logging import debug, info, error, warning
 
 import base
 import sys
@@ -25,12 +24,21 @@
 import os.path
 import tarfile
 from sat.core.i18n import _
+from sat_frontends.jp.constants import Const as C
+from sat_frontends.tools import jid
+import tempfile
+import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI
+
 
 __commands__ = ["File"]
 
+
 class Send(base.CommandBase):
     def __init__(self, host):
-        super(Send, self).__init__(host, 'send', use_progress=True, help=_('Send a file to a contact'))
+        super(Send, self).__init__(host, 'send', use_progress=True, use_verbose=True, help=_('Send a file to a contact'))
+        self.host.progress_started = lambda dummy: self.disp(_(u'File copy started'),2)
+        self.host.progress_success = lambda dummy: self.disp(_(u'File copied successfully'),2)
+        self.host.progress_failure = lambda dummy: self.disp(_(u'Error while transfering file'),error=True)
 
     def add_parser_options(self):
         self.parser.add_argument("files", type=str, nargs = '+', help=_("A list of file"))
@@ -43,79 +51,138 @@
         super(Send, self).connected()
         self.send_files()
 
+    def gotId(self, data, file_):
+        """Called when a progress id has been received
+
+        @param pid(unicode): progress id
+        @param file_(str): file path
+        """
+        #FIXME: this show progress only for last progress_id
+        self.disp(_(u"File request sent to {jid}".format(jid=self.full_dest_jid)), 1)
+        self.progress_id = data['progress']
+
+    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):
 
         for file_ in self.args.files:
             if not os.path.exists(file_):
-                error (_(u"file [%s] doesn't exist !") % file_)
+                self.disp(_(u"file [{}] doesn't exist !").format(file_), error=True)
                 self.host.quit(1)
             if not self.args.bz2 and os.path.isdir(file_):
-                error (_("[%s] is a dir ! Please send files inside or use compression") % file_)
+                self.disp(_(u"[{}] is a dir ! Please send files inside or use compression").format(file_))
                 self.host.quit(1)
 
-        full_dest_jid = self.host.get_full_jid(self.args.jid)
+        self.full_dest_jid = self.host.get_full_jid(self.args.jid)
 
         if self.args.bz2:
-            tmpfile = (os.path.basename(self.args.files[0]) or os.path.basename(os.path.dirname(self.args.files[0])) ) + '.tar.bz2' #FIXME: tmp, need an algorithm to find a good name/path
-            if os.path.exists(tmpfile):
-                error (_("tmp file_ (%s) already exists ! Please remove it"), tmpfile)
-                exit(1)
-            warning(_("bz2 is an experimental option at an early dev stage, use with caution"))
-            #FIXME: check free space, writting perm, tmp dir, filename (watch for OS used)
-            print _(u"Starting compression, please wait...")
-            sys.stdout.flush()
-            bz2 = tarfile.open(tmpfile, "w:bz2")
-            for file_ in self.args.files:
-                print _(u"Adding %s") % file_
-                bz2.add(file_)
-            bz2.close()
-            print _(u"Done !")
-            path = os.path.abspath(tmpfile)
-            self.progress_id = self.host.bridge.sendFile(full_dest_jid, path, {}, self.profile)
+            with tempfile.NamedTemporaryFile('wb', delete=False) as buf:
+                self.host.addOnQuitCallback(os.unlink, buf.name)
+                self.disp(_(u"bz2 is an experimental option, use with caution"))
+                #FIXME: check free space
+                self.disp(_(u"Starting compression, please wait..."))
+                sys.stdout.flush()
+                bz2 = tarfile.open(mode="w:bz2", fileobj=buf)
+                archive_name = u'{}.tar.bz2'.format(os.path.basename(self.args.files[0]) or u'compressed_files')
+                for file_ in self.args.files:
+                    self.disp(_(u"Adding {}").format(file_), 1)
+                    bz2.add(file_)
+                bz2.close()
+                self.disp(_(u"Done !"), 1)
+
+                self.host.bridge.fileSend(self.full_dest_jid, buf.name, archive_name, '', self.profile, callback=lambda pid, file_=buf.name: self.gotId(pid, file_), errback=self.error)
         else:
             for file_ in self.args.files:
                 path = os.path.abspath(file_)
-                self.progress_id = self.host.bridge.sendFile(full_dest_jid, path, {}, self.profile) #FIXME: show progress only for last progress_id
+                self.host.bridge.fileSend(self.full_dest_jid, path, '', '', self.profile, callback=lambda pid, file_=file_: self.gotId(pid, file_), errback=self.error)
 
 
 class Receive(base.CommandAnswering):
-    confirm_type = "FILE_TRANSFER"
 
     def __init__(self, host):
-        super(Receive, self).__init__(host, 'recv', use_progress=True, help=_('Wait for a file to be sent by a contact'))
+        super(Receive, self).__init__(host, 'receive', use_progress=True, use_verbose=True, help=_('Wait for a file to be sent by a contact'))
+        self._overwrite_refused = False # True when one overwrite as already been refused
+        self.action_callbacks = {C.META_TYPE_FILE: self.onFileAction,
+                                 C.META_TYPE_OVERWRITE: self.onOverwriteAction}
+
+    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(_(u"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(_(u"Invalid XMLUI received"), error=True)
+            return xmlui_id
 
-    @property
-    def dest_jids(self):
-        return self.args.jids
+    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(_(u"Ignoring action without from_jid data"), 1)
+            return
+        try:
+            progress_id = action_data['meta_progress_id']
+        except KeyError:
+            self.disp(_(u"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(_(u"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(_(u"ignoring action without progress id"), 1)
+            return
+        self.disp(_(u"Overwriting needed"), 1)
+
+        if progress_id == self.progress_id:
+            if self.args.force:
+                self.disp(_(u"Overwrite accepted"), 2)
+            else:
+                self.disp(_(u"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", type=base.unicode_decoder, nargs="*", help=_('Jids accepted (none means "accept everything")'))
-        self.parser.add_argument("-m", "--multiple", action="store_true", help=_("Accept multiple files (you'll have to stop manually)"))
-        self.parser.add_argument("-f", "--force", action="store_true", help=_("Force overwritting of existing files"))
-
-
-    def ask(self, data, confirm_id):
-        answer_data = {}
-        answer_data["dest_path"] = os.path.join(os.getcwd(), data['filename'])
-
-        if self.args.force or not os.path.exists(answer_data["dest_path"]):
-            self.host.bridge.confirmationAnswer(confirm_id, True, answer_data, self.profile)
-            info(_("Accepted file [%(filename)s] from %(sender)s") % {'filename':data['filename'], 'sender':data['from']})
-            self.progress_id = confirm_id
-        else:
-            self.host.bridge.confirmationAnswer(confirm_id, False, answer_data, self.profile)
-            warning(_("Refused file [%(filename)s] from %(sender)s: a file with the same name already exist") % {'filename':data['filename'], 'sender':data['from']})
-            if not self.args.multiple:
-                self.host.quit()
-
-        if not self.args.multiple and not self.args.progress:
-            #we just accept one file
-            self.host.quit()
+        self.parser.add_argument("jids", type=base.unicode_decoder, nargs="*", help=_(u'JIDs accepted (accept everything if none is specified)'))
+        self.parser.add_argument("-m", "--multiple", action="store_true", help=_(u"accept multiple files (you'll have to stop manually)"))
+        self.parser.add_argument("-f", "--force", action="store_true", help=_(u"force overwritting of existing files (/!\\ name is choosed by sended)"))
+        self.parser.add_argument("--path", default='.', metavar='DIR', help=_(u"destination path (default: working directory)"))
 
     def run(self):
         super(Receive, self).run()
+        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(_(u"Given path is not a directory !", error=True))
+            self.quit(2)
         if self.args.multiple:
             self.host.quit_on_progress_end = False
+        self.disp(_(u"waiting for incoming file request"),2)
 
 
 class File(base.CommandBase):