comparison sat/plugins/plugin_comp_file_sharing.py @ 3314:5887fb414758

component file sharing: add/parse affiliation when possible
author Goffi <goffi@goffi.org>
date Fri, 17 Jul 2020 13:00:10 +0200
parents 9d1c0feba048
children 15612c0fb421
comparison
equal deleted inserted replaced
3313:624c60293deb 3314:5887fb414758
21 import mimetypes 21 import mimetypes
22 from functools import partial 22 from functools import partial
23 import shortuuid 23 import shortuuid
24 import unicodedata 24 import unicodedata
25 from urllib.parse import urljoin, urlparse, quote, unquote 25 from urllib.parse import urljoin, urlparse, quote, unquote
26 from dataclasses import dataclass
27 from pathlib import Path 26 from pathlib import Path
28 from sat.core.i18n import _ 27 from sat.core.i18n import _
29 from sat.core.constants import Const as C 28 from sat.core.constants import Const as C
30 from sat.core import exceptions 29 from sat.core import exceptions
31 from sat.core.log import getLogger 30 from sat.core.log import getLogger
67 C.PI_DESCRIPTION: _("""Component hosting and sharing files"""), 66 C.PI_DESCRIPTION: _("""Component hosting and sharing files"""),
68 } 67 }
69 68
70 HASH_ALGO = "sha-256" 69 HASH_ALGO = "sha-256"
71 NS_COMMENTS = "org.salut-a-toi.comments" 70 NS_COMMENTS = "org.salut-a-toi.comments"
71 NS_FS_AFFILIATION = "org.salut-a-toi.file-sharing-affiliation"
72 COMMENT_NODE_PREFIX = "org.salut-a-toi.file_comments/" 72 COMMENT_NODE_PREFIX = "org.salut-a-toi.file_comments/"
73 # Directory used to buffer request body (i.e. file in case of PUT) we use more than one @ 73 # Directory used to buffer request body (i.e. file in case of PUT) we use more than one @
74 # there, to be sure than it's not conflicting with a JID 74 # there, to be sure than it's not conflicting with a JID
75 TMP_BUFFER_DIR = "@@tmp@@" 75 TMP_BUFFER_DIR = "@@tmp@@"
76 76
268 # for PUT we check early if upload_id is fine, to avoid buffering a file we'll refuse 268 # for PUT we check early if upload_id is fine, to avoid buffering a file we'll refuse
269 # we buffer the file in component's TMP_BUFFER_DIR, so we just have to rename it at the end 269 # we buffer the file in component's TMP_BUFFER_DIR, so we just have to rename it at the end
270 try: 270 try:
271 upload_id, filename = self.upload_data 271 upload_id, filename = self.upload_data
272 except exceptions.DataError as e: 272 except exceptions.DataError as e:
273 log.warning("Invalid PUT request, we stop here: {e}") 273 log.warning(f"Invalid PUT request, we stop here: {e}")
274 return self.refuseRequest() 274 return self.refuseRequest()
275 try: 275 try:
276 client, upload_request, timer = self.file_sharing.expected_uploads.pop(upload_id) 276 client, upload_request, timer = self.file_sharing.expected_uploads.pop(upload_id)
277 except KeyError: 277 except KeyError:
278 log.warning(f"unknown (expired?) upload ID received for a PUT: {upload_id!r}") 278 log.warning(f"unknown (expired?) upload ID received for a PUT: {upload_id!r}")
341 self.host.plugins["XEP-0363"].registerHandler(self._onHTTPUpload) 341 self.host.plugins["XEP-0363"].registerHandler(self._onHTTPUpload)
342 self.host.trigger.add("FILE_getDestDir", self._getDestDirTrigger) 342 self.host.trigger.add("FILE_getDestDir", self._getDestDirTrigger)
343 self.host.trigger.add( 343 self.host.trigger.add(
344 "XEP-0234_fileSendingRequest", self._fileSendingRequestTrigger, priority=1000 344 "XEP-0234_fileSendingRequest", self._fileSendingRequestTrigger, priority=1000
345 ) 345 )
346 self.host.trigger.add("XEP-0234_buildFileElement", self._addFileComments) 346 self.host.trigger.add("XEP-0234_buildFileElement", self._addFileMetadataElts)
347 self.host.trigger.add("XEP-0234_parseFileElement", self._getFileComments) 347 self.host.trigger.add("XEP-0234_parseFileElement", self._getFileMetadataElts)
348 self.host.trigger.add("XEP-0329_compGetFilesFromNode", self._addCommentsData) 348 self.host.trigger.add("XEP-0329_compGetFilesFromNode", self._addFileMetadata)
349 self.host.trigger.add(
350 "XEP-0329_compGetFilesFromNode_build_directory",
351 self._addDirectoryMetadataElts)
352 self.host.trigger.add(
353 "XEP-0329_parseResult_directory",
354 self._getDirectoryMetadataElts)
349 self.files_path = self.host.getLocalPath(None, C.FILES_DIR, profile=False) 355 self.files_path = self.host.getLocalPath(None, C.FILES_DIR, profile=False)
350 self.http_port = self.host.memory.getConfig( 356 self.http_port = self.host.memory.getConfig(
351 'component file_sharing', 'http_upload_port', 8888) 357 'component file_sharing', 'http_upload_port', 8888)
352 connection_type = self.host.memory.getConfig( 358 connection_type = self.host.memory.getConfig(
353 'component file_sharing', 'http_upload_connection_type', 'https') 359 'component file_sharing', 'http_upload_connection_type', 'https')
354 if connection_type not in ('http', 'https'): 360 if connection_type not in ('http', 'https'):
355 raise exceptions.ConfigError( 361 raise exceptions.ConfigError(
356 f'bad http_upload_connection_type, you must use one of "http" or "https"' 362 'bad http_upload_connection_type, you must use one of "http" or "https"'
357 ) 363 )
358 self.server = FileSharingSite(self) 364 self.server = FileSharingSite(self)
359 self.expected_uploads = {} 365 self.expected_uploads = {}
360 if connection_type == 'http': 366 if connection_type == 'http':
361 reactor.listenTCP(self.http_port, self.server) 367 reactor.listenTCP(self.http_port, self.server)
362 else: 368 else:
363 options = tls.getOptionsFromConfig(self.host.memory.config, "component file_sharing") 369 options = tls.getOptionsFromConfig(
370 self.host.memory.config, "component file_sharing")
364 tls.TLSOptionsCheck(options) 371 tls.TLSOptionsCheck(options)
365 context_factory = tls.getTLSContextFactory(options) 372 context_factory = tls.getTLSContextFactory(options)
366 reactor.listenSSL(self.http_port, self.server, context_factory) 373 reactor.listenSSL(self.http_port, self.server, context_factory)
367
368 374
369 def getHandler(self, client): 375 def getHandler(self, client):
370 return Comments_handler(self) 376 return Comments_handler(self)
371 377
372 def profileConnecting(self, client): 378 def profileConnecting(self, client):
579 get=url, 585 get=url,
580 headers=[], 586 headers=[],
581 ) 587 )
582 return slot 588 return slot
583 589
584 ## comments triggers ## 590 ## metadata triggers ##
585 591
586 def _addFileComments(self, file_elt, extra_args): 592 def _addFileMetadataElts(self, client, file_elt, extra_args):
593 # affiliation
594 affiliation = extra_args.get('affiliation')
595 if affiliation is not None:
596 file_elt.addElement((NS_FS_AFFILIATION, "affiliation"), content=affiliation)
597
598 # comments
587 try: 599 try:
588 comments_url = extra_args.pop("comments_url") 600 comments_url = extra_args.pop("comments_url")
589 except KeyError: 601 except KeyError:
590 return 602 return
591 603
597 count = 0 609 count = 0
598 610
599 comment_elt["count"] = str(count) 611 comment_elt["count"] = str(count)
600 return True 612 return True
601 613
602 def _getFileComments(self, file_elt, file_data): 614 def _getFileMetadataElts(self, client, file_elt, file_data):
615 # affiliation
616 try:
617 affiliation_elt = next(file_elt.elements(NS_FS_AFFILIATION, "affiliation"))
618 except StopIteration:
619 pass
620 else:
621 file_data["affiliation"] = str(affiliation_elt)
622
623 # comments
603 try: 624 try:
604 comments_elt = next(file_elt.elements(NS_COMMENTS, "comments")) 625 comments_elt = next(file_elt.elements(NS_COMMENTS, "comments"))
605 except StopIteration: 626 except StopIteration:
606 return 627 pass
607 file_data["comments_url"] = str(comments_elt) 628 else:
608 file_data["comments_count"] = comments_elt["count"] 629 file_data["comments_url"] = str(comments_elt)
630 file_data["comments_count"] = comments_elt["count"]
609 return True 631 return True
610 632
611 def _addCommentsData(self, client, iq_elt, owner, node_path, files_data): 633 def _addFileMetadata(
634 self, client, iq_elt, iq_result_elt, owner, node_path, files_data):
612 for file_data in files_data: 635 for file_data in files_data:
613 file_data["comments_url"] = uri.buildXMPPUri( 636 file_data["comments_url"] = uri.buildXMPPUri(
614 "pubsub", 637 "pubsub",
615 path=client.jid.full(), 638 path=client.jid.full(),
616 node=COMMENT_NODE_PREFIX + file_data["id"], 639 node=COMMENT_NODE_PREFIX + file_data["id"],
617 ) 640 )
618 return True 641 return True
619 642
643 def _addDirectoryMetadataElts(
644 self, client, file_data, directory_elt, owner, node_path):
645 affiliation = file_data.get('affiliation')
646 if affiliation is not None:
647 directory_elt.addElement(
648 (NS_FS_AFFILIATION, "affiliation"),
649 content=affiliation
650 )
651
652 def _getDirectoryMetadataElts(
653 self, client, elt, file_data):
654 try:
655 affiliation_elt = next(elt.elements((NS_FS_AFFILIATION, "affiliation")))
656 except StopIteration:
657 pass
658 else:
659 file_data['affiliation'] = str(affiliation_elt)
660
620 661
621 class Comments_handler(pubsub.PubSubService): 662 class Comments_handler(pubsub.PubSubService):
622 """This class is a minimal Pubsub service handling virtual nodes for comments""" 663 """This class is a minimal Pubsub service handling virtual nodes for comments"""
623 664
624 def __init__(self, plugin_parent): 665 def __init__(self, plugin_parent):
625 super(Comments_handler, self).__init__() # PubsubVirtualResource()) 666 super(Comments_handler, self).__init__()
626 self.host = plugin_parent.host 667 self.host = plugin_parent.host
627 self.plugin_parent = plugin_parent 668 self.plugin_parent = plugin_parent
628 self.discoIdentity = { 669 self.discoIdentity = {
629 "category": "pubsub", 670 "category": "pubsub",
630 "type": "virtual", # FIXME: non standard, here to avoid this service being considered as main pubsub one 671 "type": "virtual", # FIXME: non standard, here to avoid this service being considered as main pubsub one