Mercurial > libervia-backend
comparison 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 |
comparison
equal
deleted
inserted
replaced
1605:0aded9648c5c | 1606:de785fcf9a7b |
---|---|
15 # GNU Affero General Public License for more details. | 15 # GNU Affero General Public License for more details. |
16 | 16 |
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 from logging import debug, info, error, warning | |
21 | 20 |
22 import base | 21 import base |
23 import sys | 22 import sys |
24 import os | 23 import os |
25 import os.path | 24 import os.path |
26 import tarfile | 25 import tarfile |
27 from sat.core.i18n import _ | 26 from sat.core.i18n import _ |
27 from sat_frontends.jp.constants import Const as C | |
28 from sat_frontends.tools import jid | |
29 import tempfile | |
30 import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI | |
31 | |
28 | 32 |
29 __commands__ = ["File"] | 33 __commands__ = ["File"] |
30 | 34 |
35 | |
31 class Send(base.CommandBase): | 36 class Send(base.CommandBase): |
32 def __init__(self, host): | 37 def __init__(self, host): |
33 super(Send, self).__init__(host, 'send', use_progress=True, help=_('Send a file to a contact')) | 38 super(Send, self).__init__(host, 'send', use_progress=True, use_verbose=True, help=_('Send a file to a contact')) |
39 self.host.progress_started = lambda dummy: self.disp(_(u'File copy started'),2) | |
40 self.host.progress_success = lambda dummy: self.disp(_(u'File copied successfully'),2) | |
41 self.host.progress_failure = lambda dummy: self.disp(_(u'Error while transfering file'),error=True) | |
34 | 42 |
35 def add_parser_options(self): | 43 def add_parser_options(self): |
36 self.parser.add_argument("files", type=str, nargs = '+', help=_("A list of file")) | 44 self.parser.add_argument("files", type=str, nargs = '+', help=_("A list of file")) |
37 self.parser.add_argument("jid", type=base.unicode_decoder, help=_("The destination jid")) | 45 self.parser.add_argument("jid", type=base.unicode_decoder, help=_("The destination jid")) |
38 self.parser.add_argument("-b", "--bz2", action="store_true", help=_("Make a bzip2 tarball")) | 46 self.parser.add_argument("-b", "--bz2", action="store_true", help=_("Make a bzip2 tarball")) |
41 """Send files to jabber contact""" | 49 """Send files to jabber contact""" |
42 self.need_loop=True | 50 self.need_loop=True |
43 super(Send, self).connected() | 51 super(Send, self).connected() |
44 self.send_files() | 52 self.send_files() |
45 | 53 |
54 def gotId(self, data, file_): | |
55 """Called when a progress id has been received | |
56 | |
57 @param pid(unicode): progress id | |
58 @param file_(str): file path | |
59 """ | |
60 #FIXME: this show progress only for last progress_id | |
61 self.disp(_(u"File request sent to {jid}".format(jid=self.full_dest_jid)), 1) | |
62 self.progress_id = data['progress'] | |
63 | |
64 def error(self, failure): | |
65 self.disp(_("Error while trying to send a file: {reason}").format(reason=failure), error=True) | |
66 self.host.quit(1) | |
67 | |
46 def send_files(self): | 68 def send_files(self): |
47 | 69 |
48 for file_ in self.args.files: | 70 for file_ in self.args.files: |
49 if not os.path.exists(file_): | 71 if not os.path.exists(file_): |
50 error (_(u"file [%s] doesn't exist !") % file_) | 72 self.disp(_(u"file [{}] doesn't exist !").format(file_), error=True) |
51 self.host.quit(1) | 73 self.host.quit(1) |
52 if not self.args.bz2 and os.path.isdir(file_): | 74 if not self.args.bz2 and os.path.isdir(file_): |
53 error (_("[%s] is a dir ! Please send files inside or use compression") % file_) | 75 self.disp(_(u"[{}] is a dir ! Please send files inside or use compression").format(file_)) |
54 self.host.quit(1) | 76 self.host.quit(1) |
55 | 77 |
56 full_dest_jid = self.host.get_full_jid(self.args.jid) | 78 self.full_dest_jid = self.host.get_full_jid(self.args.jid) |
57 | 79 |
58 if self.args.bz2: | 80 if self.args.bz2: |
59 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 | 81 with tempfile.NamedTemporaryFile('wb', delete=False) as buf: |
60 if os.path.exists(tmpfile): | 82 self.host.addOnQuitCallback(os.unlink, buf.name) |
61 error (_("tmp file_ (%s) already exists ! Please remove it"), tmpfile) | 83 self.disp(_(u"bz2 is an experimental option, use with caution")) |
62 exit(1) | 84 #FIXME: check free space |
63 warning(_("bz2 is an experimental option at an early dev stage, use with caution")) | 85 self.disp(_(u"Starting compression, please wait...")) |
64 #FIXME: check free space, writting perm, tmp dir, filename (watch for OS used) | 86 sys.stdout.flush() |
65 print _(u"Starting compression, please wait...") | 87 bz2 = tarfile.open(mode="w:bz2", fileobj=buf) |
66 sys.stdout.flush() | 88 archive_name = u'{}.tar.bz2'.format(os.path.basename(self.args.files[0]) or u'compressed_files') |
67 bz2 = tarfile.open(tmpfile, "w:bz2") | 89 for file_ in self.args.files: |
68 for file_ in self.args.files: | 90 self.disp(_(u"Adding {}").format(file_), 1) |
69 print _(u"Adding %s") % file_ | 91 bz2.add(file_) |
70 bz2.add(file_) | 92 bz2.close() |
71 bz2.close() | 93 self.disp(_(u"Done !"), 1) |
72 print _(u"Done !") | 94 |
73 path = os.path.abspath(tmpfile) | 95 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) |
74 self.progress_id = self.host.bridge.sendFile(full_dest_jid, path, {}, self.profile) | |
75 else: | 96 else: |
76 for file_ in self.args.files: | 97 for file_ in self.args.files: |
77 path = os.path.abspath(file_) | 98 path = os.path.abspath(file_) |
78 self.progress_id = self.host.bridge.sendFile(full_dest_jid, path, {}, self.profile) #FIXME: show progress only for last progress_id | 99 self.host.bridge.fileSend(self.full_dest_jid, path, '', '', self.profile, callback=lambda pid, file_=file_: self.gotId(pid, file_), errback=self.error) |
79 | 100 |
80 | 101 |
81 class Receive(base.CommandAnswering): | 102 class Receive(base.CommandAnswering): |
82 confirm_type = "FILE_TRANSFER" | |
83 | 103 |
84 def __init__(self, host): | 104 def __init__(self, host): |
85 super(Receive, self).__init__(host, 'recv', use_progress=True, help=_('Wait for a file to be sent by a contact')) | 105 super(Receive, self).__init__(host, 'receive', use_progress=True, use_verbose=True, help=_('Wait for a file to be sent by a contact')) |
106 self._overwrite_refused = False # True when one overwrite as already been refused | |
107 self.action_callbacks = {C.META_TYPE_FILE: self.onFileAction, | |
108 C.META_TYPE_OVERWRITE: self.onOverwriteAction} | |
86 | 109 |
87 @property | 110 def getXmluiId(self, action_data): |
88 def dest_jids(self): | 111 # FIXME: we temporarily use ElementTree, but a real XMLUI managing module |
89 return self.args.jids | 112 # should be available in the futur |
113 # TODO: XMLUI module | |
114 try: | |
115 xml_ui = action_data['xmlui'] | |
116 except KeyError: | |
117 self.disp(_(u"Action has no XMLUI"), 1) | |
118 else: | |
119 ui = ET.fromstring(xml_ui.encode('utf-8')) | |
120 xmlui_id = ui.get('submit') | |
121 if not xmlui_id: | |
122 self.disp(_(u"Invalid XMLUI received"), error=True) | |
123 return xmlui_id | |
124 | |
125 def onFileAction(self, action_data, action_id, security_limit, profile): | |
126 xmlui_id = self.getXmluiId(action_data) | |
127 if xmlui_id is None: | |
128 return self.host.quitFromSignal(1) | |
129 try: | |
130 from_jid = jid.JID(action_data['meta_from_jid']) | |
131 except KeyError: | |
132 self.disp(_(u"Ignoring action without from_jid data"), 1) | |
133 return | |
134 try: | |
135 progress_id = action_data['meta_progress_id'] | |
136 except KeyError: | |
137 self.disp(_(u"ignoring action without progress id"), 1) | |
138 return | |
139 | |
140 if not self.bare_jids or from_jid.bare in self.bare_jids: | |
141 if self._overwrite_refused: | |
142 self.disp(_(u"File refused because overwrite is needed"), error=True) | |
143 self.host.bridge.launchAction(xmlui_id, {'cancelled': C.BOOL_TRUE}, profile_key=profile) | |
144 return self.host.quitFromSignal(2) | |
145 self.progress_id = progress_id | |
146 xmlui_data = {'path': self.path} | |
147 self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile) | |
148 | |
149 def onOverwriteAction(self, action_data, action_id, security_limit, profile): | |
150 xmlui_id = self.getXmluiId(action_data) | |
151 if xmlui_id is None: | |
152 return self.host.quitFromSignal(1) | |
153 try: | |
154 progress_id = action_data['meta_progress_id'] | |
155 except KeyError: | |
156 self.disp(_(u"ignoring action without progress id"), 1) | |
157 return | |
158 self.disp(_(u"Overwriting needed"), 1) | |
159 | |
160 if progress_id == self.progress_id: | |
161 if self.args.force: | |
162 self.disp(_(u"Overwrite accepted"), 2) | |
163 else: | |
164 self.disp(_(u"Refused to overwrite"), 2) | |
165 self._overwrite_refused = True | |
166 | |
167 xmlui_data = {'answer': C.boolConst(self.args.force)} | |
168 self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile) | |
90 | 169 |
91 def add_parser_options(self): | 170 def add_parser_options(self): |
92 self.parser.add_argument("jids", type=base.unicode_decoder, nargs="*", help=_('Jids accepted (none means "accept everything")')) | 171 self.parser.add_argument("jids", type=base.unicode_decoder, nargs="*", help=_(u'JIDs accepted (accept everything if none is specified)')) |
93 self.parser.add_argument("-m", "--multiple", action="store_true", help=_("Accept multiple files (you'll have to stop manually)")) | 172 self.parser.add_argument("-m", "--multiple", action="store_true", help=_(u"accept multiple files (you'll have to stop manually)")) |
94 self.parser.add_argument("-f", "--force", action="store_true", help=_("Force overwritting of existing files")) | 173 self.parser.add_argument("-f", "--force", action="store_true", help=_(u"force overwritting of existing files (/!\\ name is choosed by sended)")) |
95 | 174 self.parser.add_argument("--path", default='.', metavar='DIR', help=_(u"destination path (default: working directory)")) |
96 | |
97 def ask(self, data, confirm_id): | |
98 answer_data = {} | |
99 answer_data["dest_path"] = os.path.join(os.getcwd(), data['filename']) | |
100 | |
101 if self.args.force or not os.path.exists(answer_data["dest_path"]): | |
102 self.host.bridge.confirmationAnswer(confirm_id, True, answer_data, self.profile) | |
103 info(_("Accepted file [%(filename)s] from %(sender)s") % {'filename':data['filename'], 'sender':data['from']}) | |
104 self.progress_id = confirm_id | |
105 else: | |
106 self.host.bridge.confirmationAnswer(confirm_id, False, answer_data, self.profile) | |
107 warning(_("Refused file [%(filename)s] from %(sender)s: a file with the same name already exist") % {'filename':data['filename'], 'sender':data['from']}) | |
108 if not self.args.multiple: | |
109 self.host.quit() | |
110 | |
111 if not self.args.multiple and not self.args.progress: | |
112 #we just accept one file | |
113 self.host.quit() | |
114 | 175 |
115 def run(self): | 176 def run(self): |
116 super(Receive, self).run() | 177 super(Receive, self).run() |
178 self.bare_jids = [jid.JID(jid_).bare for jid_ in self.args.jids] | |
179 self.path = os.path.abspath(self.args.path) | |
180 if not os.path.isdir(self.path): | |
181 self.disp(_(u"Given path is not a directory !", error=True)) | |
182 self.quit(2) | |
117 if self.args.multiple: | 183 if self.args.multiple: |
118 self.host.quit_on_progress_end = False | 184 self.host.quit_on_progress_end = False |
185 self.disp(_(u"waiting for incoming file request"),2) | |
119 | 186 |
120 | 187 |
121 class File(base.CommandBase): | 188 class File(base.CommandBase): |
122 subcommands = (Send, Receive) | 189 subcommands = (Send, Receive) |
123 | 190 |