Document: set the uid as a part of the node_context context.
[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         raise NotImplementedError(repr(self))
370     
371     def create_child_collection(self, cr, objname):
372         """ Create a child collection (directory) under self
373         """
374         raise NotImplementedError(repr(self))
375
376     def rm(self, cr):
377         raise NotImplementedError(repr(self))
378
379     def rmcol(self, cr):
380         raise NotImplementedError(repr(self))
381
382     def get_domain(self, cr, filters):
383         # TODO Document
384         return []
385
386     def check_perms(self, perms):
387         """ Check the permissions of the current node.
388         
389         @param perms either an integers of the bits to check, or
390                 a string with the permission letters
391
392         Permissions of nodes are (in a unix way):
393         1, x : allow descend into dir
394         2, w : allow write into file, or modification to dir
395         4, r : allow read of file, or listing of dir contents
396         8, u : allow remove (unlink)
397         """
398         
399         if isinstance(perms, str):
400             pe2 = 0
401             chars = { 'x': 1, 'w': 2, 'r': 4, 'u': 8 }
402             for c in perms:
403                 pe2 = pe2 | chars[c]
404             perms = pe2
405         elif isinstance(perms, int):
406             if perms < 0 or perms > 15:
407                 raise ValueError("Invalid permission bits")
408         else:
409             raise ValueError("Invalid permission attribute")
410         
411         return ((self.uidperms & perms) == perms)
412
413 class node_database(node_class):
414     """ A node representing the database directory
415
416     """
417     our_type = 'database'
418     def __init__(self, path=[], parent=False, context=None):
419         super(node_database,self).__init__(path, parent, context)
420         self.unixperms = 040750
421         self.uidperms = 5
422
423     def children(self, cr, domain=None):
424         res = self._child_get(cr, domain=domain) + self._file_get(cr)
425         return res
426
427     def child(self, cr, name, domain=None):
428         res = self._child_get(cr, name, domain=None)
429         if res:
430             return res[0]
431         res = self._file_get(cr,name)
432         if res:
433             return res[0]
434         return None
435
436     def _child_get(self, cr, name=False, parent_id=False, domain=None):
437         dirobj = self.context._dirobj
438         uid = self.context.uid
439         ctx = self.context.context.copy()
440         ctx.update(self.dctx)
441         where = [('parent_id','=',parent_id)]
442         if name:
443             where.append(('name','=',name))
444             is_allowed = self.check_perms(1)
445         else:
446             is_allowed = self.check_perms(5)
447         
448         if not is_allowed:
449             raise IOError(errno.EPERM, "Permission into directory denied")
450
451         if not domain:
452             domain = []
453
454         where2 = where + domain + ['|', ('type', '=', 'directory'), \
455                     '&', ('type', '=', 'ressource'), ('ressource_parent_type_id','=',False)]
456         ids = dirobj.search(cr, uid, where2, context=ctx)
457         res = []
458         for dirr in dirobj.browse(cr, uid, ids, context=ctx):
459             klass = dirr.get_node_class(dirr, context=ctx)
460             res.append(klass(dirr.name, self, self.context,dirr))
461
462         fil_obj = dirobj.pool.get('ir.attachment')
463         ids = fil_obj.search(cr, uid, where, context=ctx)
464         if ids:
465             for fil in fil_obj.browse(cr, uid, ids, context=ctx):
466                 klass = self.context.node_file_class
467                 res.append(klass(fil.name, self, self.context, fil))
468         return res
469
470     def _file_get(self,cr, nodename=False):
471         res = []
472         return res
473
474     def _get_ttag(self,cr):
475         return 'db-%s' % cr.dbname
476
477 def mkdosname(company_name, default='noname'):
478     """ convert a string to a dos-like name"""
479     if not company_name:
480         return default
481     badchars = ' !@#$%^`~*()+={}[];:\'"/?.<>'
482     n = ''
483     for c in company_name[:8]:
484         n += (c in badchars and '_') or c
485     return n
486     
487
488 class node_dir(node_database):
489     our_type = 'collection'
490     def __init__(self, path, parent, context, dirr, dctx=None):
491         super(node_dir,self).__init__(path, parent,context)
492         self.dir_id = dirr and dirr.id or False
493         #todo: more info from dirr
494         self.mimetype = 'application/x-directory'
495             # 'httpd/unix-directory'
496         self.create_date = dirr and dirr.create_date or False
497         self.domain = dirr and dirr.domain or []
498         self.res_model = dirr and dirr.ressource_type_id and dirr.ressource_type_id.model or False
499         # TODO: the write date should be MAX(file.write)..
500         self.write_date = dirr and (dirr.write_date or dirr.create_date) or False
501         self.content_length = 0
502         self.unixperms = 040750
503         try:
504             self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
505         except Exception:
506             self.uuser = 'nobody'
507         self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
508         self.uidperms = dirr.get_dir_permissions()
509         if dctx:
510             self.dctx.update(dctx)
511         dc2 = self.context.context
512         dc2.update(self.dctx)
513         dc2['dir_id'] = self.dir_id
514         self.displayname = dirr and dirr.name or False
515         if dirr and dirr.dctx_ids:
516             for dfld in dirr.dctx_ids:
517                 try:
518                     self.dctx['dctx_' + dfld.field] = safe_eval(dfld.expr,dc2)
519                 except Exception:
520                     print "Cannot eval %s" % dfld.expr
521                     print e
522                     pass
523
524     def __eq__(self, other):
525         if type(self) != type(other):
526             return False
527         if not self.context == other.context:
528             return False
529         # Two directory nodes, for the same document.directory, may have a
530         # different context! (dynamic folders)
531         if self.dctx != other.dctx:
532             return False
533         return self.dir_id == other.dir_id
534
535     def get_data(self, cr):
536         #res = ''
537         #for child in self.children(cr):
538         #    res += child.get_data(cr)
539         return None
540
541     def _file_get(self, cr, nodename=False):
542         res = super(node_dir,self)._file_get(cr, nodename)
543         
544         is_allowed = self.check_perms(nodename and 1 or 5)
545         if not is_allowed:
546             raise IOError(errno.EPERM, "Permission into directory denied")
547
548         cntobj = self.context._dirobj.pool.get('document.directory.content')
549         uid = self.context.uid
550         ctx = self.context.context.copy()
551         ctx.update(self.dctx)
552         where = [('directory_id','=',self.dir_id) ]
553         ids = cntobj.search(cr, uid, where, context=ctx)
554         for content in cntobj.browse(cr, uid, ids, context=ctx):
555             res3 = cntobj._file_get(cr, self, nodename, content)
556             if res3:
557                 res.extend(res3)
558
559         return res
560     
561     def _child_get(self, cr, name=None, domain=None):
562         return super(node_dir,self)._child_get(cr, name, self.dir_id, domain=domain)
563
564     def rmcol(self, cr):
565         uid = self.context.uid
566         directory = self.context._dirobj.browse(cr, uid, self.dir_id)
567         res = False
568         if not directory:
569             raise OSError(2, 'Not such file or directory.')
570         if not self.check_perms('u'):
571             raise IOError(errno.EPERM,"Permission denied")
572
573         if directory._table_name=='document.directory':
574             if self.children(cr):
575                 raise OSError(39, 'Directory not empty.')
576             res = self.context._dirobj.unlink(cr, uid, [directory.id])
577         else:
578             raise OSError(1, 'Operation not permited.')
579         return res
580
581     def create_child_collection(self, cr, objname):
582         object2 = False
583         if not self.check_perms(2):
584             raise IOError(errno.EPERM,"Permission denied")
585
586         dirobj = self.context._dirobj
587         uid = self.context.uid
588         ctx = self.context.context.copy()
589         ctx.update(self.dctx)
590         obj = dirobj.browse(cr, uid, self.dir_id)
591         if obj and (obj.type == 'ressource') and not object2:
592             raise OSError(1, 'Operation not permited.')
593
594         #objname = uri2[-1]
595         val = {
596                 'name': objname,
597                 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
598                 'ressource_id': object2 and object2.id or False,
599                 'parent_id' : obj and obj.id or False
600         }
601
602         return dirobj.create(cr, uid, val)
603
604
605     def create_child(self, cr, path, data=None):
606         """ API function to create a child file object and node
607             Return the node_* created
608         """
609         if not self.check_perms(2):
610             raise IOError(errno.EPERM,"Permission denied")
611
612         dirobj = self.context._dirobj
613         uid = self.context.uid
614         ctx = self.context.context.copy()
615         ctx.update(self.dctx)
616         fil_obj=dirobj.pool.get('ir.attachment')
617         val = {
618             'name': path,
619             'datas_fname': path,
620             'parent_id': self.dir_id,
621             # Datas are not set here
622         }
623
624         fil_id = fil_obj.create(cr, uid, val, context=ctx)
625         fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
626         fnode = node_file(path, self, self.context, fil)
627         if data is not None:
628             fnode.set_data(cr, data, fil)
629         return fnode
630
631     def _get_ttag(self,cr):
632         return 'dir-%d' % self.dir_id
633
634     def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
635         """ Move directory. This operation is simple, since the present node is
636         only used for static, simple directories.
637             Note /may/ be called with ndir_node = None, to rename the document root.
638         """
639         if ndir_node and (ndir_node.context != self.context):
640             raise NotImplementedError("Cannot move directories between contexts")
641
642         if (not self.check_perms('u')) or (not ndir_node.check_perms('w')):
643             raise IOError(errno.EPERM,"Permission denied")
644
645         dir_obj = self.context._dirobj
646         if not fil_obj:
647             dbro = dir_obj.browse(cr, self.context.uid, self.dir_id, context=self.context.context)
648         else:
649             dbro = dir_obj
650             assert dbro.id == self.dir_id
651
652         if not dbro:
653             raise IndexError("Cannot locate dir %d", self.dir_id)
654
655         if (not self.parent) and ndir_node:
656             if not dbro.parent_id:
657                 raise IOError(errno.EPERM, "Cannot move the root directory!")
658             self.parent = self.context.get_dir_node(cr, dbro.parent_id)
659             assert self.parent
660
661         if self.parent != ndir_node:
662             logger.debug('Cannot move dir %r from %r to %r', self, self.parent, ndir_node)
663             raise NotImplementedError('Cannot move dir to another dir')
664
665         ret = {}
666         if new_name and (new_name != dbro.name):
667             if ndir_node.child(cr, new_name):
668                 raise IOError(errno.EEXIST, "Destination path already exists")
669             ret['name'] = new_name
670
671         del dbro
672
673         if not in_write:
674             # We have to update the data ourselves
675             if ret:
676                 ctx = self.context.context.copy()
677                 ctx['__from_node'] = True
678                 dir_obj.write(cr, self.context.uid, [self.dir_id,], ret, ctx)
679             ret = True
680
681         return ret
682
683 class node_res_dir(node_class):
684     """ A folder containing dynamic folders
685         A special sibling to node_dir, which does only contain dynamically
686         created folders foreach resource in the foreign model.
687         All folders should be of type node_res_obj and merely behave like
688         node_dirs (with limited domain).
689     """
690     our_type = 'collection'
691     res_obj_class = None
692     def __init__(self, path, parent, context, dirr, dctx=None ):
693         super(node_res_dir,self).__init__(path, parent, context)
694         self.dir_id = dirr.id
695         #todo: more info from dirr
696         self.mimetype = 'application/x-directory'
697                         # 'httpd/unix-directory'
698         self.create_date = dirr.create_date
699         # TODO: the write date should be MAX(file.write)..
700         self.write_date = dirr.write_date or dirr.create_date
701         self.content_length = 0
702         self.unixperms = 040750
703         try:
704             self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
705         except Exception:
706             self.uuser = 'nobody'
707         self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
708         self.uidperms = dirr.get_dir_permissions()
709         self.res_model = dirr.ressource_type_id and dirr.ressource_type_id.model or False
710         self.resm_id = dirr.ressource_id
711         self.res_find_all = dirr.resource_find_all
712         self.namefield = dirr.resource_field.name or 'name'
713         self.displayname = dirr.name
714         # Important: the domain is evaluated using the *parent* dctx!
715         self.domain = dirr.domain
716         self.ressource_tree = dirr.ressource_tree
717         # and then, we add our own vars in the dctx:
718         if dctx:
719             self.dctx.update(dctx)
720
721         # and then, we prepare a dctx dict, for deferred evaluation:
722         self.dctx_dict = {}
723         for dfld in dirr.dctx_ids:
724             self.dctx_dict['dctx_' + dfld.field] = dfld.expr
725
726     def __eq__(self, other):
727         if type(self) != type(other):
728             return False
729         if not self.context == other.context:
730             return False
731         # Two nodes, for the same document.directory, may have a
732         # different context! (dynamic folders)
733         if self.dctx != other.dctx:
734             return False
735         return self.dir_id == other.dir_id
736
737     def children(self, cr, domain=None):
738         return self._child_get(cr, domain=domain)
739
740     def child(self,cr, name, domain=None):
741         res = self._child_get(cr, name, domain=domain)
742         if res:
743             return res[0]
744         return None
745
746     def _child_get(self, cr, name = None, domain=None):
747         """ return virtual children of resource, based on the
748             foreign object.
749
750             Note that many objects use NULL for a name, so we should
751             better call the name_search(),name_get() set of methods
752         """
753         obj = self.context._dirobj.pool.get(self.res_model)
754         if not obj:
755             return []
756         dirobj = self.context._dirobj
757         uid = self.context.uid
758         ctx = self.context.context.copy()
759         ctx.update(self.dctx)
760         where = []
761         if self.domain:
762             app = safe_eval(self.domain, self.dctx)
763             if not app:
764                 pass
765             elif isinstance(app, list):
766                 where.extend(app)
767             elif isinstance(app, tuple):
768                 where.append(app)
769             else:
770                 raise RuntimeError("incorrect domain expr: %s" % self.domain)
771         if self.resm_id:
772             where.append(('id','=',self.resm_id))
773
774         if name:
775             where.append((self.namefield,'=',name))
776             is_allowed = self.check_perms(1)
777         else:
778             is_allowed = self.check_perms(5)
779
780         if not is_allowed:
781             raise IOError(errno.EPERM,"Permission denied")
782
783         # print "Where clause for %s" % self.res_model, where
784         if self.ressource_tree:
785             object2 = False
786             if self.resm_id:
787                 object2 = dirobj.pool.get(self.res_model).browse(cr, uid, self.resm_id) or False
788             if obj._parent_name in obj.fields_get(cr, uid):
789                 where.append((obj._parent_name,'=',object2 and object2.id or False))
790
791         resids = obj.search(cr, uid, where, context=ctx)
792         res = []
793         for bo in obj.browse(cr, uid, resids, context=ctx):
794             if not bo:
795                 continue
796             name = getattr(bo, self.namefield)
797             if not name:
798                 continue
799                 # Yes! we can't do better but skip nameless records.
800
801             res.append(self.res_obj_class(name, self.dir_id, self, self.context, self.res_model, bo))
802         return res
803
804     def _get_ttag(self,cr):
805         return 'rdir-%d' % self.dir_id
806
807 class node_res_obj(node_class):
808     """ A dynamically created folder.
809         A special sibling to node_dir, which does only contain dynamically
810         created folders foreach resource in the foreign model.
811         All folders should be of type node_res_obj and merely behave like
812         node_dirs (with limited domain).
813         """
814     our_type = 'collection'
815     def __init__(self, path, dir_id, parent, context, res_model, res_bo, res_id = None):
816         super(node_res_obj,self).__init__(path, parent,context)
817         assert parent
818         #todo: more info from dirr
819         self.dir_id = dir_id
820         self.mimetype = 'application/x-directory'
821                         # 'httpd/unix-directory'
822         self.create_date = parent.create_date
823         # TODO: the write date should be MAX(file.write)..
824         self.write_date = parent.write_date
825         self.content_length = 0
826         self.unixperms = 040750
827         self.uidperms = parent.uidperms & 15
828         self.uuser = parent.uuser
829         self.ugroup = parent.ugroup
830         self.res_model = res_model
831         self.domain = parent.domain
832         self.displayname = path
833         self.dctx_dict = parent.dctx_dict
834         self.res_find_all = parent.res_find_all
835         if res_bo:
836             self.res_id = res_bo.id
837             dc2 = self.context.context.copy()
838             dc2.update(self.dctx)
839             dc2['res_model'] = res_model
840             dc2['res_id'] = res_bo.id
841             dc2['this'] = res_bo
842             for fld,expr in self.dctx_dict.items():
843                 try:
844                     self.dctx[fld] = safe_eval(expr, dc2)
845                 except Exception:
846                     print "Cannot eval %s for %s" % (expr, fld)
847                     print e
848                     pass
849         else:
850             self.res_id = res_id
851
852     def __eq__(self, other):
853         if type(self) != type(other):
854             return False
855         if not self.context == other.context:
856             return False
857         if not self.res_model == other.res_model:
858             return False
859         if not self.res_id == other.res_id:
860             return False
861         if self.domain != other.domain:
862             return False
863         if self.res_find_all != other.res_find_all:
864             return False
865         if self.dctx != other.dctx:
866             return False
867         return self.dir_id == other.dir_id
868
869     def children(self, cr, domain=None):
870         return self._child_get(cr, domain=domain) + self._file_get(cr)
871
872     def child(self, cr, name, domain=None):
873         res = self._child_get(cr, name, domain=domain)
874         if res:
875             return res[0]
876         res = self._file_get(cr, name)
877         if res:
878             return res[0]
879         return None
880
881     def _file_get(self,cr, nodename=False):
882         res = []
883         is_allowed = self.check_perms((nodename and 1) or 5)
884         if not is_allowed:
885             raise IOError(errno.EPERM,"Permission denied")
886
887         cntobj = self.context._dirobj.pool.get('document.directory.content')
888         uid = self.context.uid
889         ctx = self.context.context.copy()
890         ctx.update(self.dctx)
891         where = [('directory_id','=',self.dir_id) ]
892         #if self.domain:
893         #    where.extend(self.domain)
894         # print "res_obj file_get clause", where
895         ids = cntobj.search(cr, uid, where, context=ctx)
896         for content in cntobj.browse(cr, uid, ids, context=ctx):
897             res3 = cntobj._file_get(cr, self, nodename, content, context=ctx)
898             if res3:
899                 res.extend(res3)
900
901         return res
902
903     def get_dav_props_DEPR(self, cr):
904         # Deprecated! (but document_ics must be cleaned, first)
905         res = {}
906         cntobj = self.context._dirobj.pool.get('document.directory.content')
907         uid = self.context.uid
908         ctx = self.context.context.copy()
909         ctx.update(self.dctx)
910         where = [('directory_id','=',self.dir_id) ]
911         ids = cntobj.search(cr, uid, where, context=ctx)
912         for content in cntobj.browse(cr, uid, ids, context=ctx):
913             if content.extension == '.ics': # FIXME: call the content class!
914                 res['http://groupdav.org/'] = ('resourcetype',)
915         return res
916
917     def get_dav_eprop_DEPR(self, cr, ns, prop):
918         # Deprecated!
919         if ns != 'http://groupdav.org/' or prop != 'resourcetype':
920             logger.warning("Who asked for %s:%s?" % (ns, prop))
921             return None
922         cntobj = self.context._dirobj.pool.get('document.directory.content')
923         uid = self.context.uid
924         ctx = self.context.context.copy()
925         ctx.update(self.dctx)
926         where = [('directory_id','=',self.dir_id) ]
927         ids = cntobj.search(cr,uid,where,context=ctx)
928         for content in cntobj.browse(cr, uid, ids, context=ctx):
929             # TODO: remove relic of GroupDAV
930             if content.extension == '.ics': # FIXME: call the content class!
931                 return ('vevent-collection','http://groupdav.org/')
932         return None
933
934     def _child_get(self, cr, name=None, domain=None):
935         dirobj = self.context._dirobj
936
937         is_allowed = self.check_perms((name and 1) or 5)
938         if not is_allowed:
939             raise IOError(errno.EPERM,"Permission denied")
940
941         uid = self.context.uid
942         ctx = self.context.context.copy()
943         ctx.update(self.dctx)
944         directory = dirobj.browse(cr, uid, self.dir_id)
945         obj = dirobj.pool.get(self.res_model)
946         where = []
947         res = []
948         if name:
949             where.append(('name','=',name))
950
951         # Directory Structure display in tree structure
952         if self.res_id and directory.ressource_tree:
953             where1 = []
954             if obj._parent_name in obj.fields_get(cr, uid):
955                 where1 = where + [(obj._parent_name, '=', self.res_id)]
956             resids = obj.search(cr, uid, where1, context=ctx)
957             for bo in obj.browse(cr, uid, resids, context=ctx):
958                 namefield = directory.resource_field.name or 'name'
959                 if not bo:
960                     continue
961                 res_name = getattr(bo, namefield)
962                 if not res_name:
963                     continue
964                 # TODO Revise
965                 klass = directory.get_node_class(directory, dynamic=True, context=ctx)
966                 res.append(klass(res_name, self.dir_id, self, self.context, self.res_model, res_bo = bo))
967
968
969         where2 = where + [('parent_id','=',self.dir_id) ]
970         ids = dirobj.search(cr, uid, where2, context=ctx)
971         for dirr in dirobj.browse(cr, uid, ids, context=ctx):
972             if dirr.type == 'directory':
973                 klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
974                 res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
975             elif dirr.type == 'ressource':
976                 # child resources can be controlled by properly set dctx
977                 klass = dirr.get_node_class(dirr, context=ctx)
978                 res.append(klass(dirr.name,self,self.context, dirr, {'active_id': self.res_id}))
979
980         fil_obj = dirobj.pool.get('ir.attachment')
981         if self.res_find_all:
982             where2 = where
983         where3 = where2  + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
984         # print "where clause for dir_obj", where2
985         ids = fil_obj.search(cr, uid, where3, context=ctx)
986         if ids:
987             for fil in fil_obj.browse(cr, uid, ids, context=ctx):
988                 klass = self.context.node_file_class
989                 res.append(klass(fil.name, self, self.context, fil))
990
991
992         # Get Child Ressource Directories
993         if directory.ressource_type_id and directory.ressource_type_id.id:
994             where4 = where + [('ressource_parent_type_id','=',directory.ressource_type_id.id)]
995             where5 = where4 + [('ressource_id','=',0)]
996             dirids = dirobj.search(cr,uid, where5)
997             where5 = where4 + [('ressource_id','=',self.res_id)]
998             dirids = dirids + dirobj.search(cr,uid, where5)
999             for dirr in dirobj.browse(cr, uid, dirids, context=ctx):
1000                 if dirr.type == 'directory' and not dirr.parent_id:
1001                     klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
1002                     res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
1003                 if dirr.type == 'ressource':
1004                     klass = dirr.get_node_class(dirr, context=ctx)
1005                     res.append(klass(dirr.name, self, self.context, dirr, {'active_id': self.res_id}))
1006         return res
1007
1008     def create_child_collection(self, cr, objname):
1009         dirobj = self.context._dirobj
1010         is_allowed = self.check_perms(2)
1011         if not is_allowed:
1012             raise IOError(errno.EPERM,"Permission denied")
1013
1014         uid = self.context.uid
1015         ctx = self.context.context.copy()
1016         ctx.update(self.dctx)
1017         res_obj = dirobj.pool.get(self.res_model)
1018
1019         object2 = res_obj.browse(cr, uid, self.res_id) or False
1020
1021         obj = dirobj.browse(cr, uid, self.dir_id)
1022         if obj and (obj.type == 'ressource') and not object2:
1023             raise OSError(1, 'Operation not permited.')
1024
1025
1026         val = {
1027                 'name': objname,
1028                 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
1029                 'ressource_id': object2 and object2.id or False,
1030                 'parent_id' : False
1031         }
1032         if (obj and (obj.type in ('directory'))) or not object2:
1033             val['parent_id'] =  obj and obj.id or False
1034
1035         return dirobj.create(cr, uid, val)
1036
1037     def create_child(self, cr, path, data=None):
1038         """ API function to create a child file object and node
1039             Return the node_* created
1040         """
1041         is_allowed = self.check_perms(2)
1042         if not is_allowed:
1043             raise IOError(errno.EPERM,"Permission denied")
1044
1045         dirobj = self.context._dirobj
1046         uid = self.context.uid
1047         ctx = self.context.context.copy()
1048         ctx.update(self.dctx)
1049         fil_obj=dirobj.pool.get('ir.attachment')
1050         val = {
1051             'name': path,
1052             'datas_fname': path,
1053             'res_model': self.res_model,
1054             'res_id': self.res_id,
1055             # Datas are not set here
1056         }
1057         if not self.res_find_all:
1058             val['parent_id'] = self.dir_id
1059
1060         fil_id = fil_obj.create(cr, uid, val, context=ctx)
1061         fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
1062         klass = self.context.node_file_class
1063         fnode = klass(path, self, self.context, fil)
1064         if data is not None:
1065             fnode.set_data(cr, data, fil)
1066         return fnode
1067
1068     def _get_ttag(self,cr):
1069         return 'rodir-%d-%d' % (self.dir_id, self.res_id)
1070
1071 node_res_dir.res_obj_class = node_res_obj
1072
1073 class node_file(node_class):
1074     our_type = 'file'
1075     def __init__(self, path, parent, context, fil):
1076         super(node_file,self).__init__(path, parent,context)
1077         self.file_id = fil.id
1078         #todo: more info from ir_attachment
1079         if fil.file_type and '/' in fil.file_type:
1080             self.mimetype = str(fil.file_type)
1081         self.create_date = fil.create_date
1082         self.write_date = fil.write_date or fil.create_date
1083         self.content_length = fil.file_size
1084         self.displayname = fil.name
1085         
1086         self.uidperms = 14
1087         if parent:
1088             if not parent.check_perms('x'):
1089                 self.uidperms = 0
1090             elif not parent.check_perms('w'):
1091                 self.uidperms = 4
1092     
1093         try:
1094             self.uuser = (fil.user_id and fil.user_id.login) or 'nobody'
1095         except Exception:
1096             self.uuser = 'nobody'
1097         self.ugroup = mkdosname(fil.company_id and fil.company_id.name, default='nogroup')
1098
1099         # This only propagates the problem to get_data. Better
1100         # fix those files to point to the root dir.
1101         self.storage_id = None
1102         par = fil.parent_id
1103         while par:
1104             if par.storage_id and par.storage_id.id:
1105                 self.storage_id = par.storage_id.id
1106                 break
1107             par = par.parent_id
1108
1109     def __eq__(self, other):
1110         if type(self) != type(other):
1111             return False
1112         if not self.context == other.context:
1113             return False
1114         if self.dctx != other.dctx:
1115             return False
1116         return self.file_id == other.file_id
1117
1118
1119     def open_data(self, cr, mode):
1120         stor = self.storage_id
1121         assert stor, "No storage for file #%s" % self.file_id
1122         if not self.check_perms(4):
1123             raise IOError(errno.EPERM, "Permission denied")
1124
1125         # If storage is not set properly, we are just screwed here, don't
1126         # try to get it from default.
1127         stobj = self.context._dirobj.pool.get('document.storage')
1128         return stobj.get_file(cr, self.context.uid, stor, self, mode=mode, context=self.context.context)
1129
1130     def rm(self, cr):
1131         uid = self.context.uid
1132         if not self.check_perms(8):
1133             raise IOError(errno.EPERM, "Permission denied")
1134         document_obj = self.context._dirobj.pool.get('ir.attachment')
1135         if self.type in ('collection','database'):
1136             return False
1137         document = document_obj.browse(cr, uid, self.file_id, context=self.context.context)
1138         res = False
1139         if document and document._table_name == 'ir.attachment':
1140             res = document_obj.unlink(cr, uid, [document.id])
1141         return res
1142
1143     def fix_ppath(self, cr, fbro):
1144         """Sometimes we may init this w/o path, parent.
1145         This function fills the missing path from the file browse object
1146
1147         Note: this may be an expensive operation, do on demand. However,
1148         once caching is in, we might want to do that at init time and keep
1149         this object anyway
1150         """
1151         if self.path or self.parent:
1152             return
1153         assert fbro
1154         uid = self.context.uid
1155
1156         dirpath = []
1157         if fbro.parent_id:
1158             dirobj = self.context._dirobj.pool.get('document.directory')
1159             dirpath = dirobj.get_full_path(cr, uid, fbro.parent_id.id, context=self.context.context)
1160         if fbro.datas_fname:
1161             dirpath.append(fbro.datas_fname)
1162         else:
1163             dirpath.append(fbro.name)
1164
1165         if len(dirpath)>1:
1166             self.path = dirpath
1167         else:
1168             self.path = dirpath[0]
1169
1170     def get_data(self, cr, fil_obj = None):
1171         """ Retrieve the data for some file.
1172             fil_obj may optionally be specified, and should be a browse object
1173             for the file. This is useful when the caller has already initiated
1174             the browse object. """
1175         # this is where storage kicks in..
1176         stor = self.storage_id
1177         assert stor, "No storage for file #%s" % self.file_id
1178         if not self.check_perms(4):
1179             raise IOError(errno.EPERM, "Permission denied")
1180
1181         # If storage is not set properly, we are just screwed here, don't
1182         # try to get it from default.
1183         stobj = self.context._dirobj.pool.get('document.storage')
1184         return stobj.get_data(cr, self.context.uid,stor, self,self.context.context, fil_obj)
1185
1186     def get_data_len(self, cr, fil_obj = None):
1187         # TODO: verify with the storage object!
1188         bin_size = self.context.context.get('bin_size', False)
1189         if bin_size and not self.content_length:
1190             self.content_length = fil_obj.db_datas
1191         return self.content_length
1192
1193     def set_data(self, cr, data, fil_obj = None):
1194         """ Store data at some file.
1195             fil_obj may optionally be specified, and should be a browse object
1196             for the file. This is useful when the caller has already initiated
1197             the browse object. """
1198         # this is where storage kicks in..
1199         stor = self.storage_id
1200         assert stor, "No storage for file #%s" % self.file_id
1201         if not self.check_perms(2):
1202             raise IOError(errno.EPERM, "Permission denied")
1203
1204         stobj = self.context._dirobj.pool.get('document.storage')
1205         return stobj.set_data(cr, self.context.uid,stor, self, data, self.context.context, fil_obj)
1206
1207     def _get_ttag(self,cr):
1208         return 'file-%d' % self.file_id
1209
1210     def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
1211         if ndir_node and ndir_node.context != self.context:
1212             raise NotImplementedError("Cannot move files between contexts")
1213
1214         if (not self.check_perms(8)) and ndir_node.check_perms(2):
1215             raise IOError(errno.EPERM, "Permission denied")
1216
1217         doc_obj = self.context._dirobj.pool.get('ir.attachment')
1218         if not fil_obj:
1219             dbro = doc_obj.browse(cr, self.context.uid, self.file_id, context=self.context.context)
1220         else:
1221             dbro = fil_obj
1222             assert dbro.id == self.file_id, "%s != %s for %r" % (dbro.id, self.file_id, self)
1223
1224         if not dbro:
1225             raise IndexError("Cannot locate doc %d", self.file_id)
1226
1227         if (not self.parent):
1228             # there *must* be a parent node for this one
1229             self.parent = self.context.get_dir_node(cr, dbro.parent_id)
1230             assert self.parent
1231         
1232         ret = {}
1233         if ndir_node and self.parent != ndir_node:
1234             if not (isinstance(self.parent, node_dir) and isinstance(ndir_node, node_dir)):
1235                 logger.debug('Cannot move file %r from %r to %r', self, self.parent, ndir_node)
1236                 raise NotImplementedError('Cannot move files between dynamic folders')
1237
1238             if not ndir_obj:
1239                 ndir_obj = self.context._dirobj.browse(cr, self.context.uid, \
1240                         ndir_node.dir_id, context=self.context.context)
1241
1242             assert ndir_obj.id == ndir_node.dir_id
1243
1244             stobj = self.context._dirobj.pool.get('document.storage')
1245             r2 = stobj.simple_move(cr, self.context.uid, self, ndir_obj, \
1246                         context=self.context.context)
1247             ret.update(r2)
1248
1249         if new_name and (new_name != dbro.name):
1250             if len(ret):
1251                 raise NotImplementedError("Cannot rename and move") # TODO
1252             stobj = self.context._dirobj.pool.get('document.storage')
1253             r2 = stobj.simple_rename(cr, self.context.uid, self, new_name, self.context.context)
1254             ret.update(r2)
1255
1256         del dbro
1257
1258         if not in_write:
1259             # We have to update the data ourselves
1260             if ret:
1261                 ctx = self.context.context.copy()
1262                 ctx['__from_node'] = True
1263                 doc_obj.write(cr, self.context.uid, [self.file_id,], ret, ctx )
1264             ret = True
1265
1266         return ret
1267
1268 class node_content(node_class):
1269     our_type = 'content'
1270     def __init__(self, path, parent, context, cnt, dctx = None, act_id=None):
1271         super(node_content,self).__init__(path, parent,context)
1272         self.cnt_id = cnt.id
1273         self.create_date = False
1274         self.write_date = False
1275         self.content_length = False
1276         self.unixperms = 0640
1277         if parent:
1278             self.uidperms = parent.uidperms & 14
1279             self.uuser = parent.uuser
1280             self.ugroup = parent.ugroup
1281         
1282         self.extension = cnt.extension
1283         self.report_id = cnt.report_id and cnt.report_id.id
1284         #self.mimetype = cnt.extension.
1285         self.displayname = path
1286         if dctx:
1287            self.dctx.update(dctx)
1288         self.act_id = act_id
1289
1290     def fill_fields(self, cr, dctx = None):
1291         """ Try to read the object and fill missing fields, like mimetype,
1292             dates etc.
1293             This function must be different from the constructor, because
1294             it uses the db cursor.
1295         """
1296
1297         cr.execute('SELECT DISTINCT mimetype FROM document_directory_content_type WHERE active AND code = %s;',
1298                 (self.extension,))
1299         res = cr.fetchall()
1300         if res and res[0][0]:
1301             self.mimetype = str(res[0][0])
1302
1303
1304     def get_data(self, cr, fil_obj = None):
1305         cntobj = self.context._dirobj.pool.get('document.directory.content')
1306         if not self.check_perms(4):
1307             raise IOError(errno.EPERM, "Permission denied")
1308
1309         ctx = self.context.context.copy()
1310         ctx.update(self.dctx)
1311         data = cntobj.process_read(cr, self.context.uid, self, ctx)
1312         if data:
1313             self.content_length = len(data)
1314         return data
1315
1316     def open_data(self, cr, mode):
1317         if mode.endswith('b'):
1318             mode = mode[:-1]
1319         if mode in ('r', 'w'):
1320             cperms = mode[:1]
1321         elif mode in ('r+', 'w+'):
1322             cperms = 'rw'
1323         else:
1324             raise IOError(errno.EINVAL, "Cannot open at mode %s" % mode)
1325         
1326         if not self.check_perms(cperms):
1327             raise IOError(errno.EPERM, "Permission denied")
1328
1329         ctx = self.context.context.copy()
1330         ctx.update(self.dctx)
1331         
1332         return nodefd_content(self, cr, mode, ctx)
1333
1334     def get_data_len(self, cr, fil_obj = None):
1335         # FIXME : here, we actually generate the content twice!!
1336         # we should have cached the generated content, but it is
1337         # not advisable to do keep it in memory, until we have a cache
1338         # expiration logic.
1339         if not self.content_length:
1340             self.get_data(cr,fil_obj)
1341         return self.content_length
1342
1343     def set_data(self, cr, data, fil_obj = None):
1344         cntobj = self.context._dirobj.pool.get('document.directory.content')
1345         if not self.check_perms(2):
1346             raise IOError(errno.EPERM, "Permission denied")
1347
1348         ctx = self.context.context.copy()
1349         ctx.update(self.dctx)
1350         return cntobj.process_write(cr, self.context.uid, self, data, ctx)
1351
1352     def _get_ttag(self,cr):
1353         return 'cnt-%d%s' % (self.cnt_id,(self.act_id and ('-' + str(self.act_id))) or '')
1354
1355     def get_dav_resourcetype(self, cr):
1356         return ''
1357
1358 class nodefd_content(StringIO, node_descriptor):
1359     """ A descriptor to content nodes
1360     """
1361     def __init__(self, parent, cr, mode, ctx):
1362         node_descriptor.__init__(self, parent)
1363         self._context=ctx
1364
1365         if mode in ('r', 'r+'):
1366             cntobj = parent.context._dirobj.pool.get('document.directory.content')
1367             data = cntobj.process_read(cr, parent.context.uid, parent, ctx)
1368             if data:
1369                 parent.content_length = len(data)
1370             StringIO.__init__(self, data)
1371         elif mode in ('w', 'w+'):
1372             StringIO.__init__(self, None)
1373             # at write, we start at 0 (= overwrite), but have the original
1374             # data available, in case of a seek()
1375         elif mode == 'a':
1376             StringIO.__init__(self, None)
1377         else:
1378             logging.getLogger('document.content').error("Incorrect mode %s specified", mode)
1379             raise IOError(errno.EINVAL, "Invalid file mode")
1380         self.mode = mode
1381
1382     def close(self):
1383         # we now open a *separate* cursor, to update the data.
1384         # FIXME: this may be improved, for concurrency handling
1385         if self.mode == 'r':
1386             StringIO.close(self)
1387             return
1388
1389         par = self._get_parent()
1390         uid = par.context.uid
1391         cr = pooler.get_db(par.context.dbname).cursor()
1392         try:
1393             if self.mode in ('w', 'w+', 'r+'):
1394                 data = self.getvalue()
1395                 cntobj = par.context._dirobj.pool.get('document.directory.content')
1396                 cntobj.process_write(cr, uid, par, data, par.context.context)
1397             elif self.mode == 'a':
1398                 raise NotImplementedError
1399             cr.commit()
1400         except Exception:
1401             logging.getLogger('document.content').exception('Cannot update db content #%d for close:', par.cnt_id)
1402             raise
1403         finally:
1404             cr.close()
1405         StringIO.close(self)
1406
1407 #eof