# -*- coding: utf-8 -*-
##############################################################################
-#
+#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# 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/>.
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
-import base64
-
-from osv import osv, fields
-from osv.orm import except_orm
-import urlparse
+# import urlparse
import pooler
from tools.safe_eval import safe_eval
-import os
+import errno
+# import os
import time
+import logging
+
+from StringIO import StringIO
#
# An object that represent an uri
# root: if we are at the first directory of a ressource
#
+logger = logging.getLogger('doc2.nodes')
+
+def _str2time(cre):
+ """ Convert a string with time representation (from db) into time (float)
+
+ Note: a place to fix if datetime is used in db.
+ """
+ if not cre:
+ return time.time()
+ frac = 0.0
+ if isinstance(cre, basestring) and '.' in cre:
+ fdot = cre.find('.')
+ frac = float(cre[fdot:])
+ cre = cre[:fdot]
+ return time.mktime(time.strptime(cre,'%Y-%m-%d %H:%M:%S')) + frac
+
def get_node_context(cr, uid, context):
- return node_context(cr,uid,context)
+ return node_context(cr, uid, context)
class node_context(object):
- """ This is the root node, representing access to some particular
- context """
+ """ This is the root node, representing access to some particular context
+
+ A context is a set of persistent data, which may influence the structure
+ of the nodes. All other transient information during a data query should
+ be passed down with function arguments.
+ """
cached_roots = {}
+ node_file_class = None
def __init__(self, cr, uid, context=None):
- # we don't cache the cr!
self.dbname = cr.dbname
self.uid = uid
self.context = context
+ if context is None:
+ context = {}
+ context['uid'] = uid
self._dirobj = pooler.get_pool(cr.dbname).get('document.directory')
+ self.node_file_class = node_file
+ self.extra_ctx = {} # Extra keys for context, that do _not_ trigger inequality
assert self._dirobj
- self.rootdir = self._dirobj._get_root_directory(cr,uid,context)
+ self._dirobj._prepare_context(cr, uid, self, context=context)
+ self.rootdir = False #self._dirobj._get_root_directory(cr,uid,context)
+
+ def __eq__(self, other):
+ if not type(other) == node_context:
+ return False
+ if self.dbname != other.dbname:
+ return False
+ if self.uid != other.uid:
+ return False
+ if self.context != other.context:
+ return False
+ if self.rootdir != other.rootdir:
+ return False
+ return True
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def get(self, name, default=None):
+ return self.context.get(name, default)
def get_uri(self, cr, uri):
""" Although this fn passes back to doc.dir, it is needed since
- it is a potential caching point """
- (ndir, duri) = self._dirobj._locate_child(cr,self.uid, self.rootdir,uri, None, self)
- while duri:
- ndir = ndir.child(cr, duri[0])
+ it is a potential caching point.
+ """
+ (ndir, duri) = self._dirobj._locate_child(cr, self.uid, self.rootdir, uri, None, self)
+ while duri:
+ ndir = ndir.child(cr, duri[0])
if not ndir:
- return False
- duri = duri[1:]
+ return False
+ duri = duri[1:]
return ndir
+ def get_dir_node(self, cr, dbro):
+ """Create (or locate) a node for a directory
+ @param dbro a browse object of document.directory
+ """
+
+ fullpath = dbro.get_full_path(context=self.context)
+ klass = dbro.get_node_class(dbro, context=self.context)
+ return klass(fullpath, None ,self, dbro)
-class node_database():
- """ A node representing the database directory
- Useless?
+ def get_file_node(self, cr, fbro):
+ """ Create or locate a node for a static file
+ @param fbro a browse object of an ir.attachment
"""
- def __init__(self,ncontext):
- self.nctx = ncontext
+ parent = None
+ if fbro.parent_id:
+ parent = self.get_dir_node(cr, fbro.parent_id)
+
+ return self.node_file_class(fbro.name, parent, self, fbro)
+
+
+class node_descriptor(object):
+ """A file-like interface to the data contents of a node.
+
+ This class is NOT a node, but an /open descriptor/ for some
+ node. It can hold references to a cursor or a file object,
+ because the life of a node_descriptor will be the open period
+ of the data.
+ It should also take care of locking, with any native mechanism
+ or using the db.
+ For the implementation, it would be OK just to wrap around file,
+ StringIO or similar class. The node_descriptor is only needed to
+ provide the link to the parent /node/ object.
+ """
+
+ def __init__(self, parent):
+ assert isinstance(parent, node_class)
+ self.name = parent.displayname
+ self.__parent = parent
+
+ def _get_parent(self):
+ return self.__parent
+ def open(self, **kwargs):
+ raise NotImplementedError
+ def close(self):
+ raise NotImplementedError
+ def read(self, size=None):
+ raise NotImplementedError
+
+ def seek(self, offset, whence=None):
+ raise NotImplementedError
+
+ def tell(self):
+ raise NotImplementedError
+
+ def write(self, str):
+ raise NotImplementedError
+
+ def size(self):
+ raise NotImplementedError
+
+ def __len__(self):
+ return self.size()
+
+ def __nonzero__(self):
+ """ Ensure that a node_descriptor will never equal False
+
+ Since we do define __len__ and __iter__ for us, we must avoid
+ being regarded as non-true objects.
+ """
+ return True
+
+ def next(self, str):
+ raise NotImplementedError
class node_class(object):
""" this is a superclass for our inodes
- It is an API for all code that wants to access the document files.
+ It is an API for all code that wants to access the document files.
Nodes have attributes which contain usual file properties
"""
our_type = 'baseclass'
+ DAV_PROPS = None
+ DAV_M_NS = None
+
def __init__(self, path, parent, context):
assert isinstance(context,node_context)
assert (not parent ) or isinstance(parent,node_class)
self.context = context
self.type=self.our_type
self.parent = parent
+ self.uidperms = 5 # computed permissions for our uid, in unix bits
self.mimetype = 'application/octet-stream'
self.create_date = None
self.write_date = None
+ self.unixperms = 0660
+ self.uuser = 'user'
+ self.ugroup = 'group'
self.content_length = 0
# dynamic context:
self.dctx = {}
if parent:
self.dctx = parent.dctx.copy()
self.displayname = 'Object'
-
+
+ def __eq__(self, other):
+ return NotImplemented
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
def full_path(self):
""" Return the components of the full path for some
- node.
+ node.
The returned list only contains the names of nodes.
"""
if self.parent:
s = []
if isinstance(self.path,list):
s+=self.path
+ elif self.path is None:
+ s.append('')
else:
s.append(self.path)
return s #map(lambda x: '/' +x, s)
+
+ def __repr__(self):
+ return "%s@/%s" % (self.our_type, '/'.join(self.full_path()))
- def children(self, cr):
+ def children(self, cr, domain=None):
print "node_class.children()"
return [] #stub
- def child(self,cr, name):
+ def child(self,cr, name, domain=None):
print "node_class.child()"
return None
+ def get_uri(self, cr, uri):
+ duri = uri
+ ndir = self
+ while duri:
+ ndir = ndir.child(cr, duri[0])
+ if not ndir:
+ return False
+ duri = duri[1:]
+ return ndir
+
def path_get(self):
print "node_class.path_get()"
return False
-
+
def get_data(self,cr):
raise TypeError('no data for %s'% self.type)
+ def open_data(self, cr, mode):
+ """ Open a node_descriptor object for this node.
+
+ @param the mode of open, eg 'r', 'w', 'a', like file.open()
+
+ This operation may lock the data for this node (and accross
+ other node hierarchies), until the descriptor is close()d. If
+ the node is locked, subsequent opens (depending on mode) may
+ immediately fail with an exception (which?).
+ For this class, there is no data, so no implementation. Each
+ child class that has data should override this.
+ """
+ raise TypeError('no data for %s' % self.type)
+
def _get_storage(self,cr):
raise RuntimeError("no storage for base class")
def get_etag(self,cr):
""" Get a tag, unique per object + modification.
-
+
see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """
- return self._get_ttag(cr) + ':' + self._get_wtag(cr)
+ return '"%s-%s"' % (self._get_ttag(cr), self._get_wtag(cr))
- def _get_wtag(self,cr):
+ def _get_wtag(self, cr):
""" Return the modification time as a unique, compact string """
- if self.write_date:
- wtime = time.mktime(time.strptime(self.write_date,'%Y-%m-%d %H:%M:%S'))
- else: wtime = time.time()
- return str(wtime)
-
- def _get_ttag(self,cr):
+ return str(_str2time(self.write_date)).replace('.','')
+
+ def _get_ttag(self, cr):
""" Get a unique tag for this type/id of object.
Must be overriden, so that each node is uniquely identified.
"""
print "node_class.get_ttag()",self
- raise RuntimeError("get_etag stub()")
-
+ raise NotImplementedError("get_ttag stub()")
+
def get_dav_props(self, cr):
""" If this class has special behaviour for GroupDAV etc, export
its capabilities """
- return {}
+ # This fn is placed here rather than WebDAV, because we want the
+ # baseclass methods to apply to all node subclasses
+ return self.DAV_PROPS or {}
+
+ def match_dav_eprop(self, cr, match, ns, prop):
+ res = self.get_dav_eprop(cr, ns, prop)
+ if res == match:
+ return True
+ return False
+
+ def get_dav_eprop(self, cr, ns, prop):
+ if not self.DAV_M_NS:
+ return None
+
+ if self.DAV_M_NS.has_key(ns):
+ prefix = self.DAV_M_NS[ns]
+ else:
+ logger.debug('No namespace: %s ("%s")',ns, prop)
+ return None
+
+ mname = prefix + "_" + prop.replace('-','_')
+
+ if not hasattr(self, mname):
+ return None
+
+ try:
+ m = getattr(self, mname)
+ r = m(cr)
+ return r
+ except AttributeError:
+ logger.debug('Property %s not supported' % prop, exc_info=True)
+ return None
+
+ def get_dav_resourcetype(self, cr):
+ """ Get the DAV resource type.
+
+ Is here because some nodes may exhibit special behaviour, like
+ CalDAV/GroupDAV collections
+ """
+ raise NotImplementedError
+
+ def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
+ """ Move this node to a new parent directory.
+ @param ndir_node the collection that this node should be moved under
+ @param new_name a name to rename this node to. If omitted, the old
+ name is preserved
+ @param fil_obj, can be None, is the browse object for the file,
+ if already available.
+ @param ndir_obj must be the browse object to the new doc.directory
+ location, where this node should be moved to.
+ in_write: When called by write(), we shouldn't attempt to write the
+ object, but instead return the dict of vals (avoid re-entrance).
+ If false, we should write all data to the object, here, as if the
+ caller won't do anything after calling move_to()
+
+ Return value:
+ True: the node is moved, the caller can update other values, too.
+ False: the node is either removed or fully updated, the caller
+ must discard the fil_obj, not attempt to write any more to it.
+ dict: values to write back to the object. *May* contain a new id!
+
+ Depending on src and target storage, implementations of this function
+ could do various things.
+ Should also consider node<->content, dir<->dir moves etc.
+
+ Move operations, as instructed from APIs (eg. request from DAV) could
+ use this function.
+ """
+ raise NotImplementedError(repr(self))
+
+ def create_child(self, cr, path, data=None):
+ """ Create a regular file under this node
+ """
+ logger.warning("Attempted to create a file under %r, not possible.", self)
+ raise IOError(errno.EPERM, "Not allowed to create files here")
+
+ def create_child_collection(self, cr, objname):
+ """ Create a child collection (directory) under self
+ """
+ logger.warning("Attempted to create a collection under %r, not possible.", self)
+ raise IOError(errno.EPERM, "Not allowed to create folders here")
+
+ def rm(self, cr):
+ raise NotImplementedError(repr(self))
+
+ def rmcol(self, cr):
+ raise NotImplementedError(repr(self))
+
+ def get_domain(self, cr, filters):
+ # TODO Document
+ return []
+
+ def check_perms(self, perms):
+ """ Check the permissions of the current node.
+
+ @param perms either an integers of the bits to check, or
+ a string with the permission letters
+
+ Permissions of nodes are (in a unix way):
+ 1, x : allow descend into dir
+ 2, w : allow write into file, or modification to dir
+ 4, r : allow read of file, or listing of dir contents
+ 8, u : allow remove (unlink)
+ """
+
+ if isinstance(perms, str):
+ pe2 = 0
+ chars = { 'x': 1, 'w': 2, 'r': 4, 'u': 8 }
+ for c in perms:
+ pe2 = pe2 | chars[c]
+ perms = pe2
+ elif isinstance(perms, int):
+ if perms < 0 or perms > 15:
+ raise ValueError("Invalid permission bits")
+ else:
+ raise ValueError("Invalid permission attribute")
+
+ return ((self.uidperms & perms) == perms)
+
+class node_database(node_class):
+ """ A node representing the database directory
+
+ """
+ our_type = 'database'
+ def __init__(self, path=[], parent=False, context=None):
+ super(node_database,self).__init__(path, parent, context)
+ self.unixperms = 040750
+ self.uidperms = 5
+
+ def children(self, cr, domain=None):
+ res = self._child_get(cr, domain=domain) + self._file_get(cr)
+ return res
- def get_dav_eprop(self,cr,ns,prop):
+ def child(self, cr, name, domain=None):
+ res = self._child_get(cr, name, domain=None)
+ if res:
+ return res[0]
+ res = self._file_get(cr,name)
+ if res:
+ return res[0]
return None
-class node_dir(node_class):
+ def _child_get(self, cr, name=False, domain=None):
+ dirobj = self.context._dirobj
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ where = [('parent_id','=', False), ('ressource_parent_type_id','=',False)]
+ if name:
+ where.append(('name','=',name))
+ is_allowed = self.check_perms(1)
+ else:
+ is_allowed = self.check_perms(5)
+
+ if not is_allowed:
+ raise IOError(errno.EPERM, "Permission into directory denied")
+
+ if domain:
+ where = where + domain
+ ids = dirobj.search(cr, uid, where, context=ctx)
+ res = []
+ for dirr in dirobj.browse(cr, uid, ids, context=ctx):
+ klass = dirr.get_node_class(dirr, context=ctx)
+ res.append(klass(dirr.name, self, self.context,dirr))
+
+ return res
+
+ def _file_get(self,cr, nodename=False):
+ res = []
+ return res
+
+ def _get_ttag(self,cr):
+ return 'db-%s' % cr.dbname
+
+def mkdosname(company_name, default='noname'):
+ """ convert a string to a dos-like name"""
+ if not company_name:
+ return default
+ badchars = ' !@#$%^`~*()+={}[];:\'"/?.<>'
+ n = ''
+ for c in company_name[:8]:
+ n += (c in badchars and '_') or c
+ return n
+
+
+def _uid2unixperms(perms, has_owner):
+ """ Convert the uidperms and the owner flag to full unix bits
+ """
+ res = 0
+ if has_owner:
+ res |= (perms & 0x07) << 6
+ res |= (perms & 0x05) << 3
+ elif perms & 0x02:
+ res |= (perms & 0x07) << 6
+ res |= (perms & 0x07) << 3
+ else:
+ res |= (perms & 0x07) << 6
+ res |= (perms & 0x05) << 3
+ res |= 0x05
+ return res
+
+class node_dir(node_database):
our_type = 'collection'
- def __init__(self,path, parent, context, dirr, dctx=None):
+ def __init__(self, path, parent, context, dirr, dctx=None):
super(node_dir,self).__init__(path, parent,context)
- self.dir_id = dirr.id
+ self.dir_id = dirr and dirr.id or False
#todo: more info from dirr
self.mimetype = 'application/x-directory'
# 'httpd/unix-directory'
- self.create_date = dirr.create_date
- self.domain = dirr.domain
- self.res_model = dirr.ressource_type_id and dirr.ressource_type_id.model or False
+ self.create_date = dirr and dirr.create_date or False
+ self.domain = dirr and dirr.domain or []
+ self.res_model = dirr and dirr.ressource_type_id and dirr.ressource_type_id.model or False
# TODO: the write date should be MAX(file.write)..
- self.write_date = dirr.write_date or dirr.create_date
+ self.write_date = dirr and (dirr.write_date or dirr.create_date) or False
self.content_length = 0
+ try:
+ self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
+ except Exception:
+ self.uuser = 'nobody'
+ self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
+ self.uidperms = dirr.get_dir_permissions()
+ self.unixperms = 040000 | _uid2unixperms(self.uidperms, dirr and dirr.user_id)
if dctx:
self.dctx.update(dctx)
dc2 = self.context.context
dc2.update(self.dctx)
dc2['dir_id'] = self.dir_id
- self.displayname = dirr.name
- if dirr.dctx_ids:
+ self.displayname = dirr and dirr.name or False
+ if dirr and dirr.dctx_ids:
for dfld in dirr.dctx_ids:
try:
self.dctx['dctx_' + dfld.field] = safe_eval(dfld.expr,dc2)
print e
pass
- def children(self,cr):
- return self._child_get(cr) + self._file_get(cr)
-
- def child(self,cr, name):
- res = self._child_get(cr,name)
- if res:
- return res[0]
- res = self._file_get(cr,name)
- if res:
- return res[0]
+ def __eq__(self, other):
+ if type(self) != type(other):
+ return False
+ if not self.context == other.context:
+ return False
+ # Two directory nodes, for the same document.directory, may have a
+ # different context! (dynamic folders)
+ if self.dctx != other.dctx:
+ return False
+ return self.dir_id == other.dir_id
+
+ def get_data(self, cr):
+ #res = ''
+ #for child in self.children(cr):
+ # res += child.get_data(cr)
return None
- def _file_get(self,cr, nodename=False):
- res = []
+ def _file_get(self, cr, nodename=False):
+ res = super(node_dir,self)._file_get(cr, nodename)
+
+ is_allowed = self.check_perms(nodename and 1 or 5)
+ if not is_allowed:
+ raise IOError(errno.EPERM, "Permission into directory denied")
+
cntobj = self.context._dirobj.pool.get('document.directory.content')
uid = self.context.uid
ctx = self.context.context.copy()
res.extend(res3)
return res
-
- def get_dav_props(self, cr):
- res = {}
- cntobj = self.context._dirobj.pool.get('document.directory.content')
+
+ def _child_get(self, cr, name=None, domain=None):
+ dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
- where = [('directory_id','=',self.dir_id) ]
- ids = cntobj.search(cr,uid,where,context=ctx)
- for content in cntobj.browse(cr,uid,ids,context=ctx):
- if content.extension == '.ics': # FIXME: call the content class!
- res['http://groupdav.org/'] = ('resourcetype',)
- break
+ where = [('parent_id','=',self.dir_id)]
+ if name:
+ where.append(('name','=',name))
+ is_allowed = self.check_perms(1)
+ else:
+ is_allowed = self.check_perms(5)
+
+ if not is_allowed:
+ raise IOError(errno.EPERM, "Permission into directory denied")
+
+ if not domain:
+ domain = []
+
+ where2 = where + domain + [('ressource_parent_type_id','=',False)]
+ ids = dirobj.search(cr, uid, where2, context=ctx)
+ res = []
+ for dirr in dirobj.browse(cr, uid, ids, context=ctx):
+ klass = dirr.get_node_class(dirr, context=ctx)
+ res.append(klass(dirr.name, self, self.context,dirr))
+
+ # Static directories should never return files with res_model/res_id
+ # because static dirs are /never/ related to a record.
+ # In fact, files related to some model and parented by the root dir
+ # (the default), will NOT be accessible in the node system unless
+ # a resource folder for that model exists (with resource_find_all=True).
+ # Having resource attachments in a common folder is bad practice,
+ # because they would be visible to all users, and their names may be
+ # the same, conflicting.
+ where += [('res_model', '=', False)]
+ fil_obj = dirobj.pool.get('ir.attachment')
+ ids = fil_obj.search(cr, uid, where, context=ctx)
+ if ids:
+ for fil in fil_obj.browse(cr, uid, ids, context=ctx):
+ klass = self.context.node_file_class
+ res.append(klass(fil.name, self, self.context, fil))
return res
- def get_dav_eprop(self,cr,ns,prop):
- if ns != 'http://groupdav.org/' or prop != 'resourcetype':
- print "Who asked for %s:%s?" % (ns,prop)
- return None
- res = {}
- cntobj = self.context._dirobj.pool.get('document.directory.content')
+ def rmcol(self, cr):
uid = self.context.uid
- ctx = self.context.context.copy()
- ctx.update(self.dctx)
- where = [('directory_id','=',self.dir_id) ]
- ids = cntobj.search(cr,uid,where,context=ctx)
- for content in cntobj.browse(cr,uid,ids,context=ctx):
- if content.extension == '.ics': # FIXME: call the content class!
- return ('vevent-collection','http://groupdav.org/')
- return None
+ directory = self.context._dirobj.browse(cr, uid, self.dir_id)
+ res = False
+ if not directory:
+ raise OSError(2, 'Not such file or directory.')
+ if not self.check_perms('u'):
+ raise IOError(errno.EPERM,"Permission denied")
+
+ if directory._table_name=='document.directory':
+ if self.children(cr):
+ raise OSError(39, 'Directory not empty.')
+ res = self.context._dirobj.unlink(cr, uid, [directory.id])
+ else:
+ raise OSError(1, 'Operation not permited.')
+ return res
+
+ def create_child_collection(self, cr, objname):
+ object2 = False
+ if not self.check_perms(2):
+ raise IOError(errno.EPERM,"Permission denied")
- def _child_get(self,cr,name = None):
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
- where = [('parent_id','=',self.dir_id) ]
- if name:
- where.append(('name','=',name))
+ obj = dirobj.browse(cr, uid, self.dir_id)
+ if obj and (obj.type == 'ressource') and not object2:
+ raise OSError(1, 'Operation not permited.')
- ids = dirobj.search(cr, uid, where + [('ressource_parent_type_id','=',False)],context=ctx)
- res = []
- if ids:
- for dirr in dirobj.browse(cr,uid,ids,context=ctx):
- if dirr.type == 'directory':
- res.append(node_dir(dirr.name,self,self.context,dirr))
- elif dirr.type == 'ressource':
- res.append(node_res_dir(dirr.name,self,self.context,dirr))
-
- fil_obj=dirobj.pool.get('ir.attachment')
- #where2 = where # + [('res_model', '=', None)]
- ids = fil_obj.search(cr,uid,where,context=ctx)
- if ids:
- for fil in fil_obj.browse(cr,uid,ids,context=ctx):
- res.append(node_file(fil.name,self,self.context,fil))
-
- return res
-
- def create_child(self,cr,path,data):
+ #objname = uri2[-1]
+ val = {
+ 'name': objname,
+ 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
+ 'ressource_id': object2 and object2.id or False,
+ 'parent_id' : obj and obj.id or False
+ }
+
+ return dirobj.create(cr, uid, val)
+
+
+ def create_child(self, cr, path, data=None):
""" API function to create a child file object and node
Return the node_* created
"""
+ if not self.check_perms(2):
+ raise IOError(errno.EPERM,"Permission denied")
+
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
# Datas are not set here
}
- fil_id = fil_obj.create(cr,uid, val, context=ctx)
- fil = fil_obj.browse(cr,uid,fil_id,context=ctx)
- fnode = node_file(path,self,self.context,fil)
- fnode.set_data(cr,data,fil)
+ fil_id = fil_obj.create(cr, uid, val, context=ctx)
+ fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
+ fnode = node_file(path, self, self.context, fil)
+ if data is not None:
+ fnode.set_data(cr, data, fil)
return fnode
-
+
def _get_ttag(self,cr):
return 'dir-%d' % self.dir_id
+ def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
+ """ Move directory. This operation is simple, since the present node is
+ only used for static, simple directories.
+ Note /may/ be called with ndir_node = None, to rename the document root.
+ """
+ if ndir_node and (ndir_node.context != self.context):
+ raise NotImplementedError("Cannot move directories between contexts")
+
+ if (not self.check_perms('u')) or (not ndir_node.check_perms('w')):
+ raise IOError(errno.EPERM,"Permission denied")
+
+ dir_obj = self.context._dirobj
+ if not fil_obj:
+ dbro = dir_obj.browse(cr, self.context.uid, self.dir_id, context=self.context.context)
+ else:
+ dbro = dir_obj
+ assert dbro.id == self.dir_id
+
+ if not dbro:
+ raise IndexError("Cannot locate dir %d", self.dir_id)
+
+ if (not self.parent) and ndir_node:
+ if not dbro.parent_id:
+ raise IOError(errno.EPERM, "Cannot move the root directory!")
+ self.parent = self.context.get_dir_node(cr, dbro.parent_id)
+ assert self.parent
+
+ if self.parent != ndir_node:
+ logger.debug('Cannot move dir %r from %r to %r', self, self.parent, ndir_node)
+ raise NotImplementedError('Cannot move dir to another dir')
+
+ ret = {}
+ if new_name and (new_name != dbro.name):
+ if ndir_node.child(cr, new_name):
+ raise IOError(errno.EEXIST, "Destination path already exists")
+ ret['name'] = new_name
+
+ del dbro
+
+ if not in_write:
+ # We have to update the data ourselves
+ if ret:
+ ctx = self.context.context.copy()
+ ctx['__from_node'] = True
+ dir_obj.write(cr, self.context.uid, [self.dir_id,], ret, ctx)
+ ret = True
+
+ return ret
+
class node_res_dir(node_class):
- """ A special sibling to node_dir, which does only contain dynamically
+ """ A folder containing dynamic folders
+ A special sibling to node_dir, which does only contain dynamically
created folders foreach resource in the foreign model.
All folders should be of type node_res_obj and merely behave like
node_dirs (with limited domain).
"""
our_type = 'collection'
- def __init__(self,path, parent, context, dirr, dctx=None ):
- super(node_res_dir,self).__init__(path, parent,context)
+ res_obj_class = None
+ def __init__(self, path, parent, context, dirr, dctx=None ):
+ super(node_res_dir,self).__init__(path, parent, context)
self.dir_id = dirr.id
#todo: more info from dirr
self.mimetype = 'application/x-directory'
# TODO: the write date should be MAX(file.write)..
self.write_date = dirr.write_date or dirr.create_date
self.content_length = 0
+ try:
+ self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
+ except Exception:
+ self.uuser = 'nobody'
+ self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
+ self.uidperms = dirr.get_dir_permissions()
+ self.unixperms = 040000 | _uid2unixperms(self.uidperms, dirr and dirr.user_id)
self.res_model = dirr.ressource_type_id and dirr.ressource_type_id.model or False
self.resm_id = dirr.ressource_id
- self.namefield = dirr.resource_field or 'name'
+ self.res_find_all = dirr.resource_find_all
+ self.namefield = dirr.resource_field.name or 'name'
self.displayname = dirr.name
- # Important: the domain is evaluated using the *parent* dctx!
+ # Important: the domain is evaluated using the *parent* dctx!
self.domain = dirr.domain
self.ressource_tree = dirr.ressource_tree
# and then, we add our own vars in the dctx:
if dctx:
self.dctx.update(dctx)
-
+
# and then, we prepare a dctx dict, for deferred evaluation:
self.dctx_dict = {}
for dfld in dirr.dctx_ids:
self.dctx_dict['dctx_' + dfld.field] = dfld.expr
- def children(self,cr):
- return self._child_get(cr)
-
- def child(self,cr, name):
- res = self._child_get(cr,name)
+ def __eq__(self, other):
+ if type(self) != type(other):
+ return False
+ if not self.context == other.context:
+ return False
+ # Two nodes, for the same document.directory, may have a
+ # different context! (dynamic folders)
+ if self.dctx != other.dctx:
+ return False
+ return self.dir_id == other.dir_id
+
+ def children(self, cr, domain=None):
+ return self._child_get(cr, domain=domain)
+
+ def child(self,cr, name, domain=None):
+ res = self._child_get(cr, name, domain=domain)
if res:
return res[0]
return None
- def _child_get(self,cr,name = None):
+ def _child_get(self, cr, name = None, domain=None):
""" return virtual children of resource, based on the
foreign object.
-
+
Note that many objects use NULL for a name, so we should
better call the name_search(),name_get() set of methods
"""
obj = self.context._dirobj.pool.get(self.res_model)
if not obj:
- print "couldn't find model", self.res_model
return []
dirobj = self.context._dirobj
uid = self.context.uid
ctx.update(self.dctx)
where = []
if self.domain:
- where += safe_eval(self.domain, self.dctx)
+ app = safe_eval(self.domain, ctx)
+ if not app:
+ pass
+ elif isinstance(app, list):
+ where.extend(app)
+ elif isinstance(app, tuple):
+ where.append(app)
+ else:
+ raise RuntimeError("incorrect domain expr: %s" % self.domain)
if self.resm_id:
where.append(('id','=',self.resm_id))
-
+
if name:
- where.append((self.namefield,'=',name))
-
+ # The =like character will match underscores against any characters
+ # including the special ones that couldn't exist in a FTP/DAV request
+ where.append((self.namefield,'=like',name.replace('\\','\\\\')))
+ is_allowed = self.check_perms(1)
+ else:
+ is_allowed = self.check_perms(5)
+
+ if not is_allowed:
+ raise IOError(errno.EPERM,"Permission denied")
+
# print "Where clause for %s" % self.res_model, where
if self.ressource_tree:
object2 = False
if self.resm_id:
- object2 = dirobj.pool.get(self.res_model).browse(cr, uid, self.resm_id) or False
- if obj._parent_name in obj.fields_get(cr, uid):
+ object2 = dirobj.pool.get(self.res_model).browse(cr, uid, self.resm_id) or False
+ if obj._parent_name in obj.fields_get(cr, uid):
where.append((obj._parent_name,'=',object2 and object2.id or False))
-
- resids = obj.search(cr,uid, where, context=ctx)
- res = []
- for bo in obj.browse(cr,uid,resids,context=ctx):
+
+ resids = obj.search(cr, uid, where, context=ctx)
+ res = []
+ for bo in obj.browse(cr, uid, resids, context=ctx):
if not bo:
continue
- name = getattr(bo,self.namefield)
- if not name:
+ res_name = getattr(bo, self.namefield)
+ if not res_name:
continue
- # Yes! we can't do better but skip nameless records.
-
- res.append(node_res_obj(name, self.dir_id, self, self.context, self.res_model, bo))
+ # Yes! we can't do better but skip nameless records.
+
+ # Escape the name for characters not supported in filenames
+ res_name = res_name.replace('/','_') # any other weird char?
+
+ if name and (res_name != name):
+ # we have matched _ to any character, but we only meant to match
+ # the special ones.
+ # Eg. 'a_c' will find 'abc', 'a/c', 'a_c', may only
+ # return 'a/c' and 'a_c'
+ continue
+
+ res.append(self.res_obj_class(res_name, self.dir_id, self, self.context, self.res_model, bo))
return res
def _get_ttag(self,cr):
return 'rdir-%d' % self.dir_id
class node_res_obj(node_class):
- """ A special sibling to node_dir, which does only contain dynamically
+ """ A dynamically created folder.
+ A special sibling to node_dir, which does only contain dynamically
created folders foreach resource in the foreign model.
All folders should be of type node_res_obj and merely behave like
node_dirs (with limited domain).
def __init__(self, path, dir_id, parent, context, res_model, res_bo, res_id = None):
super(node_res_obj,self).__init__(path, parent,context)
assert parent
- #todo: more info from dirr
+ #todo: more info from dirr
self.dir_id = dir_id
self.mimetype = 'application/x-directory'
# 'httpd/unix-directory'
# TODO: the write date should be MAX(file.write)..
self.write_date = parent.write_date
self.content_length = 0
+ self.uidperms = parent.uidperms & 15
+ self.unixperms = 040000 | _uid2unixperms(self.uidperms, True)
+ self.uuser = parent.uuser
+ self.ugroup = parent.ugroup
self.res_model = res_model
self.domain = parent.domain
- self.displayname = path
- self.dctx_dict = parent.dctx_dict
- if res_bo:
- self.res_id = res_bo.id
- dc2 = self.context.context
+ self.displayname = path
+ self.dctx_dict = parent.dctx_dict
+ if isinstance(parent, node_res_dir):
+ self.res_find_all = parent.res_find_all
+ else:
+ self.res_find_all = False
+ if res_bo:
+ self.res_id = res_bo.id
+ dc2 = self.context.context.copy()
dc2.update(self.dctx)
dc2['res_model'] = res_model
dc2['res_id'] = res_bo.id
- dc2['this'] = res_bo
+ dc2['this'] = res_bo
for fld,expr in self.dctx_dict.items():
try:
- self.dctx[fld] = safe_eval(expr,dc2)
+ self.dctx[fld] = safe_eval(expr, dc2)
except Exception,e:
print "Cannot eval %s for %s" % (expr, fld)
print e
else:
self.res_id = res_id
- def children(self,cr):
- return self._child_get(cr) + self._file_get(cr)
-
- def child(self,cr, name):
- res = self._child_get(cr,name)
+ def __eq__(self, other):
+ if type(self) != type(other):
+ return False
+ if not self.context == other.context:
+ return False
+ if not self.res_model == other.res_model:
+ return False
+ if not self.res_id == other.res_id:
+ return False
+ if self.domain != other.domain:
+ return False
+ if self.res_find_all != other.res_find_all:
+ return False
+ if self.dctx != other.dctx:
+ return False
+ return self.dir_id == other.dir_id
+
+ def children(self, cr, domain=None):
+ return self._child_get(cr, domain=domain) + self._file_get(cr)
+
+ def child(self, cr, name, domain=None):
+ res = self._child_get(cr, name, domain=domain)
if res:
return res[0]
- res = self._file_get(cr,name)
+ res = self._file_get(cr, name)
if res:
return res[0]
return None
def _file_get(self,cr, nodename=False):
res = []
+ is_allowed = self.check_perms((nodename and 1) or 5)
+ if not is_allowed:
+ raise IOError(errno.EPERM,"Permission denied")
+
cntobj = self.context._dirobj.pool.get('document.directory.content')
uid = self.context.uid
ctx = self.context.context.copy()
#if self.domain:
# where.extend(self.domain)
# print "res_obj file_get clause", where
- ids = cntobj.search(cr,uid,where,context=ctx)
- for content in cntobj.browse(cr,uid,ids,context=ctx):
- res3 = cntobj._file_get(cr,self,nodename,content, context=ctx)
+ ids = cntobj.search(cr, uid, where, context=ctx)
+ for content in cntobj.browse(cr, uid, ids, context=ctx):
+ res3 = cntobj._file_get(cr, self, nodename, content, context=ctx)
if res3:
res.extend(res3)
return res
- def get_dav_props(self, cr):
+ def get_dav_props_DEPR(self, cr):
+ # Deprecated! (but document_ics must be cleaned, first)
res = {}
cntobj = self.context._dirobj.pool.get('document.directory.content')
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = [('directory_id','=',self.dir_id) ]
- ids = cntobj.search(cr,uid,where,context=ctx)
- for content in cntobj.browse(cr,uid,ids,context=ctx):
+ ids = cntobj.search(cr, uid, where, context=ctx)
+ for content in cntobj.browse(cr, uid, ids, context=ctx):
if content.extension == '.ics': # FIXME: call the content class!
res['http://groupdav.org/'] = ('resourcetype',)
return res
- def get_dav_eprop(self,cr,ns,prop):
+ def get_dav_eprop_DEPR(self, cr, ns, prop):
+ # Deprecated!
if ns != 'http://groupdav.org/' or prop != 'resourcetype':
- print "Who asked for %s:%s?" % (ns,prop)
+ logger.warning("Who asked for %s:%s?" % (ns, prop))
return None
- res = {}
cntobj = self.context._dirobj.pool.get('document.directory.content')
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = [('directory_id','=',self.dir_id) ]
ids = cntobj.search(cr,uid,where,context=ctx)
- for content in cntobj.browse(cr,uid,ids,context=ctx):
+ for content in cntobj.browse(cr, uid, ids, context=ctx):
+ # TODO: remove relic of GroupDAV
if content.extension == '.ics': # FIXME: call the content class!
return ('vevent-collection','http://groupdav.org/')
return None
- def _child_get(self,cr,name = None):
+ def _child_get(self, cr, name=None, domain=None):
dirobj = self.context._dirobj
+
+ is_allowed = self.check_perms((name and 1) or 5)
+ if not is_allowed:
+ raise IOError(errno.EPERM,"Permission denied")
+
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
directory = dirobj.browse(cr, uid, self.dir_id)
obj = dirobj.pool.get(self.res_model)
where = []
- res = []
+ res = []
if name:
- where.append(('name','=',name))
-
+ where.append(('name','=',name))
+
# Directory Structure display in tree structure
- if self.res_id and directory.ressource_tree:
- where1 = []
- if obj._parent_name in obj.fields_get(cr, uid):
- where1 = where + [(obj._parent_name, '=', self.res_id)]
- resids = obj.search(cr,uid, where1, context=ctx)
- for bo in obj.browse(cr,uid,resids,context=ctx):
- namefield = directory.resource_field or 'name'
+ if self.res_id and directory.ressource_tree:
+ where1 = []
+ if name:
+ where1.append(('name','=like',name.replace('\\','\\\\')))
+ if obj._parent_name in obj.fields_get(cr, uid):
+ where1.append((obj._parent_name, '=', self.res_id))
+ namefield = directory.resource_field.name or 'name'
+ resids = obj.search(cr, uid, where1, context=ctx)
+ for bo in obj.browse(cr, uid, resids, context=ctx):
if not bo:
continue
res_name = getattr(bo, namefield)
if not res_name:
- continue
- res.append(node_res_obj(res_name, self.dir_id, self, self.context, self.res_model, res_bo = bo))
-
+ continue
+ res_name = res_name.replace('/', '_')
+ if name and (res_name != name):
+ continue
+ # TODO Revise
+ klass = directory.get_node_class(directory, dynamic=True, context=ctx)
+ rnode = klass(res_name, dir_id=self.dir_id, parent=self, context=self.context,
+ res_model=self.res_model, res_bo=bo)
+ rnode.res_find_all = self.res_find_all
+ res.append(rnode)
+
+
+ where2 = where + [('parent_id','=',self.dir_id) ]
+ ids = dirobj.search(cr, uid, where2, context=ctx)
+ bo = obj.browse(cr, uid, self.res_id, context=ctx)
- where2 = where + [('parent_id','=',self.dir_id) ]
- ids = dirobj.search(cr, uid, where2, context=ctx)
for dirr in dirobj.browse(cr, uid, ids, context=ctx):
+ if name and (name != dirr.name):
+ continue
if dirr.type == 'directory':
- res.append(node_res_obj(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
+ klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
+ res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = bo, res_id = self.res_id))
elif dirr.type == 'ressource':
# child resources can be controlled by properly set dctx
- res.append(node_res_dir(dirr.name,self,self.context, dirr, {'active_id': self.res_id}))
-
+ klass = dirr.get_node_class(dirr, context=ctx)
+ res.append(klass(dirr.name,self,self.context, dirr, {'active_id': self.res_id})) # bo?
-
-
fil_obj = dirobj.pool.get('ir.attachment')
- where3 = where2 + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
- # print "where clause for dir_obj", where2
+ if self.res_find_all:
+ where2 = where
+ where3 = where2 + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
+ # print "where clause for dir_obj", where3
ids = fil_obj.search(cr, uid, where3, context=ctx)
if ids:
for fil in fil_obj.browse(cr, uid, ids, context=ctx):
- res.append(node_file(fil.name, self, self.context, fil))
-
-
- # Get Child Ressource Directories
- if directory.ressource_type_id and directory.ressource_type_id.id:
- where4 = where + [('ressource_parent_type_id','=',directory.ressource_type_id.id)]
- where5 = where4 + [('ressource_id','=',0)]
- dirids = dirobj.search(cr,uid, where5)
- where5 = where4 + [('ressource_id','=',self.res_id)]
- dirids = dirids + dirobj.search(cr,uid, where5)
+ klass = self.context.node_file_class
+ res.append(klass(fil.name, self, self.context, fil))
+
+
+ # Get Child Ressource Directories
+ if directory.ressource_type_id and directory.ressource_type_id.id:
+ where4 = where + [('ressource_parent_type_id','=',directory.ressource_type_id.id)]
+ where5 = where4 + ['|', ('ressource_id','=',0), ('ressource_id','=',self.res_id)]
+ dirids = dirobj.search(cr,uid, where5)
for dirr in dirobj.browse(cr, uid, dirids, context=ctx):
if dirr.type == 'directory' and not dirr.parent_id:
- res.append(node_res_obj(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
+ klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
+ rnode = klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = bo, res_id = self.res_id)
+ rnode.res_find_all = dirr.resource_find_all
+ res.append(rnode)
if dirr.type == 'ressource':
- res.append(node_res_dir(dirr.name, self, self.context, dirr, {'active_id': self.res_id}))
+ klass = dirr.get_node_class(dirr, context=ctx)
+ rnode = klass(dirr.name, self, self.context, dirr, {'active_id': self.res_id})
+ rnode.res_find_all = dirr.resource_find_all
+ res.append(rnode)
return res
-
- def create_child(self,cr,path,data):
+
+ def create_child_collection(self, cr, objname):
+ dirobj = self.context._dirobj
+ is_allowed = self.check_perms(2)
+ if not is_allowed:
+ raise IOError(errno.EPERM,"Permission denied")
+
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ res_obj = dirobj.pool.get(self.res_model)
+
+ object2 = res_obj.browse(cr, uid, self.res_id) or False
+
+ obj = dirobj.browse(cr, uid, self.dir_id)
+ if obj and (obj.type == 'ressource') and not object2:
+ raise OSError(1, 'Operation not permited.')
+
+
+ val = {
+ 'name': objname,
+ 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
+ 'ressource_id': object2 and object2.id or False,
+ 'parent_id' : False,
+ 'resource_find_all': False,
+ }
+ if (obj and (obj.type in ('directory'))) or not object2:
+ val['parent_id'] = obj and obj.id or False
+
+ return dirobj.create(cr, uid, val)
+
+ def create_child(self, cr, path, data=None):
""" API function to create a child file object and node
Return the node_* created
"""
+ is_allowed = self.check_perms(2)
+ if not is_allowed:
+ raise IOError(errno.EPERM,"Permission denied")
+
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
val = {
'name': path,
'datas_fname': path,
- 'parent_id': self.dir_id,
'res_model': self.res_model,
'res_id': self.res_id,
# Datas are not set here
}
-
- fil_id = fil_obj.create(cr,uid, val, context=ctx)
- fil = fil_obj.browse(cr,uid,fil_id,context=ctx)
- fnode = node_file(path,self,self.context,fil)
- fnode.set_data(cr,data,fil)
+ if not self.res_find_all:
+ val['parent_id'] = self.dir_id
+
+ fil_id = fil_obj.create(cr, uid, val, context=ctx)
+ fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
+ klass = self.context.node_file_class
+ fnode = klass(path, self, self.context, fil)
+ if data is not None:
+ fnode.set_data(cr, data, fil)
return fnode
def _get_ttag(self,cr):
- return 'rodir-%d-%d' % (self.dir_id,self.res_id)
+ return 'rodir-%d-%d' % (self.dir_id, self.res_id)
+
+node_res_dir.res_obj_class = node_res_obj
class node_file(node_class):
our_type = 'file'
- def __init__(self,path, parent, context, fil):
+ def __init__(self, path, parent, context, fil):
super(node_file,self).__init__(path, parent,context)
self.file_id = fil.id
#todo: more info from ir_attachment
if fil.file_type and '/' in fil.file_type:
- self.mimetype = fil.file_type
+ self.mimetype = str(fil.file_type)
self.create_date = fil.create_date
self.write_date = fil.write_date or fil.create_date
self.content_length = fil.file_size
self.displayname = fil.name
+ self.uidperms = 14
+ if parent:
+ if not parent.check_perms('x'):
+ self.uidperms = 0
+ elif not parent.check_perms('w'):
+ self.uidperms = 4
+
+ try:
+ self.uuser = (fil.user_id and fil.user_id.login) or 'nobody'
+ except Exception:
+ self.uuser = 'nobody'
+ self.ugroup = mkdosname(fil.company_id and fil.company_id.name, default='nogroup')
+
# This only propagates the problem to get_data. Better
# fix those files to point to the root dir.
- if fil.parent_id:
- self.storage_id = fil.parent_id.storage_id.id
+ self.storage_id = None
+ par = fil.parent_id
+ while par:
+ if par.storage_id and par.storage_id.id:
+ self.storage_id = par.storage_id.id
+ break
+ par = par.parent_id
+
+ def __eq__(self, other):
+ if type(self) != type(other):
+ return False
+ if not self.context == other.context:
+ return False
+ if self.dctx != other.dctx:
+ return False
+ return self.file_id == other.file_id
+
+
+ def open_data(self, cr, mode):
+ stor = self.storage_id
+ assert stor, "No storage for file #%s" % self.file_id
+ if not self.check_perms(4):
+ raise IOError(errno.EPERM, "Permission denied")
+
+ # If storage is not set properly, we are just screwed here, don't
+ # try to get it from default.
+ stobj = self.context._dirobj.pool.get('document.storage')
+ return stobj.get_file(cr, self.context.uid, stor, self, mode=mode, context=self.context.context)
+
+ def rm(self, cr):
+ uid = self.context.uid
+ if not self.check_perms(8):
+ raise IOError(errno.EPERM, "Permission denied")
+ document_obj = self.context._dirobj.pool.get('ir.attachment')
+ if self.type in ('collection','database'):
+ return False
+ document = document_obj.browse(cr, uid, self.file_id, context=self.context.context)
+ res = False
+ if document and document._table_name == 'ir.attachment':
+ res = document_obj.unlink(cr, uid, [document.id])
+ return res
+
+ def fix_ppath(self, cr, fbro):
+ """Sometimes we may init this w/o path, parent.
+ This function fills the missing path from the file browse object
+
+ Note: this may be an expensive operation, do on demand. However,
+ once caching is in, we might want to do that at init time and keep
+ this object anyway
+ """
+ if self.path or self.parent:
+ return
+ assert fbro
+ uid = self.context.uid
+
+ dirpath = []
+ if fbro.parent_id:
+ dirobj = self.context._dirobj.pool.get('document.directory')
+ dirpath = dirobj.get_full_path(cr, uid, fbro.parent_id.id, context=self.context.context)
+ if fbro.datas_fname:
+ dirpath.append(fbro.datas_fname)
else:
- self.storage_id = None
-
-
+ dirpath.append(fbro.name)
+
+ if len(dirpath)>1:
+ self.path = dirpath
+ else:
+ self.path = dirpath[0]
+
def get_data(self, cr, fil_obj = None):
- """ Retrieve the data for some file.
+ """ Retrieve the data for some file.
fil_obj may optionally be specified, and should be a browse object
for the file. This is useful when the caller has already initiated
the browse object. """
# this is where storage kicks in..
stor = self.storage_id
- if not stor:
- data_obj = self.context._dirobj.pool.get('ir.model.data')
- data_id = data_obj._get_id(cr, self.context.uid, 'document', 'storage_db')
- if data_id:
- stor = data_obj.browse(cr, self.context.uid, data_id, context=self.context.context).res_id
- assert stor
+ assert stor, "No storage for file #%s" % self.file_id
+ if not self.check_perms(4):
+ raise IOError(errno.EPERM, "Permission denied")
+
+ # If storage is not set properly, we are just screwed here, don't
+ # try to get it from default.
stobj = self.context._dirobj.pool.get('document.storage')
- return stobj.get_data(cr,self.context.uid,stor, self,self.context.context, fil_obj)
+ return stobj.get_data(cr, self.context.uid,stor, self,self.context.context, fil_obj)
def get_data_len(self, cr, fil_obj = None):
# TODO: verify with the storage object!
return self.content_length
def set_data(self, cr, data, fil_obj = None):
- """ Store data at some file.
+ """ Store data at some file.
fil_obj may optionally be specified, and should be a browse object
for the file. This is useful when the caller has already initiated
the browse object. """
# this is where storage kicks in..
stor = self.storage_id
- if not stor:
- data_obj = self.context._dirobj.pool.get('ir.model.data')
- data_id = data_obj._get_id(cr, self.context.uid, 'document', 'storage_db')
- if data_id:
- stor = data_obj.browse(cr, self.context.uid, data_id, context=self.context.context).res_id
- assert stor
+ assert stor, "No storage for file #%s" % self.file_id
+ if not self.check_perms(2):
+ raise IOError(errno.EPERM, "Permission denied")
+
stobj = self.context._dirobj.pool.get('document.storage')
- return stobj.set_data(cr,self.context.uid,stor, self, data, self.context.context, fil_obj)
+ return stobj.set_data(cr, self.context.uid,stor, self, data, self.context.context, fil_obj)
def _get_ttag(self,cr):
return 'file-%d' % self.file_id
+ def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
+ if ndir_node and ndir_node.context != self.context:
+ raise NotImplementedError("Cannot move files between contexts")
+
+ if (not self.check_perms(8)) and ndir_node.check_perms(2):
+ raise IOError(errno.EPERM, "Permission denied")
+
+ doc_obj = self.context._dirobj.pool.get('ir.attachment')
+ if not fil_obj:
+ dbro = doc_obj.browse(cr, self.context.uid, self.file_id, context=self.context.context)
+ else:
+ dbro = fil_obj
+ assert dbro.id == self.file_id, "%s != %s for %r" % (dbro.id, self.file_id, self)
+
+ if not dbro:
+ raise IndexError("Cannot locate doc %d", self.file_id)
+
+ if (not self.parent):
+ # there *must* be a parent node for this one
+ self.parent = self.context.get_dir_node(cr, dbro.parent_id)
+ assert self.parent
+
+ ret = {}
+ if ndir_node and self.parent != ndir_node:
+ if not (isinstance(self.parent, node_dir) and isinstance(ndir_node, node_dir)):
+ logger.debug('Cannot move file %r from %r to %r', self, self.parent, ndir_node)
+ raise NotImplementedError('Cannot move files between dynamic folders')
+
+ if not ndir_obj:
+ ndir_obj = self.context._dirobj.browse(cr, self.context.uid, \
+ ndir_node.dir_id, context=self.context.context)
+
+ assert ndir_obj.id == ndir_node.dir_id
+
+ stobj = self.context._dirobj.pool.get('document.storage')
+ r2 = stobj.simple_move(cr, self.context.uid, self, ndir_obj, \
+ context=self.context.context)
+ ret.update(r2)
+
+ if new_name and (new_name != dbro.name):
+ if len(ret):
+ raise NotImplementedError("Cannot rename and move") # TODO
+ stobj = self.context._dirobj.pool.get('document.storage')
+ r2 = stobj.simple_rename(cr, self.context.uid, self, new_name, self.context.context)
+ ret.update(r2)
+
+ del dbro
+
+ if not in_write:
+ # We have to update the data ourselves
+ if ret:
+ ctx = self.context.context.copy()
+ ctx['__from_node'] = True
+ doc_obj.write(cr, self.context.uid, [self.file_id,], ret, ctx )
+ ret = True
+
+ return ret
+
class node_content(node_class):
our_type = 'content'
- def __init__(self,path, parent, context, cnt, dctx = None, act_id=None):
+ def __init__(self, path, parent, context, cnt, dctx = None, act_id=None):
super(node_content,self).__init__(path, parent,context)
self.cnt_id = cnt.id
self.create_date = False
self.write_date = False
self.content_length = False
+ self.unixperms = 0640
+ if parent:
+ self.uidperms = parent.uidperms & 14
+ self.uuser = parent.uuser
+ self.ugroup = parent.ugroup
+
self.extension = cnt.extension
self.report_id = cnt.report_id and cnt.report_id.id
#self.mimetype = cnt.extension.
if dctx:
self.dctx.update(dctx)
self.act_id = act_id
-
- def fill_fields(self,cr,dctx = None):
+
+ def fill_fields(self, cr, dctx = None):
""" Try to read the object and fill missing fields, like mimetype,
dates etc.
This function must be different from the constructor, because
it uses the db cursor.
"""
-
+
cr.execute('SELECT DISTINCT mimetype FROM document_directory_content_type WHERE active AND code = %s;',
(self.extension,))
res = cr.fetchall()
if res and res[0][0]:
- self.mimetype = res[0][0]
+ self.mimetype = str(res[0][0])
def get_data(self, cr, fil_obj = None):
cntobj = self.context._dirobj.pool.get('document.directory.content')
+ if not self.check_perms(4):
+ raise IOError(errno.EPERM, "Permission denied")
+
ctx = self.context.context.copy()
ctx.update(self.dctx)
- data = cntobj.process_read(cr,self.context.uid,self,ctx)
+ data = cntobj.process_read(cr, self.context.uid, self, ctx)
if data:
self.content_length = len(data)
return data
+ def open_data(self, cr, mode):
+ if mode.endswith('b'):
+ mode = mode[:-1]
+ if mode in ('r', 'w'):
+ cperms = mode[:1]
+ elif mode in ('r+', 'w+'):
+ cperms = 'rw'
+ else:
+ raise IOError(errno.EINVAL, "Cannot open at mode %s" % mode)
+
+ if not self.check_perms(cperms):
+ raise IOError(errno.EPERM, "Permission denied")
+
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+
+ return nodefd_content(self, cr, mode, ctx)
+
def get_data_len(self, cr, fil_obj = None):
+ # FIXME : here, we actually generate the content twice!!
+ # we should have cached the generated content, but it is
+ # not advisable to do keep it in memory, until we have a cache
+ # expiration logic.
if not self.content_length:
self.get_data(cr,fil_obj)
return self.content_length
def set_data(self, cr, data, fil_obj = None):
cntobj = self.context._dirobj.pool.get('document.directory.content')
+ if not self.check_perms(2):
+ raise IOError(errno.EPERM, "Permission denied")
+
ctx = self.context.context.copy()
ctx.update(self.dctx)
- return cntobj.process_write(cr,self.context.uid,self, data,ctx)
+ return cntobj.process_write(cr, self.context.uid, self, data, ctx)
def _get_ttag(self,cr):
return 'cnt-%d%s' % (self.cnt_id,(self.act_id and ('-' + str(self.act_id))) or '')
-class old_class():
- # the old code, remove..
- def __init__(self, cr, uid, path, object, object2=False, context={}, content=False, type='collection', root=False):
- self.cr = cr
- def _file_get(self, nodename=False):
- if not self.object:
- return []
- pool = pooler.get_pool(self.cr.dbname)
- fobj = pool.get('ir.attachment')
- res2 = []
- where = []
- if self.object2:
- where.append( ('res_model','=',self.object2._name) )
- where.append( ('res_id','=',self.object2.id) )
- else:
- where.append( ('parent_id','=',self.object.id) )
- where.append( ('res_id','=',False) )
- if nodename:
- where.append( (fobj._rec_name,'=',nodename) )
- for content in self.object.content_ids:
- res3 = content._table._file_get(self,nodename,content)
- if res3:
- res2.extend(res3)
+ def get_dav_resourcetype(self, cr):
+ return ''
- ids = fobj.search(self.cr, self.uid, where+[ ('parent_id','=',self.object and self.object.id or False) ])
- if self.object and self.root and (self.object.type=='ressource'):
- ids += fobj.search(self.cr, self.uid, where+[ ('parent_id','=',False) ])
- res = fobj.browse(self.cr, self.uid, ids, context=self.context)
- return map(lambda x: node_class(self.cr, self.uid, self.path+'/'+eval('x.'+fobj._rec_name), x, False, context=self.context, type='file', root=False), res) + res2
-
- def get_translation(self,value,lang):
- # Must go, it works on arbitrary models and could be ambiguous.
- result = value
- pool = pooler.get_pool(self.cr.dbname)
- translation_ids = pool.get('ir.translation').search(self.cr, self.uid, [('value','=',value),('lang','=',lang),('type','=','model')])
- if len(translation_ids):
- tran_id = translation_ids[0]
- translation = pool.get('ir.translation').read(self.cr, self.uid, tran_id, ['res_id','name'])
- res_model,field_name = tuple(translation['name'].split(','))
- res_id = translation['res_id']
- res = pool.get(res_model).read(self.cr, self.uid, res_id, [field_name])
- if res:
- result = res[field_name]
- return result
-
- def directory_list_for_child(self,nodename,parent=False):
- pool = pooler.get_pool(self.cr.dbname)
- where = []
- if nodename:
- nodename = self.get_translation(nodename, self.context['lang'])
- where.append(('name','=',nodename))
- if (self.object and self.object.type=='directory') or not self.object2:
- where.append(('parent_id','=',self.object and self.object.id or False))
+class nodefd_content(StringIO, node_descriptor):
+ """ A descriptor to content nodes
+ """
+ def __init__(self, parent, cr, mode, ctx):
+ node_descriptor.__init__(self, parent)
+ self._context=ctx
+ self._size = 0L
+
+ if mode in ('r', 'r+'):
+ cntobj = parent.context._dirobj.pool.get('document.directory.content')
+ data = cntobj.process_read(cr, parent.context.uid, parent, ctx)
+ if data:
+ self._size = len(data)
+ parent.content_length = len(data)
+ StringIO.__init__(self, data)
+ elif mode in ('w', 'w+'):
+ StringIO.__init__(self, None)
+ # 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:
- where.append(('parent_id','=',False))
- if self.object:
- where.append(('ressource_parent_type_id','=',self.object.ressource_type_id.id))
+ logging.getLogger('document.content').error("Incorrect mode %s specified", mode)
+ raise IOError(errno.EINVAL, "Invalid file mode")
+ self.mode = mode
+
+ def size(self):
+ return self._size
+
+ def close(self):
+ # we now open a *separate* cursor, to update the data.
+ # FIXME: this may be improved, for concurrency handling
+ if self.mode == 'r':
+ StringIO.close(self)
+ return
+
+ par = self._get_parent()
+ uid = par.context.uid
+ cr = pooler.get_db(par.context.dbname).cursor()
+ try:
+ if self.mode in ('w', 'w+', 'r+'):
+ data = self.getvalue()
+ cntobj = par.context._dirobj.pool.get('document.directory.content')
+ cntobj.process_write(cr, uid, par, data, par.context.context)
+ elif self.mode == 'a':
+ raise NotImplementedError
+ cr.commit()
+ except Exception:
+ logging.getLogger('document.content').exception('Cannot update db content #%d for close:', par.cnt_id)
+ raise
+ finally:
+ cr.close()
+ StringIO.close(self)
+
+class nodefd_static(StringIO, node_descriptor):
+ """ A descriptor to nodes with static data.
+ """
+ def __init__(self, parent, cr, mode, ctx=None):
+ node_descriptor.__init__(self, parent)
+ self._context=ctx
+ self._size = 0L
+
+ if mode in ('r', 'r+'):
+ data = parent.get_data(cr)
+ if data:
+ self._size = len(data)
+ parent.content_length = len(data)
+ StringIO.__init__(self, data)
+ elif mode in ('w', 'w+'):
+ StringIO.__init__(self, None)
+ # 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:
- where.append(('ressource_parent_type_id','=',False))
-
- ids = pool.get('document.directory').search(self.cr, self.uid, where+[('ressource_id','=',0)])
- if self.object2:
- ids += pool.get('document.directory').search(self.cr, self.uid, where+[('ressource_id','=',self.object2.id)])
- res = pool.get('document.directory').browse(self.cr, self.uid, ids, self.context)
- return res
-
- def _child_get(self, nodename=False):
- if self.type not in ('collection','database'):
- return []
- res = self.directory_list_for_child(nodename)
- result= map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name, x, x.type=='directory' and self.object2 or False, context=self.context, root=self.root), res)
- if self.type=='database':
- pool = pooler.get_pool(self.cr.dbname)
- fobj = pool.get('ir.attachment')
- vargs = [('parent_id','=',False),('res_id','=',False)]
- if nodename:
- vargs.append((fobj._rec_name,'=',nodename))
- file_ids=fobj.search(self.cr,self.uid,vargs)
-
- res = fobj.browse(self.cr, self.uid, file_ids, context=self.context)
- result +=map(lambda x: node_class(self.cr, self.uid, self.path+'/'+eval('x.'+fobj._rec_name), x, False, context=self.context, type='file', root=self.root), res)
- if self.type=='collection' and self.object.type=="ressource":
- where = self.object.domain and eval(self.object.domain, {'active_id':self.root, 'uid':self.uid}) or []
- pool = pooler.get_pool(self.cr.dbname)
- obj = pool.get(self.object.ressource_type_id.model)
- _dirname_field = obj._rec_name
- if len(obj.fields_get(self.cr, self.uid, ['dirname'])):
- _dirname_field = 'dirname'
-
- name_for = obj._name.split('.')[-1]
- if nodename and nodename.find(name_for) == 0 :
- id = int(nodename.replace(name_for,''))
- where.append(('id','=',id))
- elif nodename:
- if nodename.find('__') :
- nodename=nodename.replace('__','/')
- for invalid in INVALID_CHARS:
- if nodename.find(INVALID_CHARS[invalid]) :
- nodename=nodename.replace(INVALID_CHARS[invalid],invalid)
- nodename = self.get_translation(nodename, self.context['lang'])
- where.append((_dirname_field,'=',nodename))
-
- if self.object.ressource_tree:
- if obj._parent_name in obj.fields_get(self.cr,self.uid):
- where.append((obj._parent_name,'=',self.object2 and self.object2.id or False))
- ids = obj.search(self.cr, self.uid, where)
- res = obj.browse(self.cr, self.uid, ids,self.context)
- result+= map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name.replace('/','__'), self.object, x, context=self.context, root=x.id), res)
- return result
- else :
- if self.object2:
- return result
- else:
- if self.object2:
- return result
-
-
- ids = obj.search(self.cr, self.uid, where)
- res = obj.browse(self.cr, self.uid, ids,self.context)
- for r in res:
- if len(obj.fields_get(self.cr, self.uid, [_dirname_field])):
- r.name = eval('r.'+_dirname_field)
- else:
- r.name = False
- if not r.name:
- r.name = name_for + '%d'%r.id
- for invalid in INVALID_CHARS:
- if r.name.find(invalid) :
- r.name = r.name.replace(invalid,INVALID_CHARS[invalid])
- result2 = map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name.replace('/','__'), self.object, x, context=self.context, root=x.id), res)
- if result2:
- if self.object.ressource_tree:
- result += result2
- else:
- result = result2
- return result
-
-
- def path_get(self):
- path = self.path
- if self.path[0]=='/':
- path = self.path[1:]
- return path
+ logging.getLogger('document.nodes').error("Incorrect mode %s specified", mode)
+ raise IOError(errno.EINVAL, "Invalid file mode")
+ self.mode = mode
+
+ def size(self):
+ return self._size
+
+ def close(self):
+ # we now open a *separate* cursor, to update the data.
+ # FIXME: this may be improved, for concurrency handling
+ if self.mode == 'r':
+ StringIO.close(self)
+ return
+
+ par = self._get_parent()
+ # uid = par.context.uid
+ cr = pooler.get_db(par.context.dbname).cursor()
+ try:
+ if self.mode in ('w', 'w+', 'r+'):
+ data = self.getvalue()
+ par.set_data(cr, data)
+ elif self.mode == 'a':
+ raise NotImplementedError
+ cr.commit()
+ except Exception:
+ logging.getLogger('document.nodes').exception('Cannot update db content #%d for close:', par.cnt_id)
+ raise
+ finally:
+ cr.close()
+ StringIO.close(self)
+
+#eof
\ No newline at end of file