# HG changeset patch # User Goffi # Date 1519838919 -3600 # Node ID 67cc54b01a124b481d37f9271b40a6858986a535 # Parent c0bec8bac2b51b23ee1a3d715e3d85e76d7e5379 plugin file sharing component: first draft: this component act as an endpoint where a user can store and retrieve its files using Jingle. diff -r c0bec8bac2b5 -r 67cc54b01a12 src/plugins/plugin_comp_file_sharing.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/plugin_comp_file_sharing.py Wed Feb 28 18:28:39 2018 +0100 @@ -0,0 +1,160 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +# SAT plugin for parrot mode (experimental) +# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from sat.core.i18n import _ +from sat.core.constants import Const as C +from sat.core import exceptions +from sat.core.log import getLogger +log = getLogger(__name__) +from sat.tools.common import regex +from sat.tools import stream +from twisted.internet import defer +import os +import os.path + + +PLUGIN_INFO = { + C.PI_NAME: "File sharing component", + C.PI_IMPORT_NAME: "file_sharing", + C.PI_MODES: [C.PLUG_MODE_COMPONENT], + C.PI_TYPE: C.PLUG_TYPE_ENTRY_POINT, + C.PI_PROTOCOLS: [], + C.PI_DEPENDENCIES: ["FILE", "XEP-0234", "XEP-0260", "XEP-0261", "XEP-0329"], + C.PI_RECOMMENDATIONS: [], + C.PI_MAIN: "FileSharing", + C.PI_HANDLER: "no", + C.PI_DESCRIPTION: _(u"""Component hosting and sharing files""") +} + +PROGRESS_ID_KEY = 'progress_id' +HASH_ALGO = u'sha-256' + + +class FileSharing(object): + + def __init__(self, host): + log.info(_(u"File Sharing initialization")) + self.host = host + self._f = host.plugins['FILE'] + self._jf = host.plugins['XEP-0234'] + self._h = host.plugins['XEP-0300'] + host.trigger.add("FILE_getDestDir", self._getDestDirTrigger) + host.trigger.add("XEP-0234_fileSendingRequest", self._fileSendingRequestTrigger, priority=1000) + self.files_path = host.getLocalPath(None, C.FILES_DIR, profile=False) + + def profileConnected(self, client): + path = client.file_tmp_dir = os.path.join( + self.host.memory.getConfig('', 'local_dir'), + C.FILES_TMP_DIR, + regex.pathEscape(client.profile)) + if not os.path.exists(path): + os.makedirs(path) + + @defer.inlineCallbacks + def _fileTransferedCb(self, dummy, client, peer_jid, file_data, file_path): + if file_data[u'hash_algo'] == HASH_ALGO: + log.debug(_(u"Reusing already generated hash")) + file_hash = file_data[u'hash_hasher'].hexdigest() + else: + hasher = self._h.getHasher(HASH_ALGO) + with open('file_path') as f: + file_hash = yield self._h.calculateHash(f, hasher) + final_path = os.path.join(self.files_path, file_hash) + if os.path.isfile(final_path): + log.debug(u"file [{file_hash}] already exists, we can remove temporary one".format(file_hash = file_hash)) + os.unlink(file_path) + else: + os.rename(file_path, final_path) + log.debug(u"file [{file_hash}] moved to {files_path}".format(file_hash=file_hash, files_path=self.files_path)) + self.host.memory.setFile(client, + name=file_data[u'name'], + version=u'', + file_hash=file_hash, + hash_algo=HASH_ALGO, + size=file_data[u'size'], + path=file_data.get(u'path'), + namespace=file_data.get(u'namespace'), + owner=peer_jid) + + def _getDestDirTrigger(self, client, peer_jid, transfer_data, file_data, stream_object): + if not client.is_component: + return True, None + assert stream_object + assert 'stream_object' not in transfer_data + assert PROGRESS_ID_KEY in file_data + filename = file_data['name'] + assert filename and not '/' in filename + file_tmp_dir = self.host.getLocalPath(client, C.FILES_TMP_DIR, peer_jid.userhost(), component=True, profile=False) + file_tmp_path = file_data['file_path'] = os.path.join(file_tmp_dir, file_data['name']) + + transfer_data['finished_d'].addCallback(self._fileTransferedCb, client, peer_jid, file_data, file_tmp_path) + + self._f.openFileWrite(client, file_tmp_path, transfer_data, file_data, stream_object) + return False, defer.succeed(True) + + @defer.inlineCallbacks + def _retrieveFiles(self, client, session, content_data, content_name, file_data, file_elt): + peer_jid = session[u'peer_jid'] + try: + found_files = yield self.host.memory.getFiles(client, + peer_jid=peer_jid, + name=file_data.get(u'name'), + file_hash=file_data.get(u'hash'), + hash_algo=file_data.get(u'hash_algo'), + path=file_data.get(u'path'), + namespace=file_data.get(u'namespace')) + except exceptions.NotFound: + found_files = None + except exceptions.PermissionError: + log.warning(_(u"{peer_jid} is trying to access an unauthorized file: {name}").format( + peer_jid=peer_jid, name=file_data.get(u'name'))) + defer.returnValue(False) + + if not found_files: + log.warning(_(u"no matching file found ({file_data})").format(file_data=file_data)) + defer.returnValue(False) + + # we only use the first found file + found_file = found_files[0] + file_hash = found_file[u'hash'] + file_path = os.path.join(self.files_path, file_hash) + file_data[u'hash_hasher'] = hasher = self._h.getHasher(found_file[u'hash_algo']) + size = file_data[u'size'] = found_file[u'size'] + file_data[u'file_hash'] = file_hash + file_data[u'hash_algo'] = found_file[u'hash_algo'] + + # we complete file_elt so peer can have some details on the file + if u'name' not in file_data: + file_elt.addElement(u'name', content=found_file[u'name']) + file_elt.addElement(u'size', content=unicode(size)) + content_data['stream_object'] = stream.FileStreamObject( + self.host, + client, + file_path, + uid=self._jf.getProgressId(session, content_name), + size=size, + data_cb=lambda data: hasher.update(data), + ) + defer.returnValue(True) + + def _fileSendingRequestTrigger(self, client, session, content_data, content_name, file_data, file_elt): + if not client.is_component: + return True, None + else: + return False, self._retrieveFiles(client, session, content_data, content_name, file_data, file_elt) diff -r c0bec8bac2b5 -r 67cc54b01a12 src/plugins/plugin_misc_ip.py --- a/src/plugins/plugin_misc_ip.py Wed Feb 28 18:28:39 2018 +0100 +++ b/src/plugins/plugin_misc_ip.py Wed Feb 28 18:28:39 2018 +0100 @@ -45,6 +45,7 @@ C.PI_NAME: "IP discovery", C.PI_IMPORT_NAME: "IP", C.PI_TYPE: C.PLUG_TYPE_MISC, + C.PI_MODES: C.PLUG_MODE_BOTH, C.PI_PROTOCOLS: ["XEP-0279"], C.PI_RECOMMENDATIONS: ["NAT-PORT"], C.PI_MAIN: "IPPlugin", diff -r c0bec8bac2b5 -r 67cc54b01a12 src/plugins/plugin_xep_0047.py --- a/src/plugins/plugin_xep_0047.py Wed Feb 28 18:28:39 2018 +0100 +++ b/src/plugins/plugin_xep_0047.py Wed Feb 28 18:28:39 2018 +0100 @@ -54,6 +54,7 @@ C.PI_NAME: "In-Band Bytestream Plugin", C.PI_IMPORT_NAME: "XEP-0047", C.PI_TYPE: "XEP", + C.PI_MODES: C.PLUG_MODE_BOTH, C.PI_PROTOCOLS: ["XEP-0047"], C.PI_MAIN: "XEP_0047", C.PI_HANDLER: "yes", diff -r c0bec8bac2b5 -r 67cc54b01a12 src/plugins/plugin_xep_0115.py --- a/src/plugins/plugin_xep_0115.py Wed Feb 28 18:28:39 2018 +0100 +++ b/src/plugins/plugin_xep_0115.py Wed Feb 28 18:28:39 2018 +0100 @@ -41,6 +41,7 @@ C.PI_NAME: "XEP 0115 Plugin", C.PI_IMPORT_NAME: "XEP-0115", C.PI_TYPE: "XEP", + C.PI_MODES: C.PLUG_MODE_BOTH, C.PI_PROTOCOLS: ["XEP-0115"], C.PI_DEPENDENCIES: [], C.PI_MAIN: "XEP_0115", diff -r c0bec8bac2b5 -r 67cc54b01a12 src/plugins/plugin_xep_0260.py --- a/src/plugins/plugin_xep_0260.py Wed Feb 28 18:28:39 2018 +0100 +++ b/src/plugins/plugin_xep_0260.py Wed Feb 28 18:28:39 2018 +0100 @@ -41,6 +41,7 @@ C.PI_NAME: "Jingle SOCKS5 Bytestreams", C.PI_IMPORT_NAME: "XEP-0260", C.PI_TYPE: "XEP", + C.PI_MODES: C.PLUG_MODE_BOTH, C.PI_PROTOCOLS: ["XEP-0260"], C.PI_DEPENDENCIES: ["XEP-0166", "XEP-0065"], C.PI_RECOMMENDATIONS: ["XEP-0261"], # needed for fallback diff -r c0bec8bac2b5 -r 67cc54b01a12 src/plugins/plugin_xep_0261.py --- a/src/plugins/plugin_xep_0261.py Wed Feb 28 18:28:39 2018 +0100 +++ b/src/plugins/plugin_xep_0261.py Wed Feb 28 18:28:39 2018 +0100 @@ -38,6 +38,7 @@ C.PI_NAME: "Jingle In-Band Bytestreams", C.PI_IMPORT_NAME: "XEP-0261", C.PI_TYPE: "XEP", + C.PI_MODES: C.PLUG_MODE_BOTH, C.PI_PROTOCOLS: ["XEP-0261"], C.PI_DEPENDENCIES: ["XEP-0166", "XEP-0047"], C.PI_MAIN: "XEP_0261",