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
68 def __init__(self, cr, uid, context=None):
69 self.dbname = cr.dbname
71 self.context = context
72 self._dirobj = pooler.get_pool(cr.dbname).get('document.directory')
74 self.rootdir = False #self._dirobj._get_root_directory(cr,uid,context)
76 def __eq__(self, other):
77 if not type(other) == node_context:
79 if self.dbname != other.dbname:
81 if self.uid != other.uid:
83 if self.context != other.context:
85 if self.rootdir != other.rootdir:
89 def __ne__(self, other):
90 return not self.__eq__(other)
92 def get_uri(self, cr, uri):
93 """ Although this fn passes back to doc.dir, it is needed since
94 it is a potential caching point """
95 (ndir, duri) = self._dirobj._locate_child(cr, self.uid, self.rootdir, uri, None, self)
97 ndir = ndir.child(cr, duri[0])
103 def get_dir_node(self, cr, dbro):
104 """Create (or locate) a node for a directory
105 @param dbro a browse object of document.directory
107 fullpath = self._dirobj.get_full_path(cr, self.uid, dbro.id, self.context)
108 if dbro.type == 'directory':
109 return node_dir(fullpath, None ,self, dbro)
110 elif dbro.type == 'ressource':
111 assert dbro.ressource_parent_type_id == False
112 return node_res_dir(fullpath, None, self, dbro)
114 raise ValueError("dir node for %s type", dbro.type)
116 def get_file_node(self, cr, fbro):
117 """ Create or locate a node for a static file
118 @param fbro a browse object of an ir.attachment
122 parent = self.get_dir_node(cr, fbro.parent_id)
124 return node_file(fbro.name, parent, self, fbro)
127 class node_descriptor(object):
128 """A file-like interface to the data contents of a node.
130 This class is NOT a node, but an /open descriptor/ for some
131 node. It can hold references to a cursor or a file object,
132 because the life of a node_descriptor will be the open period
134 It should also take care of locking, with any native mechanism
136 For the implementation, it would be OK just to wrap around file,
137 StringIO or similar class. The node_descriptor is only needed to
138 provide the link to the parent /node/ object.
141 def __init__(self, parent):
142 assert isinstance(parent, node_class)
143 self.name = parent.displayname
144 self.__parent = parent
146 def _get_parent(self):
149 def open(self, **kwargs):
150 raise NotImplementedError
153 raise NotImplementedError
155 def read(self, size=None):
156 raise NotImplementedError
158 def seek(self, offset, whence=None):
159 raise NotImplementedError
162 raise NotImplementedError
164 def write(self, str):
165 raise NotImplementedError
167 class node_class(object):
168 """ this is a superclass for our inodes
169 It is an API for all code that wants to access the document files.
170 Nodes have attributes which contain usual file properties
172 our_type = 'baseclass'
176 def __init__(self, path, parent, context):
177 assert isinstance(context,node_context)
178 assert (not parent ) or isinstance(parent,node_class)
180 self.context = context
181 self.type=self.our_type
183 self.uidperms = 5 # computed permissions for our uid, in unix bits
184 self.mimetype = 'application/octet-stream'
185 self.create_date = None
186 self.write_date = None
187 self.unixperms = 0660
189 self.ugroup = 'group'
190 self.content_length = 0
194 self.dctx = parent.dctx.copy()
195 self.displayname = 'Object'
197 def __eq__(self, other):
198 return NotImplemented
200 def __ne__(self, other):
201 return not self.__eq__(other)
204 """ Return the components of the full path for some
206 The returned list only contains the names of nodes.
209 s = self.parent.full_path()
212 if isinstance(self.path,list):
214 elif self.path is None:
218 return s #map(lambda x: '/' +x, s)
221 return "%s@/%s" % (self.our_type, '/'.join(self.full_path()))
223 def children(self, cr, domain=None):
224 print "node_class.children()"
227 def child(self,cr, name, domain=None):
228 print "node_class.child()"
231 def get_uri(self, cr, uri):
235 ndir = ndir.child(cr, duri[0])
242 print "node_class.path_get()"
245 def get_data(self,cr):
246 raise TypeError('no data for %s'% self.type)
248 def open_data(self, cr, mode):
249 """ Open a node_descriptor object for this node.
251 @param the mode of open, eg 'r', 'w', 'a', like file.open()
253 This operation may lock the data for this node (and accross
254 other node hierarchies), until the descriptor is close()d. If
255 the node is locked, subsequent opens (depending on mode) may
256 immediately fail with an exception (which?).
257 For this class, there is no data, so no implementation. Each
258 child class that has data should override this.
260 raise TypeError('no data for %s' % self.type)
262 def _get_storage(self,cr):
263 raise RuntimeError("no storage for base class")
265 def get_etag(self,cr):
266 """ Get a tag, unique per object + modification.
268 see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """
269 return self._get_ttag(cr) + ':' + self._get_wtag(cr)
271 def _get_wtag(self, cr):
272 """ Return the modification time as a unique, compact string """
273 return str(_str2time(self.write_date))
275 def _get_ttag(self,cr):
276 """ Get a unique tag for this type/id of object.
277 Must be overriden, so that each node is uniquely identified.
279 print "node_class.get_ttag()",self
280 raise NotImplementedError("get_etag stub()")
282 def get_dav_props(self, cr):
283 """ If this class has special behaviour for GroupDAV etc, export
285 return self.DAV_PROPS or {}
287 def match_dav_eprop(self, cr, match, ns, prop):
288 res = self.get_dav_eprop(cr, ns, prop)
293 def get_dav_eprop(self, cr, ns, prop):
294 if not self.DAV_M_NS:
297 if self.DAV_M_NS.has_key(ns):
298 prefix = self.DAV_M_NS[ns]
300 logger.debug('No namespace: %s ("%s")',ns, prop)
303 mname = prefix + "_" + prop.replace('-','_')
305 if not hasattr(self, mname):
309 m = getattr(self, mname)
312 except AttributeError:
313 logger.debug('Property %s not supported' % prop, exc_info=True)
316 def get_dav_resourcetype(self, cr):
317 """ Get the DAV resource type.
319 Is here because some nodes may exhibit special behaviour, like
320 CalDAV/GroupDAV collections
322 raise NotImplementedError
324 def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
325 """ Move this node to a new parent directory.
326 @param ndir_node the collection that this node should be moved under
327 @param new_name a name to rename this node to. If omitted, the old
329 @param fil_obj, can be None, is the browse object for the file,
330 if already available.
331 @param ndir_obj must be the browse object to the new doc.directory
332 location, where this node should be moved to.
333 in_write: When called by write(), we shouldn't attempt to write the
334 object, but instead return the dict of vals (avoid re-entrance).
335 If false, we should write all data to the object, here, as if the
336 caller won't do anything after calling move_to()
339 True: the node is moved, the caller can update other values, too.
340 False: the node is either removed or fully updated, the caller
341 must discard the fil_obj, not attempt to write any more to it.
342 dict: values to write back to the object. *May* contain a new id!
344 Depending on src and target storage, implementations of this function
345 could do various things.
346 Should also consider node<->content, dir<->dir moves etc.
348 Move operations, as instructed from APIs (eg. request from DAV) could
351 raise NotImplementedError(repr(self))
353 def create_child(self, cr, path, data=None):
354 """ Create a regular file under this node
356 raise NotImplementedError(repr(self))
358 def create_child_collection(self, cr, objname):
359 """ Create a child collection (directory) under self
361 raise NotImplementedError(repr(self))
364 raise NotImplementedError(repr(self))
367 raise NotImplementedError(repr(self))
369 def get_domain(self, cr, filters):
373 def check_perms(self, perms):
374 """ Check the permissions of the current node.
376 @param perms either an integers of the bits to check, or
377 a string with the permission letters
379 Permissions of nodes are (in a unix way):
380 1, x : allow descend into dir
381 2, w : allow write into file, or modification to dir
382 4, r : allow read of file, or listing of dir contents
383 8, u : allow remove (unlink)
386 if isinstance(perms, str):
388 chars = { 'x': 1, 'w': 2, 'r': 4, 'u': 8 }
392 elif isinstance(perms, int):
393 if perms < 0 or perms > 15:
394 raise ValueError("Invalid permission bits")
396 raise ValueError("Invalid permission attribute")
398 return ((self.uidperms & perms) == perms)
400 class node_database(node_class):
401 """ A node representing the database directory
404 our_type = 'database'
405 def __init__(self, path=[], parent=False, context=None):
406 super(node_database,self).__init__(path, parent, context)
407 self.unixperms = 040750
410 def children(self, cr, domain=None):
411 res = self._child_get(cr, domain=domain) + self._file_get(cr)
414 def child(self, cr, name, domain=None):
415 res = self._child_get(cr, name, domain=None)
418 res = self._file_get(cr,name)
423 def _child_get(self, cr, name=False, parent_id=False, domain=None):
424 dirobj = self.context._dirobj
425 uid = self.context.uid
426 ctx = self.context.context.copy()
427 ctx.update(self.dctx)
428 where = [('parent_id','=',parent_id)]
430 where.append(('name','=',name))
431 is_allowed = self.check_perms(1)
433 is_allowed = self.check_perms(5)
436 raise IOError(errno.EPERM, "Permission into directory denied")
441 where2 = where + domain + [('type', '=', 'directory')]
442 ids = dirobj.search(cr, uid, where2, context=ctx)
444 for dirr in dirobj.browse(cr, uid, ids, context=ctx):
445 res.append(node_dir(dirr.name, self, self.context,dirr))
447 where2 = where + domain + [('type', '=', 'ressource'), ('ressource_parent_type_id','=',False)]
448 ids = dirobj.search(cr, uid, where2, context=ctx)
449 for dirr in dirobj.browse(cr, uid, ids, context=ctx):
450 res.append(node_res_dir(dirr.name, self, self.context, dirr))
452 fil_obj = dirobj.pool.get('ir.attachment')
453 ids = fil_obj.search(cr, uid, where, context=ctx)
455 for fil in fil_obj.browse(cr, uid, ids, context=ctx):
456 res.append(node_file(fil.name, self, self.context, fil))
459 def _file_get(self,cr, nodename=False):
463 def _get_ttag(self,cr):
464 return 'db-%s' % cr.dbname
466 def get_dav_resourcetype(self, cr):
467 return ('collection', 'DAV:')
469 def mkdosname(company_name, default='noname'):
470 """ convert a string to a dos-like name"""
473 badchars = ' !@#$%^`~*()+={}[];:\'"/?.<>'
475 for c in company_name[:8]:
476 n += (c in badchars and '_') or c
480 class node_dir(node_database):
481 our_type = 'collection'
482 def __init__(self, path, parent, context, dirr, dctx=None):
483 super(node_dir,self).__init__(path, parent,context)
484 self.dir_id = dirr and dirr.id or False
485 #todo: more info from dirr
486 self.mimetype = 'application/x-directory'
487 # 'httpd/unix-directory'
488 self.create_date = dirr and dirr.create_date or False
489 self.domain = dirr and dirr.domain or []
490 self.res_model = dirr and dirr.ressource_type_id and dirr.ressource_type_id.model or False
491 # TODO: the write date should be MAX(file.write)..
492 self.write_date = dirr and (dirr.write_date or dirr.create_date) or False
493 self.content_length = 0
494 self.unixperms = 040750
495 self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
496 self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
497 self.uidperms = dirr.get_dir_permissions()
499 self.dctx.update(dctx)
500 dc2 = self.context.context
501 dc2.update(self.dctx)
502 dc2['dir_id'] = self.dir_id
503 self.displayname = dirr and dirr.name or False
504 if dirr and dirr.dctx_ids:
505 for dfld in dirr.dctx_ids:
507 self.dctx['dctx_' + dfld.field] = safe_eval(dfld.expr,dc2)
509 print "Cannot eval %s" % dfld.expr
513 def __eq__(self, other):
514 if type(self) != type(other):
516 if not self.context == other.context:
518 # Two directory nodes, for the same document.directory, may have a
519 # different context! (dynamic folders)
520 if self.dctx != other.dctx:
522 return self.dir_id == other.dir_id
524 def get_data(self, cr):
526 #for child in self.children(cr):
527 # res += child.get_data(cr)
530 def _file_get(self, cr, nodename=False):
531 res = super(node_dir,self)._file_get(cr, nodename)
533 is_allowed = self.check_perms(nodename and 1 or 5)
535 raise IOError(errno.EPERM, "Permission into directory denied")
537 cntobj = self.context._dirobj.pool.get('document.directory.content')
538 uid = self.context.uid
539 ctx = self.context.context.copy()
540 ctx.update(self.dctx)
541 where = [('directory_id','=',self.dir_id) ]
542 ids = cntobj.search(cr, uid, where, context=ctx)
543 for content in cntobj.browse(cr, uid, ids, context=ctx):
544 res3 = cntobj._file_get(cr, self, nodename, content)
550 def _child_get(self, cr, name=None, domain=None):
551 return super(node_dir,self)._child_get(cr, name, self.dir_id, domain=domain)
554 uid = self.context.uid
555 directory = self.context._dirobj.browse(cr, uid, self.dir_id)
558 raise OSError(2, 'Not such file or directory.')
559 if not self.check_perms('u'):
560 raise IOError(errno.EPERM,"Permission denied")
562 if directory._table_name=='document.directory':
563 if self.children(cr):
564 raise OSError(39, 'Directory not empty.')
565 res = self.context._dirobj.unlink(cr, uid, [directory.id])
567 raise OSError(1, 'Operation not permited.')
570 def create_child_collection(self, cr, objname):
572 if not self.check_perms(2):
573 raise IOError(errno.EPERM,"Permission denied")
575 dirobj = self.context._dirobj
576 uid = self.context.uid
577 ctx = self.context.context.copy()
578 ctx.update(self.dctx)
579 obj = dirobj.browse(cr, uid, self.dir_id)
580 if obj and (obj.type == 'ressource') and not object2:
581 raise OSError(1, 'Operation not permited.')
586 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
587 'ressource_id': object2 and object2.id or False,
588 'parent_id' : obj and obj.id or False
591 return dirobj.create(cr, uid, val)
594 def create_child(self, cr, path, data=None):
595 """ API function to create a child file object and node
596 Return the node_* created
598 if not self.check_perms(2):
599 raise IOError(errno.EPERM,"Permission denied")
601 dirobj = self.context._dirobj
602 uid = self.context.uid
603 ctx = self.context.context.copy()
604 ctx.update(self.dctx)
605 fil_obj=dirobj.pool.get('ir.attachment')
609 'parent_id': self.dir_id,
610 # Datas are not set here
613 fil_id = fil_obj.create(cr, uid, val, context=ctx)
614 fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
615 fnode = node_file(path, self, self.context, fil)
617 fnode.set_data(cr, data, fil)
620 def _get_ttag(self,cr):
621 return 'dir-%d' % self.dir_id
623 def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
624 """ Move directory. This operation is simple, since the present node is
625 only used for static, simple directories.
626 Note /may/ be called with ndir_node = None, to rename the document root.
628 if ndir_node and (ndir_node.context != self.context):
629 raise NotImplementedError("Cannot move directories between contexts")
631 if (not self.check_perms('u')) or (not ndir_node.check_perms('w')):
632 raise IOError(errno.EPERM,"Permission denied")
634 dir_obj = self.context._dirobj
636 dbro = dir_obj.browse(cr, self.context.uid, self.dir_id, context=self.context.context)
639 assert dbro.id == self.dir_id
642 raise IndexError("Cannot locate dir %d", self.dir_id)
644 if (not self.parent) and ndir_node:
645 if not dbro.parent_id:
646 raise IOError(errno.EPERM, "Cannot move the root directory!")
647 self.parent = self.context.get_dir_node(cr, dbro.parent_id)
650 if self.parent != ndir_node:
651 logger.debug('Cannot move dir %r from %r to %r', self, self.parent, ndir_node)
652 raise NotImplementedError('Cannot move dir to another dir')
655 if new_name and (new_name != dbro.name):
656 if ndir_node.child(cr, new_name):
657 raise IOError(errno.EEXIST, "Destination path already exists")
658 ret['name'] = new_name
663 # We have to update the data ourselves
665 ctx = self.context.context.copy()
666 ctx['__from_node'] = True
667 dir_obj.write(cr, self.context.uid, [self.dir_id,], ret, ctx)
672 class node_res_dir(node_class):
673 """ A special sibling to node_dir, which does only contain dynamically
674 created folders foreach resource in the foreign model.
675 All folders should be of type node_res_obj and merely behave like
676 node_dirs (with limited domain).
678 our_type = 'collection'
679 def __init__(self, path, parent, context, dirr, dctx=None ):
680 super(node_res_dir,self).__init__(path, parent, context)
681 self.dir_id = dirr.id
682 #todo: more info from dirr
683 self.mimetype = 'application/x-directory'
684 # 'httpd/unix-directory'
685 self.create_date = dirr.create_date
686 # TODO: the write date should be MAX(file.write)..
687 self.write_date = dirr.write_date or dirr.create_date
688 self.content_length = 0
689 self.unixperms = 040750
690 self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
691 self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
692 self.uidperms = dirr.get_dir_permissions()
693 self.res_model = dirr.ressource_type_id and dirr.ressource_type_id.model or False
694 self.resm_id = dirr.ressource_id
695 self.res_find_all = dirr.resource_find_all
696 self.namefield = dirr.resource_field.name or 'name'
697 self.displayname = dirr.name
698 # Important: the domain is evaluated using the *parent* dctx!
699 self.domain = dirr.domain
700 self.ressource_tree = dirr.ressource_tree
701 # and then, we add our own vars in the dctx:
703 self.dctx.update(dctx)
705 # and then, we prepare a dctx dict, for deferred evaluation:
707 for dfld in dirr.dctx_ids:
708 self.dctx_dict['dctx_' + dfld.field] = dfld.expr
710 def __eq__(self, other):
711 if type(self) != type(other):
713 if not self.context == other.context:
715 # Two nodes, for the same document.directory, may have a
716 # different context! (dynamic folders)
717 if self.dctx != other.dctx:
719 return self.dir_id == other.dir_id
721 def children(self, cr, domain=None):
722 return self._child_get(cr, domain=domain)
724 def child(self,cr, name, domain=None):
725 res = self._child_get(cr, name, domain=domain)
730 def _child_get(self, cr, name = None, domain=None):
731 """ return virtual children of resource, based on the
734 Note that many objects use NULL for a name, so we should
735 better call the name_search(),name_get() set of methods
737 obj = self.context._dirobj.pool.get(self.res_model)
740 dirobj = self.context._dirobj
741 uid = self.context.uid
742 ctx = self.context.context.copy()
743 ctx.update(self.dctx)
746 app = safe_eval(self.domain, self.dctx)
749 elif isinstance(app, list):
751 elif isinstance(app, tuple):
754 raise RuntimeError("incorrect domain expr: %s" % self.domain)
756 where.append(('id','=',self.resm_id))
759 where.append((self.namefield,'=',name))
760 is_allowed = self.check_perms(1)
762 is_allowed = self.check_perms(5)
765 raise IOError(errno.EPERM,"Permission denied")
767 # print "Where clause for %s" % self.res_model, where
768 if self.ressource_tree:
771 object2 = dirobj.pool.get(self.res_model).browse(cr, uid, self.resm_id) or False
772 if obj._parent_name in obj.fields_get(cr, uid):
773 where.append((obj._parent_name,'=',object2 and object2.id or False))
775 resids = obj.search(cr, uid, where, context=ctx)
777 for bo in obj.browse(cr, uid, resids, context=ctx):
780 name = getattr(bo, self.namefield)
783 # Yes! we can't do better but skip nameless records.
785 res.append(node_res_obj(name, self.dir_id, self, self.context, self.res_model, bo))
788 def _get_ttag(self,cr):
789 return 'rdir-%d' % self.dir_id
791 def get_dav_resourcetype(self, cr):
792 return ('collection', 'DAV:')
794 class node_res_obj(node_class):
795 """ A special sibling to node_dir, which does only contain dynamically
796 created folders foreach resource in the foreign model.
797 All folders should be of type node_res_obj and merely behave like
798 node_dirs (with limited domain).
800 our_type = 'collection'
801 def __init__(self, path, dir_id, parent, context, res_model, res_bo, res_id = None):
802 super(node_res_obj,self).__init__(path, parent,context)
804 #todo: more info from dirr
806 self.mimetype = 'application/x-directory'
807 # 'httpd/unix-directory'
808 self.create_date = parent.create_date
809 # TODO: the write date should be MAX(file.write)..
810 self.write_date = parent.write_date
811 self.content_length = 0
812 self.unixperms = 040750
813 self.uidperms = parent.uidperms & 15
814 self.uuser = parent.uuser
815 self.ugroup = parent.ugroup
816 self.res_model = res_model
817 self.domain = parent.domain
818 self.displayname = path
819 self.dctx_dict = parent.dctx_dict
820 self.res_find_all = parent.res_find_all
822 self.res_id = res_bo.id
823 dc2 = self.context.context.copy()
824 dc2.update(self.dctx)
825 dc2['res_model'] = res_model
826 dc2['res_id'] = res_bo.id
828 for fld,expr in self.dctx_dict.items():
830 self.dctx[fld] = safe_eval(expr, dc2)
832 print "Cannot eval %s for %s" % (expr, fld)
838 def __eq__(self, other):
839 if type(self) != type(other):
841 if not self.context == other.context:
843 if not self.res_model == other.res_model:
845 if not self.res_id == other.res_id:
847 if self.domain != other.domain:
849 if self.res_find_all != other.res_find_all:
851 if self.dctx != other.dctx:
853 return self.dir_id == other.dir_id
855 def children(self, cr, domain=None):
856 return self._child_get(cr, domain=domain) + self._file_get(cr)
858 def child(self, cr, name, domain=None):
859 res = self._child_get(cr, name, domain=domain)
862 res = self._file_get(cr, name)
867 def _file_get(self,cr, nodename=False):
869 is_allowed = self.check_perms((nodename and 1) or 5)
871 raise IOError(errno.EPERM,"Permission denied")
873 cntobj = self.context._dirobj.pool.get('document.directory.content')
874 uid = self.context.uid
875 ctx = self.context.context.copy()
876 ctx.update(self.dctx)
877 where = [('directory_id','=',self.dir_id) ]
879 # where.extend(self.domain)
880 # print "res_obj file_get clause", where
881 ids = cntobj.search(cr, uid, where, context=ctx)
882 for content in cntobj.browse(cr, uid, ids, context=ctx):
883 res3 = cntobj._file_get(cr, self, nodename, content, context=ctx)
889 def get_dav_props(self, cr):
891 cntobj = self.context._dirobj.pool.get('document.directory.content')
892 uid = self.context.uid
893 ctx = self.context.context.copy()
894 ctx.update(self.dctx)
895 where = [('directory_id','=',self.dir_id) ]
896 ids = cntobj.search(cr, uid, where, context=ctx)
897 for content in cntobj.browse(cr, uid, ids, context=ctx):
898 if content.extension == '.ics': # FIXME: call the content class!
899 res['http://groupdav.org/'] = ('resourcetype',)
902 def get_dav_eprop(self, cr, ns, prop):
903 if ns != 'http://groupdav.org/' or prop != 'resourcetype':
904 logger.warning("Who asked for %s:%s?" % (ns, prop))
906 cntobj = self.context._dirobj.pool.get('document.directory.content')
907 uid = self.context.uid
908 ctx = self.context.context.copy()
909 ctx.update(self.dctx)
910 where = [('directory_id','=',self.dir_id) ]
911 ids = cntobj.search(cr,uid,where,context=ctx)
912 for content in cntobj.browse(cr, uid, ids, context=ctx):
913 if content.extension == '.ics': # FIXME: call the content class!
914 return ('vevent-collection','http://groupdav.org/')
917 def _child_get(self, cr, name=None, domain=None):
918 dirobj = self.context._dirobj
920 is_allowed = self.check_perms((name and 1) or 5)
922 raise IOError(errno.EPERM,"Permission denied")
924 uid = self.context.uid
925 ctx = self.context.context.copy()
926 ctx.update(self.dctx)
927 directory = dirobj.browse(cr, uid, self.dir_id)
928 obj = dirobj.pool.get(self.res_model)
932 where.append(('name','=',name))
934 # Directory Structure display in tree structure
935 if self.res_id and directory.ressource_tree:
937 if obj._parent_name in obj.fields_get(cr, uid):
938 where1 = where + [(obj._parent_name, '=', self.res_id)]
939 resids = obj.search(cr, uid, where1, context=ctx)
940 for bo in obj.browse(cr, uid, resids, context=ctx):
941 namefield = directory.resource_field.name or 'name'
944 res_name = getattr(bo, namefield)
947 res.append(node_res_obj(res_name, self.dir_id, self, self.context, self.res_model, res_bo = bo))
950 where2 = where + [('parent_id','=',self.dir_id) ]
951 ids = dirobj.search(cr, uid, where2, context=ctx)
952 for dirr in dirobj.browse(cr, uid, ids, context=ctx):
953 if dirr.type == 'directory':
954 res.append(node_res_obj(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
955 elif dirr.type == 'ressource':
956 # child resources can be controlled by properly set dctx
957 res.append(node_res_dir(dirr.name,self,self.context, dirr, {'active_id': self.res_id}))
962 fil_obj = dirobj.pool.get('ir.attachment')
963 if self.res_find_all:
965 where3 = where2 + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
966 # print "where clause for dir_obj", where2
967 ids = fil_obj.search(cr, uid, where3, context=ctx)
969 for fil in fil_obj.browse(cr, uid, ids, context=ctx):
970 res.append(node_file(fil.name, self, self.context, fil))
973 # Get Child Ressource Directories
974 if directory.ressource_type_id and directory.ressource_type_id.id:
975 where4 = where + [('ressource_parent_type_id','=',directory.ressource_type_id.id)]
976 where5 = where4 + [('ressource_id','=',0)]
977 dirids = dirobj.search(cr,uid, where5)
978 where5 = where4 + [('ressource_id','=',self.res_id)]
979 dirids = dirids + dirobj.search(cr,uid, where5)
980 for dirr in dirobj.browse(cr, uid, dirids, context=ctx):
981 if dirr.type == 'directory' and not dirr.parent_id:
982 res.append(node_res_obj(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
983 if dirr.type == 'ressource':
984 res.append(node_res_dir(dirr.name, self, self.context, dirr, {'active_id': self.res_id}))
987 def create_child_collection(self, cr, objname):
988 dirobj = self.context._dirobj
989 is_allowed = self.check_perms(2)
991 raise IOError(errno.EPERM,"Permission denied")
993 uid = self.context.uid
994 ctx = self.context.context.copy()
995 ctx.update(self.dctx)
996 res_obj = dirobj.pool.get(self.context.context['res_model'])
998 object2 = res_obj.browse(cr, uid, self.context.context['res_id']) or False
1000 obj = dirobj.browse(cr, uid, self.dir_id)
1001 if obj and (obj.type == 'ressource') and not object2:
1002 raise OSError(1, 'Operation not permited.')
1007 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
1008 'ressource_id': object2 and object2.id or False,
1011 if (obj and (obj.type in ('directory'))) or not object2:
1012 val['parent_id'] = obj and obj.id or False
1014 return dirobj.create(cr, uid, val)
1016 def create_child(self, cr, path, data=None):
1017 """ API function to create a child file object and node
1018 Return the node_* created
1020 is_allowed = self.check_perms(2)
1022 raise IOError(errno.EPERM,"Permission denied")
1024 dirobj = self.context._dirobj
1025 uid = self.context.uid
1026 ctx = self.context.context.copy()
1027 ctx.update(self.dctx)
1028 fil_obj=dirobj.pool.get('ir.attachment')
1031 'datas_fname': path,
1032 'res_model': self.res_model,
1033 'res_id': self.res_id,
1034 # Datas are not set here
1036 if not self.res_find_all:
1037 val['parent_id'] = self.dir_id
1039 fil_id = fil_obj.create(cr, uid, val, context=ctx)
1040 fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
1041 fnode = node_file(path, self, self.context, fil)
1042 if data is not None:
1043 fnode.set_data(cr, data, fil)
1046 def _get_ttag(self,cr):
1047 return 'rodir-%d-%d' % (self.dir_id, self.res_id)
1049 def get_dav_resourcetype(self, cr):
1050 return ('collection', 'DAV:')
1052 class node_file(node_class):
1054 def __init__(self, path, parent, context, fil):
1055 super(node_file,self).__init__(path, parent,context)
1056 self.file_id = fil.id
1057 #todo: more info from ir_attachment
1058 if fil.file_type and '/' in fil.file_type:
1059 self.mimetype = str(fil.file_type)
1060 self.create_date = fil.create_date
1061 self.write_date = fil.write_date or fil.create_date
1062 self.content_length = fil.file_size
1063 self.displayname = fil.name
1067 if not parent.check_perms('x'):
1069 elif not parent.check_perms('w'):
1072 self.uuser = (fil.user_id and fil.user_id.login) or 'nobody'
1073 self.ugroup = mkdosname(fil.company_id and fil.company_id.name, default='nogroup')
1075 # This only propagates the problem to get_data. Better
1076 # fix those files to point to the root dir.
1077 self.storage_id = None
1080 if par.storage_id and par.storage_id.id:
1081 self.storage_id = par.storage_id.id
1085 def __eq__(self, other):
1086 if type(self) != type(other):
1088 if not self.context == other.context:
1090 if self.dctx != other.dctx:
1092 return self.file_id == other.file_id
1095 def open_data(self, cr, mode):
1096 stor = self.storage_id
1097 assert stor, "No storage for file #%s" % self.file_id
1098 if not self.check_perms(4):
1099 raise IOError(errno.EPERM, "Permission denied")
1101 # If storage is not set properly, we are just screwed here, don't
1102 # try to get it from default.
1103 stobj = self.context._dirobj.pool.get('document.storage')
1104 return stobj.get_file(cr, self.context.uid, stor, self, mode=mode, context=self.context.context)
1107 uid = self.context.uid
1108 if not self.check_perms(8):
1109 raise IOError(errno.EPERM, "Permission denied")
1110 document_obj = self.context._dirobj.pool.get('ir.attachment')
1111 if self.type in ('collection','database'):
1113 document = document_obj.browse(cr, uid, self.file_id, context=self.context.context)
1115 if document and document._table_name == 'ir.attachment':
1116 res = document_obj.unlink(cr, uid, [document.id])
1119 def fix_ppath(self, cr, fbro):
1120 """Sometimes we may init this w/o path, parent.
1121 This function fills the missing path from the file browse object
1123 Note: this may be an expensive operation, do on demand. However,
1124 once caching is in, we might want to do that at init time and keep
1127 if self.path or self.parent:
1130 uid = self.context.uid
1134 dirobj = self.context._dirobj.pool.get('document.directory')
1135 dirpath = dirobj.get_full_path(cr, uid, fbro.parent_id.id, context=self.context.context)
1136 if fbro.datas_fname:
1137 dirpath.append(fbro.datas_fname)
1139 dirpath.append(fbro.name)
1144 self.path = dirpath[0]
1146 def get_data(self, cr, fil_obj = None):
1147 """ Retrieve the data for some file.
1148 fil_obj may optionally be specified, and should be a browse object
1149 for the file. This is useful when the caller has already initiated
1150 the browse object. """
1151 # this is where storage kicks in..
1152 stor = self.storage_id
1153 assert stor, "No storage for file #%s" % self.file_id
1154 if not self.check_perms(4):
1155 raise IOError(errno.EPERM, "Permission denied")
1157 # If storage is not set properly, we are just screwed here, don't
1158 # try to get it from default.
1159 stobj = self.context._dirobj.pool.get('document.storage')
1160 return stobj.get_data(cr, self.context.uid,stor, self,self.context.context, fil_obj)
1162 def get_data_len(self, cr, fil_obj = None):
1163 # TODO: verify with the storage object!
1164 bin_size = self.context.context.get('bin_size', False)
1165 if bin_size and not self.content_length:
1166 self.content_length = fil_obj.db_datas
1167 return self.content_length
1169 def set_data(self, cr, data, fil_obj = None):
1170 """ Store data at some file.
1171 fil_obj may optionally be specified, and should be a browse object
1172 for the file. This is useful when the caller has already initiated
1173 the browse object. """
1174 # this is where storage kicks in..
1175 stor = self.storage_id
1176 assert stor, "No storage for file #%s" % self.file_id
1177 if not self.check_perms(2):
1178 raise IOError(errno.EPERM, "Permission denied")
1180 stobj = self.context._dirobj.pool.get('document.storage')
1181 return stobj.set_data(cr, self.context.uid,stor, self, data, self.context.context, fil_obj)
1183 def _get_ttag(self,cr):
1184 return 'file-%d' % self.file_id
1186 def get_dav_resourcetype(self, cr):
1189 def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
1190 if ndir_node and ndir_node.context != self.context:
1191 raise NotImplementedError("Cannot move files between contexts")
1193 if (not self.check_perms(8)) and ndir_node.check_perms(2):
1194 raise IOError(errno.EPERM, "Permission denied")
1196 doc_obj = self.context._dirobj.pool.get('ir.attachment')
1198 dbro = doc_obj.browse(cr, self.context.uid, self.file_id, context=self.context.context)
1201 assert dbro.id == self.file_id, "%s != %s for %r" % (dbro.id, self.file_id, self)
1204 raise IndexError("Cannot locate doc %d", self.file_id)
1206 if (not self.parent):
1207 # there *must* be a parent node for this one
1208 self.parent = self.context.get_dir_node(cr, dbro.parent_id)
1212 if ndir_node and self.parent != ndir_node:
1213 if not (isinstance(self.parent, node_dir) and isinstance(ndir_node, node_dir)):
1214 logger.debug('Cannot move file %r from %r to %r', self, self.parent, ndir_node)
1215 raise NotImplementedError('Cannot move files between dynamic folders')
1218 ndir_obj = self.context._dirobj.browse(cr, self.context.uid, \
1219 ndir_node.dir_id, context=self.context.context)
1221 assert ndir_obj.id == ndir_node.dir_id
1223 stobj = self.context._dirobj.pool.get('document.storage')
1224 r2 = stobj.simple_move(cr, self.context.uid, self, ndir_obj, \
1225 context=self.context.context)
1228 if new_name and (new_name != dbro.name):
1230 raise NotImplementedError("Cannot rename and move") # TODO
1231 stobj = self.context._dirobj.pool.get('document.storage')
1232 r2 = stobj.simple_rename(cr, self.context.uid, self, new_name, self.context.context)
1238 # We have to update the data ourselves
1240 ctx = self.context.context.copy()
1241 ctx['__from_node'] = True
1242 doc_obj.write(cr, self.context.uid, [self.file_id,], ret, ctx )
1247 class node_content(node_class):
1248 our_type = 'content'
1249 def __init__(self, path, parent, context, cnt, dctx = None, act_id=None):
1250 super(node_content,self).__init__(path, parent,context)
1251 self.cnt_id = cnt.id
1252 self.create_date = False
1253 self.write_date = False
1254 self.content_length = False
1255 self.unixperms = 0640
1257 self.uidperms = parent.uidperms & 14
1258 self.uuser = parent.uuser
1259 self.ugroup = parent.ugroup
1261 self.extension = cnt.extension
1262 self.report_id = cnt.report_id and cnt.report_id.id
1263 #self.mimetype = cnt.extension.
1264 self.displayname = path
1266 self.dctx.update(dctx)
1267 self.act_id = act_id
1269 def fill_fields(self, cr, dctx = None):
1270 """ Try to read the object and fill missing fields, like mimetype,
1272 This function must be different from the constructor, because
1273 it uses the db cursor.
1276 cr.execute('SELECT DISTINCT mimetype FROM document_directory_content_type WHERE active AND code = %s;',
1279 if res and res[0][0]:
1280 self.mimetype = str(res[0][0])
1283 def get_data(self, cr, fil_obj = None):
1284 cntobj = self.context._dirobj.pool.get('document.directory.content')
1285 if not self.check_perms(4):
1286 raise IOError(errno.EPERM, "Permission denied")
1288 ctx = self.context.context.copy()
1289 ctx.update(self.dctx)
1290 data = cntobj.process_read(cr, self.context.uid, self, ctx)
1292 self.content_length = len(data)
1295 def open_data(self, cr, mode):
1296 if mode.endswith('b'):
1298 if mode in ('r', 'w'):
1300 elif mode in ('r+', 'w+'):
1303 raise IOError(errno.EINVAL, "Cannot open at mode %s" % mode)
1305 if not self.check_perms(cperms):
1306 raise IOError(errno.EPERM, "Permission denied")
1308 ctx = self.context.context.copy()
1309 ctx.update(self.dctx)
1311 return nodefd_content(self, cr, mode, ctx)
1313 def get_data_len(self, cr, fil_obj = None):
1314 # FIXME : here, we actually generate the content twice!!
1315 # we should have cached the generated content, but it is
1316 # not advisable to do keep it in memory, until we have a cache
1318 if not self.content_length:
1319 self.get_data(cr,fil_obj)
1320 return self.content_length
1322 def set_data(self, cr, data, fil_obj = None):
1323 cntobj = self.context._dirobj.pool.get('document.directory.content')
1324 if not self.check_perms(2):
1325 raise IOError(errno.EPERM, "Permission denied")
1327 ctx = self.context.context.copy()
1328 ctx.update(self.dctx)
1329 return cntobj.process_write(cr, self.context.uid, self, data, ctx)
1331 def _get_ttag(self,cr):
1332 return 'cnt-%d%s' % (self.cnt_id,(self.act_id and ('-' + str(self.act_id))) or '')
1334 def get_dav_resourcetype(self, cr):
1337 class nodefd_content(StringIO, node_descriptor):
1338 """ A descriptor to content nodes
1340 def __init__(self, parent, cr, mode, ctx):
1341 node_descriptor.__init__(self, parent)
1344 if mode in ('r', 'r+'):
1345 cntobj = parent.context._dirobj.pool.get('document.directory.content')
1346 data = cntobj.process_read(cr, parent.context.uid, parent, ctx)
1348 parent.content_length = len(data)
1349 StringIO.__init__(self, data)
1350 elif mode in ('w', 'w+'):
1351 StringIO.__init__(self, None)
1352 # at write, we start at 0 (= overwrite), but have the original
1353 # data available, in case of a seek()
1355 StringIO.__init__(self, None)
1357 logging.getLogger('document.content').error("Incorrect mode %s specified", mode)
1358 raise IOError(errno.EINVAL, "Invalid file mode")
1362 # we now open a *separate* cursor, to update the data.
1363 # FIXME: this may be improved, for concurrency handling
1364 if self.mode == 'r':
1365 StringIO.close(self)
1368 par = self._get_parent()
1369 uid = par.context.uid
1370 cr = pooler.get_db(par.context.dbname).cursor()
1372 if self.mode in ('w', 'w+', 'r+'):
1373 data = self.getvalue()
1374 cntobj = par.context._dirobj.pool.get('document.directory.content')
1375 cntobj.process_write(cr, uid, par, data, par.context.context)
1376 elif self.mode == 'a':
1377 raise NotImplementedError
1380 logging.getLogger('document.content').exception('Cannot update db content #%d for close:', par.cnt_id)
1384 StringIO.close(self)