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 )