comparison sat/plugins/plugin_xep_0234.py @ 4037:524856bd7b19

massive refactoring to switch from camelCase to snake_case: historically, Libervia (SàT before) was using camelCase as allowed by PEP8 when using a pre-PEP8 code, to use the same coding style as in Twisted. However, snake_case is more readable and it's better to follow PEP8 best practices, so it has been decided to move on full snake_case. Because Libervia has a huge codebase, this ended with a ugly mix of camelCase and snake_case. To fix that, this patch does a big refactoring by renaming every function and method (including bridge) that are not coming from Twisted or Wokkel, to use fully snake_case. This is a massive change, and may result in some bugs.
author Goffi <goffi@goffi.org>
date Sat, 08 Apr 2023 13:54:42 +0200
parents 8e7d5796fb23
children 2ced30f6d5de
comparison
equal deleted inserted replaced
4036:c4464d7ae97b 4037:524856bd7b19
69 human_name = D_("file transfer") 69 human_name = D_("file transfer")
70 70
71 def __init__(self, host): 71 def __init__(self, host):
72 log.info(_("plugin Jingle File Transfer initialization")) 72 log.info(_("plugin Jingle File Transfer initialization"))
73 self.host = host 73 self.host = host
74 host.registerNamespace("jingle-ft", NS_JINGLE_FT) 74 host.register_namespace("jingle-ft", NS_JINGLE_FT)
75 self._j = host.plugins["XEP-0166"] # shortcut to access jingle 75 self._j = host.plugins["XEP-0166"] # shortcut to access jingle
76 self._j.registerApplication(NS_JINGLE_FT, self) 76 self._j.register_application(NS_JINGLE_FT, self)
77 self._f = host.plugins["FILE"] 77 self._f = host.plugins["FILE"]
78 self._f.register(self, priority=10000) 78 self._f.register(self, priority=10000)
79 self._hash = self.host.plugins["XEP-0300"] 79 self._hash = self.host.plugins["XEP-0300"]
80 host.bridge.addMethod( 80 host.bridge.add_method(
81 "fileJingleSend", 81 "file_jingle_send",
82 ".plugin", 82 ".plugin",
83 in_sign="ssssa{ss}s", 83 in_sign="ssssa{ss}s",
84 out_sign="", 84 out_sign="",
85 method=self._fileSend, 85 method=self._file_send,
86 async_=True, 86 async_=True,
87 ) 87 )
88 host.bridge.addMethod( 88 host.bridge.add_method(
89 "fileJingleRequest", 89 "file_jingle_request",
90 ".plugin", 90 ".plugin",
91 in_sign="sssssa{ss}s", 91 in_sign="sssssa{ss}s",
92 out_sign="s", 92 out_sign="s",
93 method=self._fileJingleRequest, 93 method=self._file_jingle_request,
94 async_=True, 94 async_=True,
95 ) 95 )
96 96
97 def getHandler(self, client): 97 def get_handler(self, client):
98 return XEP_0234_handler() 98 return XEP_0234_handler()
99 99
100 def getProgressId(self, session, content_name): 100 def get_progress_id(self, session, content_name):
101 """Return a unique progress ID 101 """Return a unique progress ID
102 102
103 @param session(dict): jingle session 103 @param session(dict): jingle session
104 @param content_name(unicode): name of the content 104 @param content_name(unicode): name of the content
105 @return (unicode): unique progress id 105 @return (unicode): unique progress id
106 """ 106 """
107 return "{}_{}".format(session["id"], content_name) 107 return "{}_{}".format(session["id"], content_name)
108 108
109 async def canHandleFileSend(self, client, peer_jid, filepath): 109 async def can_handle_file_send(self, client, peer_jid, filepath):
110 if peer_jid.resource: 110 if peer_jid.resource:
111 return await self.host.hasFeature(client, NS_JINGLE_FT, peer_jid) 111 return await self.host.hasFeature(client, NS_JINGLE_FT, peer_jid)
112 else: 112 else:
113 # if we have a bare jid, Jingle Message Initiation will be tried 113 # if we have a bare jid, Jingle Message Initiation will be tried
114 return True 114 return True
115 115
116 # generic methods 116 # generic methods
117 117
118 def buildFileElement( 118 def build_file_element(
119 self, client, name=None, file_hash=None, hash_algo=None, size=None, 119 self, client, name=None, file_hash=None, hash_algo=None, size=None,
120 mime_type=None, desc=None, modified=None, transfer_range=None, path=None, 120 mime_type=None, desc=None, modified=None, transfer_range=None, path=None,
121 namespace=None, file_elt=None, **kwargs): 121 namespace=None, file_elt=None, **kwargs):
122 """Generate a <file> element with available metadata 122 """Generate a <file> element with available metadata
123 123
166 range_elt["offset"] = transfer_range.offset 166 range_elt["offset"] = transfer_range.offset
167 if transfer_range.length is not None: 167 if transfer_range.length is not None:
168 range_elt["length"] = transfer_range.length 168 range_elt["length"] = transfer_range.length
169 if file_hash is not None: 169 if file_hash is not None:
170 if not file_hash: 170 if not file_hash:
171 file_elt.addChild(self._hash.buildHashUsedElt()) 171 file_elt.addChild(self._hash.build_hash_used_elt())
172 else: 172 else:
173 file_elt.addChild(self._hash.buildHashElt(file_hash, hash_algo)) 173 file_elt.addChild(self._hash.build_hash_elt(file_hash, hash_algo))
174 elif hash_algo is not None: 174 elif hash_algo is not None:
175 file_elt.addChild(self._hash.buildHashUsedElt(hash_algo)) 175 file_elt.addChild(self._hash.build_hash_used_elt(hash_algo))
176 self.host.trigger.point( 176 self.host.trigger.point(
177 "XEP-0234_buildFileElement", client, file_elt, extra_args=kwargs) 177 "XEP-0234_buildFileElement", client, file_elt, extra_args=kwargs)
178 if kwargs: 178 if kwargs:
179 for kw in kwargs: 179 for kw in kwargs:
180 log.debug("ignored keyword: {}".format(kw)) 180 log.debug("ignored keyword: {}".format(kw))
181 return file_elt 181 return file_elt
182 182
183 def buildFileElementFromDict(self, client, file_data, **kwargs): 183 def build_file_element_from_dict(self, client, file_data, **kwargs):
184 """like buildFileElement but get values from a file_data dict 184 """like build_file_element but get values from a file_data dict
185 185
186 @param file_data(dict): metadata to use 186 @param file_data(dict): metadata to use
187 @param **kwargs: data to override 187 @param **kwargs: data to override
188 """ 188 """
189 if kwargs: 189 if kwargs:
193 file_data["mime_type"] = ( 193 file_data["mime_type"] = (
194 f'{file_data.pop("media_type")}/{file_data.pop("media_subtype")}' 194 f'{file_data.pop("media_type")}/{file_data.pop("media_subtype")}'
195 ) 195 )
196 except KeyError: 196 except KeyError:
197 pass 197 pass
198 return self.buildFileElement(client, **file_data) 198 return self.build_file_element(client, **file_data)
199 199
200 async def parseFileElement( 200 async def parse_file_element(
201 self, client, file_elt, file_data=None, given=False, parent_elt=None, 201 self, client, file_elt, file_data=None, given=False, parent_elt=None,
202 keep_empty_range=False): 202 keep_empty_range=False):
203 """Parse a <file> element and file dictionary accordingly 203 """Parse a <file> element and file dictionary accordingly
204 204
205 @param file_data(dict, None): dict where the data will be set 205 @param file_data(dict, None): dict where the data will be set
246 if name == "..": 246 if name == "..":
247 # we don't want to go to parent dir when joining to a path 247 # we don't want to go to parent dir when joining to a path
248 name = "--" 248 name = "--"
249 file_data["name"] = name 249 file_data["name"] = name
250 elif name is not None and ("/" in name or "\\" in name): 250 elif name is not None and ("/" in name or "\\" in name):
251 file_data["name"] = regex.pathEscape(name) 251 file_data["name"] = regex.path_escape(name)
252 252
253 try: 253 try:
254 file_data["mime_type"] = str( 254 file_data["mime_type"] = str(
255 next(file_elt.elements(NS_JINGLE_FT, "media-type")) 255 next(file_elt.elements(NS_JINGLE_FT, "media-type"))
256 ) 256 )
282 file_data["transfer_range"] = Range(offset=offset, length=length) 282 file_data["transfer_range"] = Range(offset=offset, length=length)
283 283
284 prefix = "given_" if given else "" 284 prefix = "given_" if given else ""
285 hash_algo_key, hash_key = "hash_algo", prefix + "file_hash" 285 hash_algo_key, hash_key = "hash_algo", prefix + "file_hash"
286 try: 286 try:
287 file_data[hash_algo_key], file_data[hash_key] = self._hash.parseHashElt( 287 file_data[hash_algo_key], file_data[hash_key] = self._hash.parse_hash_elt(
288 file_elt 288 file_elt
289 ) 289 )
290 except exceptions.NotFound: 290 except exceptions.NotFound:
291 pass 291 pass
292 292
294 294
295 return file_data 295 return file_data
296 296
297 # bridge methods 297 # bridge methods
298 298
299 def _fileSend( 299 def _file_send(
300 self, 300 self,
301 peer_jid, 301 peer_jid,
302 filepath, 302 filepath,
303 name="", 303 name="",
304 file_desc="", 304 file_desc="",
305 extra=None, 305 extra=None,
306 profile=C.PROF_KEY_NONE, 306 profile=C.PROF_KEY_NONE,
307 ): 307 ):
308 client = self.host.getClient(profile) 308 client = self.host.get_client(profile)
309 return defer.ensureDeferred(self.fileSend( 309 return defer.ensureDeferred(self.file_send(
310 client, 310 client,
311 jid.JID(peer_jid), 311 jid.JID(peer_jid),
312 filepath, 312 filepath,
313 name or None, 313 name or None,
314 file_desc or None, 314 file_desc or None,
315 extra or None, 315 extra or None,
316 )) 316 ))
317 317
318 async def fileSend( 318 async def file_send(
319 self, client, peer_jid, filepath, name, file_desc=None, extra=None 319 self, client, peer_jid, filepath, name, file_desc=None, extra=None
320 ): 320 ):
321 """Send a file using jingle file transfer 321 """Send a file using jingle file transfer
322 322
323 @param peer_jid(jid.JID): destinee jid 323 @param peer_jid(jid.JID): destinee jid
349 ], 349 ],
350 encrypted = encrypted 350 encrypted = encrypted
351 ) 351 )
352 return await progress_id_d 352 return await progress_id_d
353 353
354 def _fileJingleRequest( 354 def _file_jingle_request(
355 self, peer_jid, filepath, name="", file_hash="", hash_algo="", extra=None, 355 self, peer_jid, filepath, name="", file_hash="", hash_algo="", extra=None,
356 profile=C.PROF_KEY_NONE): 356 profile=C.PROF_KEY_NONE):
357 client = self.host.getClient(profile) 357 client = self.host.get_client(profile)
358 return defer.ensureDeferred(self.fileJingleRequest( 358 return defer.ensureDeferred(self.file_jingle_request(
359 client, 359 client,
360 jid.JID(peer_jid), 360 jid.JID(peer_jid),
361 filepath, 361 filepath,
362 name or None, 362 name or None,
363 file_hash or None, 363 file_hash or None,
364 hash_algo or None, 364 hash_algo or None,
365 extra or None, 365 extra or None,
366 )) 366 ))
367 367
368 async def fileJingleRequest( 368 async def file_jingle_request(
369 self, client, peer_jid, filepath, name=None, file_hash=None, hash_algo=None, 369 self, client, peer_jid, filepath, name=None, file_hash=None, hash_algo=None,
370 extra=None): 370 extra=None):
371 """Request a file using jingle file transfer 371 """Request a file using jingle file transfer
372 372
373 @param peer_jid(jid.JID): destinee jid 373 @param peer_jid(jid.JID): destinee jid
405 ) 405 )
406 return await progress_id_d 406 return await progress_id_d
407 407
408 # jingle callbacks 408 # jingle callbacks
409 409
410 def jingleDescriptionElt( 410 def jingle_description_elt(
411 self, client, session, content_name, filepath, name, extra, progress_id_d 411 self, client, session, content_name, filepath, name, extra, progress_id_d
412 ): 412 ):
413 return domish.Element((NS_JINGLE_FT, "description")) 413 return domish.Element((NS_JINGLE_FT, "description"))
414 414
415 def jingleSessionInit( 415 def jingle_session_init(
416 self, client, session, content_name, filepath, name, extra, progress_id_d 416 self, client, session, content_name, filepath, name, extra, progress_id_d
417 ): 417 ):
418 if extra is None: 418 if extra is None:
419 extra = {} 419 extra = {}
420 else: 420 else:
422 raise ValueError( 422 raise ValueError(
423 _("only the following keys are allowed in extra: {keys}").format( 423 _("only the following keys are allowed in extra: {keys}").format(
424 keys=", ".join(EXTRA_ALLOWED) 424 keys=", ".join(EXTRA_ALLOWED)
425 ) 425 )
426 ) 426 )
427 progress_id_d.callback(self.getProgressId(session, content_name)) 427 progress_id_d.callback(self.get_progress_id(session, content_name))
428 content_data = session["contents"][content_name] 428 content_data = session["contents"][content_name]
429 application_data = content_data["application_data"] 429 application_data = content_data["application_data"]
430 assert "file_path" not in application_data 430 assert "file_path" not in application_data
431 application_data["file_path"] = filepath 431 application_data["file_path"] = filepath
432 file_data = application_data["file_data"] = {} 432 file_data = application_data["file_data"] = {}
433 desc_elt = self.jingleDescriptionElt( 433 desc_elt = self.jingle_description_elt(
434 client, session, content_name, filepath, name, extra, progress_id_d) 434 client, session, content_name, filepath, name, extra, progress_id_d)
435 file_elt = desc_elt.addElement("file") 435 file_elt = desc_elt.addElement("file")
436 436
437 if content_data["senders"] == self._j.ROLE_INITIATOR: 437 if content_data["senders"] == self._j.ROLE_INITIATOR:
438 # we send a file 438 # we send a file
447 file_data["size"] = os.path.getsize(filepath) 447 file_data["size"] = os.path.getsize(filepath)
448 if "namespace" in extra: 448 if "namespace" in extra:
449 file_data["namespace"] = extra["namespace"] 449 file_data["namespace"] = extra["namespace"]
450 if "path" in extra: 450 if "path" in extra:
451 file_data["path"] = extra["path"] 451 file_data["path"] = extra["path"]
452 self.buildFileElementFromDict( 452 self.build_file_element_from_dict(
453 client, file_data, file_elt=file_elt, file_hash="") 453 client, file_data, file_elt=file_elt, file_hash="")
454 else: 454 else:
455 # we request a file 455 # we request a file
456 file_hash = extra.pop("file_hash", "") 456 file_hash = extra.pop("file_hash", "")
457 if not name and not file_hash: 457 if not name and not file_hash:
460 file_data["name"] = name 460 file_data["name"] = name
461 if file_hash: 461 if file_hash:
462 file_data["file_hash"] = file_hash 462 file_data["file_hash"] = file_hash
463 file_data["hash_algo"] = extra["hash_algo"] 463 file_data["hash_algo"] = extra["hash_algo"]
464 else: 464 else:
465 file_data["hash_algo"] = self._hash.getDefaultAlgo() 465 file_data["hash_algo"] = self._hash.get_default_algo()
466 if "namespace" in extra: 466 if "namespace" in extra:
467 file_data["namespace"] = extra["namespace"] 467 file_data["namespace"] = extra["namespace"]
468 if "path" in extra: 468 if "path" in extra:
469 file_data["path"] = extra["path"] 469 file_data["path"] = extra["path"]
470 self.buildFileElementFromDict(client, file_data, file_elt=file_elt) 470 self.build_file_element_from_dict(client, file_data, file_elt=file_elt)
471 471
472 return desc_elt 472 return desc_elt
473 473
474 async def jingleRequestConfirmation( 474 async def jingle_request_confirmation(
475 self, client, action, session, content_name, desc_elt 475 self, client, action, session, content_name, desc_elt
476 ): 476 ):
477 """This method request confirmation for a jingle session""" 477 """This method request confirmation for a jingle session"""
478 content_data = session["contents"][content_name] 478 content_data = session["contents"][content_name]
479 senders = content_data["senders"] 479 senders = content_data["senders"]
483 # first we grab file informations 483 # first we grab file informations
484 try: 484 try:
485 file_elt = next(desc_elt.elements(NS_JINGLE_FT, "file")) 485 file_elt = next(desc_elt.elements(NS_JINGLE_FT, "file"))
486 except StopIteration: 486 except StopIteration:
487 raise failure.Failure(exceptions.DataError) 487 raise failure.Failure(exceptions.DataError)
488 file_data = {"progress_id": self.getProgressId(session, content_name)} 488 file_data = {"progress_id": self.get_progress_id(session, content_name)}
489 489
490 if senders == self._j.ROLE_RESPONDER: 490 if senders == self._j.ROLE_RESPONDER:
491 # we send the file 491 # we send the file
492 return await self._fileSendingRequestConf( 492 return await self._file_sending_request_conf(
493 client, session, content_data, content_name, file_data, file_elt 493 client, session, content_data, content_name, file_data, file_elt
494 ) 494 )
495 else: 495 else:
496 # we receive the file 496 # we receive the file
497 return await self._file_receiving_request_conf( 497 return await self._file_receiving_request_conf(
498 client, session, content_data, content_name, file_data, file_elt 498 client, session, content_data, content_name, file_data, file_elt
499 ) 499 )
500 500
501 async def _fileSendingRequestConf( 501 async def _file_sending_request_conf(
502 self, client, session, content_data, content_name, file_data, file_elt 502 self, client, session, content_data, content_name, file_data, file_elt
503 ): 503 ):
504 """parse file_elt, and handle file retrieving/permission checking""" 504 """parse file_elt, and handle file retrieving/permission checking"""
505 await self.parseFileElement(client, file_elt, file_data) 505 await self.parse_file_element(client, file_elt, file_data)
506 content_data["application_data"]["file_data"] = file_data 506 content_data["application_data"]["file_data"] = file_data
507 finished_d = content_data["finished_d"] = defer.Deferred() 507 finished_d = content_data["finished_d"] = defer.Deferred()
508 508
509 # confirmed_d is a deferred returning confimed value (only used if cont is False) 509 # confirmed_d is a deferred returning confimed value (only used if cont is False)
510 cont, confirmed_d = self.host.trigger.returnPoint( 510 cont, confirmed_d = self.host.trigger.return_point(
511 "XEP-0234_fileSendingRequest", 511 "XEP-0234_fileSendingRequest",
512 client, 512 client,
513 session, 513 session,
514 content_data, 514 content_data,
515 content_name, 515 content_name,
519 if not cont: 519 if not cont:
520 confirmed = await confirmed_d 520 confirmed = await confirmed_d
521 if confirmed: 521 if confirmed:
522 args = [client, session, content_name, content_data] 522 args = [client, session, content_name, content_data]
523 finished_d.addCallbacks( 523 finished_d.addCallbacks(
524 self._finishedCb, self._finishedEb, args, None, args 524 self._finished_cb, self._finished_eb, args, None, args
525 ) 525 )
526 return confirmed 526 return confirmed
527 527
528 log.warning(_("File continue is not implemented yet")) 528 log.warning(_("File continue is not implemented yet"))
529 return False 529 return False
530 530
531 async def _file_receiving_request_conf( 531 async def _file_receiving_request_conf(
532 self, client, session, content_data, content_name, file_data, file_elt 532 self, client, session, content_data, content_name, file_data, file_elt
533 ): 533 ):
534 """parse file_elt, and handle user permission/file opening""" 534 """parse file_elt, and handle user permission/file opening"""
535 await self.parseFileElement(client, file_elt, file_data, given=True) 535 await self.parse_file_element(client, file_elt, file_data, given=True)
536 try: 536 try:
537 hash_algo, file_data["given_file_hash"] = self._hash.parseHashElt(file_elt) 537 hash_algo, file_data["given_file_hash"] = self._hash.parse_hash_elt(file_elt)
538 except exceptions.NotFound: 538 except exceptions.NotFound:
539 try: 539 try:
540 hash_algo = self._hash.parseHashUsedElt(file_elt) 540 hash_algo = self._hash.parse_hash_used_elt(file_elt)
541 except exceptions.NotFound: 541 except exceptions.NotFound:
542 raise failure.Failure(exceptions.DataError) 542 raise failure.Failure(exceptions.DataError)
543 543
544 if hash_algo is not None: 544 if hash_algo is not None:
545 file_data["hash_algo"] = hash_algo 545 file_data["hash_algo"] = hash_algo
546 file_data["hash_hasher"] = hasher = self._hash.getHasher(hash_algo) 546 file_data["hash_hasher"] = hasher = self._hash.get_hasher(hash_algo)
547 file_data["data_cb"] = lambda data: hasher.update(data) 547 file_data["data_cb"] = lambda data: hasher.update(data)
548 548
549 try: 549 try:
550 file_data["size"] = int(file_data["size"]) 550 file_data["size"] = int(file_data["size"])
551 except ValueError: 551 except ValueError:
562 562
563 # now we actualy request permission to user 563 # now we actualy request permission to user
564 564
565 # deferred to track end of transfer 565 # deferred to track end of transfer
566 finished_d = content_data["finished_d"] = defer.Deferred() 566 finished_d = content_data["finished_d"] = defer.Deferred()
567 confirmed = await self._f.getDestDir( 567 confirmed = await self._f.get_dest_dir(
568 client, session["peer_jid"], content_data, file_data, stream_object=True 568 client, session["peer_jid"], content_data, file_data, stream_object=True
569 ) 569 )
570 if confirmed: 570 if confirmed:
571 await self.host.trigger.asyncPoint( 571 await self.host.trigger.async_point(
572 "XEP-0234_file_receiving_request_conf", 572 "XEP-0234_file_receiving_request_conf",
573 client, session, content_data, file_elt 573 client, session, content_data, file_elt
574 ) 574 )
575 args = [client, session, content_name, content_data] 575 args = [client, session, content_name, content_data]
576 finished_d.addCallbacks( 576 finished_d.addCallbacks(
577 self._finishedCb, self._finishedEb, args, None, args 577 self._finished_cb, self._finished_eb, args, None, args
578 ) 578 )
579 return confirmed 579 return confirmed
580 580
581 async def jingleHandler(self, client, action, session, content_name, desc_elt): 581 async def jingle_handler(self, client, action, session, content_name, desc_elt):
582 content_data = session["contents"][content_name] 582 content_data = session["contents"][content_name]
583 application_data = content_data["application_data"] 583 application_data = content_data["application_data"]
584 if action in (self._j.A_ACCEPTED_ACK,): 584 if action in (self._j.A_ACCEPTED_ACK,):
585 pass 585 pass
586 elif action == self._j.A_SESSION_INITIATE: 586 elif action == self._j.A_SESSION_INITIATE:
606 size = int(str(size_elt)) 606 size = int(str(size_elt))
607 except (StopIteration, ValueError): 607 except (StopIteration, ValueError):
608 size = None 608 size = None
609 # XXX: hash security is not critical here, so we just take the higher 609 # XXX: hash security is not critical here, so we just take the higher
610 # mandatory one 610 # mandatory one
611 hasher = file_data["hash_hasher"] = self._hash.getHasher() 611 hasher = file_data["hash_hasher"] = self._hash.get_hasher()
612 progress_id = self.getProgressId(session, content_name) 612 progress_id = self.get_progress_id(session, content_name)
613 try: 613 try:
614 content_data["stream_object"] = stream.FileStreamObject( 614 content_data["stream_object"] = stream.FileStreamObject(
615 self.host, 615 self.host,
616 client, 616 client,
617 file_path, 617 file_path,
619 uid=progress_id, 619 uid=progress_id,
620 size=size, 620 size=size,
621 data_cb=lambda data: hasher.update(data), 621 data_cb=lambda data: hasher.update(data),
622 ) 622 )
623 except Exception as e: 623 except Exception as e:
624 self.host.bridge.progressError( 624 self.host.bridge.progress_error(
625 progress_id, C.PROGRESS_ERROR_FAILED, client.profile 625 progress_id, C.PROGRESS_ERROR_FAILED, client.profile
626 ) 626 )
627 await self._j.terminate( 627 await self._j.terminate(
628 client, self._j.REASON_FAILED_APPLICATION, session) 628 client, self._j.REASON_FAILED_APPLICATION, session)
629 raise e 629 raise e
630 else: 630 else:
631 # we are sending the file 631 # we are sending the file
632 size = file_data["size"] 632 size = file_data["size"]
633 # XXX: hash security is not critical here, so we just take the higher 633 # XXX: hash security is not critical here, so we just take the higher
634 # mandatory one 634 # mandatory one
635 hasher = file_data["hash_hasher"] = self._hash.getHasher() 635 hasher = file_data["hash_hasher"] = self._hash.get_hasher()
636 content_data["stream_object"] = stream.FileStreamObject( 636 content_data["stream_object"] = stream.FileStreamObject(
637 self.host, 637 self.host,
638 client, 638 client,
639 file_path, 639 file_path,
640 uid=self.getProgressId(session, content_name), 640 uid=self.get_progress_id(session, content_name),
641 size=size, 641 size=size,
642 data_cb=lambda data: hasher.update(data), 642 data_cb=lambda data: hasher.update(data),
643 ) 643 )
644 finished_d = content_data["finished_d"] = defer.Deferred() 644 finished_d = content_data["finished_d"] = defer.Deferred()
645 args = [client, session, content_name, content_data] 645 args = [client, session, content_name, content_data]
646 finished_d.addCallbacks(self._finishedCb, self._finishedEb, args, None, args) 646 finished_d.addCallbacks(self._finished_cb, self._finished_eb, args, None, args)
647 await self.host.trigger.asyncPoint( 647 await self.host.trigger.async_point(
648 "XEP-0234_jingle_handler", 648 "XEP-0234_jingle_handler",
649 client, session, content_data, desc_elt 649 client, session, content_data, desc_elt
650 ) 650 )
651 else: 651 else:
652 log.warning("FIXME: unmanaged action {}".format(action)) 652 log.warning("FIXME: unmanaged action {}".format(action))
653 return desc_elt 653 return desc_elt
654 654
655 def jingleSessionInfo(self, client, action, session, content_name, jingle_elt): 655 def jingle_session_info(self, client, action, session, content_name, jingle_elt):
656 """Called on session-info action 656 """Called on session-info action
657 657
658 manage checksum, and ignore <received/> element 658 manage checksum, and ignore <received/> element
659 """ 659 """
660 # TODO: manage <received/> element 660 # TODO: manage <received/> element
679 file_data = content_data["application_data"]["file_data"] 679 file_data = content_data["application_data"]["file_data"]
680 try: 680 try:
681 file_elt = next(elt.elements(NS_JINGLE_FT, "file")) 681 file_elt = next(elt.elements(NS_JINGLE_FT, "file"))
682 except StopIteration: 682 except StopIteration:
683 raise exceptions.DataError 683 raise exceptions.DataError
684 algo, file_data["given_file_hash"] = self._hash.parseHashElt(file_elt) 684 algo, file_data["given_file_hash"] = self._hash.parse_hash_elt(file_elt)
685 if algo != file_data.get("hash_algo"): 685 if algo != file_data.get("hash_algo"):
686 log.warning( 686 log.warning(
687 "Hash algorithm used in given hash ({peer_algo}) doesn't correspond to the one we have used ({our_algo}) [{profile}]".format( 687 "Hash algorithm used in given hash ({peer_algo}) doesn't correspond to the one we have used ({our_algo}) [{profile}]".format(
688 peer_algo=algo, 688 peer_algo=algo,
689 our_algo=file_data.get("hash_algo"), 689 our_algo=file_data.get("hash_algo"),
690 profile=client.profile, 690 profile=client.profile,
691 ) 691 )
692 ) 692 )
693 else: 693 else:
694 self._receiverTryTerminate( 694 self._receiver_try_terminate(
695 client, session, content_name, content_data 695 client, session, content_name, content_data
696 ) 696 )
697 else: 697 else:
698 raise NotImplementedError 698 raise NotImplementedError
699 699
700 def jingleTerminate(self, client, action, session, content_name, jingle_elt): 700 def jingle_terminate(self, client, action, session, content_name, jingle_elt):
701 if jingle_elt.decline: 701 if jingle_elt.decline:
702 # progress is the only way to tell to frontends that session has been declined 702 # progress is the only way to tell to frontends that session has been declined
703 progress_id = self.getProgressId(session, content_name) 703 progress_id = self.get_progress_id(session, content_name)
704 self.host.bridge.progressError( 704 self.host.bridge.progress_error(
705 progress_id, C.PROGRESS_ERROR_DECLINED, client.profile 705 progress_id, C.PROGRESS_ERROR_DECLINED, client.profile
706 ) 706 )
707 elif not jingle_elt.success: 707 elif not jingle_elt.success:
708 progress_id = self.getProgressId(session, content_name) 708 progress_id = self.get_progress_id(session, content_name)
709 first_child = jingle_elt.firstChildElement() 709 first_child = jingle_elt.firstChildElement()
710 if first_child is not None: 710 if first_child is not None:
711 reason = first_child.name 711 reason = first_child.name
712 if jingle_elt.text is not None: 712 if jingle_elt.text is not None:
713 reason = f"{reason} - {jingle_elt.text}" 713 reason = f"{reason} - {jingle_elt.text}"
714 else: 714 else:
715 reason = C.PROGRESS_ERROR_FAILED 715 reason = C.PROGRESS_ERROR_FAILED
716 self.host.bridge.progressError( 716 self.host.bridge.progress_error(
717 progress_id, reason, client.profile 717 progress_id, reason, client.profile
718 ) 718 )
719 719
720 def _sendCheckSum(self, client, session, content_name, content_data): 720 def _send_check_sum(self, client, session, content_name, content_data):
721 """Send the session-info with the hash checksum""" 721 """Send the session-info with the hash checksum"""
722 file_data = content_data["application_data"]["file_data"] 722 file_data = content_data["application_data"]["file_data"]
723 hasher = file_data["hash_hasher"] 723 hasher = file_data["hash_hasher"]
724 hash_ = hasher.hexdigest() 724 hash_ = hasher.hexdigest()
725 log.debug("Calculated hash: {}".format(hash_)) 725 log.debug("Calculated hash: {}".format(hash_))
726 iq_elt, jingle_elt = self._j.buildSessionInfo(client, session) 726 iq_elt, jingle_elt = self._j.build_session_info(client, session)
727 checksum_elt = jingle_elt.addElement((NS_JINGLE_FT, "checksum")) 727 checksum_elt = jingle_elt.addElement((NS_JINGLE_FT, "checksum"))
728 checksum_elt["creator"] = content_data["creator"] 728 checksum_elt["creator"] = content_data["creator"]
729 checksum_elt["name"] = content_name 729 checksum_elt["name"] = content_name
730 file_elt = checksum_elt.addElement("file") 730 file_elt = checksum_elt.addElement("file")
731 file_elt.addChild(self._hash.buildHashElt(hash_)) 731 file_elt.addChild(self._hash.build_hash_elt(hash_))
732 iq_elt.send() 732 iq_elt.send()
733 733
734 def _receiverTryTerminate( 734 def _receiver_try_terminate(
735 self, client, session, content_name, content_data, last_try=False 735 self, client, session, content_name, content_data, last_try=False
736 ): 736 ):
737 """Try to terminate the session 737 """Try to terminate the session
738 738
739 This method must only be used by the receiver. 739 This method must only be used by the receiver.
751 log.warning( 751 log.warning(
752 "sender didn't sent hash checksum, we can't check the file [{profile}]".format( 752 "sender didn't sent hash checksum, we can't check the file [{profile}]".format(
753 profile=client.profile 753 profile=client.profile
754 ) 754 )
755 ) 755 )
756 self._j.delayedContentTerminate(client, session, content_name) 756 self._j.delayed_content_terminate(client, session, content_name)
757 content_data["stream_object"].close() 757 content_data["stream_object"].close()
758 return True 758 return True
759 return False 759 return False
760 hasher = file_data["hash_hasher"] 760 hasher = file_data["hash_hasher"]
761 hash_ = hasher.hexdigest() 761 hash_ = hasher.hexdigest()
773 progress_metadata = None 773 progress_metadata = None
774 error = "Hash mismatch: given={algo}:{given}, calculated={algo}:{our}".format( 774 error = "Hash mismatch: given={algo}:{given}, calculated={algo}:{our}".format(
775 algo=file_data["hash_algo"], given=given_hash, our=hash_ 775 algo=file_data["hash_algo"], given=given_hash, our=hash_
776 ) 776 )
777 777
778 self._j.delayedContentTerminate(client, session, content_name) 778 self._j.delayed_content_terminate(client, session, content_name)
779 content_data["stream_object"].close(progress_metadata, error) 779 content_data["stream_object"].close(progress_metadata, error)
780 # we may have the last_try timer still active, so we try to cancel it 780 # we may have the last_try timer still active, so we try to cancel it
781 try: 781 try:
782 content_data["last_try_timer"].cancel() 782 content_data["last_try_timer"].cancel()
783 except (KeyError, internet_error.AlreadyCalled): 783 except (KeyError, internet_error.AlreadyCalled):
784 pass 784 pass
785 return True 785 return True
786 786
787 def _finishedCb(self, __, client, session, content_name, content_data): 787 def _finished_cb(self, __, client, session, content_name, content_data):
788 log.info("File transfer terminated") 788 log.info("File transfer terminated")
789 if content_data["senders"] != session["role"]: 789 if content_data["senders"] != session["role"]:
790 # we terminate the session only if we are the receiver, 790 # we terminate the session only if we are the receiver,
791 # as recommanded in XEP-0234 §2 (after example 6) 791 # as recommanded in XEP-0234 §2 (after example 6)
792 content_data["transfer_finished"] = True 792 content_data["transfer_finished"] = True
793 if not self._receiverTryTerminate( 793 if not self._receiver_try_terminate(
794 client, session, content_name, content_data 794 client, session, content_name, content_data
795 ): 795 ):
796 # we have not received the hash yet, we wait 5 more seconds 796 # we have not received the hash yet, we wait 5 more seconds
797 content_data["last_try_timer"] = reactor.callLater( 797 content_data["last_try_timer"] = reactor.callLater(
798 5, 798 5,
799 self._receiverTryTerminate, 799 self._receiver_try_terminate,
800 client, 800 client,
801 session, 801 session,
802 content_name, 802 content_name,
803 content_data, 803 content_data,
804 last_try=True, 804 last_try=True,
805 ) 805 )
806 else: 806 else:
807 # we are the sender, we send the checksum 807 # we are the sender, we send the checksum
808 self._sendCheckSum(client, session, content_name, content_data) 808 self._send_check_sum(client, session, content_name, content_data)
809 content_data["stream_object"].close() 809 content_data["stream_object"].close()
810 810
811 def _finishedEb(self, failure, client, session, content_name, content_data): 811 def _finished_eb(self, failure, client, session, content_name, content_data):
812 log.warning("Error while streaming file: {}".format(failure)) 812 log.warning("Error while streaming file: {}".format(failure))
813 content_data["stream_object"].close() 813 content_data["stream_object"].close()
814 self._j.contentTerminate( 814 self._j.content_terminate(
815 client, session, content_name, reason=self._j.REASON_FAILED_TRANSPORT 815 client, session, content_name, reason=self._j.REASON_FAILED_TRANSPORT
816 ) 816 )
817 817
818 818
819 @implementer(iwokkel.IDisco) 819 @implementer(iwokkel.IDisco)