libervia-backend/src/plugins/plugin_comp_file_sharing.py
Goffi 261e3b3099 file sharing component: thumbnails integration first draft:
if an uploaded file is detected as an image, 2 thumbnails (small and medium size) are generated automatically.
2018-03-02 17:53:31 +01:00

185 lines
8.2 KiB
Python
Executable file
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 <http://www.gnu.org/licenses/>.
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
import mimetypes
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-0264", "XEP-0329"],
C.PI_RECOMMENDATIONS: [],
C.PI_MAIN: "FileSharing",
C.PI_HANDLER: "no",
C.PI_DESCRIPTION: _(u"""Component hosting and sharing files""")
}
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']
self._t = host.plugins['XEP-0264']
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):
name = file_data[u'name']
extra = {}
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))
mime_type = file_data.get(u'mime_type')
if not mime_type or mime_type == u'application/octet-stream':
mime_type = mimetypes.guess_type(name)[0]
if mime_type is not None and mime_type.startswith(u'image'):
thumbnails = extra.setdefault(C.KEY_THUMBNAILS, [])
for max_thumb_size in (self._t.SIZE_SMALL, self._t.SIZE_MEDIUM):
try:
thumb_size, thumb_id = yield self._t.generateThumbnail(final_path,
max_thumb_size,
# we keep thumbnails for 6 months
60*60*24*31*6)
except Exception as e:
log.warning(_(u"Can't create thumbnail: {reason}").format(reason=e))
break
thumbnails.append({u'id': thumb_id, u'size': thumb_size})
self.host.memory.setFile(client,
name=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'),
mime_type=mime_type,
owner=peer_jid,
extra=extra)
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 C.KEY_PROGRESS_ID 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)