1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
24 from tools.safe_eval import safe_eval
31 from StringIO import StringIO
34 # An object that represent an uri
35 # path: the uri of the object
36 # content: the Content it belongs to (_print.pdf)
37 # type: content or collection
38 # content: objct = res.partner
39 # collection: object = directory, object2 = res.partner
40 # file: objct = ir.attachement
41 # root: if we are at the first directory of a ressource
44 logger = logging.getLogger('doc2.nodes')
47 """ Convert a string with time representation (from db) into time (float)
49 Note: a place to fix if datetime is used in db.
54 if isinstance(cre, basestring) and '.' in cre:
56 frac = float(cre[fdot:])
58 return time.mktime(time.strptime(cre,'%Y-%m-%d %H:%M:%S')) + frac
60 def get_node_context(cr, uid, context):
61 return node_context(cr, uid, context)
63 class node_context(object):
64 """ This is the root node, representing access to some particular context
66 A context is a set of persistent data, which may influence the structure
67 of the nodes. All other transient information during a data query should
68 be passed down with function arguments.
71 node_file_class = None
73 def __init__(self, cr, uid, context=None):
74 self.dbname = cr.dbname
76 self.context = context
80 self._dirobj = pooler.get_pool(cr.dbname).get('document.directory')
81 self.node_file_class = node_file
82 self.extra_ctx = {} # Extra keys for context, that do _not_ trigger inequality
84 self._dirobj._prepare_context(cr, uid, self, context=context)
85 self.rootdir = False #self._dirobj._get_root_directory(cr,uid,context)
87 def __eq__(self, other):
88 if not type(other) == node_context:
90 if self.dbname != other.dbname:
92 if self.uid != other.uid:
94 if self.context != other.context:
96 if self.rootdir != other.rootdir:
100 def __ne__(self, other):
101 return not self.__eq__(other)
103 def get(self, name, default=None):
104 return self.context.get(name, default)
106 def get_uri(self, cr, uri):
107 """ Although this fn passes back to doc.dir, it is needed since
108 it is a potential caching point.
110 (ndir, duri) = self._dirobj._locate_child(cr, self.uid, self.rootdir, uri, None, self)
112 ndir = ndir.child(cr, duri[0])
118 def get_dir_node(self, cr, dbro):
119 """Create (or locate) a node for a directory
120 @param dbro a browse object of document.directory
123 fullpath = dbro.get_full_path(context=self.context)
124 klass = dbro.get_node_class(dbro, context=self.context)
125 return klass(fullpath, None ,self, dbro)
127 def get_file_node(self, cr, fbro):
128 """ Create or locate a node for a static file
129 @param fbro a browse object of an ir.attachment
133 parent = self.get_dir_node(cr, fbro.parent_id)
135 return self.node_file_class(fbro.name, parent, self, fbro)
138 class node_descriptor(object):
139 """A file-like interface to the data contents of a node.
141 This class is NOT a node, but an /open descriptor/ for some
142 node. It can hold references to a cursor or a file object,
143 because the life of a node_descriptor will be the open period
145 It should also take care of locking, with any native mechanism
147 For the implementation, it would be OK just to wrap around file,
148 StringIO or similar class. The node_descriptor is only needed to
149 provide the link to the parent /node/ object.
152 def __init__(self, parent):
153 assert isinstance(parent, node_class)
154 self.name = parent.displayname
155 self.__parent = parent
157 def _get_parent(self):
160 def open(self, **kwargs):
161 raise NotImplementedError
164 raise NotImplementedError
166 def read(self, size=None):
167 raise NotImplementedError
169 def seek(self, offset, whence=None):
170 raise NotImplementedError
173 raise NotImplementedError
175 def write(self, str):
176 raise NotImplementedError
179 raise NotImplementedError
184 def __nonzero__(self):
185 """ Ensure that a node_descriptor will never equal False
187 Since we do define __len__ and __iter__ for us, we must avoid
188 being regarded as non-true objects.
193 raise NotImplementedError
195 class node_class(object):
196 """ this is a superclass for our inodes
197 It is an API for all code that wants to access the document files.
198 Nodes have attributes which contain usual file properties
200 our_type = 'baseclass'
204 def __init__(self, path, parent, context):
205 assert isinstance(context,node_context)
206 assert (not parent ) or isinstance(parent,node_class)
208 self.context = context
209 self.type=self.our_type
211 self.uidperms = 5 # computed permissions for our uid, in unix bits
212 self.mimetype = 'application/octet-stream'
213 self.create_date = None
214 self.write_date = None
215 self.unixperms = 0660
217 self.ugroup = 'group'
218 self.content_length = 0
222 self.dctx = parent.dctx.copy()
223 self.displayname = 'Object'
225 def __eq__(self, other):
226 return NotImplemented
228 def __ne__(self, other):
229 return not self.__eq__(other)
232 """ Return the components of the full path for some
234 The returned list only contains the names of nodes.
237 s = self.parent.full_path()
240 if isinstance(self.path,list):
242 elif self.path is None:
246 return s #map(lambda x: '/' +x, s)
249 return "%s@/%s" % (self.our_type, '/'.join(self.full_path()))
251 def children(self, cr, domain=None):
252 print "node_class.children()"
255 def child(self,cr, name, domain=None):
256 print "node_class.child()"
259 def get_uri(self, cr, uri):
263 ndir = ndir.child(cr, duri[0])
270 print "node_class.path_get()"
273 def get_data(self,cr):
274 raise TypeError('no data for %s'% self.type)
276 def open_data(self, cr, mode):
277 """ Open a node_descriptor object for this node.
279 @param the mode of open, eg 'r', 'w', 'a', like file.open()
281 This operation may lock the data for this node (and accross
282 other node hierarchies), until the descriptor is close()d. If
283 the node is locked, subsequent opens (depending on mode) may
284 immediately fail with an exception (which?).
285 For this class, there is no data, so no implementation. Each
286 child class that has data should override this.
288 raise TypeError('no data for %s' % self.type)
290 def _get_storage(self,cr):
291 raise RuntimeError("no storage for base class")
293 def get_etag(self,cr):
294 """ Get a tag, unique per object + modification.
296 see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """
297 return '"%s-%s"' % (self._get_ttag(cr), self._get_wtag(cr))
299 def _get_wtag(self, cr):
300 """ Return the modification time as a unique, compact string """
301 return str(_str2time(self.write_date)).replace('.','')
303 def _get_ttag(self, cr):
304 """ Get a unique tag for this type/id of object.
305 Must be overriden, so that each node is uniquely identified.
307 print "node_class.get_ttag()",self
308 raise NotImplementedError("get_ttag stub()")
310 def get_dav_props(self, cr):
311 """ If this class has special behaviour for GroupDAV etc, export
313 # This fn is placed here rather than WebDAV, because we want the
314 # baseclass methods to apply to all node subclasses
315 return self.DAV_PROPS or {}
317 def match_dav_eprop(self, cr, match, ns, prop):
318 res = self.get_dav_eprop(cr, ns, prop)
323 def get_dav_eprop(self, cr, ns, prop):
324 if not self.DAV_M_NS:
327 if self.DAV_M_NS.has_key(ns):
328 prefix = self.DAV_M_NS[ns]
330 logger.debug('No namespace: %s ("%s")',ns, prop)
333 mname = prefix + "_" + prop.replace('-','_')
335 if not hasattr(self, mname):
339 m = getattr(self, mname)
342 except AttributeError:
343 logger.debug('Property %s not supported' % prop, exc_info=True)
346 def get_dav_resourcetype(self, cr):
347 """ Get the DAV resource type.
349 Is here because some nodes may exhibit special behaviour, like
350 CalDAV/GroupDAV collections
352 raise NotImplementedError
354 def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
355 """ Move this node to a new parent directory.
356 @param ndir_node the collection that this node should be moved under
357 @param new_name a name to rename this node to. If omitted, the old
359 @param fil_obj, can be None, is the browse object for the file,
360 if already available.
361 @param ndir_obj must be the browse object to the new doc.directory
362 location, where this node should be moved to.
363 in_write: When called by write(), we shouldn't attempt to write the
364 object, but instead return the dict of vals (avoid re-entrance).
365 If false, we should write all data to the object, here, as if the
366 caller won't do anything after calling move_to()
369 True: the node is moved, the caller can update other values, too.
370 False: the node is either removed or fully updated, the caller
371 must discard the fil_obj, not attempt to write any more to it.
372 dict: values to write back to the object. *May* contain a new id!
374 Depending on src and target storage, implementations of this function
375 could do various things.
376 Should also consider node<->content, dir<->dir moves etc.
378 Move operations, as instructed from APIs (eg. request from DAV) could
381 raise NotImplementedError(repr(self))
383 def create_child(self, cr, path, data=None):
384 """ Create a regular file under this node
386 logger.warning("Attempted to create a file under %r, not possible.", self)
387 raise IOError(errno.EPERM, "Not allowed to create files here")
389 def create_child_collection(self, cr, objname):
390 """ Create a child collection (directory) under self
392 logger.warning("Attempted to create a collection under %r, not possible.", self)
393 raise IOError(errno.EPERM, "Not allowed to create folders here")
396 raise NotImplementedError(repr(self))
399 raise NotImplementedError(repr(self))
401 def get_domain(self, cr, filters):
405 def check_perms(self, perms):
406 """ Check the permissions of the current node.
408 @param perms either an integers of the bits to check, or
409 a string with the permission letters
411 Permissions of nodes are (in a unix way):
412 1, x : allow descend into dir
413 2, w : allow write into file, or modification to dir
414 4, r : allow read of file, or listing of dir contents
415 8, u : allow remove (unlink)
418 if isinstance(perms, str):
420 chars = { 'x': 1, 'w': 2, 'r': 4, 'u': 8 }
424 elif isinstance(perms, int):
425 if perms < 0 or perms > 15:
426 raise ValueError("Invalid permission bits")
428 raise ValueError("Invalid permission attribute")
430 return ((self.uidperms & perms) == perms)
432 class node_database(node_class):
433 """ A node representing the database directory
436 our_type = 'database'
437 def __init__(self, path=[], parent=False, context=None):
438 super(node_database,self).__init__(path, parent, context)
439 self.unixperms = 040750
442 def children(self, cr, domain=None):
443 res = self._child_get(cr, domain=domain) + self._file_get(cr)
446 def child(self, cr, name, domain=None):
447 res = self._child_get(cr, name, domain=None)
450 res = self._file_get(cr,name)
455 def _child_get(self, cr, name=False, domain=None):
456 dirobj = self.context._dirobj
457 uid = self.context.uid
458 ctx = self.context.context.copy()
459 ctx.update(self.dctx)
460 where = [('parent_id','=', False), ('ressource_parent_type_id','=',False)]
462 where.append(('name','=',name))
463 is_allowed = self.check_perms(1)
465 is_allowed = self.check_perms(5)
468 raise IOError(errno.EPERM, "Permission into directory denied")
471 where = where + domain
472 ids = dirobj.search(cr, uid, where, context=ctx)
474 for dirr in dirobj.browse(cr, uid, ids, context=ctx):
475 klass = dirr.get_node_class(dirr, context=ctx)
476 res.append(klass(dirr.name, self, self.context,dirr))
480 def _file_get(self,cr, nodename=False):
484 def _get_ttag(self,cr):
485 return 'db-%s' % cr.dbname
487 def mkdosname(company_name, default='noname'):
488 """ convert a string to a dos-like name"""
491 badchars = ' !@#$%^`~*()+={}[];:\'"/?.<>'
493 for c in company_name[:8]:
494 n += (c in badchars and '_') or c
498 def _uid2unixperms(perms, has_owner):
499 """ Convert the uidperms and the owner flag to full unix bits
503 res |= (perms & 0x07) << 6
504 res |= (perms & 0x05) << 3
506 res |= (perms & 0x07) << 6
507 res |= (perms & 0x07) << 3
509 res |= (perms & 0x07) << 6
510 res |= (perms & 0x05) << 3
514 class node_dir(node_database):
515 our_type = 'collection'
516 def __init__(self, path, parent, context, dirr, dctx=None):
517 super(node_dir,self).__init__(path, parent,context)
518 self.dir_id = dirr and dirr.id or False
519 #todo: more info from dirr
520 self.mimetype = 'application/x-directory'
521 # 'httpd/unix-directory'
522 self.create_date = dirr and dirr.create_date or False
523 self.domain = dirr and dirr.domain or []
524 self.res_model = dirr and dirr.ressource_type_id and dirr.ressource_type_id.model or False
525 # TODO: the write date should be MAX(file.write)..
526 self.write_date = dirr and (dirr.write_date or dirr.create_date) or False
527 self.content_length = 0
529 self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
531 self.uuser = 'nobody'
532 self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
533 self.uidperms = dirr.get_dir_permissions()
534 self.unixperms = 040000 | _uid2unixperms(self.uidperms, dirr and dirr.user_id)
536 self.dctx.update(dctx)
537 dc2 = self.context.context
538 dc2.update(self.dctx)
539 dc2['dir_id'] = self.dir_id
540 self.displayname = dirr and dirr.name or False
541 if dirr and dirr.dctx_ids:
542 for dfld in dirr.dctx_ids:
544 self.dctx['dctx_' + dfld.field] = safe_eval(dfld.expr,dc2)
546 print "Cannot eval %s" % dfld.expr
550 def __eq__(self, other):
551 if type(self) != type(other):
553 if not self.context == other.context:
555 # Two directory nodes, for the same document.directory, may have a
556 # different context! (dynamic folders)
557 if self.dctx != other.dctx:
559 return self.dir_id == other.dir_id
561 def get_data(self, cr):
563 #for child in self.children(cr):
564 # res += child.get_data(cr)
567 def _file_get(self, cr, nodename=False):
568 res = super(node_dir,self)._file_get(cr, nodename)
570 is_allowed = self.check_perms(nodename and 1 or 5)
572 raise IOError(errno.EPERM, "Permission into directory denied")
574 cntobj = self.context._dirobj.pool.get('document.directory.content')
575 uid = self.context.uid
576 ctx = self.context.context.copy()
577 ctx.update(self.dctx)
578 where = [('directory_id','=',self.dir_id) ]
579 ids = cntobj.search(cr, uid, where, context=ctx)
580 for content in cntobj.browse(cr, uid, ids, context=ctx):
581 res3 = cntobj._file_get(cr, self, nodename, content)
587 def _child_get(self, cr, name=None, domain=None):
588 dirobj = self.context._dirobj
589 uid = self.context.uid
590 ctx = self.context.context.copy()
591 ctx.update(self.dctx)
592 where = [('parent_id','=',self.dir_id)]
594 where.append(('name','=',name))
595 is_allowed = self.check_perms(1)
597 is_allowed = self.check_perms(5)
600 raise IOError(errno.EPERM, "Permission into directory denied")
605 where2 = where + domain + [('ressource_parent_type_id','=',False)]
606 ids = dirobj.search(cr, uid, where2, context=ctx)
608 for dirr in dirobj.browse(cr, uid, ids, context=ctx):
609 klass = dirr.get_node_class(dirr, context=ctx)
610 res.append(klass(dirr.name, self, self.context,dirr))
612 # Static directories should never return files with res_model/res_id
613 # because static dirs are /never/ related to a record.
614 # In fact, files related to some model and parented by the root dir
615 # (the default), will NOT be accessible in the node system unless
616 # a resource folder for that model exists (with resource_find_all=True).
617 # Having resource attachments in a common folder is bad practice,
618 # because they would be visible to all users, and their names may be
619 # the same, conflicting.
620 where += [('res_model', '=', False)]
621 fil_obj = dirobj.pool.get('ir.attachment')
622 ids = fil_obj.search(cr, uid, where, context=ctx)
624 for fil in fil_obj.browse(cr, uid, ids, context=ctx):
625 klass = self.context.node_file_class
626 res.append(klass(fil.name, self, self.context, fil))
630 uid = self.context.uid
631 directory = self.context._dirobj.browse(cr, uid, self.dir_id)
634 raise OSError(2, 'Not such file or directory.')
635 if not self.check_perms('u'):
636 raise IOError(errno.EPERM,"Permission denied")
638 if directory._table_name=='document.directory':
639 if self.children(cr):
640 raise OSError(39, 'Directory not empty.')
641 res = self.context._dirobj.unlink(cr, uid, [directory.id])
643 raise OSError(1, 'Operation not permited.')
646 def create_child_collection(self, cr, objname):
648 if not self.check_perms(2):
649 raise IOError(errno.EPERM,"Permission denied")
651 dirobj = self.context._dirobj
652 uid = self.context.uid
653 ctx = self.context.context.copy()
654 ctx.update(self.dctx)
655 obj = dirobj.browse(cr, uid, self.dir_id)
656 if obj and (obj.type == 'ressource') and not object2:
657 raise OSError(1, 'Operation not permited.')
662 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
663 'ressource_id': object2 and object2.id or False,
664 'parent_id' : obj and obj.id or False
667 return dirobj.create(cr, uid, val)
670 def create_child(self, cr, path, data=None):
671 """ API function to create a child file object and node
672 Return the node_* created
674 if not self.check_perms(2):
675 raise IOError(errno.EPERM,"Permission denied")
677 dirobj = self.context._dirobj
678 uid = self.context.uid
679 ctx = self.context.context.copy()
680 ctx.update(self.dctx)
681 fil_obj=dirobj.pool.get('ir.attachment')
685 'parent_id': self.dir_id,
686 # Datas are not set here
689 fil_id = fil_obj.create(cr, uid, val, context=ctx)
690 fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
691 fnode = node_file(path, self, self.context, fil)
693 fnode.set_data(cr, data, fil)
696 def _get_ttag(self,cr):
697 return 'dir-%d' % self.dir_id
699 def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
700 """ Move directory. This operation is simple, since the present node is
701 only used for static, simple directories.
702 Note /may/ be called with ndir_node = None, to rename the document root.
704 if ndir_node and (ndir_node.context != self.context):
705 raise NotImplementedError("Cannot move directories between contexts")
707 if (not self.check_perms('u')) or (not ndir_node.check_perms('w')):
708 raise IOError(errno.EPERM,"Permission denied")
710 dir_obj = self.context._dirobj
712 dbro = dir_obj.browse(cr, self.context.uid, self.dir_id, context=self.context.context)
715 assert dbro.id == self.dir_id
718 raise IndexError("Cannot locate dir %d", self.dir_id)
720 if (not self.parent) and ndir_node:
721 if not dbro.parent_id:
722 raise IOError(errno.EPERM, "Cannot move the root directory!")
723 self.parent = self.context.get_dir_node(cr, dbro.parent_id)
726 if self.parent != ndir_node:
727 logger.debug('Cannot move dir %r from %r to %r', self, self.parent, ndir_node)
728 raise NotImplementedError('Cannot move dir to another dir')
731 if new_name and (new_name != dbro.name):
732 if ndir_node.child(cr, new_name):
733 raise IOError(errno.EEXIST, "Destination path already exists")
734 ret['name'] = new_name
739 # We have to update the data ourselves
741 ctx = self.context.context.copy()
742 ctx['__from_node'] = True
743 dir_obj.write(cr, self.context.uid, [self.dir_id,], ret, ctx)
748 class node_res_dir(node_class):
749 """ A folder containing dynamic folders
750 A special sibling to node_dir, which does only contain dynamically
751 created folders foreach resource in the foreign model.
752 All folders should be of type node_res_obj and merely behave like
753 node_dirs (with limited domain).
755 our_type = 'collection'
757 def __init__(self, path, parent, context, dirr, dctx=None ):
758 super(node_res_dir,self).__init__(path, parent, context)
759 self.dir_id = dirr.id
760 #todo: more info from dirr
761 self.mimetype = 'application/x-directory'
762 # 'httpd/unix-directory'
763 self.create_date = dirr.create_date
764 # TODO: the write date should be MAX(file.write)..
765 self.write_date = dirr.write_date or dirr.create_date
766 self.content_length = 0
768 self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
770 self.uuser = 'nobody'
771 self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
772 self.uidperms = dirr.get_dir_permissions()
773 self.unixperms = 040000 | _uid2unixperms(self.uidperms, dirr and dirr.user_id)
774 self.res_model = dirr.ressource_type_id and dirr.ressource_type_id.model or False
775 self.resm_id = dirr.ressource_id
776 self.res_find_all = dirr.resource_find_all
777 self.namefield = dirr.resource_field.name or 'name'
778 self.displayname = dirr.name
779 # Important: the domain is evaluated using the *parent* dctx!
780 self.domain = dirr.domain
781 self.ressource_tree = dirr.ressource_tree
782 # and then, we add our own vars in the dctx:
784 self.dctx.update(dctx)
786 # and then, we prepare a dctx dict, for deferred evaluation:
788 for dfld in dirr.dctx_ids:
789 self.dctx_dict['dctx_' + dfld.field] = dfld.expr
791 def __eq__(self, other):
792 if type(self) != type(other):
794 if not self.context == other.context:
796 # Two nodes, for the same document.directory, may have a
797 # different context! (dynamic folders)
798 if self.dctx != other.dctx:
800 return self.dir_id == other.dir_id
802 def children(self, cr, domain=None):
803 return self._child_get(cr, domain=domain)
805 def child(self,cr, name, domain=None):
806 res = self._child_get(cr, name, domain=domain)
811 def _child_get(self, cr, name = None, domain=None):
812 """ return virtual children of resource, based on the
815 Note that many objects use NULL for a name, so we should
816 better call the name_search(),name_get() set of methods
818 obj = self.context._dirobj.pool.get(self.res_model)
821 dirobj = self.context._dirobj
822 uid = self.context.uid
823 ctx = self.context.context.copy()
824 ctx.update(self.dctx)
827 app = safe_eval(self.domain, ctx)
830 elif isinstance(app, list):
832 elif isinstance(app, tuple):
835 raise RuntimeError("incorrect domain expr: %s" % self.domain)
837 where.append(('id','=',self.resm_id))
840 where.append((self.namefield,'=',name))
841 is_allowed = self.check_perms(1)
843 is_allowed = self.check_perms(5)
846 raise IOError(errno.EPERM,"Permission denied")
848 # print "Where clause for %s" % self.res_model, where
849 if self.ressource_tree:
852 object2 = dirobj.pool.get(self.res_model).browse(cr, uid, self.resm_id) or False
853 if obj._parent_name in obj.fields_get(cr, uid):
854 where.append((obj._parent_name,'=',object2 and object2.id or False))
856 resids = obj.search(cr, uid, where, context=ctx)
858 for bo in obj.browse(cr, uid, resids, context=ctx):
861 name = getattr(bo, self.namefield)
864 # Yes! we can't do better but skip nameless records.
866 res.append(self.res_obj_class(name, self.dir_id, self, self.context, self.res_model, bo))
869 def _get_ttag(self,cr):
870 return 'rdir-%d' % self.dir_id
872 class node_res_obj(node_class):
873 """ A dynamically created folder.
874 A special sibling to node_dir, which does only contain dynamically
875 created folders foreach resource in the foreign model.
876 All folders should be of type node_res_obj and merely behave like
877 node_dirs (with limited domain).
879 our_type = 'collection'
880 def __init__(self, path, dir_id, parent, context, res_model, res_bo, res_id = None):
881 super(node_res_obj,self).__init__(path, parent,context)
883 #todo: more info from dirr
885 self.mimetype = 'application/x-directory'
886 # 'httpd/unix-directory'
887 self.create_date = parent.create_date
888 # TODO: the write date should be MAX(file.write)..
889 self.write_date = parent.write_date
890 self.content_length = 0
891 self.uidperms = parent.uidperms & 15
892 self.unixperms = 040000 | _uid2unixperms(self.uidperms, True)
893 self.uuser = parent.uuser
894 self.ugroup = parent.ugroup
895 self.res_model = res_model
896 self.domain = parent.domain
897 self.displayname = path
898 self.dctx_dict = parent.dctx_dict
899 self.res_find_all = parent.res_find_all
901 self.res_id = res_bo.id
902 dc2 = self.context.context.copy()
903 dc2.update(self.dctx)
904 dc2['res_model'] = res_model
905 dc2['res_id'] = res_bo.id
907 for fld,expr in self.dctx_dict.items():
909 self.dctx[fld] = safe_eval(expr, dc2)
911 print "Cannot eval %s for %s" % (expr, fld)
917 def __eq__(self, other):
918 if type(self) != type(other):
920 if not self.context == other.context:
922 if not self.res_model == other.res_model:
924 if not self.res_id == other.res_id:
926 if self.domain != other.domain:
928 if self.res_find_all != other.res_find_all:
930 if self.dctx != other.dctx:
932 return self.dir_id == other.dir_id
934 def children(self, cr, domain=None):
935 return self._child_get(cr, domain=domain) + self._file_get(cr)
937 def child(self, cr, name, domain=None):
938 res = self._child_get(cr, name, domain=domain)
941 res = self._file_get(cr, name)
946 def _file_get(self,cr, nodename=False):
948 is_allowed = self.check_perms((nodename and 1) or 5)
950 raise IOError(errno.EPERM,"Permission denied")
952 cntobj = self.context._dirobj.pool.get('document.directory.content')
953 uid = self.context.uid
954 ctx = self.context.context.copy()
955 ctx.update(self.dctx)
956 where = [('directory_id','=',self.dir_id) ]
958 # where.extend(self.domain)
959 # print "res_obj file_get clause", where
960 ids = cntobj.search(cr, uid, where, context=ctx)
961 for content in cntobj.browse(cr, uid, ids, context=ctx):
962 res3 = cntobj._file_get(cr, self, nodename, content, context=ctx)
968 def get_dav_props_DEPR(self, cr):
969 # Deprecated! (but document_ics must be cleaned, first)
971 cntobj = self.context._dirobj.pool.get('document.directory.content')
972 uid = self.context.uid
973 ctx = self.context.context.copy()
974 ctx.update(self.dctx)
975 where = [('directory_id','=',self.dir_id) ]
976 ids = cntobj.search(cr, uid, where, context=ctx)
977 for content in cntobj.browse(cr, uid, ids, context=ctx):
978 if content.extension == '.ics': # FIXME: call the content class!
979 res['http://groupdav.org/'] = ('resourcetype',)
982 def get_dav_eprop_DEPR(self, cr, ns, prop):
984 if ns != 'http://groupdav.org/' or prop != 'resourcetype':
985 logger.warning("Who asked for %s:%s?" % (ns, prop))
987 cntobj = self.context._dirobj.pool.get('document.directory.content')
988 uid = self.context.uid
989 ctx = self.context.context.copy()
990 ctx.update(self.dctx)
991 where = [('directory_id','=',self.dir_id) ]
992 ids = cntobj.search(cr,uid,where,context=ctx)
993 for content in cntobj.browse(cr, uid, ids, context=ctx):
994 # TODO: remove relic of GroupDAV
995 if content.extension == '.ics': # FIXME: call the content class!
996 return ('vevent-collection','http://groupdav.org/')
999 def _child_get(self, cr, name=None, domain=None):
1000 dirobj = self.context._dirobj
1002 is_allowed = self.check_perms((name and 1) or 5)
1004 raise IOError(errno.EPERM,"Permission denied")
1006 uid = self.context.uid
1007 ctx = self.context.context.copy()
1008 ctx.update(self.dctx)
1009 directory = dirobj.browse(cr, uid, self.dir_id)
1010 obj = dirobj.pool.get(self.res_model)
1014 where.append(('name','=',name))
1016 # Directory Structure display in tree structure
1017 if self.res_id and directory.ressource_tree:
1019 if obj._parent_name in obj.fields_get(cr, uid):
1020 where1 = where + [(obj._parent_name, '=', self.res_id)]
1021 namefield = directory.resource_field.name or 'name'
1022 resids = obj.search(cr, uid, where1, context=ctx)
1023 for bo in obj.browse(cr, uid, resids, context=ctx):
1026 res_name = getattr(bo, namefield)
1030 klass = directory.get_node_class(directory, dynamic=True, context=ctx)
1031 res.append(klass(res_name, dir_id=self.dir_id, parent=self, context=self.context, res_model=self.res_model, res_bo=bo))
1034 where2 = where + [('parent_id','=',self.dir_id) ]
1035 ids = dirobj.search(cr, uid, where2, context=ctx)
1036 for dirr in dirobj.browse(cr, uid, ids, context=ctx):
1037 if dirr.type == 'directory':
1038 klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
1039 res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
1040 elif dirr.type == 'ressource':
1041 # child resources can be controlled by properly set dctx
1042 klass = dirr.get_node_class(dirr, context=ctx)
1043 res.append(klass(dirr.name,self,self.context, dirr, {'active_id': self.res_id}))
1045 fil_obj = dirobj.pool.get('ir.attachment')
1046 if self.res_find_all:
1048 where3 = where2 + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
1049 # print "where clause for dir_obj", where2
1050 ids = fil_obj.search(cr, uid, where3, context=ctx)
1052 for fil in fil_obj.browse(cr, uid, ids, context=ctx):
1053 klass = self.context.node_file_class
1054 res.append(klass(fil.name, self, self.context, fil))
1057 # Get Child Ressource Directories
1058 if directory.ressource_type_id and directory.ressource_type_id.id:
1059 where4 = where + [('ressource_parent_type_id','=',directory.ressource_type_id.id)]
1060 where5 = where4 + [('ressource_id','=',0)]
1061 dirids = dirobj.search(cr,uid, where5)
1062 where5 = where4 + [('ressource_id','=',self.res_id)]
1063 dirids = dirids + dirobj.search(cr,uid, where5)
1064 for dirr in dirobj.browse(cr, uid, dirids, context=ctx):
1065 if dirr.type == 'directory' and not dirr.parent_id:
1066 klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
1067 res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
1068 if dirr.type == 'ressource':
1069 klass = dirr.get_node_class(dirr, context=ctx)
1070 res.append(klass(dirr.name, self, self.context, dirr, {'active_id': self.res_id}))
1073 def create_child_collection(self, cr, objname):
1074 dirobj = self.context._dirobj
1075 is_allowed = self.check_perms(2)
1077 raise IOError(errno.EPERM,"Permission denied")
1079 uid = self.context.uid
1080 ctx = self.context.context.copy()
1081 ctx.update(self.dctx)
1082 res_obj = dirobj.pool.get(self.res_model)
1084 object2 = res_obj.browse(cr, uid, self.res_id) or False
1086 obj = dirobj.browse(cr, uid, self.dir_id)
1087 if obj and (obj.type == 'ressource') and not object2:
1088 raise OSError(1, 'Operation not permited.')
1093 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
1094 'ressource_id': object2 and object2.id or False,
1097 if (obj and (obj.type in ('directory'))) or not object2:
1098 val['parent_id'] = obj and obj.id or False
1100 return dirobj.create(cr, uid, val)
1102 def create_child(self, cr, path, data=None):
1103 """ API function to create a child file object and node
1104 Return the node_* created
1106 is_allowed = self.check_perms(2)
1108 raise IOError(errno.EPERM,"Permission denied")
1110 dirobj = self.context._dirobj
1111 uid = self.context.uid
1112 ctx = self.context.context.copy()
1113 ctx.update(self.dctx)
1114 fil_obj=dirobj.pool.get('ir.attachment')
1117 'datas_fname': path,
1118 'res_model': self.res_model,
1119 'res_id': self.res_id,
1120 # Datas are not set here
1122 if not self.res_find_all:
1123 val['parent_id'] = self.dir_id
1125 fil_id = fil_obj.create(cr, uid, val, context=ctx)
1126 fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
1127 klass = self.context.node_file_class
1128 fnode = klass(path, self, self.context, fil)
1129 if data is not None:
1130 fnode.set_data(cr, data, fil)
1133 def _get_ttag(self,cr):
1134 return 'rodir-%d-%d' % (self.dir_id, self.res_id)
1136 node_res_dir.res_obj_class = node_res_obj
1138 class node_file(node_class):
1140 def __init__(self, path, parent, context, fil):
1141 super(node_file,self).__init__(path, parent,context)
1142 self.file_id = fil.id
1143 #todo: more info from ir_attachment
1144 if fil.file_type and '/' in fil.file_type:
1145 self.mimetype = str(fil.file_type)
1146 self.create_date = fil.create_date
1147 self.write_date = fil.write_date or fil.create_date
1148 self.content_length = fil.file_size
1149 self.displayname = fil.name
1153 if not parent.check_perms('x'):
1155 elif not parent.check_perms('w'):
1159 self.uuser = (fil.user_id and fil.user_id.login) or 'nobody'
1161 self.uuser = 'nobody'
1162 self.ugroup = mkdosname(fil.company_id and fil.company_id.name, default='nogroup')
1164 # This only propagates the problem to get_data. Better
1165 # fix those files to point to the root dir.
1166 self.storage_id = None
1169 if par.storage_id and par.storage_id.id:
1170 self.storage_id = par.storage_id.id
1174 def __eq__(self, other):
1175 if type(self) != type(other):
1177 if not self.context == other.context:
1179 if self.dctx != other.dctx:
1181 return self.file_id == other.file_id
1184 def open_data(self, cr, mode):
1185 stor = self.storage_id
1186 assert stor, "No storage for file #%s" % self.file_id
1187 if not self.check_perms(4):
1188 raise IOError(errno.EPERM, "Permission denied")
1190 # If storage is not set properly, we are just screwed here, don't
1191 # try to get it from default.
1192 stobj = self.context._dirobj.pool.get('document.storage')
1193 return stobj.get_file(cr, self.context.uid, stor, self, mode=mode, context=self.context.context)
1196 uid = self.context.uid
1197 if not self.check_perms(8):
1198 raise IOError(errno.EPERM, "Permission denied")
1199 document_obj = self.context._dirobj.pool.get('ir.attachment')
1200 if self.type in ('collection','database'):
1202 document = document_obj.browse(cr, uid, self.file_id, context=self.context.context)
1204 if document and document._table_name == 'ir.attachment':
1205 res = document_obj.unlink(cr, uid, [document.id])
1208 def fix_ppath(self, cr, fbro):
1209 """Sometimes we may init this w/o path, parent.
1210 This function fills the missing path from the file browse object
1212 Note: this may be an expensive operation, do on demand. However,
1213 once caching is in, we might want to do that at init time and keep
1216 if self.path or self.parent:
1219 uid = self.context.uid
1223 dirobj = self.context._dirobj.pool.get('document.directory')
1224 dirpath = dirobj.get_full_path(cr, uid, fbro.parent_id.id, context=self.context.context)
1225 if fbro.datas_fname:
1226 dirpath.append(fbro.datas_fname)
1228 dirpath.append(fbro.name)
1233 self.path = dirpath[0]
1235 def get_data(self, cr, fil_obj = None):
1236 """ Retrieve the data for some file.
1237 fil_obj may optionally be specified, and should be a browse object
1238 for the file. This is useful when the caller has already initiated
1239 the browse object. """
1240 # this is where storage kicks in..
1241 stor = self.storage_id
1242 assert stor, "No storage for file #%s" % self.file_id
1243 if not self.check_perms(4):
1244 raise IOError(errno.EPERM, "Permission denied")
1246 # If storage is not set properly, we are just screwed here, don't
1247 # try to get it from default.
1248 stobj = self.context._dirobj.pool.get('document.storage')
1249 return stobj.get_data(cr, self.context.uid,stor, self,self.context.context, fil_obj)
1251 def get_data_len(self, cr, fil_obj = None):
1252 # TODO: verify with the storage object!
1253 bin_size = self.context.context.get('bin_size', False)
1254 if bin_size and not self.content_length:
1255 self.content_length = fil_obj.db_datas
1256 return self.content_length
1258 def set_data(self, cr, data, fil_obj = None):
1259 """ Store data at some file.
1260 fil_obj may optionally be specified, and should be a browse object
1261 for the file. This is useful when the caller has already initiated
1262 the browse object. """
1263 # this is where storage kicks in..
1264 stor = self.storage_id
1265 assert stor, "No storage for file #%s" % self.file_id
1266 if not self.check_perms(2):
1267 raise IOError(errno.EPERM, "Permission denied")
1269 stobj = self.context._dirobj.pool.get('document.storage')
1270 return stobj.set_data(cr, self.context.uid,stor, self, data, self.context.context, fil_obj)
1272 def _get_ttag(self,cr):
1273 return 'file-%d' % self.file_id
1275 def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
1276 if ndir_node and ndir_node.context != self.context:
1277 raise NotImplementedError("Cannot move files between contexts")
1279 if (not self.check_perms(8)) and ndir_node.check_perms(2):
1280 raise IOError(errno.EPERM, "Permission denied")
1282 doc_obj = self.context._dirobj.pool.get('ir.attachment')
1284 dbro = doc_obj.browse(cr, self.context.uid, self.file_id, context=self.context.context)
1287 assert dbro.id == self.file_id, "%s != %s for %r" % (dbro.id, self.file_id, self)
1290 raise IndexError("Cannot locate doc %d", self.file_id)
1292 if (not self.parent):
1293 # there *must* be a parent node for this one
1294 self.parent = self.context.get_dir_node(cr, dbro.parent_id)
1298 if ndir_node and self.parent != ndir_node:
1299 if not (isinstance(self.parent, node_dir) and isinstance(ndir_node, node_dir)):
1300 logger.debug('Cannot move file %r from %r to %r', self, self.parent, ndir_node)
1301 raise NotImplementedError('Cannot move files between dynamic folders')
1304 ndir_obj = self.context._dirobj.browse(cr, self.context.uid, \
1305 ndir_node.dir_id, context=self.context.context)
1307 assert ndir_obj.id == ndir_node.dir_id
1309 stobj = self.context._dirobj.pool.get('document.storage')
1310 r2 = stobj.simple_move(cr, self.context.uid, self, ndir_obj, \
1311 context=self.context.context)
1314 if new_name and (new_name != dbro.name):
1316 raise NotImplementedError("Cannot rename and move") # TODO
1317 stobj = self.context._dirobj.pool.get('document.storage')
1318 r2 = stobj.simple_rename(cr, self.context.uid, self, new_name, self.context.context)
1324 # We have to update the data ourselves
1326 ctx = self.context.context.copy()
1327 ctx['__from_node'] = True
1328 doc_obj.write(cr, self.context.uid, [self.file_id,], ret, ctx )
1333 class node_content(node_class):
1334 our_type = 'content'
1335 def __init__(self, path, parent, context, cnt, dctx = None, act_id=None):
1336 super(node_content,self).__init__(path, parent,context)
1337 self.cnt_id = cnt.id
1338 self.create_date = False
1339 self.write_date = False
1340 self.content_length = False
1341 self.unixperms = 0640
1343 self.uidperms = parent.uidperms & 14
1344 self.uuser = parent.uuser
1345 self.ugroup = parent.ugroup
1347 self.extension = cnt.extension
1348 self.report_id = cnt.report_id and cnt.report_id.id
1349 #self.mimetype = cnt.extension.
1350 self.displayname = path
1352 self.dctx.update(dctx)
1353 self.act_id = act_id
1355 def fill_fields(self, cr, dctx = None):
1356 """ Try to read the object and fill missing fields, like mimetype,
1358 This function must be different from the constructor, because
1359 it uses the db cursor.
1362 cr.execute('SELECT DISTINCT mimetype FROM document_directory_content_type WHERE active AND code = %s;',
1365 if res and res[0][0]:
1366 self.mimetype = str(res[0][0])
1369 def get_data(self, cr, fil_obj = None):
1370 cntobj = self.context._dirobj.pool.get('document.directory.content')
1371 if not self.check_perms(4):
1372 raise IOError(errno.EPERM, "Permission denied")
1374 ctx = self.context.context.copy()
1375 ctx.update(self.dctx)
1376 data = cntobj.process_read(cr, self.context.uid, self, ctx)
1378 self.content_length = len(data)
1381 def open_data(self, cr, mode):
1382 if mode.endswith('b'):
1384 if mode in ('r', 'w'):
1386 elif mode in ('r+', 'w+'):
1389 raise IOError(errno.EINVAL, "Cannot open at mode %s" % mode)
1391 if not self.check_perms(cperms):
1392 raise IOError(errno.EPERM, "Permission denied")
1394 ctx = self.context.context.copy()
1395 ctx.update(self.dctx)
1397 return nodefd_content(self, cr, mode, ctx)
1399 def get_data_len(self, cr, fil_obj = None):
1400 # FIXME : here, we actually generate the content twice!!
1401 # we should have cached the generated content, but it is
1402 # not advisable to do keep it in memory, until we have a cache
1404 if not self.content_length:
1405 self.get_data(cr,fil_obj)
1406 return self.content_length
1408 def set_data(self, cr, data, fil_obj = None):
1409 cntobj = self.context._dirobj.pool.get('document.directory.content')
1410 if not self.check_perms(2):
1411 raise IOError(errno.EPERM, "Permission denied")
1413 ctx = self.context.context.copy()
1414 ctx.update(self.dctx)
1415 return cntobj.process_write(cr, self.context.uid, self, data, ctx)
1417 def _get_ttag(self,cr):
1418 return 'cnt-%d%s' % (self.cnt_id,(self.act_id and ('-' + str(self.act_id))) or '')
1420 def get_dav_resourcetype(self, cr):
1423 class nodefd_content(StringIO, node_descriptor):
1424 """ A descriptor to content nodes
1426 def __init__(self, parent, cr, mode, ctx):
1427 node_descriptor.__init__(self, parent)
1431 if mode in ('r', 'r+'):
1432 cntobj = parent.context._dirobj.pool.get('document.directory.content')
1433 data = cntobj.process_read(cr, parent.context.uid, parent, ctx)
1435 self._size = len(data)
1436 parent.content_length = len(data)
1437 StringIO.__init__(self, data)
1438 elif mode in ('w', 'w+'):
1439 StringIO.__init__(self, None)
1440 # at write, we start at 0 (= overwrite), but have the original
1441 # data available, in case of a seek()
1443 StringIO.__init__(self, None)
1445 logging.getLogger('document.content').error("Incorrect mode %s specified", mode)
1446 raise IOError(errno.EINVAL, "Invalid file mode")
1453 # we now open a *separate* cursor, to update the data.
1454 # FIXME: this may be improved, for concurrency handling
1455 if self.mode == 'r':
1456 StringIO.close(self)
1459 par = self._get_parent()
1460 uid = par.context.uid
1461 cr = pooler.get_db(par.context.dbname).cursor()
1463 if self.mode in ('w', 'w+', 'r+'):
1464 data = self.getvalue()
1465 cntobj = par.context._dirobj.pool.get('document.directory.content')
1466 cntobj.process_write(cr, uid, par, data, par.context.context)
1467 elif self.mode == 'a':
1468 raise NotImplementedError
1471 logging.getLogger('document.content').exception('Cannot update db content #%d for close:', par.cnt_id)
1475 StringIO.close(self)
1477 class nodefd_static(StringIO, node_descriptor):
1478 """ A descriptor to nodes with static data.
1480 def __init__(self, parent, cr, mode, ctx=None):
1481 node_descriptor.__init__(self, parent)
1485 if mode in ('r', 'r+'):
1486 data = parent.get_data(cr)
1488 self._size = len(data)
1489 parent.content_length = len(data)
1490 StringIO.__init__(self, data)
1491 elif mode in ('w', 'w+'):
1492 StringIO.__init__(self, None)
1493 # at write, we start at 0 (= overwrite), but have the original
1494 # data available, in case of a seek()
1496 StringIO.__init__(self, None)
1498 logging.getLogger('document.nodes').error("Incorrect mode %s specified", mode)
1499 raise IOError(errno.EINVAL, "Invalid file mode")
1506 # we now open a *separate* cursor, to update the data.
1507 # FIXME: this may be improved, for concurrency handling
1508 if self.mode == 'r':
1509 StringIO.close(self)
1512 par = self._get_parent()
1513 # uid = par.context.uid
1514 cr = pooler.get_db(par.context.dbname).cursor()
1516 if self.mode in ('w', 'w+', 'r+'):
1517 data = self.getvalue()
1518 par.set_data(cr, data)
1519 elif self.mode == 'a':
1520 raise NotImplementedError
1523 logging.getLogger('document.nodes').exception('Cannot update db content #%d for close:', par.cnt_id)
1527 StringIO.close(self)