From f3cb7cd41539de507f800f10826f003017a40c8a Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 29 Jun 2010 17:05:30 +0300 Subject: [PATCH] Document storage: implement node_descriptors. bzr revid: p_christ@hol.gr-20100629140530-btusqmlz2lyadfbx --- addons/document/document_storage.py | 102 ++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/addons/document/document_storage.py b/addons/document/document_storage.py index 90be2bc47df..b2db4eb36a6 100644 --- a/addons/document/document_storage.py +++ b/addons/document/document_storage.py @@ -24,7 +24,9 @@ from osv import osv, fields import os import tools import base64 +import errno import logging +from StringIO import StringIO from tools.misc import ustr from tools.translate import _ @@ -34,6 +36,7 @@ from osv.orm import except_orm import random import string import netsvc +import nodes from content_index import cntIndex DMS_ROOT_PATH = tools.config.get('document_path', os.path.join(tools.config.get('root_path'), 'filestore')) @@ -84,6 +87,64 @@ def create_directory(path): os.makedirs(path) return dir_name +class nodefd_file(nodes.node_descriptor): + """ A descriptor to a real file + + Inheriting directly from file doesn't work, since file exports + some read-only attributes (like 'name') that we don't like. + """ + def __init__(self, parent, path, mode): + nodes.node_descriptor.__init__(self, parent) + self.__file = open(path, mode) + + for attr in ('closed', 'read', 'write', 'seek', 'tell'): + setattr(self,attr, getattr(self.__file, attr)) + + def close(self): + # TODO: locking in init, close() + self.__file.close() + + +class nodefd_db(StringIO, nodes.node_descriptor): + """ A descriptor to db data + """ + def __init__(self, parent, ira_browse, mode): + nodes.node_descriptor.__init__(self, parent) + if mode.endswith('b'): + mode = mode[:-1] + + if mode == 'r': + StringIO.__init__(self, ira_browse.db_datas) + elif mode == 'w': + StringIO.__init__(self, ira_browse.db_datas) + # at write, we start at 0 (= overwrite), but have the original + # data available, in case of a seek() + elif mode == 'a': + StringIO.__init__(self, None) + else: + logging.getLogger('document.storage').error("Incorrect mode %s specified", mode) + raise IOError(errno.EINVAL, "Invalid file mode") + self.mode = mode + + def close(self): + # we now open a *separate* cursor, to update the data. + # FIXME: this may be improved, for concurrency handling + uid = self.__parent.context.uid + cr = pooler.get_db(self.__parent.context.dbname).cursor() + if mode == 'w': + out = self.getvalue() + cr.execute('UPDATE ir_attachment SET db_datas = %s, file_size=%d WHERE id = %s', + (out, len(out), self.__parent.file_id)) + elif mode == 'a': + out = self.getvalue() + cr.execute("UPDATE ir_attachment " \ + "SET db_datas = COALESCE(db_datas,'') || %s, " \ + " file_size = COALESCE(file_size, 0) + %d " \ + " WHERE id = %s", + (out, len(out), self.__parent.file_id)) + cr.commit() + cr.close() + StringIO.close(self) class document_storage(osv.osv): """ The primary object for data storage. @@ -147,6 +208,47 @@ class document_storage(osv.osv): ira = self.pool.get('ir.attachment').browse(cr, uid, file_node.file_id, context=context) return self.__get_data_3(cr, uid, boo, ira, context) + def get_file(self, cr, uid, id, file_node, mode, context=None): + if context is None: + context = {} + boo = self.browse(cr, uid, id, context) + if not boo.online: + raise RuntimeError('media offline') + + ira = self.pool.get('ir.attachment').browse(cr, uid, file_node.file_id, context=context) + if boo.type == 'filestore': + if not ira.store_fname: + # On a migrated db, some files may have the wrong storage type + # try to fix their directory. + if ira.file_size: + self._doclog.warning( "ir.attachment #%d does not have a filename, but is at filestore, fix it!" % ira.id) + raise IOError(errno.ENOENT, 'No file can be located') + fpath = os.path.join(boo.path, ira.store_fname) + if self._debug: + self._doclog.debug("Trying to read \"%s\".."% fpath) + return nodefd_file(file_node, path=fpath, mode=mode) + + elif boo.type == 'db': + # TODO: we need a better api for large files + if self._debug: + self._doclog.debug("Trying to obtain db_datas for ir.attachment[%d]", ira.id) + return nodefd_db(file_node, ira_browse=ira, mode=mode) + + elif boo.type == 'realstore': + if not ira.store_fname: + # On a migrated db, some files may have the wrong storage type + # try to fix their directory. + if ira.file_size: + self._doclog.warning("ir.attachment #%d does not have a filename, trying the name." %ira.id) + sfname = ira.name + fpath = os.path.join(boo.path,ira.store_fname or ira.name) + if not os.path.exists(fpath): + raise IOError("File not found: %s" % fpath) + return nodefd_file(file_node, path=fpath, mode=mode) + + else: + raise TypeError("No %s storage" % boo.type) + def __get_data_3(self, cr, uid, boo, ira, context): if not boo.online: raise RuntimeError('media offline')