Mercurial > libervia-backend
comparison sat_frontends/jp/cmd_file.py @ 3028:ab2696e34d29
Python 3 port:
/!\ this is a huge commit
/!\ starting from this commit, SàT is needs Python 3.6+
/!\ SàT maybe be instable or some feature may not work anymore, this will improve with time
This patch port backend, bridge and frontends to Python 3.
Roughly this has been done this way:
- 2to3 tools has been applied (with python 3.7)
- all references to python2 have been replaced with python3 (notably shebangs)
- fixed files not handled by 2to3 (notably the shell script)
- several manual fixes
- fixed issues reported by Python 3 that where not handled in Python 2
- replaced "async" with "async_" when needed (it's a reserved word from Python 3.7)
- replaced zope's "implements" with @implementer decorator
- temporary hack to handle data pickled in database, as str or bytes may be returned,
to be checked later
- fixed hash comparison for password
- removed some code which is not needed anymore with Python 3
- deactivated some code which needs to be checked (notably certificate validation)
- tested with jp, fixed reported issues until some basic commands worked
- ported Primitivus (after porting dependencies like urwid satext)
- more manual fixes
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 13 Aug 2019 19:08:41 +0200 |
parents | 18a98a541f7a |
children | fee60f17ebac |
comparison
equal
deleted
inserted
replaced
3027:ff5bcb12ae60 | 3028:ab2696e34d29 |
---|---|
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 | 20 |
21 import base | 21 from . import base |
22 import sys | 22 import sys |
23 import os | 23 import os |
24 import os.path | 24 import os.path |
25 import tarfile | 25 import tarfile |
26 from sat.core.i18n import _ | 26 from sat.core.i18n import _ |
48 ) | 48 ) |
49 self.need_loop = True | 49 self.need_loop = True |
50 | 50 |
51 def add_parser_options(self): | 51 def add_parser_options(self): |
52 self.parser.add_argument( | 52 self.parser.add_argument( |
53 "files", type=str, nargs="+", metavar="file", help=_(u"a list of file") | 53 "files", type=str, nargs="+", metavar="file", help=_("a list of file") |
54 ) | 54 ) |
55 self.parser.add_argument( | 55 self.parser.add_argument( |
56 "jid", type=base.unicode_decoder, help=_(u"the destination jid") | 56 "jid", help=_("the destination jid") |
57 ) | 57 ) |
58 self.parser.add_argument( | 58 self.parser.add_argument( |
59 "-b", "--bz2", action="store_true", help=_(u"make a bzip2 tarball") | 59 "-b", "--bz2", action="store_true", help=_("make a bzip2 tarball") |
60 ) | 60 ) |
61 self.parser.add_argument( | 61 self.parser.add_argument( |
62 "-d", | 62 "-d", |
63 "--path", | 63 "--path", |
64 type=base.unicode_decoder, | 64 help=("path to the directory where the file must be stored"), |
65 help=(u"path to the directory where the file must be stored"), | |
66 ) | 65 ) |
67 self.parser.add_argument( | 66 self.parser.add_argument( |
68 "-N", | 67 "-N", |
69 "--namespace", | 68 "--namespace", |
70 type=base.unicode_decoder, | 69 help=("namespace of the file"), |
71 help=(u"namespace of the file"), | |
72 ) | 70 ) |
73 self.parser.add_argument( | 71 self.parser.add_argument( |
74 "-n", | 72 "-n", |
75 "--name", | 73 "--name", |
76 type=base.unicode_decoder, | 74 default="", |
77 default=u"", | 75 help=("name to use (DEFAULT: use source file name)"), |
78 help=(u"name to use (DEFAULT: use source file name)"), | |
79 ) | 76 ) |
80 | 77 |
81 def start(self): | 78 def start(self): |
82 """Send files to jabber contact""" | 79 """Send files to jabber contact""" |
83 self.send_files() | 80 self.send_files() |
84 | 81 |
85 def onProgressStarted(self, metadata): | 82 def onProgressStarted(self, metadata): |
86 self.disp(_(u"File copy started"), 2) | 83 self.disp(_("File copy started"), 2) |
87 | 84 |
88 def onProgressFinished(self, metadata): | 85 def onProgressFinished(self, metadata): |
89 self.disp(_(u"File sent successfully"), 2) | 86 self.disp(_("File sent successfully"), 2) |
90 | 87 |
91 def onProgressError(self, error_msg): | 88 def onProgressError(self, error_msg): |
92 if error_msg == C.PROGRESS_ERROR_DECLINED: | 89 if error_msg == C.PROGRESS_ERROR_DECLINED: |
93 self.disp(_(u"The file has been refused by your contact")) | 90 self.disp(_("The file has been refused by your contact")) |
94 else: | 91 else: |
95 self.disp(_(u"Error while sending file: {}").format(error_msg), error=True) | 92 self.disp(_("Error while sending file: {}").format(error_msg), error=True) |
96 | 93 |
97 def gotId(self, data, file_): | 94 def gotId(self, data, file_): |
98 """Called when a progress id has been received | 95 """Called when a progress id has been received |
99 | 96 |
100 @param pid(unicode): progress id | 97 @param pid(unicode): progress id |
101 @param file_(str): file path | 98 @param file_(str): file path |
102 """ | 99 """ |
103 # FIXME: this show progress only for last progress_id | 100 # FIXME: this show progress only for last progress_id |
104 self.disp(_(u"File request sent to {jid}".format(jid=self.full_dest_jid)), 1) | 101 self.disp(_("File request sent to {jid}".format(jid=self.full_dest_jid)), 1) |
105 try: | 102 try: |
106 self.progress_id = data["progress"] | 103 self.progress_id = data["progress"] |
107 except KeyError: | 104 except KeyError: |
108 # TODO: if 'xmlui' key is present, manage xmlui message display | 105 # TODO: if 'xmlui' key is present, manage xmlui message display |
109 self.disp( | 106 self.disp( |
110 _(u"Can't send file to {jid}".format(jid=self.full_dest_jid)), error=True | 107 _("Can't send file to {jid}".format(jid=self.full_dest_jid)), error=True |
111 ) | 108 ) |
112 self.host.quit(2) | 109 self.host.quit(2) |
113 | 110 |
114 def error(self, failure): | 111 def error(self, failure): |
115 self.disp( | 112 self.disp( |
119 self.host.quit(1) | 116 self.host.quit(1) |
120 | 117 |
121 def send_files(self): | 118 def send_files(self): |
122 for file_ in self.args.files: | 119 for file_ in self.args.files: |
123 if not os.path.exists(file_): | 120 if not os.path.exists(file_): |
124 self.disp(_(u"file [{}] doesn't exist !").format(file_), error=True) | 121 self.disp(_("file [{}] doesn't exist !").format(file_), error=True) |
125 self.host.quit(1) | 122 self.host.quit(1) |
126 if not self.args.bz2 and os.path.isdir(file_): | 123 if not self.args.bz2 and os.path.isdir(file_): |
127 self.disp( | 124 self.disp( |
128 _( | 125 _( |
129 u"[{}] is a dir ! Please send files inside or use compression" | 126 "[{}] is a dir ! Please send files inside or use compression" |
130 ).format(file_) | 127 ).format(file_) |
131 ) | 128 ) |
132 self.host.quit(1) | 129 self.host.quit(1) |
133 | 130 |
134 self.full_dest_jid = self.host.get_full_jid(self.args.jid) | 131 self.full_dest_jid = self.host.get_full_jid(self.args.jid) |
135 extra = {} | 132 extra = {} |
136 if self.args.path: | 133 if self.args.path: |
137 extra[u"path"] = self.args.path | 134 extra["path"] = self.args.path |
138 if self.args.namespace: | 135 if self.args.namespace: |
139 extra[u"namespace"] = self.args.namespace | 136 extra["namespace"] = self.args.namespace |
140 | 137 |
141 if self.args.bz2: | 138 if self.args.bz2: |
142 with tempfile.NamedTemporaryFile("wb", delete=False) as buf: | 139 with tempfile.NamedTemporaryFile("wb", delete=False) as buf: |
143 self.host.addOnQuitCallback(os.unlink, buf.name) | 140 self.host.addOnQuitCallback(os.unlink, buf.name) |
144 self.disp(_(u"bz2 is an experimental option, use with caution")) | 141 self.disp(_("bz2 is an experimental option, use with caution")) |
145 # FIXME: check free space | 142 # FIXME: check free space |
146 self.disp(_(u"Starting compression, please wait...")) | 143 self.disp(_("Starting compression, please wait...")) |
147 sys.stdout.flush() | 144 sys.stdout.flush() |
148 bz2 = tarfile.open(mode="w:bz2", fileobj=buf) | 145 bz2 = tarfile.open(mode="w:bz2", fileobj=buf) |
149 archive_name = u"{}.tar.bz2".format( | 146 archive_name = "{}.tar.bz2".format( |
150 os.path.basename(self.args.files[0]) or u"compressed_files" | 147 os.path.basename(self.args.files[0]) or "compressed_files" |
151 ) | 148 ) |
152 for file_ in self.args.files: | 149 for file_ in self.args.files: |
153 self.disp(_(u"Adding {}").format(file_), 1) | 150 self.disp(_("Adding {}").format(file_), 1) |
154 bz2.add(file_) | 151 bz2.add(file_) |
155 bz2.close() | 152 bz2.close() |
156 self.disp(_(u"Done !"), 1) | 153 self.disp(_("Done !"), 1) |
157 | 154 |
158 self.host.bridge.fileSend( | 155 self.host.bridge.fileSend( |
159 self.full_dest_jid, | 156 self.full_dest_jid, |
160 buf.name, | 157 buf.name, |
161 self.args.name or archive_name, | 158 self.args.name or archive_name, |
191 ) | 188 ) |
192 self.need_loop = True | 189 self.need_loop = True |
193 | 190 |
194 @property | 191 @property |
195 def filename(self): | 192 def filename(self): |
196 return self.args.name or self.args.hash or u"output" | 193 return self.args.name or self.args.hash or "output" |
197 | 194 |
198 def add_parser_options(self): | 195 def add_parser_options(self): |
199 self.parser.add_argument( | 196 self.parser.add_argument( |
200 "jid", type=base.unicode_decoder, help=_(u"the destination jid") | 197 "jid", help=_("the destination jid") |
201 ) | 198 ) |
202 self.parser.add_argument( | 199 self.parser.add_argument( |
203 "-D", | 200 "-D", |
204 "--dest", | 201 "--dest", |
205 type=base.unicode_decoder, | |
206 help=_( | 202 help=_( |
207 u"destination path where the file will be saved (default: [current_dir]/[name|hash])" | 203 "destination path where the file will be saved (default: [current_dir]/[name|hash])" |
208 ), | 204 ), |
209 ) | 205 ) |
210 self.parser.add_argument( | 206 self.parser.add_argument( |
211 "-n", | 207 "-n", |
212 "--name", | 208 "--name", |
213 type=base.unicode_decoder, | 209 default="", |
214 default=u"", | 210 help=_("name of the file"), |
215 help=_(u"name of the file"), | |
216 ) | 211 ) |
217 self.parser.add_argument( | 212 self.parser.add_argument( |
218 "-H", | 213 "-H", |
219 "--hash", | 214 "--hash", |
220 type=base.unicode_decoder, | 215 default="", |
221 default=u"", | 216 help=_("hash of the file"), |
222 help=_(u"hash of the file"), | |
223 ) | 217 ) |
224 self.parser.add_argument( | 218 self.parser.add_argument( |
225 "-a", | 219 "-a", |
226 "--hash-algo", | 220 "--hash-algo", |
227 type=base.unicode_decoder, | 221 default="sha-256", |
228 default=u"sha-256", | 222 help=_("hash algorithm use for --hash (default: sha-256)"), |
229 help=_(u"hash algorithm use for --hash (default: sha-256)"), | |
230 ) | 223 ) |
231 self.parser.add_argument( | 224 self.parser.add_argument( |
232 "-d", | 225 "-d", |
233 "--path", | 226 "--path", |
234 type=base.unicode_decoder, | 227 help=("path to the directory containing the file"), |
235 help=(u"path to the directory containing the file"), | |
236 ) | 228 ) |
237 self.parser.add_argument( | 229 self.parser.add_argument( |
238 "-N", | 230 "-N", |
239 "--namespace", | 231 "--namespace", |
240 type=base.unicode_decoder, | 232 help=("namespace of the file"), |
241 help=(u"namespace of the file"), | |
242 ) | 233 ) |
243 self.parser.add_argument( | 234 self.parser.add_argument( |
244 "-f", | 235 "-f", |
245 "--force", | 236 "--force", |
246 action="store_true", | 237 action="store_true", |
247 help=_(u"overwrite existing file without confirmation"), | 238 help=_("overwrite existing file without confirmation"), |
248 ) | 239 ) |
249 | 240 |
250 def onProgressStarted(self, metadata): | 241 def onProgressStarted(self, metadata): |
251 self.disp(_(u"File copy started"), 2) | 242 self.disp(_("File copy started"), 2) |
252 | 243 |
253 def onProgressFinished(self, metadata): | 244 def onProgressFinished(self, metadata): |
254 self.disp(_(u"File received successfully"), 2) | 245 self.disp(_("File received successfully"), 2) |
255 | 246 |
256 def onProgressError(self, error_msg): | 247 def onProgressError(self, error_msg): |
257 if error_msg == C.PROGRESS_ERROR_DECLINED: | 248 if error_msg == C.PROGRESS_ERROR_DECLINED: |
258 self.disp(_(u"The file request has been refused")) | 249 self.disp(_("The file request has been refused")) |
259 else: | 250 else: |
260 self.disp(_(u"Error while requesting file: {}").format(error_msg), error=True) | 251 self.disp(_("Error while requesting file: {}").format(error_msg), error=True) |
261 | 252 |
262 def gotId(self, progress_id): | 253 def gotId(self, progress_id): |
263 """Called when a progress id has been received | 254 """Called when a progress id has been received |
264 | 255 |
265 @param progress_id(unicode): progress id | 256 @param progress_id(unicode): progress id |
273 ) | 264 ) |
274 self.host.quit(1) | 265 self.host.quit(1) |
275 | 266 |
276 def start(self): | 267 def start(self): |
277 if not self.args.name and not self.args.hash: | 268 if not self.args.name and not self.args.hash: |
278 self.parser.error(_(u"at least one of --name or --hash must be provided")) | 269 self.parser.error(_("at least one of --name or --hash must be provided")) |
279 # extra = dict(self.args.extra) | 270 # extra = dict(self.args.extra) |
280 if self.args.dest: | 271 if self.args.dest: |
281 path = os.path.abspath(os.path.expanduser(self.args.dest)) | 272 path = os.path.abspath(os.path.expanduser(self.args.dest)) |
282 if os.path.isdir(path): | 273 if os.path.isdir(path): |
283 path = os.path.join(path, self.filename) | 274 path = os.path.join(path, self.filename) |
284 else: | 275 else: |
285 path = os.path.abspath(self.filename) | 276 path = os.path.abspath(self.filename) |
286 | 277 |
287 if os.path.exists(path) and not self.args.force: | 278 if os.path.exists(path) and not self.args.force: |
288 message = _(u"File {path} already exists! Do you want to overwrite?").format( | 279 message = _("File {path} already exists! Do you want to overwrite?").format( |
289 path=path | 280 path=path |
290 ) | 281 ) |
291 confirm = raw_input(u"{} (y/N) ".format(message).encode("utf-8")) | 282 confirm = input("{} (y/N) ".format(message).encode("utf-8")) |
292 if confirm not in (u"y", u"Y"): | 283 if confirm not in ("y", "Y"): |
293 self.disp(_(u"file request cancelled")) | 284 self.disp(_("file request cancelled")) |
294 self.host.quit(2) | 285 self.host.quit(2) |
295 | 286 |
296 self.full_dest_jid = self.host.get_full_jid(self.args.jid) | 287 self.full_dest_jid = self.host.get_full_jid(self.args.jid) |
297 extra = {} | 288 extra = {} |
298 if self.args.path: | 289 if self.args.path: |
299 extra[u"path"] = self.args.path | 290 extra["path"] = self.args.path |
300 if self.args.namespace: | 291 if self.args.namespace: |
301 extra[u"namespace"] = self.args.namespace | 292 extra["namespace"] = self.args.namespace |
302 self.host.bridge.fileJingleRequest( | 293 self.host.bridge.fileJingleRequest( |
303 self.full_dest_jid, | 294 self.full_dest_jid, |
304 path, | 295 path, |
305 self.args.name, | 296 self.args.name, |
306 self.args.hash, | 297 self.args.hash, |
307 self.args.hash_algo if self.args.hash else u"", | 298 self.args.hash_algo if self.args.hash else "", |
308 extra, | 299 extra, |
309 self.profile, | 300 self.profile, |
310 callback=self.gotId, | 301 callback=self.gotId, |
311 errback=partial( | 302 errback=partial( |
312 self.errback, | 303 self.errback, |
313 msg=_(u"can't request file: {}"), | 304 msg=_("can't request file: {}"), |
314 exit_code=C.EXIT_BRIDGE_ERRBACK, | 305 exit_code=C.EXIT_BRIDGE_ERRBACK, |
315 ), | 306 ), |
316 ) | 307 ) |
317 | 308 |
318 | 309 |
330 C.META_TYPE_FILE: self.onFileAction, | 321 C.META_TYPE_FILE: self.onFileAction, |
331 C.META_TYPE_OVERWRITE: self.onOverwriteAction, | 322 C.META_TYPE_OVERWRITE: self.onOverwriteAction, |
332 } | 323 } |
333 | 324 |
334 def onProgressStarted(self, metadata): | 325 def onProgressStarted(self, metadata): |
335 self.disp(_(u"File copy started"), 2) | 326 self.disp(_("File copy started"), 2) |
336 | 327 |
337 def onProgressFinished(self, metadata): | 328 def onProgressFinished(self, metadata): |
338 self.disp(_(u"File received successfully"), 2) | 329 self.disp(_("File received successfully"), 2) |
339 if metadata.get("hash_verified", False): | 330 if metadata.get("hash_verified", False): |
340 try: | 331 try: |
341 self.disp( | 332 self.disp( |
342 _(u"hash checked: {algo}:{checksum}").format( | 333 _("hash checked: {algo}:{checksum}").format( |
343 algo=metadata["hash_algo"], checksum=metadata["hash"] | 334 algo=metadata["hash_algo"], checksum=metadata["hash"] |
344 ), | 335 ), |
345 1, | 336 1, |
346 ) | 337 ) |
347 except KeyError: | 338 except KeyError: |
348 self.disp(_(u"hash is checked but hash value is missing", 1), error=True) | 339 self.disp(_("hash is checked but hash value is missing", 1), error=True) |
349 else: | 340 else: |
350 self.disp(_(u"hash can't be verified"), 1) | 341 self.disp(_("hash can't be verified"), 1) |
351 | 342 |
352 def onProgressError(self, error_msg): | 343 def onProgressError(self, error_msg): |
353 self.disp(_(u"Error while receiving file: {}").format(error_msg), error=True) | 344 self.disp(_("Error while receiving file: {}").format(error_msg), error=True) |
354 | 345 |
355 def getXmluiId(self, action_data): | 346 def getXmluiId(self, action_data): |
356 # FIXME: we temporarily use ElementTree, but a real XMLUI managing module | 347 # FIXME: we temporarily use ElementTree, but a real XMLUI managing module |
357 # should be available in the futur | 348 # should be available in the futur |
358 # TODO: XMLUI module | 349 # TODO: XMLUI module |
359 try: | 350 try: |
360 xml_ui = action_data["xmlui"] | 351 xml_ui = action_data["xmlui"] |
361 except KeyError: | 352 except KeyError: |
362 self.disp(_(u"Action has no XMLUI"), 1) | 353 self.disp(_("Action has no XMLUI"), 1) |
363 else: | 354 else: |
364 ui = ET.fromstring(xml_ui.encode("utf-8")) | 355 ui = ET.fromstring(xml_ui.encode("utf-8")) |
365 xmlui_id = ui.get("submit") | 356 xmlui_id = ui.get("submit") |
366 if not xmlui_id: | 357 if not xmlui_id: |
367 self.disp(_(u"Invalid XMLUI received"), error=True) | 358 self.disp(_("Invalid XMLUI received"), error=True) |
368 return xmlui_id | 359 return xmlui_id |
369 | 360 |
370 def onFileAction(self, action_data, action_id, security_limit, profile): | 361 def onFileAction(self, action_data, action_id, security_limit, profile): |
371 xmlui_id = self.getXmluiId(action_data) | 362 xmlui_id = self.getXmluiId(action_data) |
372 if xmlui_id is None: | 363 if xmlui_id is None: |
373 return self.host.quitFromSignal(1) | 364 return self.host.quitFromSignal(1) |
374 try: | 365 try: |
375 from_jid = jid.JID(action_data["meta_from_jid"]) | 366 from_jid = jid.JID(action_data["meta_from_jid"]) |
376 except KeyError: | 367 except KeyError: |
377 self.disp(_(u"Ignoring action without from_jid data"), 1) | 368 self.disp(_("Ignoring action without from_jid data"), 1) |
378 return | 369 return |
379 try: | 370 try: |
380 progress_id = action_data["meta_progress_id"] | 371 progress_id = action_data["meta_progress_id"] |
381 except KeyError: | 372 except KeyError: |
382 self.disp(_(u"ignoring action without progress id"), 1) | 373 self.disp(_("ignoring action without progress id"), 1) |
383 return | 374 return |
384 | 375 |
385 if not self.bare_jids or from_jid.bare in self.bare_jids: | 376 if not self.bare_jids or from_jid.bare in self.bare_jids: |
386 if self._overwrite_refused: | 377 if self._overwrite_refused: |
387 self.disp(_(u"File refused because overwrite is needed"), error=True) | 378 self.disp(_("File refused because overwrite is needed"), error=True) |
388 self.host.bridge.launchAction( | 379 self.host.bridge.launchAction( |
389 xmlui_id, {"cancelled": C.BOOL_TRUE}, profile_key=profile | 380 xmlui_id, {"cancelled": C.BOOL_TRUE}, profile_key=profile |
390 ) | 381 ) |
391 return self.host.quitFromSignal(2) | 382 return self.host.quitFromSignal(2) |
392 self.progress_id = progress_id | 383 self.progress_id = progress_id |
398 if xmlui_id is None: | 389 if xmlui_id is None: |
399 return self.host.quitFromSignal(1) | 390 return self.host.quitFromSignal(1) |
400 try: | 391 try: |
401 progress_id = action_data["meta_progress_id"] | 392 progress_id = action_data["meta_progress_id"] |
402 except KeyError: | 393 except KeyError: |
403 self.disp(_(u"ignoring action without progress id"), 1) | 394 self.disp(_("ignoring action without progress id"), 1) |
404 return | 395 return |
405 self.disp(_(u"Overwriting needed"), 1) | 396 self.disp(_("Overwriting needed"), 1) |
406 | 397 |
407 if progress_id == self.progress_id: | 398 if progress_id == self.progress_id: |
408 if self.args.force: | 399 if self.args.force: |
409 self.disp(_(u"Overwrite accepted"), 2) | 400 self.disp(_("Overwrite accepted"), 2) |
410 else: | 401 else: |
411 self.disp(_(u"Refused to overwrite"), 2) | 402 self.disp(_("Refused to overwrite"), 2) |
412 self._overwrite_refused = True | 403 self._overwrite_refused = True |
413 | 404 |
414 xmlui_data = {"answer": C.boolConst(self.args.force)} | 405 xmlui_data = {"answer": C.boolConst(self.args.force)} |
415 self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile) | 406 self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile) |
416 | 407 |
417 def add_parser_options(self): | 408 def add_parser_options(self): |
418 self.parser.add_argument( | 409 self.parser.add_argument( |
419 "jids", | 410 "jids", |
420 type=base.unicode_decoder, | |
421 nargs="*", | 411 nargs="*", |
422 help=_(u"jids accepted (accept everything if none is specified)"), | 412 help=_("jids accepted (accept everything if none is specified)"), |
423 ) | 413 ) |
424 self.parser.add_argument( | 414 self.parser.add_argument( |
425 "-m", | 415 "-m", |
426 "--multiple", | 416 "--multiple", |
427 action="store_true", | 417 action="store_true", |
428 help=_(u"accept multiple files (you'll have to stop manually)"), | 418 help=_("accept multiple files (you'll have to stop manually)"), |
429 ) | 419 ) |
430 self.parser.add_argument( | 420 self.parser.add_argument( |
431 "-f", | 421 "-f", |
432 "--force", | 422 "--force", |
433 action="store_true", | 423 action="store_true", |
434 help=_( | 424 help=_( |
435 u"force overwritting of existing files (/!\\ name is choosed by sender)" | 425 "force overwritting of existing files (/!\\ name is choosed by sender)" |
436 ), | 426 ), |
437 ) | 427 ) |
438 self.parser.add_argument( | 428 self.parser.add_argument( |
439 "--path", | 429 "--path", |
440 default=".", | 430 default=".", |
441 metavar="DIR", | 431 metavar="DIR", |
442 help=_(u"destination path (default: working directory)"), | 432 help=_("destination path (default: working directory)"), |
443 ) | 433 ) |
444 | 434 |
445 def start(self): | 435 def start(self): |
446 self.bare_jids = [jid.JID(jid_).bare for jid_ in self.args.jids] | 436 self.bare_jids = [jid.JID(jid_).bare for jid_ in self.args.jids] |
447 self.path = os.path.abspath(self.args.path) | 437 self.path = os.path.abspath(self.args.path) |
448 if not os.path.isdir(self.path): | 438 if not os.path.isdir(self.path): |
449 self.disp(_(u"Given path is not a directory !", error=True)) | 439 self.disp(_("Given path is not a directory !", error=True)) |
450 self.host.quit(2) | 440 self.host.quit(2) |
451 if self.args.multiple: | 441 if self.args.multiple: |
452 self.host.quit_on_progress_end = False | 442 self.host.quit_on_progress_end = False |
453 self.disp(_(u"waiting for incoming file request"), 2) | 443 self.disp(_("waiting for incoming file request"), 2) |
454 | 444 |
455 | 445 |
456 class Upload(base.CommandBase): | 446 class Upload(base.CommandBase): |
457 def __init__(self, host): | 447 def __init__(self, host): |
458 super(Upload, self).__init__( | 448 super(Upload, self).__init__( |
462 | 452 |
463 def add_parser_options(self): | 453 def add_parser_options(self): |
464 self.parser.add_argument("file", type=str, help=_("file to upload")) | 454 self.parser.add_argument("file", type=str, help=_("file to upload")) |
465 self.parser.add_argument( | 455 self.parser.add_argument( |
466 "jid", | 456 "jid", |
467 type=base.unicode_decoder, | |
468 nargs="?", | 457 nargs="?", |
469 help=_("jid of upload component (nothing to autodetect)"), | 458 help=_("jid of upload component (nothing to autodetect)"), |
470 ) | 459 ) |
471 self.parser.add_argument( | 460 self.parser.add_argument( |
472 "--ignore-tls-errors", | 461 "--ignore-tls-errors", |
473 action="store_true", | 462 action="store_true", |
474 help=_("ignore invalide TLS certificate"), | 463 help=_("ignore invalide TLS certificate"), |
475 ) | 464 ) |
476 | 465 |
477 def onProgressStarted(self, metadata): | 466 def onProgressStarted(self, metadata): |
478 self.disp(_(u"File upload started"), 2) | 467 self.disp(_("File upload started"), 2) |
479 | 468 |
480 def onProgressFinished(self, metadata): | 469 def onProgressFinished(self, metadata): |
481 self.disp(_(u"File uploaded successfully"), 2) | 470 self.disp(_("File uploaded successfully"), 2) |
482 try: | 471 try: |
483 url = metadata["url"] | 472 url = metadata["url"] |
484 except KeyError: | 473 except KeyError: |
485 self.disp(u"download URL not found in metadata") | 474 self.disp("download URL not found in metadata") |
486 else: | 475 else: |
487 self.disp(_(u"URL to retrieve the file:"), 1) | 476 self.disp(_("URL to retrieve the file:"), 1) |
488 # XXX: url is display alone on a line to make parsing easier | 477 # XXX: url is display alone on a line to make parsing easier |
489 self.disp(url) | 478 self.disp(url) |
490 | 479 |
491 def onProgressError(self, error_msg): | 480 def onProgressError(self, error_msg): |
492 self.disp(_(u"Error while uploading file: {}").format(error_msg), error=True) | 481 self.disp(_("Error while uploading file: {}").format(error_msg), error=True) |
493 | 482 |
494 def gotId(self, data, file_): | 483 def gotId(self, data, file_): |
495 """Called when a progress id has been received | 484 """Called when a progress id has been received |
496 | 485 |
497 @param pid(unicode): progress id | 486 @param pid(unicode): progress id |
499 """ | 488 """ |
500 try: | 489 try: |
501 self.progress_id = data["progress"] | 490 self.progress_id = data["progress"] |
502 except KeyError: | 491 except KeyError: |
503 # TODO: if 'xmlui' key is present, manage xmlui message display | 492 # TODO: if 'xmlui' key is present, manage xmlui message display |
504 self.disp(_(u"Can't upload file"), error=True) | 493 self.disp(_("Can't upload file"), error=True) |
505 self.host.quit(2) | 494 self.host.quit(2) |
506 | 495 |
507 def error(self, failure): | 496 def error(self, failure): |
508 self.disp( | 497 self.disp( |
509 _("Error while trying to upload a file: {reason}").format(reason=failure), | 498 _("Error while trying to upload a file: {reason}").format(reason=failure), |
512 self.host.quit(1) | 501 self.host.quit(1) |
513 | 502 |
514 def start(self): | 503 def start(self): |
515 file_ = self.args.file | 504 file_ = self.args.file |
516 if not os.path.exists(file_): | 505 if not os.path.exists(file_): |
517 self.disp(_(u"file [{}] doesn't exist !").format(file_), error=True) | 506 self.disp(_("file [{}] doesn't exist !").format(file_), error=True) |
518 self.host.quit(1) | 507 self.host.quit(1) |
519 if os.path.isdir(file_): | 508 if os.path.isdir(file_): |
520 self.disp(_(u"[{}] is a dir! Can't upload a dir").format(file_)) | 509 self.disp(_("[{}] is a dir! Can't upload a dir").format(file_)) |
521 self.host.quit(1) | 510 self.host.quit(1) |
522 | 511 |
523 self.full_dest_jid = ( | 512 self.full_dest_jid = ( |
524 self.host.get_full_jid(self.args.jid) if self.args.jid is not None else "" | 513 self.host.get_full_jid(self.args.jid) if self.args.jid is not None else "" |
525 ) | 514 ) |
545 super(ShareList, self).__init__( | 534 super(ShareList, self).__init__( |
546 host, | 535 host, |
547 "list", | 536 "list", |
548 use_output=C.OUTPUT_LIST_DICT, | 537 use_output=C.OUTPUT_LIST_DICT, |
549 extra_outputs=extra_outputs, | 538 extra_outputs=extra_outputs, |
550 help=_(u"retrieve files shared by an entity"), | 539 help=_("retrieve files shared by an entity"), |
551 use_verbose=True, | 540 use_verbose=True, |
552 ) | 541 ) |
553 self.need_loop = True | 542 self.need_loop = True |
554 | 543 |
555 def add_parser_options(self): | 544 def add_parser_options(self): |
556 self.parser.add_argument( | 545 self.parser.add_argument( |
557 "-d", | 546 "-d", |
558 "--path", | 547 "--path", |
559 default=u"", | 548 default="", |
560 help=_(u"path to the directory containing the files"), | 549 help=_("path to the directory containing the files"), |
561 ) | 550 ) |
562 self.parser.add_argument( | 551 self.parser.add_argument( |
563 "jid", | 552 "jid", |
564 type=base.unicode_decoder, | |
565 nargs="?", | 553 nargs="?", |
566 default="", | 554 default="", |
567 help=_("jid of sharing entity (nothing to check our own jid)"), | 555 help=_("jid of sharing entity (nothing to check our own jid)"), |
568 ) | 556 ) |
569 | 557 |
570 def file_gen(self, files_data): | 558 def file_gen(self, files_data): |
571 for file_data in files_data: | 559 for file_data in files_data: |
572 yield file_data[u"name"] | 560 yield file_data["name"] |
573 yield file_data.get(u"size", "") | 561 yield file_data.get("size", "") |
574 yield file_data.get(u"hash", "") | 562 yield file_data.get("hash", "") |
575 | 563 |
576 def _name_filter(self, name, row): | 564 def _name_filter(self, name, row): |
577 if row.type == C.FILE_TYPE_DIRECTORY: | 565 if row.type == C.FILE_TYPE_DIRECTORY: |
578 return A.color(C.A_DIRECTORY, name) | 566 return A.color(C.A_DIRECTORY, name) |
579 elif row.type == C.FILE_TYPE_FILE: | 567 elif row.type == C.FILE_TYPE_FILE: |
580 return A.color(C.A_FILE, name) | 568 return A.color(C.A_FILE, name) |
581 else: | 569 else: |
582 self.disp(_(u"unknown file type: {type}").format(type=row.type), error=True) | 570 self.disp(_("unknown file type: {type}").format(type=row.type), error=True) |
583 return name | 571 return name |
584 | 572 |
585 def _size_filter(self, size, row): | 573 def _size_filter(self, size, row): |
586 if not size: | 574 if not size: |
587 return u"" | 575 return "" |
588 size = int(size) | 576 size = int(size) |
589 # cf. https://stackoverflow.com/a/1094933 (thanks) | 577 # cf. https://stackoverflow.com/a/1094933 (thanks) |
590 suffix = u"o" | 578 suffix = "o" |
591 for unit in [u"", u"Ki", u"Mi", u"Gi", u"Ti", u"Pi", u"Ei", u"Zi"]: | 579 for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]: |
592 if abs(size) < 1024.0: | 580 if abs(size) < 1024.0: |
593 return A.color(A.BOLD, u"{:.2f}".format(size), unit, suffix) | 581 return A.color(A.BOLD, "{:.2f}".format(size), unit, suffix) |
594 size /= 1024.0 | 582 size /= 1024.0 |
595 | 583 |
596 return A.color(A.BOLD, u"{:.2f}".format(size), u"Yi", suffix) | 584 return A.color(A.BOLD, "{:.2f}".format(size), "Yi", suffix) |
597 | 585 |
598 def default_output(self, files_data): | 586 def default_output(self, files_data): |
599 """display files a way similar to ls""" | 587 """display files a way similar to ls""" |
600 files_data.sort(key=lambda d: d["name"].lower()) | 588 files_data.sort(key=lambda d: d["name"].lower()) |
601 show_header = False | 589 show_header = False |
602 if self.verbosity == 0: | 590 if self.verbosity == 0: |
603 headers = (u"name", u"type") | 591 headers = ("name", "type") |
604 elif self.verbosity == 1: | 592 elif self.verbosity == 1: |
605 headers = (u"name", u"type", u"size") | 593 headers = ("name", "type", "size") |
606 elif self.verbosity > 1: | 594 elif self.verbosity > 1: |
607 show_header = True | 595 show_header = True |
608 headers = (u"name", u"type", u"size", u"hash") | 596 headers = ("name", "type", "size", "hash") |
609 table = common.Table.fromDict( | 597 table = common.Table.fromDict( |
610 self.host, | 598 self.host, |
611 files_data, | 599 files_data, |
612 headers, | 600 headers, |
613 filters={u"name": self._name_filter, u"size": self._size_filter}, | 601 filters={"name": self._name_filter, "size": self._size_filter}, |
614 defaults={u"size": u"", u"hash": u""}, | 602 defaults={"size": "", "hash": ""}, |
615 ) | 603 ) |
616 table.display_blank(show_header=show_header, hide_cols=["type"]) | 604 table.display_blank(show_header=show_header, hide_cols=["type"]) |
617 | 605 |
618 def _FISListCb(self, files_data): | 606 def _FISListCb(self, files_data): |
619 self.output(files_data) | 607 self.output(files_data) |
626 {}, | 614 {}, |
627 self.profile, | 615 self.profile, |
628 callback=self._FISListCb, | 616 callback=self._FISListCb, |
629 errback=partial( | 617 errback=partial( |
630 self.errback, | 618 self.errback, |
631 msg=_(u"can't retrieve shared files: {}"), | 619 msg=_("can't retrieve shared files: {}"), |
632 exit_code=C.EXIT_BRIDGE_ERRBACK, | 620 exit_code=C.EXIT_BRIDGE_ERRBACK, |
633 ), | 621 ), |
634 ) | 622 ) |
635 | 623 |
636 | 624 |
637 class SharePath(base.CommandBase): | 625 class SharePath(base.CommandBase): |
638 def __init__(self, host): | 626 def __init__(self, host): |
639 super(SharePath, self).__init__( | 627 super(SharePath, self).__init__( |
640 host, "path", help=_(u"share a file or directory"), use_verbose=True | 628 host, "path", help=_("share a file or directory"), use_verbose=True |
641 ) | 629 ) |
642 self.need_loop = True | 630 self.need_loop = True |
643 | 631 |
644 def add_parser_options(self): | 632 def add_parser_options(self): |
645 self.parser.add_argument( | 633 self.parser.add_argument( |
646 "-n", | 634 "-n", |
647 "--name", | 635 "--name", |
648 type=base.unicode_decoder, | 636 default="", |
649 default=u"", | 637 help=_("virtual name to use (default: use directory/file name)"), |
650 help=_(u"virtual name to use (default: use directory/file name)"), | |
651 ) | 638 ) |
652 perm_group = self.parser.add_mutually_exclusive_group() | 639 perm_group = self.parser.add_mutually_exclusive_group() |
653 perm_group.add_argument( | 640 perm_group.add_argument( |
654 "-j", | 641 "-j", |
655 "--jid", | 642 "--jid", |
656 type=base.unicode_decoder, | |
657 action="append", | 643 action="append", |
658 dest="jids", | 644 dest="jids", |
659 default=[], | 645 default=[], |
660 help=_(u"jid of contacts allowed to retrieve the files"), | 646 help=_("jid of contacts allowed to retrieve the files"), |
661 ) | 647 ) |
662 perm_group.add_argument( | 648 perm_group.add_argument( |
663 "--public", | 649 "--public", |
664 action="store_true", | 650 action="store_true", |
665 help=_( | 651 help=_( |
666 u"share publicly the file(s) (/!\\ *everybody* will be able to access them)" | 652 "share publicly the file(s) (/!\\ *everybody* will be able to access them)" |
667 ), | 653 ), |
668 ) | 654 ) |
669 self.parser.add_argument( | 655 self.parser.add_argument( |
670 "path", | 656 "path", |
671 type=base.unicode_decoder, | 657 help=_("path to a file or directory to share"), |
672 help=_(u"path to a file or directory to share"), | |
673 ) | 658 ) |
674 | 659 |
675 def _FISSharePathCb(self, name): | 660 def _FISSharePathCb(self, name): |
676 self.disp( | 661 self.disp( |
677 _(u'{path} shared under the name "{name}"').format(path=self.path, name=name) | 662 _('{path} shared under the name "{name}"').format(path=self.path, name=name) |
678 ) | 663 ) |
679 self.host.quit() | 664 self.host.quit() |
680 | 665 |
681 def start(self): | 666 def start(self): |
682 self.path = os.path.abspath(self.args.path) | 667 self.path = os.path.abspath(self.args.path) |
683 if self.args.public: | 668 if self.args.public: |
684 access = {u"read": {u"type": u"public"}} | 669 access = {"read": {"type": "public"}} |
685 else: | 670 else: |
686 jids = self.args.jids | 671 jids = self.args.jids |
687 if jids: | 672 if jids: |
688 access = {u"read": {u"type": "whitelist", u"jids": jids}} | 673 access = {"read": {"type": "whitelist", "jids": jids}} |
689 else: | 674 else: |
690 access = {} | 675 access = {} |
691 self.host.bridge.FISSharePath( | 676 self.host.bridge.FISSharePath( |
692 self.args.name, | 677 self.args.name, |
693 self.path, | 678 self.path, |
694 json.dumps(access, ensure_ascii=False), | 679 json.dumps(access, ensure_ascii=False), |
695 self.profile, | 680 self.profile, |
696 callback=self._FISSharePathCb, | 681 callback=self._FISSharePathCb, |
697 errback=partial( | 682 errback=partial( |
698 self.errback, | 683 self.errback, |
699 msg=_(u"can't share path: {}"), | 684 msg=_("can't share path: {}"), |
700 exit_code=C.EXIT_BRIDGE_ERRBACK, | 685 exit_code=C.EXIT_BRIDGE_ERRBACK, |
701 ), | 686 ), |
702 ) | 687 ) |
703 | 688 |
704 | 689 |
705 class ShareInvite(base.CommandBase): | 690 class ShareInvite(base.CommandBase): |
706 def __init__(self, host): | 691 def __init__(self, host): |
707 super(ShareInvite, self).__init__( | 692 super(ShareInvite, self).__init__( |
708 host, "invite", help=_(u"send invitation for a shared repository") | 693 host, "invite", help=_("send invitation for a shared repository") |
709 ) | 694 ) |
710 self.need_loop = True | 695 self.need_loop = True |
711 | 696 |
712 def add_parser_options(self): | 697 def add_parser_options(self): |
713 self.parser.add_argument( | 698 self.parser.add_argument( |
714 "-n", | 699 "-n", |
715 "--name", | 700 "--name", |
716 type=base.unicode_decoder, | 701 default="", |
717 default=u"", | 702 help=_("name of the repository"), |
718 help=_(u"name of the repository"), | |
719 ) | 703 ) |
720 self.parser.add_argument( | 704 self.parser.add_argument( |
721 "-N", | 705 "-N", |
722 "--namespace", | 706 "--namespace", |
723 type=base.unicode_decoder, | 707 default="", |
724 default=u"", | 708 help=_("namespace of the repository"), |
725 help=_(u"namespace of the repository"), | |
726 ) | 709 ) |
727 self.parser.add_argument( | 710 self.parser.add_argument( |
728 "-P", | 711 "-P", |
729 "--path", | 712 "--path", |
730 type=base.unicode_decoder, | 713 help=_("path to the repository"), |
731 help=_(u"path to the repository"), | |
732 ) | 714 ) |
733 self.parser.add_argument( | 715 self.parser.add_argument( |
734 "-t", | 716 "-t", |
735 "--type", | 717 "--type", |
736 choices=[u"files", u"photos"], | 718 choices=["files", "photos"], |
737 default=u"files", | 719 default="files", |
738 help=_(u"type of the repository"), | 720 help=_("type of the repository"), |
739 ) | 721 ) |
740 self.parser.add_argument( | 722 self.parser.add_argument( |
741 "-T", | 723 "-T", |
742 "--thumbnail", | 724 "--thumbnail", |
743 type=base.unicode_decoder, | 725 help=_("https URL of a image to use as thumbnail"), |
744 help=_(u"https URL of a image to use as thumbnail"), | |
745 ) | 726 ) |
746 self.parser.add_argument( | 727 self.parser.add_argument( |
747 "service", | 728 "service", |
748 type=base.unicode_decoder, | 729 help=_("jid of the file sharing service hosting the repository"), |
749 help=_(u"jid of the file sharing service hosting the repository"), | |
750 ) | 730 ) |
751 self.parser.add_argument( | 731 self.parser.add_argument( |
752 "jid", | 732 "jid", |
753 type=base.unicode_decoder, | 733 help=_("jid of the person to invite"), |
754 help=_(u"jid of the person to invite"), | |
755 ) | 734 ) |
756 | 735 |
757 def _FISInviteCb(self): | 736 def _FISInviteCb(self): |
758 self.disp( | 737 self.disp( |
759 _(u'invitation sent to {entity}').format(entity=self.args.jid) | 738 _('invitation sent to {entity}').format(entity=self.args.jid) |
760 ) | 739 ) |
761 self.host.quit() | 740 self.host.quit() |
762 | 741 |
763 def start(self): | 742 def start(self): |
764 self.path = os.path.normpath(self.args.path) if self.args.path else u"" | 743 self.path = os.path.normpath(self.args.path) if self.args.path else "" |
765 extra = {} | 744 extra = {} |
766 if self.args.thumbnail is not None: | 745 if self.args.thumbnail is not None: |
767 if not self.args.thumbnail.startswith(u'http'): | 746 if not self.args.thumbnail.startswith('http'): |
768 self.parser.error(_(u"only http(s) links are allowed with --thumbnail")) | 747 self.parser.error(_("only http(s) links are allowed with --thumbnail")) |
769 else: | 748 else: |
770 extra[u'thumb_url'] = self.args.thumbnail | 749 extra['thumb_url'] = self.args.thumbnail |
771 self.host.bridge.FISInvite( | 750 self.host.bridge.FISInvite( |
772 self.args.jid, | 751 self.args.jid, |
773 self.args.service, | 752 self.args.service, |
774 self.args.type, | 753 self.args.type, |
775 self.args.namespace, | 754 self.args.namespace, |
778 data_format.serialise(extra), | 757 data_format.serialise(extra), |
779 self.profile, | 758 self.profile, |
780 callback=self._FISInviteCb, | 759 callback=self._FISInviteCb, |
781 errback=partial( | 760 errback=partial( |
782 self.errback, | 761 self.errback, |
783 msg=_(u"can't send invitation: {}"), | 762 msg=_("can't send invitation: {}"), |
784 exit_code=C.EXIT_BRIDGE_ERRBACK, | 763 exit_code=C.EXIT_BRIDGE_ERRBACK, |
785 ), | 764 ), |
786 ) | 765 ) |
787 | 766 |
788 | 767 |
789 class Share(base.CommandBase): | 768 class Share(base.CommandBase): |
790 subcommands = (ShareList, SharePath, ShareInvite) | 769 subcommands = (ShareList, SharePath, ShareInvite) |
791 | 770 |
792 def __init__(self, host): | 771 def __init__(self, host): |
793 super(Share, self).__init__( | 772 super(Share, self).__init__( |
794 host, "share", use_profile=False, help=_(u"files sharing management") | 773 host, "share", use_profile=False, help=_("files sharing management") |
795 ) | 774 ) |
796 | 775 |
797 | 776 |
798 class File(base.CommandBase): | 777 class File(base.CommandBase): |
799 subcommands = (Send, Request, Receive, Upload, Share) | 778 subcommands = (Send, Request, Receive, Upload, Share) |
800 | 779 |
801 def __init__(self, host): | 780 def __init__(self, host): |
802 super(File, self).__init__( | 781 super(File, self).__init__( |
803 host, "file", use_profile=False, help=_(u"files sending/receiving/management") | 782 host, "file", use_profile=False, help=_("files sending/receiving/management") |
804 ) | 783 ) |