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 osv import osv, fields
25 from osv.orm import except_orm
28 from tools.safe_eval import safe_eval
34 from StringIO import StringIO
37 # An object that represent an uri
38 # path: the uri of the object
39 # content: the Content it belongs to (_print.pdf)
40 # type: content or collection
41 # content: objct = res.partner
42 # collection: object = directory, object2 = res.partner
43 # file: objct = ir.attachement
44 # root: if we are at the first directory of a ressource
47 def get_node_context(cr, uid, context):
48 return node_context(cr, uid, context)
50 class node_context(object):
51 """ This is the root node, representing access to some particular
55 def __init__(self, cr, uid, context=None):
56 self.dbname = cr.dbname
58 self.context = context
59 self._dirobj = pooler.get_pool(cr.dbname).get('document.directory')
61 self.rootdir = False #self._dirobj._get_root_directory(cr,uid,context)
63 def __eq__(self, other):
64 if not type(other) == node_context:
66 if self.dbname != other.dbname:
68 if self.uid != other.uid:
70 if self.context != other.context:
72 if self.rootdir != other.rootdir:
76 def __ne__(self, other):
77 return not self.__eq__(other)
79 def get_uri(self, cr, uri):
80 """ Although this fn passes back to doc.dir, it is needed since
81 it is a potential caching point """
82 (ndir, duri) = self._dirobj._locate_child(cr, self.uid, self.rootdir, uri, None, self)
84 ndir = ndir.child(cr, duri[0])
90 def get_dir_node(self, cr, dbro):
91 """Create (or locate) a node for a directory
92 @param dbro a browse object of document.directory
94 fullpath = self._dirobj.get_full_path(cr, self.uid, dbro.id, self.context)
95 if dbro.type == 'directory':
96 return node_dir(fullpath, None ,self, dbro)
97 elif dbro.type == 'ressource':
98 assert dbro.ressource_parent_type_id == False
99 return node_res_dir(fullpath, None, self, dbro)
101 raise ValueError("dir node for %s type", dbro.type)
103 def get_file_node(self, cr, fbro):
104 """ Create or locate a node for a static file
105 @param fbro a browse object of an ir.attachment
109 parent = self.get_dir_node(cr, fbro.parent_id)
111 return node_file(fbro.name,parent,self,fbro)
114 class node_descriptor(object):
115 """A file-like interface to the data contents of a node.
117 This class is NOT a node, but an /open descriptor/ for some
118 node. It can hold references to a cursor or a file object,
119 because the life of a node_descriptor will be the open period
121 It should also take care of locking, with any native mechanism
123 For the implementation, it would be OK just to wrap around file,
124 StringIO or similar class. The node_descriptor is only needed to
125 provide the link to the parent /node/ object.
128 def __init__(self, parent):
129 assert isinstance(parent, node_class)
130 self.name = parent.displayname
131 self.__parent = parent
133 def _get_parent(self):
136 def open(self, **kwargs):
137 raise NotImplementedError
140 raise NotImplementedError
142 def read(self, size=None):
143 raise NotImplementedError
145 def seek(self, offset, whence=None):
146 raise NotImplementedError
149 raise NotImplementedError
151 def write(self, str):
152 raise NotImplementedError
154 class node_class(object):
155 """ this is a superclass for our inodes
156 It is an API for all code that wants to access the document files.
157 Nodes have attributes which contain usual file properties
159 our_type = 'baseclass'
161 def __init__(self, path, parent, context):
162 assert isinstance(context,node_context)
163 assert (not parent ) or isinstance(parent,node_class)
165 self.context = context
166 self.type=self.our_type
168 self.uidperms = 5 # computed permissions for our uid, in unix bits
169 self.mimetype = 'application/octet-stream'
170 self.create_date = None
171 self.write_date = None
172 self.unixperms = 0660
174 self.ugroup = 'group'
175 self.content_length = 0
179 self.dctx = parent.dctx.copy()
180 self.displayname = 'Object'
182 def __eq__(self, other):
183 return NotImplemented
185 def __ne__(self, other):
186 return not self.__eq__(other)
189 """ Return the components of the full path for some
191 The returned list only contains the names of nodes.
194 s = self.parent.full_path()
197 if isinstance(self.path,list):
199 elif self.path is None:
203 return s #map(lambda x: '/' +x, s)
206 return "%s@/%s" % (self.our_type, '/'.join(self.full_path()))
208 def children(self, cr, domain=None):
209 print "node_class.children()"
212 def child(self,cr, name, domain=None):
213 print "node_class.child()"
216 def get_uri(self, cr, uri):
220 ndir = ndir.child(cr, duri[0])
227 print "node_class.path_get()"
230 def get_data(self,cr):
231 raise TypeError('no data for %s'% self.type)
233 def open_data(self, cr, mode):
234 """ Open a node_descriptor object for this node.
236 @param the mode of open, eg 'r', 'w', 'a', like file.open()
238 This operation may lock the data for this node (and accross
239 other node hierarchies), until the descriptor is close()d. If
240 the node is locked, subsequent opens (depending on mode) may
241 immediately fail with an exception (which?).
242 For this class, there is no data, so no implementation. Each
243 child class that has data should override this.
245 raise TypeError('no data for %s' % self.type)
247 def _get_storage(self,cr):
248 raise RuntimeError("no storage for base class")
250 def get_etag(self,cr):
251 """ Get a tag, unique per object + modification.
253 see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """
254 return self._get_ttag(cr) + ':' + self._get_wtag(cr)
256 def _get_wtag(self,cr):
257 """ Return the modification time as a unique, compact string """
259 wtime = time.mktime(time.strptime(self.write_date,'%Y-%m-%d %H:%M:%S'))
260 else: wtime = time.time()
263 def _get_ttag(self,cr):
264 """ Get a unique tag for this type/id of object.
265 Must be overriden, so that each node is uniquely identified.
267 print "node_class.get_ttag()",self
268 raise RuntimeError("get_etag stub()")
270 def get_dav_props(self, cr):
271 """ If this class has special behaviour for GroupDAV etc, export
275 def match_dav_eprop(self, cr, match, ns, prop):
276 res = self.get_dav_eprop(cr, ns, prop)
281 def get_dav_eprop(self, cr, ns, prop):
284 def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
285 """ Move this node to a new parent directory.
286 @param ndir_node the collection that this node should be moved under
287 @param new_name a name to rename this node to. If omitted, the old
289 @param fil_obj, can be None, is the browse object for the file,
290 if already available.
291 @param ndir_obj must be the browse object to the new doc.directory
292 location, where this node should be moved to.
293 in_write: When called by write(), we shouldn't attempt to write the
294 object, but instead return the dict of vals (avoid re-entrance).
295 If false, we should write all data to the object, here, as if the
296 caller won't do anything after calling move_to()
299 True: the node is moved, the caller can update other values, too.
300 False: the node is either removed or fully updated, the caller
301 must discard the fil_obj, not attempt to write any more to it.
302 dict: values to write back to the object. *May* contain a new id!
304 Depending on src and target storage, implementations of this function
305 could do various things.
306 Should also consider node<->content, dir<->dir moves etc.
308 Move operations, as instructed from APIs (eg. request from DAV) could
311 raise NotImplementedError(repr(self))
313 def create_child(self, cr, path, data=None):
314 """ Create a regular file under this node
316 raise NotImplementedError(repr(self))
318 def create_child_collection(self, cr, objname):
319 """ Create a child collection (directory) under self
321 raise NotImplementedError(repr(self))
324 raise NotImplementedError(repr(self))
327 raise NotImplementedError(repr(self))
329 def get_domain(self, cr, filters):
332 def check_perms(self, perms):
333 """ Check the permissions of the current node.
335 @param perms either an integers of the bits to check, or
336 a string with the permission letters
338 Permissions of nodes are (in a unix way):
339 1, x : allow descend into dir
340 2, w : allow write into file, or modification to dir
341 4, r : allow read of file, or listing of dir contents
342 8, u : allow remove (unlink)
345 if isinstance(perms, str):
347 chars = { 'x': 1, 'w': 2, 'r': 4, 'u': 8 }
351 elif isinstance(perms, int):
352 if perms < 0 or perms > 15:
353 raise ValueError("Invalid permission bits")
355 raise ValueError("Invalid permission attribute")
357 return ((self.uidperms & perms) == perms)
359 class node_database(node_class):
360 """ A node representing the database directory
363 our_type = 'database'
364 def __init__(self, path=[], parent=False, context=None):
365 super(node_database,self).__init__(path, parent, context)
366 self.unixperms = 040750
369 def children(self, cr, domain=None):
370 res = self._child_get(cr, domain=domain) + self._file_get(cr)
373 def child(self, cr, name, domain=None):
374 res = self._child_get(cr, name, domain=None)
377 res = self._file_get(cr,name)
382 def _child_get(self, cr, name=False, parent_id=False, domain=None):
383 dirobj = self.context._dirobj
384 uid = self.context.uid
385 ctx = self.context.context.copy()
386 ctx.update(self.dctx)
387 where = [('parent_id','=',parent_id)]
389 where.append(('name','=',name))
390 is_allowed = self.check_perms(1)
392 is_allowed = self.check_perms(5)
395 raise IOError(errno.EPERM, "Permission into directory denied")
400 where2 = where + domain + [('type', '=', 'directory')]
401 ids = dirobj.search(cr, uid, where2, context=ctx)
403 for dirr in dirobj.browse(cr, uid, ids, context=ctx):
404 res.append(node_dir(dirr.name, self, self.context,dirr))
406 where2 = where + domain + [('type', '=', 'ressource'), ('ressource_parent_type_id','=',False)]
407 ids = dirobj.search(cr, uid, where2, context=ctx)
408 for dirr in dirobj.browse(cr, uid, ids, context=ctx):
409 res.append(node_res_dir(dirr.name, self, self.context, dirr))
411 fil_obj = dirobj.pool.get('ir.attachment')
412 ids = fil_obj.search(cr, uid, where, context=ctx)
414 for fil in fil_obj.browse(cr, uid, ids, context=ctx):
415 res.append(node_file(fil.name, self, self.context, fil))
418 def _file_get(self,cr, nodename=False):
422 def _get_ttag(self,cr):
423 return 'db-%s' % cr.dbname
425 def mkdosname(company_name, default='noname'):
426 """ convert a string to a dos-like name"""
429 badchars = ' !@#$%^`~*()+={}[];:\'"/?.<>'
431 for c in company_name[:8]:
432 n += (c in badchars and '_') or c
436 class node_dir(node_database):
437 our_type = 'collection'
438 def __init__(self, path, parent, context, dirr, dctx=None):
439 super(node_dir,self).__init__(path, parent,context)
440 self.dir_id = dirr and dirr.id or False
441 #todo: more info from dirr
442 self.mimetype = 'application/x-directory'
443 # 'httpd/unix-directory'
444 self.create_date = dirr and dirr.create_date or False
445 self.domain = dirr and dirr.domain or []
446 self.res_model = dirr and dirr.ressource_type_id and dirr.ressource_type_id.model or False
447 # TODO: the write date should be MAX(file.write)..
448 self.write_date = dirr and (dirr.write_date or dirr.create_date) or False
449 self.content_length = 0
450 self.unixperms = 040750
451 self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
452 self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
453 self.uidperms = dirr.get_dir_permissions()
455 self.dctx.update(dctx)
456 dc2 = self.context.context
457 dc2.update(self.dctx)
458 dc2['dir_id'] = self.dir_id
459 self.displayname = dirr and dirr.name or False
460 if dirr and dirr.dctx_ids:
461 for dfld in dirr.dctx_ids:
463 self.dctx['dctx_' + dfld.field] = safe_eval(dfld.expr,dc2)
465 print "Cannot eval %s" % dfld.expr
469 def __eq__(self, other):
470 if type(self) != type(other):
472 if not self.context == other.context:
474 # Two directory nodes, for the same document.directory, may have a
475 # different context! (dynamic folders)
476 if self.dctx != other.dctx:
478 return self.dir_id == other.dir_id
480 def get_data(self, cr):
482 #for child in self.children(cr):
483 # res += child.get_data(cr)
486 def _file_get(self, cr, nodename=False):
487 res = super(node_dir,self)._file_get(cr, nodename)
489 is_allowed = self.check_perms(nodename and 1 or 5)
491 raise IOError(errno.EPERM, "Permission into directory denied")
493 cntobj = self.context._dirobj.pool.get('document.directory.content')
494 uid = self.context.uid
495 ctx = self.context.context.copy()
496 ctx.update(self.dctx)
497 where = [('directory_id','=',self.dir_id) ]
498 ids = cntobj.search(cr, uid, where, context=ctx)
499 for content in cntobj.browse(cr, uid, ids, context=ctx):
500 res3 = cntobj._file_get(cr, self, nodename, content)
506 def _child_get(self, cr, name=None, domain=None):
507 return super(node_dir,self)._child_get(cr, name, self.dir_id, domain=domain)
510 uid = self.context.uid
511 directory = self.context._dirobj.browse(cr, uid, self.dir_id)
514 raise OSError(2, 'Not such file or directory.')
515 if not self.check_perms('u'):
516 raise IOError(errno.EPERM,"Permission denied")
518 if directory._table_name=='document.directory':
519 if self.children(cr):
520 raise OSError(39, 'Directory not empty.')
521 res = self.context._dirobj.unlink(cr, uid, [directory.id])
523 raise OSError(1, 'Operation not permited.')
526 def create_child_collection(self, cr, objname):
528 if not self.check_perms(2):
529 raise IOError(errno.EPERM,"Permission denied")
531 dirobj = self.context._dirobj
532 uid = self.context.uid
533 ctx = self.context.context.copy()
534 ctx.update(self.dctx)
535 obj = dirobj.browse(cr, uid, self.dir_id)
536 if obj and (obj.type == 'ressource') and not object2:
537 raise OSError(1, 'Operation not permited.')
542 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
543 'ressource_id': object2 and object2.id or False,
544 'parent_id' : obj and obj.id or False
547 return dirobj.create(cr, uid, val)
550 def create_child(self, cr, path, data=None):
551 """ API function to create a child file object and node
552 Return the node_* created
554 if not self.check_perms(2):
555 raise IOError(errno.EPERM,"Permission denied")
557 dirobj = self.context._dirobj
558 uid = self.context.uid
559 ctx = self.context.context.copy()
560 ctx.update(self.dctx)
561 fil_obj=dirobj.pool.get('ir.attachment')
565 'parent_id': self.dir_id,
566 # Datas are not set here
569 fil_id = fil_obj.create(cr, uid, val, context=ctx)
570 fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
571 fnode = node_file(path, self, self.context, fil)
573 fnode.set_data(cr, data, fil)
576 def get_etag(self, cr):
577 """ Get a tag, unique per object + modification.
579 see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """
580 return self._get_ttag(cr) + ':' + self._get_wtag(cr)
582 def _get_wtag(self, cr):
583 """ Return the modification time as a unique, compact string """
585 wtime = time.mktime(time.strptime(self.write_date, '%Y-%m-%d %H:%M:%S'))
586 else: wtime = time.time()
589 def _get_ttag(self,cr):
590 return 'dir-%d' % self.dir_id
592 def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
593 """ Move directory. This operation is simple, since the present node is
594 only used for static, simple directories.
595 Note /may/ be called with ndir_node = None, to rename the document root.
597 if ndir_node and (ndir_node.context != self.context):
598 raise NotImplementedError("Cannot move directories between contexts")
600 if (not self.check_perms('u')) or (not ndir_node.check_perms('w')):
601 raise IOError(errno.EPERM,"Permission denied")
603 dir_obj = self.context._dirobj
605 dbro = dir_obj.browse(cr, self.context.uid, self.dir_id, context=self.context.context)
608 assert dbro.id == self.dir_id
611 raise IndexError("Cannot locate dir %d", self.dir_id)
613 if (not self.parent) and ndir_node:
614 if not dbro.parent_id:
615 raise IOError(errno.EPERM, "Cannot move the root directory!")
616 self.parent = self.context.get_dir_node(cr, dbro.parent_id.id)
619 # TODO: test if parent is writable.
621 if self.parent != ndir_node:
622 logger.debug('Cannot move dir %r from %r to %r', self, self.parent, ndir_node)
623 raise NotImplementedError('Cannot move dir to another dir')
626 if new_name and (new_name != dbro.name):
627 ret['name'] = new_name
632 # We have to update the data ourselves
634 ctx = self.context.context.copy()
635 ctx['__from_node'] = True
636 dir_obj.write(cr, self.context.uid, [self.dir_id,], ret, ctx)
641 class node_res_dir(node_class):
642 """ A special sibling to node_dir, which does only contain dynamically
643 created folders foreach resource in the foreign model.
644 All folders should be of type node_res_obj and merely behave like
645 node_dirs (with limited domain).
647 our_type = 'collection'
648 def __init__(self, path, parent, context, dirr, dctx=None ):
649 super(node_res_dir,self).__init__(path, parent, context)
650 self.dir_id = dirr.id
651 #todo: more info from dirr
652 self.mimetype = 'application/x-directory'
653 # 'httpd/unix-directory'
654 self.create_date = dirr.create_date
655 # TODO: the write date should be MAX(file.write)..
656 self.write_date = dirr.write_date or dirr.create_date
657 self.content_length = 0
658 self.unixperms = 040750
659 self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
660 self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
661 self.uidperms = dirr.get_dir_permissions()
662 self.res_model = dirr.ressource_type_id and dirr.ressource_type_id.model or False
663 self.resm_id = dirr.ressource_id
664 self.res_find_all = dirr.resource_find_all
665 self.namefield = dirr.resource_field.name or 'name'
666 self.displayname = dirr.name
667 # Important: the domain is evaluated using the *parent* dctx!
668 self.domain = dirr.domain
669 self.ressource_tree = dirr.ressource_tree
670 # and then, we add our own vars in the dctx:
672 self.dctx.update(dctx)
674 # and then, we prepare a dctx dict, for deferred evaluation:
676 for dfld in dirr.dctx_ids:
677 self.dctx_dict['dctx_' + dfld.field] = dfld.expr
679 def __eq__(self, other):
680 if type(self) != type(other):
682 if not self.context == other.context:
684 # Two nodes, for the same document.directory, may have a
685 # different context! (dynamic folders)
686 if self.dctx != other.dctx:
688 return self.dir_id == other.dir_id
690 def children(self, cr, domain=None):
691 return self._child_get(cr, domain=domain)
693 def child(self,cr, name, domain=None):
694 res = self._child_get(cr, name, domain=domain)
699 def _child_get(self, cr, name = None, domain=None):
700 """ return virtual children of resource, based on the
703 Note that many objects use NULL for a name, so we should
704 better call the name_search(),name_get() set of methods
706 obj = self.context._dirobj.pool.get(self.res_model)
709 dirobj = self.context._dirobj
710 uid = self.context.uid
711 ctx = self.context.context.copy()
712 ctx.update(self.dctx)
715 app = safe_eval(self.domain, self.dctx)
718 elif isinstance(app, list):
720 elif isinstance(app, tuple):
723 raise RuntimeError("incorrect domain expr: %s" % self.domain)
725 where.append(('id','=',self.resm_id))
728 where.append((self.namefield,'=',name))
729 is_allowed = self.check_perms(1)
731 is_allowed = self.check_perms(5)
734 raise IOError(errno.EPERM,"Permission denied")
736 # print "Where clause for %s" % self.res_model, where
737 if self.ressource_tree:
740 object2 = dirobj.pool.get(self.res_model).browse(cr, uid, self.resm_id) or False
741 if obj._parent_name in obj.fields_get(cr, uid):
742 where.append((obj._parent_name,'=',object2 and object2.id or False))
744 resids = obj.search(cr, uid, where, context=ctx)
746 for bo in obj.browse(cr, uid, resids, context=ctx):
749 name = getattr(bo, self.namefield)
752 # Yes! we can't do better but skip nameless records.
754 res.append(node_res_obj(name, self.dir_id, self, self.context, self.res_model, bo))
757 def _get_ttag(self,cr):
758 return 'rdir-%d' % self.dir_id
760 class node_res_obj(node_class):
761 """ A special sibling to node_dir, which does only contain dynamically
762 created folders foreach resource in the foreign model.
763 All folders should be of type node_res_obj and merely behave like
764 node_dirs (with limited domain).
766 our_type = 'collection'
767 def __init__(self, path, dir_id, parent, context, res_model, res_bo, res_id = None):
768 super(node_res_obj,self).__init__(path, parent,context)
770 #todo: more info from dirr
772 self.mimetype = 'application/x-directory'
773 # 'httpd/unix-directory'
774 self.create_date = parent.create_date
775 # TODO: the write date should be MAX(file.write)..
776 self.write_date = parent.write_date
777 self.content_length = 0
778 self.unixperms = 040750
779 self.uidperms = parent.uidperms & 15
780 self.uuser = parent.uuser
781 self.ugroup = parent.ugroup
782 self.res_model = res_model
783 self.domain = parent.domain
784 self.displayname = path
785 self.dctx_dict = parent.dctx_dict
786 self.res_find_all = parent.res_find_all
788 self.res_id = res_bo.id
789 dc2 = self.context.context.copy()
790 dc2.update(self.dctx)
791 dc2['res_model'] = res_model
792 dc2['res_id'] = res_bo.id
794 for fld,expr in self.dctx_dict.items():
796 self.dctx[fld] = safe_eval(expr, dc2)
798 print "Cannot eval %s for %s" % (expr, fld)
804 def __eq__(self, other):
805 if type(self) != type(other):
807 if not self.context == other.context:
809 if not self.res_model == other.res_model:
811 if not self.res_id == other.res_id:
813 if self.domain != other.domain:
815 if self.res_find_all != other.res_find_all:
817 if self.dctx != other.dctx:
819 return self.dir_id == other.dir_id
821 def children(self, cr, domain=None):
822 return self._child_get(cr, domain=domain) + self._file_get(cr)
824 def child(self, cr, name, domain=None):
825 res = self._child_get(cr, name, domain=domain)
828 res = self._file_get(cr, name)
833 def _file_get(self,cr, nodename=False):
835 is_allowed = self.check_perms((nodename and 1) or 5)
837 raise IOError(errno.EPERM,"Permission denied")
839 cntobj = self.context._dirobj.pool.get('document.directory.content')
840 uid = self.context.uid
841 ctx = self.context.context.copy()
842 ctx.update(self.dctx)
843 where = [('directory_id','=',self.dir_id) ]
845 # where.extend(self.domain)
846 # print "res_obj file_get clause", where
847 ids = cntobj.search(cr, uid, where, context=ctx)
848 for content in cntobj.browse(cr, uid, ids, context=ctx):
849 res3 = cntobj._file_get(cr, self, nodename, content, context=ctx)
855 def get_dav_props(self, cr):
857 cntobj = self.context._dirobj.pool.get('document.directory.content')
858 uid = self.context.uid
859 ctx = self.context.context.copy()
860 ctx.update(self.dctx)
861 where = [('directory_id','=',self.dir_id) ]
862 ids = cntobj.search(cr, uid, where, context=ctx)
863 for content in cntobj.browse(cr, uid, ids, context=ctx):
864 if content.extension == '.ics': # FIXME: call the content class!
865 res['http://groupdav.org/'] = ('resourcetype',)
868 def get_dav_eprop(self, cr, ns, prop):
869 if ns != 'http://groupdav.org/' or prop != 'resourcetype':
870 print "Who asked for %s:%s?" % (ns, prop)
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) ]
878 ids = cntobj.search(cr,uid,where,context=ctx)
879 for content in cntobj.browse(cr, uid, ids, context=ctx):
880 if content.extension == '.ics': # FIXME: call the content class!
881 return ('vevent-collection','http://groupdav.org/')
884 def _child_get(self, cr, name=None, domain=None):
885 dirobj = self.context._dirobj
887 is_allowed = self.check_perms((name and 1) or 5)
889 raise IOError(errno.EPERM,"Permission denied")
891 uid = self.context.uid
892 ctx = self.context.context.copy()
893 ctx.update(self.dctx)
894 directory = dirobj.browse(cr, uid, self.dir_id)
895 obj = dirobj.pool.get(self.res_model)
899 where.append(('name','=',name))
901 # Directory Structure display in tree structure
902 if self.res_id and directory.ressource_tree:
904 if obj._parent_name in obj.fields_get(cr, uid):
905 where1 = where + [(obj._parent_name, '=', self.res_id)]
906 resids = obj.search(cr, uid, where1, context=ctx)
907 for bo in obj.browse(cr, uid, resids, context=ctx):
908 namefield = directory.resource_field.name or 'name'
911 res_name = getattr(bo, namefield)
914 res.append(node_res_obj(res_name, self.dir_id, self, self.context, self.res_model, res_bo = bo))
917 where2 = where + [('parent_id','=',self.dir_id) ]
918 ids = dirobj.search(cr, uid, where2, context=ctx)
919 for dirr in dirobj.browse(cr, uid, ids, context=ctx):
920 if dirr.type == 'directory':
921 res.append(node_res_obj(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
922 elif dirr.type == 'ressource':
923 # child resources can be controlled by properly set dctx
924 res.append(node_res_dir(dirr.name,self,self.context, dirr, {'active_id': self.res_id}))
929 fil_obj = dirobj.pool.get('ir.attachment')
930 if self.res_find_all:
932 where3 = where2 + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
933 # print "where clause for dir_obj", where2
934 ids = fil_obj.search(cr, uid, where3, context=ctx)
936 for fil in fil_obj.browse(cr, uid, ids, context=ctx):
937 res.append(node_file(fil.name, self, self.context, fil))
940 # Get Child Ressource Directories
941 if directory.ressource_type_id and directory.ressource_type_id.id:
942 where4 = where + [('ressource_parent_type_id','=',directory.ressource_type_id.id)]
943 where5 = where4 + [('ressource_id','=',0)]
944 dirids = dirobj.search(cr,uid, where5)
945 where5 = where4 + [('ressource_id','=',self.res_id)]
946 dirids = dirids + dirobj.search(cr,uid, where5)
947 for dirr in dirobj.browse(cr, uid, dirids, context=ctx):
948 if dirr.type == 'directory' and not dirr.parent_id:
949 res.append(node_res_obj(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
950 if dirr.type == 'ressource':
951 res.append(node_res_dir(dirr.name, self, self.context, dirr, {'active_id': self.res_id}))
954 def create_child_collection(self, cr, objname):
955 dirobj = self.context._dirobj
956 is_allowed = self.check_perms(2)
958 raise IOError(errno.EPERM,"Permission denied")
960 uid = self.context.uid
961 ctx = self.context.context.copy()
962 ctx.update(self.dctx)
963 res_obj = dirobj.pool.get(self.context.context['res_model'])
965 object2 = res_obj.browse(cr, uid, self.context.context['res_id']) or False
967 obj = dirobj.browse(cr, uid, self.dir_id)
968 if obj and (obj.type == 'ressource') and not object2:
969 raise OSError(1, 'Operation not permited.')
974 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
975 'ressource_id': object2 and object2.id or False,
978 if (obj and (obj.type in ('directory'))) or not object2:
979 val['parent_id'] = obj and obj.id or False
981 return dirobj.create(cr, uid, val)
983 def create_child(self, cr, path, data=None):
984 """ API function to create a child file object and node
985 Return the node_* created
987 is_allowed = self.check_perms(2)
989 raise IOError(errno.EPERM,"Permission denied")
991 dirobj = self.context._dirobj
992 uid = self.context.uid
993 ctx = self.context.context.copy()
994 ctx.update(self.dctx)
995 fil_obj=dirobj.pool.get('ir.attachment')
999 'res_model': self.res_model,
1000 'res_id': self.res_id,
1001 # Datas are not set here
1003 if not self.res_find_all:
1004 val['parent_id'] = self.dir_id
1006 fil_id = fil_obj.create(cr, uid, val, context=ctx)
1007 fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
1008 fnode = node_file(path, self, self.context, fil)
1009 if data is not None:
1010 fnode.set_data(cr, data, fil)
1013 def _get_ttag(self,cr):
1014 return 'rodir-%d-%d' % (self.dir_id, self.res_id)
1016 class node_file(node_class):
1018 def __init__(self, path, parent, context, fil):
1019 super(node_file,self).__init__(path, parent,context)
1020 self.file_id = fil.id
1021 #todo: more info from ir_attachment
1022 if fil.file_type and '/' in fil.file_type:
1023 self.mimetype = str(fil.file_type)
1024 self.create_date = fil.create_date
1025 self.write_date = fil.write_date or fil.create_date
1026 self.content_length = fil.file_size
1027 self.displayname = fil.name
1031 if not parent.check_perms('x'):
1033 elif not parent.check_perms('w'):
1036 self.uuser = (fil.user_id and fil.user_id.login) or 'nobody'
1037 self.ugroup = mkdosname(fil.company_id and fil.company_id.name, default='nogroup')
1039 # This only propagates the problem to get_data. Better
1040 # fix those files to point to the root dir.
1041 self.storage_id = None
1044 if par.storage_id and par.storage_id.id:
1045 self.storage_id = par.storage_id.id
1049 def __eq__(self, other):
1050 if type(self) != type(other):
1052 if not self.context == other.context:
1054 if self.dctx != other.dctx:
1056 return self.file_id == other.file_id
1059 def open_data(self, cr, mode):
1060 stor = self.storage_id
1061 assert stor, "No storage for file #%s" % self.file_id
1062 if not self.check_perms(4):
1063 raise IOError(errno.EPERM, "Permission denied")
1065 # If storage is not set properly, we are just screwed here, don't
1066 # try to get it from default.
1067 stobj = self.context._dirobj.pool.get('document.storage')
1068 return stobj.get_file(cr, self.context.uid, stor, self, mode=mode, context=self.context.context)
1071 uid = self.context.uid
1072 if not self.check_perms(8):
1073 raise IOError(errno.EPERM, "Permission denied")
1074 document_obj = self.context._dirobj.pool.get('ir.attachment')
1075 if self.type in ('collection','database'):
1077 document = document_obj.browse(cr, uid, self.file_id, context=self.context.context)
1079 if document and document._table_name == 'ir.attachment':
1080 res = document_obj.unlink(cr, uid, [document.id])
1083 def fix_ppath(self, cr, fbro):
1084 """Sometimes we may init this w/o path, parent.
1085 This function fills the missing path from the file browse object
1087 Note: this may be an expensive operation, do on demand. However,
1088 once caching is in, we might want to do that at init time and keep
1091 if self.path or self.parent:
1094 uid = self.context.uid
1098 dirobj = self.context._dirobj.pool.get('document.directory')
1099 dirpath = dirobj.get_full_path(cr, uid, fbro.parent_id.id, context=self.context.context)
1100 if fbro.datas_fname:
1101 dirpath.append(fbro.datas_fname)
1103 dirpath.append(fbro.name)
1108 self.path = dirpath[0]
1110 def get_data(self, cr, fil_obj = None):
1111 """ Retrieve the data for some file.
1112 fil_obj may optionally be specified, and should be a browse object
1113 for the file. This is useful when the caller has already initiated
1114 the browse object. """
1115 # this is where storage kicks in..
1116 stor = self.storage_id
1117 assert stor, "No storage for file #%s" % self.file_id
1118 if not self.check_perms(4):
1119 raise IOError(errno.EPERM, "Permission denied")
1121 # If storage is not set properly, we are just screwed here, don't
1122 # try to get it from default.
1123 stobj = self.context._dirobj.pool.get('document.storage')
1124 return stobj.get_data(cr, self.context.uid,stor, self,self.context.context, fil_obj)
1126 def get_data_len(self, cr, fil_obj = None):
1127 # TODO: verify with the storage object!
1128 bin_size = self.context.context.get('bin_size', False)
1129 if bin_size and not self.content_length:
1130 self.content_length = fil_obj.db_datas
1131 return self.content_length
1133 def set_data(self, cr, data, fil_obj = None):
1134 """ Store data at some file.
1135 fil_obj may optionally be specified, and should be a browse object
1136 for the file. This is useful when the caller has already initiated
1137 the browse object. """
1138 # this is where storage kicks in..
1139 stor = self.storage_id
1140 assert stor, "No storage for file #%s" % self.file_id
1141 if not self.check_perms(2):
1142 raise IOError(errno.EPERM, "Permission denied")
1144 stobj = self.context._dirobj.pool.get('document.storage')
1145 return stobj.set_data(cr, self.context.uid,stor, self, data, self.context.context, fil_obj)
1147 def _get_ttag(self,cr):
1148 return 'file-%d' % self.file_id
1150 def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
1151 if ndir_node.context != self.context:
1152 raise NotImplementedError("Cannot move files between contexts")
1154 if (not self.check_perms(8)) and ndir_node.check_perms(2):
1155 raise IOError(errno.EPERM, "Permission denied")
1157 doc_obj = self.context._dirobj.pool.get('ir.attachment')
1159 dbro = doc_obj.browse(cr, self.context.uid, self.file_id, context=self.context.context)
1162 assert dbro.id == self.file_id, "%s != %s for %r" % (dbro.id, self.file_id, self)
1165 raise IndexError("Cannot locate doc %d", self.file_id)
1167 if (not self.parent):
1168 # there *must* be a parent node for this one
1169 self.parent = self.context.get_dir_node(cr, dbro.parent_id.id)
1173 if self.parent != ndir_node:
1174 if not (isinstance(self.parent, node_dir) and isinstance(ndir_node, node_dir)):
1175 logger.debug('Cannot move file %r from %r to %r', self, self.parent, ndir_node)
1176 raise NotImplementedError('Cannot move files between dynamic folders')
1179 ndir_obj = self.context._dirobj.browse(cr, self.context.uid, \
1180 ndir_node.dir_id, context=self.context.context)
1182 assert ndir_obj.id == ndir_node.dir_id
1184 stobj = self.context._dirobj.pool.get('document.storage')
1185 r2 = stobj.simple_move(cr, self.context.uid, self, ndir_obj, \
1186 context=self.context.context)
1189 if new_name and (new_name != dbro.name):
1191 raise NotImplementedError("Cannot rename and move") # TODO
1192 stobj = self.context._dirobj.pool.get('document.storage')
1193 r2 = stobj.simple_rename(cr, self.context.uid, self, new_name, self.context.context)
1199 # We have to update the data ourselves
1201 ctx = self.context.context.copy()
1202 ctx['__from_node'] = True
1203 doc_obj.write(cr, self.context.uid, [self.file_id,], ret, ctx )
1208 class node_content(node_class):
1209 our_type = 'content'
1210 def __init__(self, path, parent, context, cnt, dctx = None, act_id=None):
1211 super(node_content,self).__init__(path, parent,context)
1212 self.cnt_id = cnt.id
1213 self.create_date = False
1214 self.write_date = False
1215 self.content_length = False
1216 self.unixperms = 0640
1218 self.uidperms = parent.uidperms & 14
1219 self.uuser = parent.uuser
1220 self.ugroup = parent.ugroup
1222 self.extension = cnt.extension
1223 self.report_id = cnt.report_id and cnt.report_id.id
1224 #self.mimetype = cnt.extension.
1225 self.displayname = path
1227 self.dctx.update(dctx)
1228 self.act_id = act_id
1230 def fill_fields(self, cr, dctx = None):
1231 """ Try to read the object and fill missing fields, like mimetype,
1233 This function must be different from the constructor, because
1234 it uses the db cursor.
1237 cr.execute('SELECT DISTINCT mimetype FROM document_directory_content_type WHERE active AND code = %s;',
1240 if res and res[0][0]:
1241 self.mimetype = str(res[0][0])
1244 def get_data(self, cr, fil_obj = None):
1245 cntobj = self.context._dirobj.pool.get('document.directory.content')
1246 if not self.check_perms(4):
1247 raise IOError(errno.EPERM, "Permission denied")
1249 ctx = self.context.context.copy()
1250 ctx.update(self.dctx)
1251 data = cntobj.process_read(cr, self.context.uid, self, ctx)
1253 self.content_length = len(data)
1256 def open_data(self, cr, mode):
1257 if mode.endswith('b'):
1259 if mode in ('r', 'w'):
1261 elif mode in ('r+', 'w+'):
1264 raise IOError(errno.EINVAL, "Cannot open at mode %s" % mode)
1266 if not self.check_perms(cperms):
1267 raise IOError(errno.EPERM, "Permission denied")
1269 ctx = self.context.context.copy()
1270 ctx.update(self.dctx)
1272 return nodefd_content(self, cr, mode, ctx)
1274 def get_data_len(self, cr, fil_obj = None):
1275 # FIXME : here, we actually generate the content twice!!
1276 # we should have cached the generated content, but it is
1277 # not advisable to do keep it in memory, until we have a cache
1279 if not self.content_length:
1280 self.get_data(cr,fil_obj)
1281 return self.content_length
1283 def set_data(self, cr, data, fil_obj = None):
1284 cntobj = self.context._dirobj.pool.get('document.directory.content')
1285 if not self.check_perms(2):
1286 raise IOError(errno.EPERM, "Permission denied")
1288 ctx = self.context.context.copy()
1289 ctx.update(self.dctx)
1290 return cntobj.process_write(cr, self.context.uid, self, data, ctx)
1292 def _get_ttag(self,cr):
1293 return 'cnt-%d%s' % (self.cnt_id,(self.act_id and ('-' + str(self.act_id))) or '')
1296 class nodefd_content(StringIO, node_descriptor):
1297 """ A descriptor to content nodes
1299 def __init__(self, parent, cr, mode, ctx):
1300 node_descriptor.__init__(self, parent)
1303 if mode in ('r', 'r+'):
1304 cntobj = parent.context._dirobj.pool.get('document.directory.content')
1305 data = cntobj.process_read(cr, parent.context.uid, parent, ctx)
1307 parent.content_length = len(data)
1308 StringIO.__init__(self, data)
1309 elif mode in ('w', 'w+'):
1310 StringIO.__init__(self, None)
1311 # at write, we start at 0 (= overwrite), but have the original
1312 # data available, in case of a seek()
1314 StringIO.__init__(self, None)
1316 logging.getLogger('document.content').error("Incorrect mode %s specified", mode)
1317 raise IOError(errno.EINVAL, "Invalid file mode")
1321 # we now open a *separate* cursor, to update the data.
1322 # FIXME: this may be improved, for concurrency handling
1323 if self.mode == 'r':
1324 StringIO.close(self)
1327 par = self._get_parent()
1328 uid = par.context.uid
1329 cr = pooler.get_db(par.context.dbname).cursor()
1331 if self.mode in ('w', 'w+', 'r+'):
1332 data = self.getvalue()
1333 cntobj = par.context._dirobj.pool.get('document.directory.content')
1334 cntobj.process_write(cr, uid, parent, data, ctx)
1335 elif self.mode == 'a':
1336 raise NotImplementedError
1338 except Exception, e:
1339 logging.getLogger('document.content').exception('Cannot update db content #%d for close:', par.cnt_id)
1343 StringIO.close(self)