component file sharing: add/parse affiliation when possible

This commit is contained in:
Goffi 2020-07-17 13:00:10 +02:00
commit d7844a98b4
5 changed files with 110 additions and 39 deletions

View file

@ -23,7 +23,6 @@ from functools import partial
import shortuuid
import unicodedata
from urllib.parse import urljoin, urlparse, quote, unquote
from dataclasses import dataclass
from pathlib import Path
from sat.core.i18n import _
from sat.core.constants import Const as C
@ -69,6 +68,7 @@ PLUGIN_INFO = {
HASH_ALGO = "sha-256"
NS_COMMENTS = "org.salut-a-toi.comments"
NS_FS_AFFILIATION = "org.salut-a-toi.file-sharing-affiliation"
COMMENT_NODE_PREFIX = "org.salut-a-toi.file_comments/"
# Directory used to buffer request body (i.e. file in case of PUT) we use more than one @
# there, to be sure than it's not conflicting with a JID
@ -270,7 +270,7 @@ class FileSharingRequest(server.Request):
try:
upload_id, filename = self.upload_data
except exceptions.DataError as e:
log.warning("Invalid PUT request, we stop here: {e}")
log.warning(f"Invalid PUT request, we stop here: {e}")
return self.refuseRequest()
try:
client, upload_request, timer = self.file_sharing.expected_uploads.pop(upload_id)
@ -343,9 +343,15 @@ class FileSharing:
self.host.trigger.add(
"XEP-0234_fileSendingRequest", self._fileSendingRequestTrigger, priority=1000
)
self.host.trigger.add("XEP-0234_buildFileElement", self._addFileComments)
self.host.trigger.add("XEP-0234_parseFileElement", self._getFileComments)
self.host.trigger.add("XEP-0329_compGetFilesFromNode", self._addCommentsData)
self.host.trigger.add("XEP-0234_buildFileElement", self._addFileMetadataElts)
self.host.trigger.add("XEP-0234_parseFileElement", self._getFileMetadataElts)
self.host.trigger.add("XEP-0329_compGetFilesFromNode", self._addFileMetadata)
self.host.trigger.add(
"XEP-0329_compGetFilesFromNode_build_directory",
self._addDirectoryMetadataElts)
self.host.trigger.add(
"XEP-0329_parseResult_directory",
self._getDirectoryMetadataElts)
self.files_path = self.host.getLocalPath(None, C.FILES_DIR, profile=False)
self.http_port = self.host.memory.getConfig(
'component file_sharing', 'http_upload_port', 8888)
@ -353,19 +359,19 @@ class FileSharing:
'component file_sharing', 'http_upload_connection_type', 'https')
if connection_type not in ('http', 'https'):
raise exceptions.ConfigError(
f'bad http_upload_connection_type, you must use one of "http" or "https"'
'bad http_upload_connection_type, you must use one of "http" or "https"'
)
self.server = FileSharingSite(self)
self.expected_uploads = {}
if connection_type == 'http':
reactor.listenTCP(self.http_port, self.server)
else:
options = tls.getOptionsFromConfig(self.host.memory.config, "component file_sharing")
options = tls.getOptionsFromConfig(
self.host.memory.config, "component file_sharing")
tls.TLSOptionsCheck(options)
context_factory = tls.getTLSContextFactory(options)
reactor.listenSSL(self.http_port, self.server, context_factory)
def getHandler(self, client):
return Comments_handler(self)
@ -581,9 +587,15 @@ class FileSharing:
)
return slot
## comments triggers ##
## metadata triggers ##
def _addFileComments(self, file_elt, extra_args):
def _addFileMetadataElts(self, client, file_elt, extra_args):
# affiliation
affiliation = extra_args.get('affiliation')
if affiliation is not None:
file_elt.addElement((NS_FS_AFFILIATION, "affiliation"), content=affiliation)
# comments
try:
comments_url = extra_args.pop("comments_url")
except KeyError:
@ -599,16 +611,27 @@ class FileSharing:
comment_elt["count"] = str(count)
return True
def _getFileComments(self, file_elt, file_data):
def _getFileMetadataElts(self, client, file_elt, file_data):
# affiliation
try:
affiliation_elt = next(file_elt.elements(NS_FS_AFFILIATION, "affiliation"))
except StopIteration:
pass
else:
file_data["affiliation"] = str(affiliation_elt)
# comments
try:
comments_elt = next(file_elt.elements(NS_COMMENTS, "comments"))
except StopIteration:
return
file_data["comments_url"] = str(comments_elt)
file_data["comments_count"] = comments_elt["count"]
pass
else:
file_data["comments_url"] = str(comments_elt)
file_data["comments_count"] = comments_elt["count"]
return True
def _addCommentsData(self, client, iq_elt, owner, node_path, files_data):
def _addFileMetadata(
self, client, iq_elt, iq_result_elt, owner, node_path, files_data):
for file_data in files_data:
file_data["comments_url"] = uri.buildXMPPUri(
"pubsub",
@ -617,12 +640,30 @@ class FileSharing:
)
return True
def _addDirectoryMetadataElts(
self, client, file_data, directory_elt, owner, node_path):
affiliation = file_data.get('affiliation')
if affiliation is not None:
directory_elt.addElement(
(NS_FS_AFFILIATION, "affiliation"),
content=affiliation
)
def _getDirectoryMetadataElts(
self, client, elt, file_data):
try:
affiliation_elt = next(elt.elements((NS_FS_AFFILIATION, "affiliation")))
except StopIteration:
pass
else:
file_data['affiliation'] = str(affiliation_elt)
class Comments_handler(pubsub.PubSubService):
"""This class is a minimal Pubsub service handling virtual nodes for comments"""
def __init__(self, plugin_parent):
super(Comments_handler, self).__init__() # PubsubVirtualResource())
super(Comments_handler, self).__init__()
self.host = plugin_parent.host
self.plugin_parent = plugin_parent
self.discoIdentity = {

View file

@ -79,7 +79,7 @@ class FileSharingInvitation(object):
type_human = _("file sharing")
log.info(_(
'{profile} has received an invitation for a files repository ({type_human}) '
'with namespace "{namespace}" at path [{path}]').format(
'with namespace {namespace!r} at path [{path}]').format(
profile=client.profile, type_human=type_human, namespace=namespace, path=path)
)
return defer.ensureDeferred(

View file

@ -105,7 +105,8 @@ class XEP_0234(object):
# generic methods
def buildFileElement(self, name=None, file_hash=None, hash_algo=None, size=None,
def buildFileElement(
self, client, name=None, file_hash=None, hash_algo=None, size=None,
mime_type=None, desc=None, modified=None, transfer_range=None, path=None,
namespace=None, file_elt=None, **kwargs):
"""Generate a <file> element with available metadata
@ -113,17 +114,20 @@ class XEP_0234(object):
@param file_hash(unicode, None): hash of the file
empty string to set <hash-used/> element
@param hash_algo(unicode, None): hash algorithm used
if file_hash is None and hash_algo is set, a <hash-used/> element will be generated
if file_hash is None and hash_algo is set, a <hash-used/> element will be
generated
@param transfer_range(Range, None): where transfer must start/stop
@param modified(int, unicode, None): date of last modification
0 to use current date
int to use an unix timestamp
else must be an unicode string which will be used as it (it must be an XMPP time)
else must be an unicode string which will be used as it (it must be an XMPP
time)
@param file_elt(domish.Element, None): element to use
None to create a new one
@param **kwargs: data for plugin extension (ignored by default)
@return (domish.Element): generated element
@trigger XEP-0234_buildFileElement(file_elt, extra_args): can be used to extend elements to add
@trigger XEP-0234_buildFileElement(file_elt, extra_args): can be used to extend
elements to add
"""
if file_elt is None:
file_elt = domish.Element((NS_JINGLE_FT, "file"))
@ -159,13 +163,14 @@ class XEP_0234(object):
file_elt.addChild(self._hash.buildHashElt(file_hash, hash_algo))
elif hash_algo is not None:
file_elt.addChild(self._hash.buildHashUsedElt(hash_algo))
self.host.trigger.point("XEP-0234_buildFileElement", file_elt, extra_args=kwargs)
self.host.trigger.point(
"XEP-0234_buildFileElement", client, file_elt, extra_args=kwargs)
if kwargs:
for kw in kwargs:
log.debug("ignored keyword: {}".format(kw))
return file_elt
def buildFileElementFromDict(self, file_data, **kwargs):
def buildFileElementFromDict(self, client, file_data, **kwargs):
"""like buildFileElement but get values from a file_data dict
@param file_data(dict): metadata to use
@ -174,10 +179,11 @@ class XEP_0234(object):
if kwargs:
file_data = file_data.copy()
file_data.update(kwargs)
return self.buildFileElement(**file_data)
return self.buildFileElement(client, **file_data)
def parseFileElement(self, file_elt, file_data=None, given=False, parent_elt=None,
keep_empty_range=False,):
def parseFileElement(
self, client, file_elt, file_data=None, given=False, parent_elt=None,
keep_empty_range=False):
"""Parse a <file> element and file dictionary accordingly
@param file_data(dict, None): dict where the data will be set
@ -268,7 +274,7 @@ class XEP_0234(object):
except exceptions.NotFound:
pass
self.host.trigger.point("XEP-0234_parseFileElement", file_elt, file_data)
self.host.trigger.point("XEP-0234_parseFileElement", client, file_elt, file_data)
return file_data
@ -423,7 +429,8 @@ class XEP_0234(object):
file_data["namespace"] = extra["namespace"]
if "path" in extra:
file_data["path"] = extra["path"]
self.buildFileElementFromDict(file_data, file_elt=file_elt, file_hash="")
self.buildFileElementFromDict(
client, file_data, file_elt=file_elt, file_hash="")
else:
# we request a file
file_hash = extra.pop("file_hash", "")
@ -440,7 +447,7 @@ class XEP_0234(object):
file_data["namespace"] = extra["namespace"]
if "path" in extra:
file_data["path"] = extra["path"]
self.buildFileElementFromDict(file_data, file_elt=file_elt)
self.buildFileElementFromDict(client, file_data, file_elt=file_elt)
return desc_elt
@ -474,7 +481,7 @@ class XEP_0234(object):
self, client, session, content_data, content_name, file_data, file_elt
):
"""parse file_elt, and handle file retrieving/permission checking"""
self.parseFileElement(file_elt, file_data)
self.parseFileElement(client, file_elt, file_data)
content_data["application_data"]["file_data"] = file_data
finished_d = content_data["finished_d"] = defer.Deferred()
@ -504,7 +511,7 @@ class XEP_0234(object):
self, client, session, content_data, content_name, file_data, file_elt
):
"""parse file_elt, and handle user permission/file opening"""
self.parseFileElement(file_elt, file_data, given=True)
self.parseFileElement(client, file_elt, file_data, given=True)
try:
hash_algo, file_data["given_file_hash"] = self._hash.parseHashElt(file_elt)
except exceptions.NotFound:

View file

@ -84,7 +84,7 @@ class XEP_0264(object):
## triggers ##
def _addFileThumbnails(self, file_elt, extra_args):
def _addFileThumbnails(self, client, file_elt, extra_args):
try:
thumbnails = extra_args["extra"][C.KEY_THUMBNAILS]
except KeyError:
@ -98,7 +98,7 @@ class XEP_0264(object):
thumbnail_elt["height"] = str(height)
return True
def _getFileThumbnails(self, file_elt, file_data):
def _getFileThumbnails(self, client, file_elt, file_data):
thumbnails = []
for thumbnail_elt in file_elt.elements(NS_THUMBS, "thumbnail"):
uri = thumbnail_elt["uri"]

View file

@ -417,7 +417,8 @@ class XEP_0329(object):
size = os.path.getsize(path)
mime_type = mimetypes.guess_type(path, strict=False)[0]
file_elt = self._jf.buildFileElement(
name=name, size=size, mime_type=mime_type, modified=os.path.getmtime(path)
client=client, name=name, size=size, mime_type=mime_type,
modified=os.path.getmtime(path)
)
query_elt.addChild(file_elt)
@ -590,16 +591,32 @@ class XEP_0329(object):
query_elt = iq_result_elt.addElement((NS_FIS, "query"))
query_elt["node"] = node_path
if not self.host.trigger.point(
"XEP-0329_compGetFilesFromNode", client, iq_elt, owner, node_path, files_data
"XEP-0329_compGetFilesFromNode",
client,
iq_elt,
iq_result_elt,
owner,
node_path,
files_data
):
return
for file_data in files_data:
if file_data['type'] == C.FILE_TYPE_DIRECTORY:
directory_elt = query_elt.addElement("directory")
directory_elt['name'] = file_data['name']
self.host.trigger.point(
"XEP-0329_compGetFilesFromNode_build_directory",
client,
file_data,
directory_elt,
owner,
node_path,
)
else:
file_elt = self._jf.buildFileElementFromDict(
file_data, modified=file_data.get("modified", file_data["created"])
client,
file_data,
modified=file_data.get("modified", file_data["created"])
)
query_elt.addChild(file_elt)
client.send(iq_result_elt)
@ -609,7 +626,7 @@ class XEP_0329(object):
client, iq_elt, self._compGetRootNodesCb, self._compGetFilesFromNodeCb
)
def _parseResult(self, iq_elt):
def _parseResult(self, iq_elt, client):
query_elt = next(iq_elt.elements(NS_FIS, "query"))
files = []
@ -617,7 +634,7 @@ class XEP_0329(object):
if elt.name == "file":
# we have a file
try:
file_data = self._jf.parseFileElement(elt)
file_data = self._jf.parseFileElement(client, elt)
except exceptions.DataError:
continue
file_data["type"] = C.FILE_TYPE_FILE
@ -625,6 +642,12 @@ class XEP_0329(object):
# we have a directory
file_data = {"name": elt["name"], "type": C.FILE_TYPE_DIRECTORY}
self.host.trigger.point(
"XEP-0329_parseResult_directory",
client,
elt,
file_data,
)
else:
log.warning(
_(f"unexpected element, ignoring: {elt.toXml()}")
@ -665,7 +688,7 @@ class XEP_0329(object):
if path:
query_elt["node"] = path
d = iq_elt.send()
d.addCallback(self._parseResult)
d.addCallback(self._parseResult, client)
return d
def _localSharesGet(self, profile):