diff --git a/document_sftp/README.rst b/document_sftp/README.rst index 5a637b99..19601c2f 100644 --- a/document_sftp/README.rst +++ b/document_sftp/README.rst @@ -71,7 +71,7 @@ Images Contributors ------------ -* Holger Brunn +* Holger Brunn Do not contact contributors directly about help with questions or problems concerning this addon, but use the `community mailing list `_ or the `appropriate specialized mailinglist `_ for help, and the bug tracker linked in `Bug Tracker`_ above for technical issues. diff --git a/document_sftp/document_sftp_handle.py b/document_sftp/document_sftp_handle.py index a5e0b53c..7ef716d2 100644 --- a/document_sftp/document_sftp_handle.py +++ b/document_sftp/document_sftp_handle.py @@ -1,20 +1,44 @@ # -*- coding: utf-8 -*- # © 2016 Therp BV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from paramiko import SFTP_EOF, SFTPHandle -from base64 import b64decode +from paramiko import SFTP_EOF, SFTP_OK, SFTP_PERMISSION_DENIED, SFTPHandle +from base64 import b64decode, b64encode +from openerp.models import NewId +from openerp.exceptions import AccessError class DocumentSFTPHandle(SFTPHandle): - def __init__(self, attachment, flags=0): - self.attachment = attachment + def __init__(self, record, flags=0): + self.record = record super(DocumentSFTPHandle, self).__init__(flags) def stat(self): - return self.attachment.env['document.sftp.root']._file(self.attachment) + return self.record.env['document.sftp.root']._file(self.record) def read(self, offset, length): - data = b64decode(self.attachment.datas) + data = b64decode(self.record.datas) if offset > len(data): return SFTP_EOF return data[offset:offset + length] + + def write(self, offset, write_data): + data = b64decode(self.record.datas) if self.record.datas else '' + if offset > len(data): + return SFTP_EOF + try: + self.record.update({ + 'datas': b64encode( + data[0:offset] + write_data + + data[offset + len(write_data):] + ), + }) + if isinstance(self.record.id, NewId): + self.record.create(self.record._cache) + except AccessError: + return SFTP_PERMISSION_DENIED + + # we need this commit, because this runs in its own thread with its own + # cursor + self.record.env.cr.commit() + # TODO: do we want to clear caches here? + return SFTP_OK diff --git a/document_sftp/document_sftp_server.py b/document_sftp/document_sftp_server.py index 383ac6e8..fa375331 100644 --- a/document_sftp/document_sftp_server.py +++ b/document_sftp/document_sftp_server.py @@ -19,6 +19,7 @@ class DocumentSFTPServer(ServerInterface): if not user: return AUTH_FAILED user.sudo(user.id).check_credentials(password) + self.env = self.env(user=user.id) return AUTH_SUCCESSFUL except AccessDenied: pass @@ -37,6 +38,7 @@ class DocumentSFTPServer(ServerInterface): 'Ignoring key of unknown type for line %s', line) continue if RSAKey(data=decodebytes(key_data)) == key: + self.env = self.env(user=user.id) return AUTH_SUCCESSFUL return AUTH_FAILED diff --git a/document_sftp/document_sftp_sftp_server.py b/document_sftp/document_sftp_sftp_server.py index ed4e7597..09f36fab 100644 --- a/document_sftp/document_sftp_sftp_server.py +++ b/document_sftp/document_sftp_sftp_server.py @@ -7,7 +7,7 @@ from openerp import api class DocumentSFTPSftpServerInterface(SFTPServerInterface): def __init__(self, server, env): - self.env = env + self.env = server.env def list_folder(self, path): if not path or path in ('/', '.'): @@ -38,6 +38,7 @@ class DocumentSFTPSftpServerInterface(SFTPServerInterface): return handler._open(path, flags, attr) def session_ended(self): + self.env.cr.commit() self.env.cr.close() return super(DocumentSFTPSftpServerInterface, self).session_ended() diff --git a/document_sftp/models/document_sftp_root.py b/document_sftp/models/document_sftp_root.py index 093a2814..c3e80629 100644 --- a/document_sftp/models/document_sftp_root.py +++ b/document_sftp/models/document_sftp_root.py @@ -2,12 +2,13 @@ # © 2016 Therp BV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import stat +import time try: from paramiko import SFTPAttributes - from ..document_sftp_handle import DocumentSFTPHandle except ImportError: # pragma: no cover pass -from openerp import api, models +from ..document_sftp_handle import DocumentSFTPHandle +from openerp import api, models, fields class DocumentSFTPRoot(models.AbstractModel): @@ -36,6 +37,9 @@ class DocumentSFTPRoot(models.AbstractModel): result.st_group = 0 result.st_size = attachment.file_size result.st_mode = stat.S_IFREG | stat.S_IRUSR + result.st_mtime = time.mktime(fields.Datetime.from_string( + attachment.create_date + ).timetuple()) return result @api.model @@ -62,3 +66,10 @@ class DocumentSFTPRoot(models.AbstractModel): def _lstat(self, path): """Return attributes about a link""" return self._stat(path) + + @api.model + def _split_path(self, path): + """Return a list of normalized and stripped path components""" + # TODO: normalization + path = path.strip('/') + return path.split('/') diff --git a/document_sftp/models/document_sftp_root_by_model.py b/document_sftp/models/document_sftp_root_by_model.py index 76d0c818..e9bf468c 100644 --- a/document_sftp/models/document_sftp_root_by_model.py +++ b/document_sftp/models/document_sftp_root_by_model.py @@ -41,8 +41,7 @@ class DocumentSFTPRootByModel(models.Model): @api.model def _list_folder(self, path): - path = path.strip('/') - components = path.split('/') + components = self._split_path(path) result = [] if len(components) == 1: for model in self.env['ir.model'].search([ @@ -55,17 +54,11 @@ class DocumentSFTPRootByModel(models.Model): result.append(self._directory(model.model)) elif len(components) == 2: model = components[-1] - seen = set([]) if model not in self.env.registry: return SFTP_NO_SUCH_FILE - for attachment in self.env['ir.attachment'].search([ - ('res_model', '=', model), - ('res_id', '!=', False), - ], order='res_id asc'): + for record in self.env[model].search([], order='id asc'): # TODO: better lump ids together in steps of 100 or something? - if attachment.res_id not in seen: - seen.add(attachment.res_id) - result.append(self._directory(str(attachment.res_id))) + result.append(self._directory(str(record.id))) elif len(components) == 3: model = components[-2] res_id = int(components[-1]) @@ -81,10 +74,32 @@ class DocumentSFTPRootByModel(models.Model): @api.model def _open(self, path, flags, attr): if flags & os.O_WRONLY or flags & os.O_RDWR: - # TODO: do something more sensible here - return SFTP_PERMISSION_DENIED - path = path.strip('/') - components = path.split('/') + return self._open_write(path, flags, attr) + return self._open_read(path, flags, attr) + + @api.model + def _open_write(self, path, flags, attr): + components = self._split_path(path) + if len(components) == 4: + # TODO: locking! + existing = self.env['ir.attachment'].search([ + ('res_model', '=', components[-3]), + ('res_id', '=', int(components[-2])), + ('datas_fname', '=', components[-1]), + ]) + return self._file_handle( + existing or self.env['ir.attachment'].new({ + 'res_model': components[-3], + 'res_id': int(components[-2]), + 'datas_fname': components[-1], + 'name': components[-1], + }) + ) + return SFTP_PERMISSION_DENIED + + @api.model + def _open_read(self, path, flags, attr): + components = self._split_path(path) if len(components) == 4: return self._file_handle(self.env['ir.attachment'].search([ ('res_model', '=', components[-3]), diff --git a/document_sftp/views/res_users.xml b/document_sftp/views/res_users.xml index aacc1c51..c31d4356 100644 --- a/document_sftp/views/res_users.xml +++ b/document_sftp/views/res_users.xml @@ -20,7 +20,7 @@ - +