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