document: base the dir permissions on ir.rule, not on sql call.
[odoo/odoo.git] / addons / document / nodes.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21
22 # import urlparse
23 import pooler
24 from tools.safe_eval import safe_eval
25
26 import errno
27 # import os
28 import time
29 import logging
30
31 from StringIO import StringIO
32
33 #
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
42 #
43
44 logger = logging.getLogger('doc2.nodes')
45
46 def _str2time(cre):
47     """ Convert a string with time representation (from db) into time (float)
48     
49         Note: a place to fix if datetime is used in db.
50     """
51     if not cre:
52         return time.time()
53     frac = 0.0
54     if isinstance(cre, basestring) and '.' in cre:
55         fdot = cre.find('.')
56         frac = float(cre[fdot:])
57         cre = cre[:fdot]
58     return time.mktime(time.strptime(cre,'%Y-%m-%d %H:%M:%S')) + frac
59
60 def get_node_context(cr, uid, context):
61     return node_context(cr, uid, context)
62
63 class node_context(object):
64     """ This is the root node, representing access to some particular context
65     
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.
69     """
70     cached_roots = {}
71     node_file_class = None
72
73     def __init__(self, cr, uid, context=None):
74         self.dbname = cr.dbname
75         self.uid = uid
76         self.context = context
77         if context is None:
78             context = {}
79         context['uid'] = uid
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
83         assert self._dirobj
84         self._dirobj._prepare_context(cr, uid, self, context=context)
85         self.rootdir = False #self._dirobj._get_root_directory(cr,uid,context)
86
87     def __eq__(self, other):
88         if not type(other) == node_context:
89             return False
90         if self.dbname != other.dbname:
91             return False
92         if self.uid != other.uid:
93             return False
94         if self.context != other.context:
95             return False
96         if self.rootdir != other.rootdir:
97             return False
98         return True
99
100     def __ne__(self, other):
101         return not self.__eq__(other)
102     
103     def get(self, name, default=None):
104         return self.context.get(name, default)
105
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.
109         """
110         (ndir, duri) =  self._dirobj._locate_child(cr, self.uid, self.rootdir, uri, None, self)
111         while duri:
112             ndir = ndir.child(cr, duri[0])
113             if not ndir:
114                 return False
115             duri = duri[1:]
116         return ndir
117
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
121         """
122         
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)
126
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
130         """
131         parent = None
132         if fbro.parent_id:
133             parent = self.get_dir_node(cr, fbro.parent_id)
134
135         return self.node_file_class(fbro.name, parent, self, fbro)
136
137
138 class node_descriptor(object):
139     """A file-like interface to the data contents of a node.
140
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
144        of the data.
145        It should also take care of locking, with any native mechanism
146        or using the db.
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.
150     """
151
152     def __init__(self, parent):
153         assert isinstance(parent, node_class)
154         self.name = parent.displayname
155         self.__parent = parent
156
157     def _get_parent(self):
158         return self.__parent
159
160     def open(self, **kwargs):
161         raise NotImplementedError
162
163     def close(self):
164         raise NotImplementedError
165
166     def read(self, size=None):
167         raise NotImplementedError
168
169     def seek(self, offset, whence=None):
170         raise NotImplementedError
171
172     def tell(self):
173         raise NotImplementedError
174
175     def write(self, str):
176         raise NotImplementedError
177
178 class node_class(object):
179     """ this is a superclass for our inodes
180         It is an API for all code that wants to access the document files.
181         Nodes have attributes which contain usual file properties
182         """
183     our_type = 'baseclass'
184     DAV_PROPS = None
185     DAV_M_NS = None
186
187     def __init__(self, path, parent, context):
188         assert isinstance(context,node_context)
189         assert (not parent ) or isinstance(parent,node_class)
190         self.path = path
191         self.context = context
192         self.type=self.our_type
193         self.parent = parent
194         self.uidperms = 5   # computed permissions for our uid, in unix bits
195         self.mimetype = 'application/octet-stream'
196         self.create_date = None
197         self.write_date = None
198         self.unixperms = 0660
199         self.uuser = 'user'
200         self.ugroup = 'group'
201         self.content_length = 0
202         # dynamic context:
203         self.dctx = {}
204         if parent:
205             self.dctx = parent.dctx.copy()
206         self.displayname = 'Object'
207
208     def __eq__(self, other):
209         return NotImplemented
210
211     def __ne__(self, other):
212         return not self.__eq__(other)
213
214     def full_path(self):
215         """ Return the components of the full path for some
216             node.
217             The returned list only contains the names of nodes.
218         """
219         if self.parent:
220             s = self.parent.full_path()
221         else:
222             s = []
223         if isinstance(self.path,list):
224             s+=self.path
225         elif self.path is None:
226             s.append('')
227         else:
228             s.append(self.path)
229         return s #map(lambda x: '/' +x, s)
230         
231     def __repr__(self):
232         return "%s@/%s" % (self.our_type, '/'.join(self.full_path()))
233
234     def children(self, cr, domain=None):
235         print "node_class.children()"
236         return [] #stub
237
238     def child(self,cr, name, domain=None):
239         print "node_class.child()"
240         return None
241
242     def get_uri(self, cr, uri):
243         duri = uri
244         ndir = self
245         while duri:
246             ndir = ndir.child(cr, duri[0])
247             if not ndir:
248                 return False
249             duri = duri[1:]
250         return ndir
251
252     def path_get(self):
253         print "node_class.path_get()"
254         return False
255
256     def get_data(self,cr):
257         raise TypeError('no data for %s'% self.type)
258
259     def open_data(self, cr, mode):
260         """ Open a node_descriptor object for this node.
261
262         @param the mode of open, eg 'r', 'w', 'a', like file.open()
263
264         This operation may lock the data for this node (and accross
265         other node hierarchies), until the descriptor is close()d. If
266         the node is locked, subsequent opens (depending on mode) may
267         immediately fail with an exception (which?).
268         For this class, there is no data, so no implementation. Each
269         child class that has data should override this.
270         """
271         raise TypeError('no data for %s' % self.type)
272
273     def _get_storage(self,cr):
274         raise RuntimeError("no storage for base class")
275
276     def get_etag(self,cr):
277         """ Get a tag, unique per object + modification.
278
279             see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """
280         return '"%s-%s"' % (self._get_ttag(cr), self._get_wtag(cr))
281
282     def _get_wtag(self, cr):
283         """ Return the modification time as a unique, compact string """
284         return str(_str2time(self.write_date)).replace('.','')
285
286     def _get_ttag(self, cr):
287         """ Get a unique tag for this type/id of object.
288             Must be overriden, so that each node is uniquely identified.
289         """
290         print "node_class.get_ttag()",self
291         raise NotImplementedError("get_ttag stub()")
292
293     def get_dav_props(self, cr):
294         """ If this class has special behaviour for GroupDAV etc, export
295         its capabilities """
296         # This fn is placed here rather than WebDAV, because we want the
297         # baseclass methods to apply to all node subclasses
298         return self.DAV_PROPS or {}
299
300     def match_dav_eprop(self, cr, match, ns, prop):
301         res = self.get_dav_eprop(cr, ns, prop)
302         if res == match:
303             return True
304         return False
305
306     def get_dav_eprop(self, cr, ns, prop):
307         if not self.DAV_M_NS:
308             return None
309         
310         if self.DAV_M_NS.has_key(ns):
311             prefix = self.DAV_M_NS[ns]
312         else:
313             logger.debug('No namespace: %s ("%s")',ns, prop)
314             return None
315
316         mname = prefix + "_" + prop.replace('-','_')
317
318         if not hasattr(self, mname):
319             return None
320
321         try:
322             m = getattr(self, mname)
323             r = m(cr)
324             return r
325         except AttributeError:
326             logger.debug('Property %s not supported' % prop, exc_info=True)
327         return None
328
329     def get_dav_resourcetype(self, cr):
330         """ Get the DAV resource type.
331         
332             Is here because some nodes may exhibit special behaviour, like
333             CalDAV/GroupDAV collections
334         """
335         raise NotImplementedError
336
337     def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
338         """ Move this node to a new parent directory.
339         @param ndir_node the collection that this node should be moved under
340         @param new_name a name to rename this node to. If omitted, the old
341             name is preserved
342         @param fil_obj, can be None, is the browse object for the file,
343             if already available.
344         @param ndir_obj must be the browse object to the new doc.directory
345             location, where this node should be moved to.
346         in_write: When called by write(), we shouldn't attempt to write the
347             object, but instead return the dict of vals (avoid re-entrance).
348             If false, we should write all data to the object, here, as if the
349             caller won't do anything after calling move_to()
350
351         Return value:
352             True: the node is moved, the caller can update other values, too.
353             False: the node is either removed or fully updated, the caller
354                 must discard the fil_obj, not attempt to write any more to it.
355             dict: values to write back to the object. *May* contain a new id!
356
357         Depending on src and target storage, implementations of this function
358         could do various things.
359         Should also consider node<->content, dir<->dir moves etc.
360
361         Move operations, as instructed from APIs (eg. request from DAV) could
362         use this function.
363         """
364         raise NotImplementedError(repr(self))
365
366     def create_child(self, cr, path, data=None):
367         """ Create a regular file under this node
368         """
369         logger.warning("Attempted to create a file under %r, not possible.", self)
370         raise IOError(errno.EPERM, "Not allowed to create files here")
371     
372     def create_child_collection(self, cr, objname):
373         """ Create a child collection (directory) under self
374         """
375         logger.warning("Attempted to create a collection under %r, not possible.", self)
376         raise IOError(errno.EPERM, "Not allowed to create folders here")
377
378     def rm(self, cr):
379         raise NotImplementedError(repr(self))
380
381     def rmcol(self, cr):
382         raise NotImplementedError(repr(self))
383
384     def get_domain(self, cr, filters):
385         # TODO Document
386         return []
387
388     def check_perms(self, perms):
389         """ Check the permissions of the current node.
390         
391         @param perms either an integers of the bits to check, or
392                 a string with the permission letters
393
394         Permissions of nodes are (in a unix way):
395         1, x : allow descend into dir
396         2, w : allow write into file, or modification to dir
397         4, r : allow read of file, or listing of dir contents
398         8, u : allow remove (unlink)
399         """
400         
401         if isinstance(perms, str):
402             pe2 = 0
403             chars = { 'x': 1, 'w': 2, 'r': 4, 'u': 8 }
404             for c in perms:
405                 pe2 = pe2 | chars[c]
406             perms = pe2
407         elif isinstance(perms, int):
408             if perms < 0 or perms > 15:
409                 raise ValueError("Invalid permission bits")
410         else:
411             raise ValueError("Invalid permission attribute")
412         
413         return ((self.uidperms & perms) == perms)
414
415 class node_database(node_class):
416     """ A node representing the database directory
417
418     """
419     our_type = 'database'
420     def __init__(self, path=[], parent=False, context=None):
421         super(node_database,self).__init__(path, parent, context)
422         self.unixperms = 040750
423         self.uidperms = 5
424
425     def children(self, cr, domain=None):
426         res = self._child_get(cr, domain=domain) + self._file_get(cr)
427         return res
428
429     def child(self, cr, name, domain=None):
430         res = self._child_get(cr, name, domain=None)
431         if res:
432             return res[0]
433         res = self._file_get(cr,name)
434         if res:
435             return res[0]
436         return None
437
438     def _child_get(self, cr, name=False, domain=None):
439         dirobj = self.context._dirobj
440         uid = self.context.uid
441         ctx = self.context.context.copy()
442         ctx.update(self.dctx)
443         where = [('parent_id','=', False), ('ressource_parent_type_id','=',False)]
444         if name:
445             where.append(('name','=',name))
446             is_allowed = self.check_perms(1)
447         else:
448             is_allowed = self.check_perms(5)
449         
450         if not is_allowed:
451             raise IOError(errno.EPERM, "Permission into directory denied")
452
453         if domain:
454             where = where + domain
455         ids = dirobj.search(cr, uid, where, context=ctx)
456         res = []
457         for dirr in dirobj.browse(cr, uid, ids, context=ctx):
458             klass = dirr.get_node_class(dirr, context=ctx)
459             res.append(klass(dirr.name, self, self.context,dirr))
460
461         return res
462
463     def _file_get(self,cr, nodename=False):
464         res = []
465         return res
466
467     def _get_ttag(self,cr):
468         return 'db-%s' % cr.dbname
469
470 def mkdosname(company_name, default='noname'):
471     """ convert a string to a dos-like name"""
472     if not company_name:
473         return default
474     badchars = ' !@#$%^`~*()+={}[];:\'"/?.<>'
475     n = ''
476     for c in company_name[:8]:
477         n += (c in badchars and '_') or c
478     return n
479     
480
481 def _uid2unixperms(perms, has_owner):
482     """ Convert the uidperms and the owner flag to full unix bits
483     """
484     res = 0
485     res |= (perms & 0x07) << 6
486     res |= (perms & 0x05) << 3
487     if not has_owner:
488         res |= 0x05
489     return res
490
491 class node_dir(node_database):
492     our_type = 'collection'
493     def __init__(self, path, parent, context, dirr, dctx=None):
494         super(node_dir,self).__init__(path, parent,context)
495         self.dir_id = dirr and dirr.id or False
496         #todo: more info from dirr
497         self.mimetype = 'application/x-directory'
498             # 'httpd/unix-directory'
499         self.create_date = dirr and dirr.create_date or False
500         self.domain = dirr and dirr.domain or []
501         self.res_model = dirr and dirr.ressource_type_id and dirr.ressource_type_id.model or False
502         # TODO: the write date should be MAX(file.write)..
503         self.write_date = dirr and (dirr.write_date or dirr.create_date) or False
504         self.content_length = 0
505         try:
506             self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
507         except Exception:
508             self.uuser = 'nobody'
509         self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
510         self.uidperms = dirr.get_dir_permissions()
511         self.unixperms = 040000 | _uid2unixperms(self.uidperms, dirr and dirr.user_id)
512         if dctx:
513             self.dctx.update(dctx)
514         dc2 = self.context.context
515         dc2.update(self.dctx)
516         dc2['dir_id'] = self.dir_id
517         self.displayname = dirr and dirr.name or False
518         if dirr and dirr.dctx_ids:
519             for dfld in dirr.dctx_ids:
520                 try:
521                     self.dctx['dctx_' + dfld.field] = safe_eval(dfld.expr,dc2)
522                 except Exception:
523                     print "Cannot eval %s" % dfld.expr
524                     print e
525                     pass
526
527     def __eq__(self, other):
528         if type(self) != type(other):
529             return False
530         if not self.context == other.context:
531             return False
532         # Two directory nodes, for the same document.directory, may have a
533         # different context! (dynamic folders)
534         if self.dctx != other.dctx:
535             return False
536         return self.dir_id == other.dir_id
537
538     def get_data(self, cr):
539         #res = ''
540         #for child in self.children(cr):
541         #    res += child.get_data(cr)
542         return None
543
544     def _file_get(self, cr, nodename=False):
545         res = super(node_dir,self)._file_get(cr, nodename)
546         
547         is_allowed = self.check_perms(nodename and 1 or 5)
548         if not is_allowed:
549             raise IOError(errno.EPERM, "Permission into directory denied")
550
551         cntobj = self.context._dirobj.pool.get('document.directory.content')
552         uid = self.context.uid
553         ctx = self.context.context.copy()
554         ctx.update(self.dctx)
555         where = [('directory_id','=',self.dir_id) ]
556         ids = cntobj.search(cr, uid, where, context=ctx)
557         for content in cntobj.browse(cr, uid, ids, context=ctx):
558             res3 = cntobj._file_get(cr, self, nodename, content)
559             if res3:
560                 res.extend(res3)
561
562         return res
563     
564     def _child_get(self, cr, name=None, domain=None):
565         dirobj = self.context._dirobj
566         uid = self.context.uid
567         ctx = self.context.context.copy()
568         ctx.update(self.dctx)
569         where = [('parent_id','=',self.dir_id)]
570         if name:
571             where.append(('name','=',name))
572             is_allowed = self.check_perms(1)
573         else:
574             is_allowed = self.check_perms(5)
575         
576         if not is_allowed:
577             raise IOError(errno.EPERM, "Permission into directory denied")
578
579         if not domain:
580             domain = []
581
582         where2 = where + domain + [('ressource_parent_type_id','=',False)]
583         ids = dirobj.search(cr, uid, where2, context=ctx)
584         res = []
585         for dirr in dirobj.browse(cr, uid, ids, context=ctx):
586             klass = dirr.get_node_class(dirr, context=ctx)
587             res.append(klass(dirr.name, self, self.context,dirr))
588
589         # Static directories should never return files with res_model/res_id
590         # because static dirs are /never/ related to a record.
591         # In fact, files related to some model and parented by the root dir
592         # (the default), will NOT be accessible in the node system unless
593         # a resource folder for that model exists (with resource_find_all=True).
594         # Having resource attachments in a common folder is bad practice,
595         # because they would be visible to all users, and their names may be
596         # the same, conflicting.
597         where += [('res_model', '=', False)]
598         fil_obj = dirobj.pool.get('ir.attachment')
599         ids = fil_obj.search(cr, uid, where, context=ctx)
600         if ids:
601             for fil in fil_obj.browse(cr, uid, ids, context=ctx):
602                 klass = self.context.node_file_class
603                 res.append(klass(fil.name, self, self.context, fil))
604         return res
605
606     def rmcol(self, cr):
607         uid = self.context.uid
608         directory = self.context._dirobj.browse(cr, uid, self.dir_id)
609         res = False
610         if not directory:
611             raise OSError(2, 'Not such file or directory.')
612         if not self.check_perms('u'):
613             raise IOError(errno.EPERM,"Permission denied")
614
615         if directory._table_name=='document.directory':
616             if self.children(cr):
617                 raise OSError(39, 'Directory not empty.')
618             res = self.context._dirobj.unlink(cr, uid, [directory.id])
619         else:
620             raise OSError(1, 'Operation not permited.')
621         return res
622
623     def create_child_collection(self, cr, objname):
624         object2 = False
625         if not self.check_perms(2):
626             raise IOError(errno.EPERM,"Permission denied")
627
628         dirobj = self.context._dirobj
629         uid = self.context.uid
630         ctx = self.context.context.copy()
631         ctx.update(self.dctx)
632         obj = dirobj.browse(cr, uid, self.dir_id)
633         if obj and (obj.type == 'ressource') and not object2:
634             raise OSError(1, 'Operation not permited.')
635
636         #objname = uri2[-1]
637         val = {
638                 'name': objname,
639                 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
640                 'ressource_id': object2 and object2.id or False,
641                 'parent_id' : obj and obj.id or False
642         }
643
644         return dirobj.create(cr, uid, val)
645
646
647     def create_child(self, cr, path, data=None):
648         """ API function to create a child file object and node
649             Return the node_* created
650         """
651         if not self.check_perms(2):
652             raise IOError(errno.EPERM,"Permission denied")
653
654         dirobj = self.context._dirobj
655         uid = self.context.uid
656         ctx = self.context.context.copy()
657         ctx.update(self.dctx)
658         fil_obj=dirobj.pool.get('ir.attachment')
659         val = {
660             'name': path,
661             'datas_fname': path,
662             'parent_id': self.dir_id,
663             # Datas are not set here
664         }
665
666         fil_id = fil_obj.create(cr, uid, val, context=ctx)
667         fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
668         fnode = node_file(path, self, self.context, fil)
669         if data is not None:
670             fnode.set_data(cr, data, fil)
671         return fnode
672
673     def _get_ttag(self,cr):
674         return 'dir-%d' % self.dir_id
675
676     def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
677         """ Move directory. This operation is simple, since the present node is
678         only used for static, simple directories.
679             Note /may/ be called with ndir_node = None, to rename the document root.
680         """
681         if ndir_node and (ndir_node.context != self.context):
682             raise NotImplementedError("Cannot move directories between contexts")
683
684         if (not self.check_perms('u')) or (not ndir_node.check_perms('w')):
685             raise IOError(errno.EPERM,"Permission denied")
686
687         dir_obj = self.context._dirobj
688         if not fil_obj:
689             dbro = dir_obj.browse(cr, self.context.uid, self.dir_id, context=self.context.context)
690         else:
691             dbro = dir_obj
692             assert dbro.id == self.dir_id
693
694         if not dbro:
695             raise IndexError("Cannot locate dir %d", self.dir_id)
696
697         if (not self.parent) and ndir_node:
698             if not dbro.parent_id:
699                 raise IOError(errno.EPERM, "Cannot move the root directory!")
700             self.parent = self.context.get_dir_node(cr, dbro.parent_id)
701             assert self.parent
702
703         if self.parent != ndir_node:
704             logger.debug('Cannot move dir %r from %r to %r', self, self.parent, ndir_node)
705             raise NotImplementedError('Cannot move dir to another dir')
706
707         ret = {}
708         if new_name and (new_name != dbro.name):
709             if ndir_node.child(cr, new_name):
710                 raise IOError(errno.EEXIST, "Destination path already exists")
711             ret['name'] = new_name
712
713         del dbro
714
715         if not in_write:
716             # We have to update the data ourselves
717             if ret:
718                 ctx = self.context.context.copy()
719                 ctx['__from_node'] = True
720                 dir_obj.write(cr, self.context.uid, [self.dir_id,], ret, ctx)
721             ret = True
722
723         return ret
724
725 class node_res_dir(node_class):
726     """ A folder containing dynamic folders
727         A special sibling to node_dir, which does only contain dynamically
728         created folders foreach resource in the foreign model.
729         All folders should be of type node_res_obj and merely behave like
730         node_dirs (with limited domain).
731     """
732     our_type = 'collection'
733     res_obj_class = None
734     def __init__(self, path, parent, context, dirr, dctx=None ):
735         super(node_res_dir,self).__init__(path, parent, context)
736         self.dir_id = dirr.id
737         #todo: more info from dirr
738         self.mimetype = 'application/x-directory'
739                         # 'httpd/unix-directory'
740         self.create_date = dirr.create_date
741         # TODO: the write date should be MAX(file.write)..
742         self.write_date = dirr.write_date or dirr.create_date
743         self.content_length = 0
744         try:
745             self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
746         except Exception:
747             self.uuser = 'nobody'
748         self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
749         self.uidperms = dirr.get_dir_permissions()
750         self.unixperms = 040000 | _uid2unixperms(self.uidperms, dirr and dirr.user_id)
751         self.res_model = dirr.ressource_type_id and dirr.ressource_type_id.model or False
752         self.resm_id = dirr.ressource_id
753         self.res_find_all = dirr.resource_find_all
754         self.namefield = dirr.resource_field.name or 'name'
755         self.displayname = dirr.name
756         # Important: the domain is evaluated using the *parent* dctx!
757         self.domain = dirr.domain
758         self.ressource_tree = dirr.ressource_tree
759         # and then, we add our own vars in the dctx:
760         if dctx:
761             self.dctx.update(dctx)
762
763         # and then, we prepare a dctx dict, for deferred evaluation:
764         self.dctx_dict = {}
765         for dfld in dirr.dctx_ids:
766             self.dctx_dict['dctx_' + dfld.field] = dfld.expr
767
768     def __eq__(self, other):
769         if type(self) != type(other):
770             return False
771         if not self.context == other.context:
772             return False
773         # Two nodes, for the same document.directory, may have a
774         # different context! (dynamic folders)
775         if self.dctx != other.dctx:
776             return False
777         return self.dir_id == other.dir_id
778
779     def children(self, cr, domain=None):
780         return self._child_get(cr, domain=domain)
781
782     def child(self,cr, name, domain=None):
783         res = self._child_get(cr, name, domain=domain)
784         if res:
785             return res[0]
786         return None
787
788     def _child_get(self, cr, name = None, domain=None):
789         """ return virtual children of resource, based on the
790             foreign object.
791
792             Note that many objects use NULL for a name, so we should
793             better call the name_search(),name_get() set of methods
794         """
795         obj = self.context._dirobj.pool.get(self.res_model)
796         if not obj:
797             return []
798         dirobj = self.context._dirobj
799         uid = self.context.uid
800         ctx = self.context.context.copy()
801         ctx.update(self.dctx)
802         where = []
803         if self.domain:
804             app = safe_eval(self.domain, ctx)
805             if not app:
806                 pass
807             elif isinstance(app, list):
808                 where.extend(app)
809             elif isinstance(app, tuple):
810                 where.append(app)
811             else:
812                 raise RuntimeError("incorrect domain expr: %s" % self.domain)
813         if self.resm_id:
814             where.append(('id','=',self.resm_id))
815
816         if name:
817             where.append((self.namefield,'=',name))
818             is_allowed = self.check_perms(1)
819         else:
820             is_allowed = self.check_perms(5)
821
822         if not is_allowed:
823             raise IOError(errno.EPERM,"Permission denied")
824
825         # print "Where clause for %s" % self.res_model, where
826         if self.ressource_tree:
827             object2 = False
828             if self.resm_id:
829                 object2 = dirobj.pool.get(self.res_model).browse(cr, uid, self.resm_id) or False
830             if obj._parent_name in obj.fields_get(cr, uid):
831                 where.append((obj._parent_name,'=',object2 and object2.id or False))
832
833         resids = obj.search(cr, uid, where, context=ctx)
834         res = []
835         for bo in obj.browse(cr, uid, resids, context=ctx):
836             if not bo:
837                 continue
838             name = getattr(bo, self.namefield)
839             if not name:
840                 continue
841                 # Yes! we can't do better but skip nameless records.
842
843             res.append(self.res_obj_class(name, self.dir_id, self, self.context, self.res_model, bo))
844         return res
845
846     def _get_ttag(self,cr):
847         return 'rdir-%d' % self.dir_id
848
849 class node_res_obj(node_class):
850     """ A dynamically created folder.
851         A special sibling to node_dir, which does only contain dynamically
852         created folders foreach resource in the foreign model.
853         All folders should be of type node_res_obj and merely behave like
854         node_dirs (with limited domain).
855         """
856     our_type = 'collection'
857     def __init__(self, path, dir_id, parent, context, res_model, res_bo, res_id = None):
858         super(node_res_obj,self).__init__(path, parent,context)
859         assert parent
860         #todo: more info from dirr
861         self.dir_id = dir_id
862         self.mimetype = 'application/x-directory'
863                         # 'httpd/unix-directory'
864         self.create_date = parent.create_date
865         # TODO: the write date should be MAX(file.write)..
866         self.write_date = parent.write_date
867         self.content_length = 0
868         self.uidperms = parent.uidperms & 15
869         self.unixperms = 040000 | _uid2unixperms(self.uidperms, True)
870         self.uuser = parent.uuser
871         self.ugroup = parent.ugroup
872         self.res_model = res_model
873         self.domain = parent.domain
874         self.displayname = path
875         self.dctx_dict = parent.dctx_dict
876         self.res_find_all = parent.res_find_all
877         if res_bo:
878             self.res_id = res_bo.id
879             dc2 = self.context.context.copy()
880             dc2.update(self.dctx)
881             dc2['res_model'] = res_model
882             dc2['res_id'] = res_bo.id
883             dc2['this'] = res_bo
884             for fld,expr in self.dctx_dict.items():
885                 try:
886                     self.dctx[fld] = safe_eval(expr, dc2)
887                 except Exception:
888                     print "Cannot eval %s for %s" % (expr, fld)
889                     print e
890                     pass
891         else:
892             self.res_id = res_id
893
894     def __eq__(self, other):
895         if type(self) != type(other):
896             return False
897         if not self.context == other.context:
898             return False
899         if not self.res_model == other.res_model:
900             return False
901         if not self.res_id == other.res_id:
902             return False
903         if self.domain != other.domain:
904             return False
905         if self.res_find_all != other.res_find_all:
906             return False
907         if self.dctx != other.dctx:
908             return False
909         return self.dir_id == other.dir_id
910
911     def children(self, cr, domain=None):
912         return self._child_get(cr, domain=domain) + self._file_get(cr)
913
914     def child(self, cr, name, domain=None):
915         res = self._child_get(cr, name, domain=domain)
916         if res:
917             return res[0]
918         res = self._file_get(cr, name)
919         if res:
920             return res[0]
921         return None
922
923     def _file_get(self,cr, nodename=False):
924         res = []
925         is_allowed = self.check_perms((nodename and 1) or 5)
926         if not is_allowed:
927             raise IOError(errno.EPERM,"Permission denied")
928
929         cntobj = self.context._dirobj.pool.get('document.directory.content')
930         uid = self.context.uid
931         ctx = self.context.context.copy()
932         ctx.update(self.dctx)
933         where = [('directory_id','=',self.dir_id) ]
934         #if self.domain:
935         #    where.extend(self.domain)
936         # print "res_obj file_get clause", where
937         ids = cntobj.search(cr, uid, where, context=ctx)
938         for content in cntobj.browse(cr, uid, ids, context=ctx):
939             res3 = cntobj._file_get(cr, self, nodename, content, context=ctx)
940             if res3:
941                 res.extend(res3)
942
943         return res
944
945     def get_dav_props_DEPR(self, cr):
946         # Deprecated! (but document_ics must be cleaned, first)
947         res = {}
948         cntobj = self.context._dirobj.pool.get('document.directory.content')
949         uid = self.context.uid
950         ctx = self.context.context.copy()
951         ctx.update(self.dctx)
952         where = [('directory_id','=',self.dir_id) ]
953         ids = cntobj.search(cr, uid, where, context=ctx)
954         for content in cntobj.browse(cr, uid, ids, context=ctx):
955             if content.extension == '.ics': # FIXME: call the content class!
956                 res['http://groupdav.org/'] = ('resourcetype',)
957         return res
958
959     def get_dav_eprop_DEPR(self, cr, ns, prop):
960         # Deprecated!
961         if ns != 'http://groupdav.org/' or prop != 'resourcetype':
962             logger.warning("Who asked for %s:%s?" % (ns, prop))
963             return None
964         cntobj = self.context._dirobj.pool.get('document.directory.content')
965         uid = self.context.uid
966         ctx = self.context.context.copy()
967         ctx.update(self.dctx)
968         where = [('directory_id','=',self.dir_id) ]
969         ids = cntobj.search(cr,uid,where,context=ctx)
970         for content in cntobj.browse(cr, uid, ids, context=ctx):
971             # TODO: remove relic of GroupDAV
972             if content.extension == '.ics': # FIXME: call the content class!
973                 return ('vevent-collection','http://groupdav.org/')
974         return None
975
976     def _child_get(self, cr, name=None, domain=None):
977         dirobj = self.context._dirobj
978
979         is_allowed = self.check_perms((name and 1) or 5)
980         if not is_allowed:
981             raise IOError(errno.EPERM,"Permission denied")
982
983         uid = self.context.uid
984         ctx = self.context.context.copy()
985         ctx.update(self.dctx)
986         directory = dirobj.browse(cr, uid, self.dir_id)
987         obj = dirobj.pool.get(self.res_model)
988         where = []
989         res = []
990         if name:
991             where.append(('name','=',name))
992
993         # Directory Structure display in tree structure
994         if self.res_id and directory.ressource_tree:
995             where1 = []
996             if obj._parent_name in obj.fields_get(cr, uid):
997                 where1 = where + [(obj._parent_name, '=', self.res_id)]
998             namefield = directory.resource_field.name or 'name'
999             resids = obj.search(cr, uid, where1, context=ctx)
1000             for bo in obj.browse(cr, uid, resids, context=ctx):
1001                 if not bo:
1002                     continue
1003                 res_name = getattr(bo, namefield)
1004                 if not res_name:
1005                     continue
1006                 # TODO Revise
1007                 klass = directory.get_node_class(directory, dynamic=True, context=ctx)
1008                 res.append(klass(res_name, dir_id=self.dir_id, parent=self, context=self.context, res_model=self.res_model, res_bo=bo))
1009
1010
1011         where2 = where + [('parent_id','=',self.dir_id) ]
1012         ids = dirobj.search(cr, uid, where2, context=ctx)
1013         for dirr in dirobj.browse(cr, uid, ids, context=ctx):
1014             if dirr.type == 'directory':
1015                 klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
1016                 res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
1017             elif dirr.type == 'ressource':
1018                 # child resources can be controlled by properly set dctx
1019                 klass = dirr.get_node_class(dirr, context=ctx)
1020                 res.append(klass(dirr.name,self,self.context, dirr, {'active_id': self.res_id}))
1021
1022         fil_obj = dirobj.pool.get('ir.attachment')
1023         if self.res_find_all:
1024             where2 = where
1025         where3 = where2  + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
1026         # print "where clause for dir_obj", where2
1027         ids = fil_obj.search(cr, uid, where3, context=ctx)
1028         if ids:
1029             for fil in fil_obj.browse(cr, uid, ids, context=ctx):
1030                 klass = self.context.node_file_class
1031                 res.append(klass(fil.name, self, self.context, fil))
1032
1033
1034         # Get Child Ressource Directories
1035         if directory.ressource_type_id and directory.ressource_type_id.id:
1036             where4 = where + [('ressource_parent_type_id','=',directory.ressource_type_id.id)]
1037             where5 = where4 + [('ressource_id','=',0)]
1038             dirids = dirobj.search(cr,uid, where5)
1039             where5 = where4 + [('ressource_id','=',self.res_id)]
1040             dirids = dirids + dirobj.search(cr,uid, where5)
1041             for dirr in dirobj.browse(cr, uid, dirids, context=ctx):
1042                 if dirr.type == 'directory' and not dirr.parent_id:
1043                     klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
1044                     res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
1045                 if dirr.type == 'ressource':
1046                     klass = dirr.get_node_class(dirr, context=ctx)
1047                     res.append(klass(dirr.name, self, self.context, dirr, {'active_id': self.res_id}))
1048         return res
1049
1050     def create_child_collection(self, cr, objname):
1051         dirobj = self.context._dirobj
1052         is_allowed = self.check_perms(2)
1053         if not is_allowed:
1054             raise IOError(errno.EPERM,"Permission denied")
1055
1056         uid = self.context.uid
1057         ctx = self.context.context.copy()
1058         ctx.update(self.dctx)
1059         res_obj = dirobj.pool.get(self.res_model)
1060
1061         object2 = res_obj.browse(cr, uid, self.res_id) or False
1062
1063         obj = dirobj.browse(cr, uid, self.dir_id)
1064         if obj and (obj.type == 'ressource') and not object2:
1065             raise OSError(1, 'Operation not permited.')
1066
1067
1068         val = {
1069                 'name': objname,
1070                 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
1071                 'ressource_id': object2 and object2.id or False,
1072                 'parent_id' : False
1073         }
1074         if (obj and (obj.type in ('directory'))) or not object2:
1075             val['parent_id'] =  obj and obj.id or False
1076
1077         return dirobj.create(cr, uid, val)
1078
1079     def create_child(self, cr, path, data=None):
1080         """ API function to create a child file object and node
1081             Return the node_* created
1082         """
1083         is_allowed = self.check_perms(2)
1084         if not is_allowed:
1085             raise IOError(errno.EPERM,"Permission denied")
1086
1087         dirobj = self.context._dirobj
1088         uid = self.context.uid
1089         ctx = self.context.context.copy()
1090         ctx.update(self.dctx)
1091         fil_obj=dirobj.pool.get('ir.attachment')
1092         val = {
1093             'name': path,
1094             'datas_fname': path,
1095             'res_model': self.res_model,
1096             'res_id': self.res_id,
1097             # Datas are not set here
1098         }
1099         if not self.res_find_all:
1100             val['parent_id'] = self.dir_id
1101
1102         fil_id = fil_obj.create(cr, uid, val, context=ctx)
1103         fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
1104         klass = self.context.node_file_class
1105         fnode = klass(path, self, self.context, fil)
1106         if data is not None:
1107             fnode.set_data(cr, data, fil)
1108         return fnode
1109
1110     def _get_ttag(self,cr):
1111         return 'rodir-%d-%d' % (self.dir_id, self.res_id)
1112
1113 node_res_dir.res_obj_class = node_res_obj
1114
1115 class node_file(node_class):
1116     our_type = 'file'
1117     def __init__(self, path, parent, context, fil):
1118         super(node_file,self).__init__(path, parent,context)
1119         self.file_id = fil.id
1120         #todo: more info from ir_attachment
1121         if fil.file_type and '/' in fil.file_type:
1122             self.mimetype = str(fil.file_type)
1123         self.create_date = fil.create_date
1124         self.write_date = fil.write_date or fil.create_date
1125         self.content_length = fil.file_size
1126         self.displayname = fil.name
1127         
1128         self.uidperms = 14
1129         if parent:
1130             if not parent.check_perms('x'):
1131                 self.uidperms = 0
1132             elif not parent.check_perms('w'):
1133                 self.uidperms = 4
1134     
1135         try:
1136             self.uuser = (fil.user_id and fil.user_id.login) or 'nobody'
1137         except Exception:
1138             self.uuser = 'nobody'
1139         self.ugroup = mkdosname(fil.company_id and fil.company_id.name, default='nogroup')
1140
1141         # This only propagates the problem to get_data. Better
1142         # fix those files to point to the root dir.
1143         self.storage_id = None
1144         par = fil.parent_id
1145         while par:
1146             if par.storage_id and par.storage_id.id:
1147                 self.storage_id = par.storage_id.id
1148                 break
1149             par = par.parent_id
1150
1151     def __eq__(self, other):
1152         if type(self) != type(other):
1153             return False
1154         if not self.context == other.context:
1155             return False
1156         if self.dctx != other.dctx:
1157             return False
1158         return self.file_id == other.file_id
1159
1160
1161     def open_data(self, cr, mode):
1162         stor = self.storage_id
1163         assert stor, "No storage for file #%s" % self.file_id
1164         if not self.check_perms(4):
1165             raise IOError(errno.EPERM, "Permission denied")
1166
1167         # If storage is not set properly, we are just screwed here, don't
1168         # try to get it from default.
1169         stobj = self.context._dirobj.pool.get('document.storage')
1170         return stobj.get_file(cr, self.context.uid, stor, self, mode=mode, context=self.context.context)
1171
1172     def rm(self, cr):
1173         uid = self.context.uid
1174         if not self.check_perms(8):
1175             raise IOError(errno.EPERM, "Permission denied")
1176         document_obj = self.context._dirobj.pool.get('ir.attachment')
1177         if self.type in ('collection','database'):
1178             return False
1179         document = document_obj.browse(cr, uid, self.file_id, context=self.context.context)
1180         res = False
1181         if document and document._table_name == 'ir.attachment':
1182             res = document_obj.unlink(cr, uid, [document.id])
1183         return res
1184
1185     def fix_ppath(self, cr, fbro):
1186         """Sometimes we may init this w/o path, parent.
1187         This function fills the missing path from the file browse object
1188
1189         Note: this may be an expensive operation, do on demand. However,
1190         once caching is in, we might want to do that at init time and keep
1191         this object anyway
1192         """
1193         if self.path or self.parent:
1194             return
1195         assert fbro
1196         uid = self.context.uid
1197
1198         dirpath = []
1199         if fbro.parent_id:
1200             dirobj = self.context._dirobj.pool.get('document.directory')
1201             dirpath = dirobj.get_full_path(cr, uid, fbro.parent_id.id, context=self.context.context)
1202         if fbro.datas_fname:
1203             dirpath.append(fbro.datas_fname)
1204         else:
1205             dirpath.append(fbro.name)
1206
1207         if len(dirpath)>1:
1208             self.path = dirpath
1209         else:
1210             self.path = dirpath[0]
1211
1212     def get_data(self, cr, fil_obj = None):
1213         """ Retrieve the data for some file.
1214             fil_obj may optionally be specified, and should be a browse object
1215             for the file. This is useful when the caller has already initiated
1216             the browse object. """
1217         # this is where storage kicks in..
1218         stor = self.storage_id
1219         assert stor, "No storage for file #%s" % self.file_id
1220         if not self.check_perms(4):
1221             raise IOError(errno.EPERM, "Permission denied")
1222
1223         # If storage is not set properly, we are just screwed here, don't
1224         # try to get it from default.
1225         stobj = self.context._dirobj.pool.get('document.storage')
1226         return stobj.get_data(cr, self.context.uid,stor, self,self.context.context, fil_obj)
1227
1228     def get_data_len(self, cr, fil_obj = None):
1229         # TODO: verify with the storage object!
1230         bin_size = self.context.context.get('bin_size', False)
1231         if bin_size and not self.content_length:
1232             self.content_length = fil_obj.db_datas
1233         return self.content_length
1234
1235     def set_data(self, cr, data, fil_obj = None):
1236         """ Store data at 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(2):
1244             raise IOError(errno.EPERM, "Permission denied")
1245
1246         stobj = self.context._dirobj.pool.get('document.storage')
1247         return stobj.set_data(cr, self.context.uid,stor, self, data, self.context.context, fil_obj)
1248
1249     def _get_ttag(self,cr):
1250         return 'file-%d' % self.file_id
1251
1252     def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
1253         if ndir_node and ndir_node.context != self.context:
1254             raise NotImplementedError("Cannot move files between contexts")
1255
1256         if (not self.check_perms(8)) and ndir_node.check_perms(2):
1257             raise IOError(errno.EPERM, "Permission denied")
1258
1259         doc_obj = self.context._dirobj.pool.get('ir.attachment')
1260         if not fil_obj:
1261             dbro = doc_obj.browse(cr, self.context.uid, self.file_id, context=self.context.context)
1262         else:
1263             dbro = fil_obj
1264             assert dbro.id == self.file_id, "%s != %s for %r" % (dbro.id, self.file_id, self)
1265
1266         if not dbro:
1267             raise IndexError("Cannot locate doc %d", self.file_id)
1268
1269         if (not self.parent):
1270             # there *must* be a parent node for this one
1271             self.parent = self.context.get_dir_node(cr, dbro.parent_id)
1272             assert self.parent
1273         
1274         ret = {}
1275         if ndir_node and self.parent != ndir_node:
1276             if not (isinstance(self.parent, node_dir) and isinstance(ndir_node, node_dir)):
1277                 logger.debug('Cannot move file %r from %r to %r', self, self.parent, ndir_node)
1278                 raise NotImplementedError('Cannot move files between dynamic folders')
1279
1280             if not ndir_obj:
1281                 ndir_obj = self.context._dirobj.browse(cr, self.context.uid, \
1282                         ndir_node.dir_id, context=self.context.context)
1283
1284             assert ndir_obj.id == ndir_node.dir_id
1285
1286             stobj = self.context._dirobj.pool.get('document.storage')
1287             r2 = stobj.simple_move(cr, self.context.uid, self, ndir_obj, \
1288                         context=self.context.context)
1289             ret.update(r2)
1290
1291         if new_name and (new_name != dbro.name):
1292             if len(ret):
1293                 raise NotImplementedError("Cannot rename and move") # TODO
1294             stobj = self.context._dirobj.pool.get('document.storage')
1295             r2 = stobj.simple_rename(cr, self.context.uid, self, new_name, self.context.context)
1296             ret.update(r2)
1297
1298         del dbro
1299
1300         if not in_write:
1301             # We have to update the data ourselves
1302             if ret:
1303                 ctx = self.context.context.copy()
1304                 ctx['__from_node'] = True
1305                 doc_obj.write(cr, self.context.uid, [self.file_id,], ret, ctx )
1306             ret = True
1307
1308         return ret
1309
1310 class node_content(node_class):
1311     our_type = 'content'
1312     def __init__(self, path, parent, context, cnt, dctx = None, act_id=None):
1313         super(node_content,self).__init__(path, parent,context)
1314         self.cnt_id = cnt.id
1315         self.create_date = False
1316         self.write_date = False
1317         self.content_length = False
1318         self.unixperms = 0640
1319         if parent:
1320             self.uidperms = parent.uidperms & 14
1321             self.uuser = parent.uuser
1322             self.ugroup = parent.ugroup
1323         
1324         self.extension = cnt.extension
1325         self.report_id = cnt.report_id and cnt.report_id.id
1326         #self.mimetype = cnt.extension.
1327         self.displayname = path
1328         if dctx:
1329            self.dctx.update(dctx)
1330         self.act_id = act_id
1331
1332     def fill_fields(self, cr, dctx = None):
1333         """ Try to read the object and fill missing fields, like mimetype,
1334             dates etc.
1335             This function must be different from the constructor, because
1336             it uses the db cursor.
1337         """
1338
1339         cr.execute('SELECT DISTINCT mimetype FROM document_directory_content_type WHERE active AND code = %s;',
1340                 (self.extension,))
1341         res = cr.fetchall()
1342         if res and res[0][0]:
1343             self.mimetype = str(res[0][0])
1344
1345
1346     def get_data(self, cr, fil_obj = None):
1347         cntobj = self.context._dirobj.pool.get('document.directory.content')
1348         if not self.check_perms(4):
1349             raise IOError(errno.EPERM, "Permission denied")
1350
1351         ctx = self.context.context.copy()
1352         ctx.update(self.dctx)
1353         data = cntobj.process_read(cr, self.context.uid, self, ctx)
1354         if data:
1355             self.content_length = len(data)
1356         return data
1357
1358     def open_data(self, cr, mode):
1359         if mode.endswith('b'):
1360             mode = mode[:-1]
1361         if mode in ('r', 'w'):
1362             cperms = mode[:1]
1363         elif mode in ('r+', 'w+'):
1364             cperms = 'rw'
1365         else:
1366             raise IOError(errno.EINVAL, "Cannot open at mode %s" % mode)
1367         
1368         if not self.check_perms(cperms):
1369             raise IOError(errno.EPERM, "Permission denied")
1370
1371         ctx = self.context.context.copy()
1372         ctx.update(self.dctx)
1373         
1374         return nodefd_content(self, cr, mode, ctx)
1375
1376     def get_data_len(self, cr, fil_obj = None):
1377         # FIXME : here, we actually generate the content twice!!
1378         # we should have cached the generated content, but it is
1379         # not advisable to do keep it in memory, until we have a cache
1380         # expiration logic.
1381         if not self.content_length:
1382             self.get_data(cr,fil_obj)
1383         return self.content_length
1384
1385     def set_data(self, cr, data, fil_obj = None):
1386         cntobj = self.context._dirobj.pool.get('document.directory.content')
1387         if not self.check_perms(2):
1388             raise IOError(errno.EPERM, "Permission denied")
1389
1390         ctx = self.context.context.copy()
1391         ctx.update(self.dctx)
1392         return cntobj.process_write(cr, self.context.uid, self, data, ctx)
1393
1394     def _get_ttag(self,cr):
1395         return 'cnt-%d%s' % (self.cnt_id,(self.act_id and ('-' + str(self.act_id))) or '')
1396
1397     def get_dav_resourcetype(self, cr):
1398         return ''
1399
1400 class nodefd_content(StringIO, node_descriptor):
1401     """ A descriptor to content nodes
1402     """
1403     def __init__(self, parent, cr, mode, ctx):
1404         node_descriptor.__init__(self, parent)
1405         self._context=ctx
1406
1407         if mode in ('r', 'r+'):
1408             cntobj = parent.context._dirobj.pool.get('document.directory.content')
1409             data = cntobj.process_read(cr, parent.context.uid, parent, ctx)
1410             if data:
1411                 parent.content_length = len(data)
1412             StringIO.__init__(self, data)
1413         elif mode in ('w', 'w+'):
1414             StringIO.__init__(self, None)
1415             # at write, we start at 0 (= overwrite), but have the original
1416             # data available, in case of a seek()
1417         elif mode == 'a':
1418             StringIO.__init__(self, None)
1419         else:
1420             logging.getLogger('document.content').error("Incorrect mode %s specified", mode)
1421             raise IOError(errno.EINVAL, "Invalid file mode")
1422         self.mode = mode
1423
1424     def close(self):
1425         # we now open a *separate* cursor, to update the data.
1426         # FIXME: this may be improved, for concurrency handling
1427         if self.mode == 'r':
1428             StringIO.close(self)
1429             return
1430
1431         par = self._get_parent()
1432         uid = par.context.uid
1433         cr = pooler.get_db(par.context.dbname).cursor()
1434         try:
1435             if self.mode in ('w', 'w+', 'r+'):
1436                 data = self.getvalue()
1437                 cntobj = par.context._dirobj.pool.get('document.directory.content')
1438                 cntobj.process_write(cr, uid, par, data, par.context.context)
1439             elif self.mode == 'a':
1440                 raise NotImplementedError
1441             cr.commit()
1442         except Exception:
1443             logging.getLogger('document.content').exception('Cannot update db content #%d for close:', par.cnt_id)
1444             raise
1445         finally:
1446             cr.close()
1447         StringIO.close(self)
1448
1449 #eof