[MERGE] Merge crm and project improvement
[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             # The =like character will match underscores against any characters
841             # including the special ones that couldn't exist in a FTP/DAV request
842             where.append((self.namefield,'=like',name.replace('\\','\\\\')))
843             is_allowed = self.check_perms(1)
844         else:
845             is_allowed = self.check_perms(5)
846
847         if not is_allowed:
848             raise IOError(errno.EPERM,"Permission denied")
849
850         # print "Where clause for %s" % self.res_model, where
851         if self.ressource_tree:
852             object2 = False
853             if self.resm_id:
854                 object2 = dirobj.pool.get(self.res_model).browse(cr, uid, self.resm_id) or False
855             if obj._parent_name in obj.fields_get(cr, uid):
856                 where.append((obj._parent_name,'=',object2 and object2.id or False))
857
858         resids = obj.search(cr, uid, where, context=ctx)
859         res = []
860         for bo in obj.browse(cr, uid, resids, context=ctx):
861             if not bo:
862                 continue
863             res_name = getattr(bo, self.namefield)
864             if not res_name:
865                 continue
866                 # Yes! we can't do better but skip nameless records.
867             
868             # Escape the name for characters not supported in filenames
869             res_name = res_name.replace('/','_') # any other weird char?
870             
871             if name and (res_name != name):
872                 # we have matched _ to any character, but we only meant to match
873                 # the special ones.
874                 # Eg. 'a_c' will find 'abc', 'a/c', 'a_c', may only
875                 # return 'a/c' and 'a_c'
876                 continue
877
878             res.append(self.res_obj_class(res_name, self.dir_id, self, self.context, self.res_model, bo))
879         return res
880
881     def _get_ttag(self,cr):
882         return 'rdir-%d' % self.dir_id
883
884 class node_res_obj(node_class):
885     """ A dynamically created folder.
886         A special sibling to node_dir, which does only contain dynamically
887         created folders foreach resource in the foreign model.
888         All folders should be of type node_res_obj and merely behave like
889         node_dirs (with limited domain).
890         """
891     our_type = 'collection'
892     def __init__(self, path, dir_id, parent, context, res_model, res_bo, res_id = None):
893         super(node_res_obj,self).__init__(path, parent,context)
894         assert parent
895         #todo: more info from dirr
896         self.dir_id = dir_id
897         self.mimetype = 'application/x-directory'
898                         # 'httpd/unix-directory'
899         self.create_date = parent.create_date
900         # TODO: the write date should be MAX(file.write)..
901         self.write_date = parent.write_date
902         self.content_length = 0
903         self.uidperms = parent.uidperms & 15
904         self.unixperms = 040000 | _uid2unixperms(self.uidperms, True)
905         self.uuser = parent.uuser
906         self.ugroup = parent.ugroup
907         self.res_model = res_model
908         self.domain = parent.domain
909         self.displayname = path
910         self.dctx_dict = parent.dctx_dict
911         if isinstance(parent, node_res_dir):
912             self.res_find_all = parent.res_find_all
913         else:
914             self.res_find_all = False
915         if res_bo:
916             self.res_id = res_bo.id
917             dc2 = self.context.context.copy()
918             dc2.update(self.dctx)
919             dc2['res_model'] = res_model
920             dc2['res_id'] = res_bo.id
921             dc2['this'] = res_bo
922             for fld,expr in self.dctx_dict.items():
923                 try:
924                     self.dctx[fld] = safe_eval(expr, dc2)
925                 except Exception,e:
926                     print "Cannot eval %s for %s" % (expr, fld)
927                     print e
928                     pass
929         else:
930             self.res_id = res_id
931
932     def __eq__(self, other):
933         if type(self) != type(other):
934             return False
935         if not self.context == other.context:
936             return False
937         if not self.res_model == other.res_model:
938             return False
939         if not self.res_id == other.res_id:
940             return False
941         if self.domain != other.domain:
942             return False
943         if self.res_find_all != other.res_find_all:
944             return False
945         if self.dctx != other.dctx:
946             return False
947         return self.dir_id == other.dir_id
948
949     def children(self, cr, domain=None):
950         return self._child_get(cr, domain=domain) + self._file_get(cr)
951
952     def child(self, cr, name, domain=None):
953         res = self._child_get(cr, name, domain=domain)
954         if res:
955             return res[0]
956         res = self._file_get(cr, name)
957         if res:
958             return res[0]
959         return None
960
961     def _file_get(self,cr, nodename=False):
962         res = []
963         is_allowed = self.check_perms((nodename and 1) or 5)
964         if not is_allowed:
965             raise IOError(errno.EPERM,"Permission denied")
966
967         cntobj = self.context._dirobj.pool.get('document.directory.content')
968         uid = self.context.uid
969         ctx = self.context.context.copy()
970         ctx.update(self.dctx)
971         where = [('directory_id','=',self.dir_id) ]
972         #if self.domain:
973         #    where.extend(self.domain)
974         # print "res_obj file_get clause", where
975         ids = cntobj.search(cr, uid, where, context=ctx)
976         for content in cntobj.browse(cr, uid, ids, context=ctx):
977             res3 = cntobj._file_get(cr, self, nodename, content, context=ctx)
978             if res3:
979                 res.extend(res3)
980
981         return res
982
983     def get_dav_props_DEPR(self, cr):
984         # Deprecated! (but document_ics must be cleaned, first)
985         res = {}
986         cntobj = self.context._dirobj.pool.get('document.directory.content')
987         uid = self.context.uid
988         ctx = self.context.context.copy()
989         ctx.update(self.dctx)
990         where = [('directory_id','=',self.dir_id) ]
991         ids = cntobj.search(cr, uid, where, context=ctx)
992         for content in cntobj.browse(cr, uid, ids, context=ctx):
993             if content.extension == '.ics': # FIXME: call the content class!
994                 res['http://groupdav.org/'] = ('resourcetype',)
995         return res
996
997     def get_dav_eprop_DEPR(self, cr, ns, prop):
998         # Deprecated!
999         if ns != 'http://groupdav.org/' or prop != 'resourcetype':
1000             logger.warning("Who asked for %s:%s?" % (ns, prop))
1001             return None
1002         cntobj = self.context._dirobj.pool.get('document.directory.content')
1003         uid = self.context.uid
1004         ctx = self.context.context.copy()
1005         ctx.update(self.dctx)
1006         where = [('directory_id','=',self.dir_id) ]
1007         ids = cntobj.search(cr,uid,where,context=ctx)
1008         for content in cntobj.browse(cr, uid, ids, context=ctx):
1009             # TODO: remove relic of GroupDAV
1010             if content.extension == '.ics': # FIXME: call the content class!
1011                 return ('vevent-collection','http://groupdav.org/')
1012         return None
1013
1014     def _child_get(self, cr, name=None, domain=None):
1015         dirobj = self.context._dirobj
1016
1017         is_allowed = self.check_perms((name and 1) or 5)
1018         if not is_allowed:
1019             raise IOError(errno.EPERM,"Permission denied")
1020
1021         uid = self.context.uid
1022         ctx = self.context.context.copy()
1023         ctx.update(self.dctx)
1024         directory = dirobj.browse(cr, uid, self.dir_id)
1025         obj = dirobj.pool.get(self.res_model)
1026         where = []
1027         res = []
1028         if name:
1029             where.append(('name','=',name))
1030
1031         # Directory Structure display in tree structure
1032         if self.res_id and directory.ressource_tree:
1033             where1 = []
1034             if name:
1035                 where1.append(('name','=like',name.replace('\\','\\\\')))
1036             if obj._parent_name in obj.fields_get(cr, uid):
1037                 where1.append((obj._parent_name, '=', self.res_id))
1038             namefield = directory.resource_field.name or 'name'
1039             resids = obj.search(cr, uid, where1, context=ctx)
1040             for bo in obj.browse(cr, uid, resids, context=ctx):
1041                 if not bo:
1042                     continue
1043                 res_name = getattr(bo, namefield)
1044                 if not res_name:
1045                     continue
1046                 res_name = res_name.replace('/', '_')
1047                 if name and (res_name != name):
1048                     continue
1049                 # TODO Revise
1050                 klass = directory.get_node_class(directory, dynamic=True, context=ctx)
1051                 rnode = klass(res_name, dir_id=self.dir_id, parent=self, context=self.context,
1052                                 res_model=self.res_model, res_bo=bo)
1053                 rnode.res_find_all = self.res_find_all
1054                 res.append(rnode)
1055
1056
1057         where2 = where + [('parent_id','=',self.dir_id) ]
1058         ids = dirobj.search(cr, uid, where2, context=ctx)
1059         bo = obj.browse(cr, uid, self.res_id, context=ctx)
1060         
1061         for dirr in dirobj.browse(cr, uid, ids, context=ctx):
1062             if name and (name != dirr.name):
1063                 continue
1064             if dirr.type == 'directory':
1065                 klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
1066                 res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = bo, res_id = self.res_id))
1067             elif dirr.type == 'ressource':
1068                 # child resources can be controlled by properly set dctx
1069                 klass = dirr.get_node_class(dirr, context=ctx)
1070                 res.append(klass(dirr.name,self,self.context, dirr, {'active_id': self.res_id})) # bo?
1071
1072         fil_obj = dirobj.pool.get('ir.attachment')
1073         if self.res_find_all:
1074             where2 = where
1075         where3 = where2 + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
1076         # print "where clause for dir_obj", where3
1077         ids = fil_obj.search(cr, uid, where3, context=ctx)
1078         if ids:
1079             for fil in fil_obj.browse(cr, uid, ids, context=ctx):
1080                 klass = self.context.node_file_class
1081                 res.append(klass(fil.name, self, self.context, fil))
1082
1083
1084         # Get Child Ressource Directories
1085         if directory.ressource_type_id and directory.ressource_type_id.id:
1086             where4 = where + [('ressource_parent_type_id','=',directory.ressource_type_id.id)]
1087             where5 = where4 + ['|', ('ressource_id','=',0), ('ressource_id','=',self.res_id)]
1088             dirids = dirobj.search(cr,uid, where5)
1089             for dirr in dirobj.browse(cr, uid, dirids, context=ctx):
1090                 if dirr.type == 'directory' and not dirr.parent_id:
1091                     klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
1092                     rnode = klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = bo, res_id = self.res_id)
1093                     rnode.res_find_all = dirr.resource_find_all
1094                     res.append(rnode)
1095                 if dirr.type == 'ressource':
1096                     klass = dirr.get_node_class(dirr, context=ctx)
1097                     rnode = klass(dirr.name, self, self.context, dirr, {'active_id': self.res_id})
1098                     rnode.res_find_all = dirr.resource_find_all
1099                     res.append(rnode)
1100         return res
1101
1102     def create_child_collection(self, cr, objname):
1103         dirobj = self.context._dirobj
1104         is_allowed = self.check_perms(2)
1105         if not is_allowed:
1106             raise IOError(errno.EPERM,"Permission denied")
1107
1108         uid = self.context.uid
1109         ctx = self.context.context.copy()
1110         ctx.update(self.dctx)
1111         res_obj = dirobj.pool.get(self.res_model)
1112
1113         object2 = res_obj.browse(cr, uid, self.res_id) or False
1114
1115         obj = dirobj.browse(cr, uid, self.dir_id)
1116         if obj and (obj.type == 'ressource') and not object2:
1117             raise OSError(1, 'Operation not permited.')
1118
1119
1120         val = {
1121                 'name': objname,
1122                 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
1123                 'ressource_id': object2 and object2.id or False,
1124                 'parent_id' : False,
1125                 'resource_find_all': False,
1126         }
1127         if (obj and (obj.type in ('directory'))) or not object2:
1128             val['parent_id'] =  obj and obj.id or False
1129
1130         return dirobj.create(cr, uid, val)
1131
1132     def create_child(self, cr, path, data=None):
1133         """ API function to create a child file object and node
1134             Return the node_* created
1135         """
1136         is_allowed = self.check_perms(2)
1137         if not is_allowed:
1138             raise IOError(errno.EPERM,"Permission denied")
1139
1140         dirobj = self.context._dirobj
1141         uid = self.context.uid
1142         ctx = self.context.context.copy()
1143         ctx.update(self.dctx)
1144         fil_obj=dirobj.pool.get('ir.attachment')
1145         val = {
1146             'name': path,
1147             'datas_fname': path,
1148             'res_model': self.res_model,
1149             'res_id': self.res_id,
1150             # Datas are not set here
1151         }
1152         if not self.res_find_all:
1153             val['parent_id'] = self.dir_id
1154
1155         fil_id = fil_obj.create(cr, uid, val, context=ctx)
1156         fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
1157         klass = self.context.node_file_class
1158         fnode = klass(path, self, self.context, fil)
1159         if data is not None:
1160             fnode.set_data(cr, data, fil)
1161         return fnode
1162
1163     def _get_ttag(self,cr):
1164         return 'rodir-%d-%d' % (self.dir_id, self.res_id)
1165
1166 node_res_dir.res_obj_class = node_res_obj
1167
1168 class node_file(node_class):
1169     our_type = 'file'
1170     def __init__(self, path, parent, context, fil):
1171         super(node_file,self).__init__(path, parent,context)
1172         self.file_id = fil.id
1173         #todo: more info from ir_attachment
1174         if fil.file_type and '/' in fil.file_type:
1175             self.mimetype = str(fil.file_type)
1176         self.create_date = fil.create_date
1177         self.write_date = fil.write_date or fil.create_date
1178         self.content_length = fil.file_size
1179         self.displayname = fil.name
1180         
1181         self.uidperms = 14
1182         if parent:
1183             if not parent.check_perms('x'):
1184                 self.uidperms = 0
1185             elif not parent.check_perms('w'):
1186                 self.uidperms = 4
1187     
1188         try:
1189             self.uuser = (fil.user_id and fil.user_id.login) or 'nobody'
1190         except Exception:
1191             self.uuser = 'nobody'
1192         self.ugroup = mkdosname(fil.company_id and fil.company_id.name, default='nogroup')
1193
1194         # This only propagates the problem to get_data. Better
1195         # fix those files to point to the root dir.
1196         self.storage_id = None
1197         par = fil.parent_id
1198         while par:
1199             if par.storage_id and par.storage_id.id:
1200                 self.storage_id = par.storage_id.id
1201                 break
1202             par = par.parent_id
1203
1204     def __eq__(self, other):
1205         if type(self) != type(other):
1206             return False
1207         if not self.context == other.context:
1208             return False
1209         if self.dctx != other.dctx:
1210             return False
1211         return self.file_id == other.file_id
1212
1213
1214     def open_data(self, cr, mode):
1215         stor = self.storage_id
1216         assert stor, "No storage for file #%s" % self.file_id
1217         if not self.check_perms(4):
1218             raise IOError(errno.EPERM, "Permission denied")
1219
1220         # If storage is not set properly, we are just screwed here, don't
1221         # try to get it from default.
1222         stobj = self.context._dirobj.pool.get('document.storage')
1223         return stobj.get_file(cr, self.context.uid, stor, self, mode=mode, context=self.context.context)
1224
1225     def rm(self, cr):
1226         uid = self.context.uid
1227         if not self.check_perms(8):
1228             raise IOError(errno.EPERM, "Permission denied")
1229         document_obj = self.context._dirobj.pool.get('ir.attachment')
1230         if self.type in ('collection','database'):
1231             return False
1232         document = document_obj.browse(cr, uid, self.file_id, context=self.context.context)
1233         res = False
1234         if document and document._table_name == 'ir.attachment':
1235             res = document_obj.unlink(cr, uid, [document.id])
1236         return res
1237
1238     def fix_ppath(self, cr, fbro):
1239         """Sometimes we may init this w/o path, parent.
1240         This function fills the missing path from the file browse object
1241
1242         Note: this may be an expensive operation, do on demand. However,
1243         once caching is in, we might want to do that at init time and keep
1244         this object anyway
1245         """
1246         if self.path or self.parent:
1247             return
1248         assert fbro
1249         uid = self.context.uid
1250
1251         dirpath = []
1252         if fbro.parent_id:
1253             dirobj = self.context._dirobj.pool.get('document.directory')
1254             dirpath = dirobj.get_full_path(cr, uid, fbro.parent_id.id, context=self.context.context)
1255         if fbro.datas_fname:
1256             dirpath.append(fbro.datas_fname)
1257         else:
1258             dirpath.append(fbro.name)
1259
1260         if len(dirpath)>1:
1261             self.path = dirpath
1262         else:
1263             self.path = dirpath[0]
1264
1265     def get_data(self, cr, fil_obj = None):
1266         """ Retrieve the data for some file.
1267             fil_obj may optionally be specified, and should be a browse object
1268             for the file. This is useful when the caller has already initiated
1269             the browse object. """
1270         # this is where storage kicks in..
1271         stor = self.storage_id
1272         assert stor, "No storage for file #%s" % self.file_id
1273         if not self.check_perms(4):
1274             raise IOError(errno.EPERM, "Permission denied")
1275
1276         # If storage is not set properly, we are just screwed here, don't
1277         # try to get it from default.
1278         stobj = self.context._dirobj.pool.get('document.storage')
1279         return stobj.get_data(cr, self.context.uid,stor, self,self.context.context, fil_obj)
1280
1281     def get_data_len(self, cr, fil_obj = None):
1282         # TODO: verify with the storage object!
1283         bin_size = self.context.context.get('bin_size', False)
1284         if bin_size and not self.content_length:
1285             self.content_length = fil_obj.db_datas
1286         return self.content_length
1287
1288     def set_data(self, cr, data, fil_obj = None):
1289         """ Store data at some file.
1290             fil_obj may optionally be specified, and should be a browse object
1291             for the file. This is useful when the caller has already initiated
1292             the browse object. """
1293         # this is where storage kicks in..
1294         stor = self.storage_id
1295         assert stor, "No storage for file #%s" % self.file_id
1296         if not self.check_perms(2):
1297             raise IOError(errno.EPERM, "Permission denied")
1298
1299         stobj = self.context._dirobj.pool.get('document.storage')
1300         return stobj.set_data(cr, self.context.uid,stor, self, data, self.context.context, fil_obj)
1301
1302     def _get_ttag(self,cr):
1303         return 'file-%d' % self.file_id
1304
1305     def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
1306         if ndir_node and ndir_node.context != self.context:
1307             raise NotImplementedError("Cannot move files between contexts")
1308
1309         if (not self.check_perms(8)) and ndir_node.check_perms(2):
1310             raise IOError(errno.EPERM, "Permission denied")
1311
1312         doc_obj = self.context._dirobj.pool.get('ir.attachment')
1313         if not fil_obj:
1314             dbro = doc_obj.browse(cr, self.context.uid, self.file_id, context=self.context.context)
1315         else:
1316             dbro = fil_obj
1317             assert dbro.id == self.file_id, "%s != %s for %r" % (dbro.id, self.file_id, self)
1318
1319         if not dbro:
1320             raise IndexError("Cannot locate doc %d", self.file_id)
1321
1322         if (not self.parent):
1323             # there *must* be a parent node for this one
1324             self.parent = self.context.get_dir_node(cr, dbro.parent_id)
1325             assert self.parent
1326         
1327         ret = {}
1328         if ndir_node and self.parent != ndir_node:
1329             if not (isinstance(self.parent, node_dir) and isinstance(ndir_node, node_dir)):
1330                 logger.debug('Cannot move file %r from %r to %r', self, self.parent, ndir_node)
1331                 raise NotImplementedError('Cannot move files between dynamic folders')
1332
1333             if not ndir_obj:
1334                 ndir_obj = self.context._dirobj.browse(cr, self.context.uid, \
1335                         ndir_node.dir_id, context=self.context.context)
1336
1337             assert ndir_obj.id == ndir_node.dir_id
1338
1339             stobj = self.context._dirobj.pool.get('document.storage')
1340             r2 = stobj.simple_move(cr, self.context.uid, self, ndir_obj, \
1341                         context=self.context.context)
1342             ret.update(r2)
1343
1344         if new_name and (new_name != dbro.name):
1345             if len(ret):
1346                 raise NotImplementedError("Cannot rename and move") # TODO
1347             stobj = self.context._dirobj.pool.get('document.storage')
1348             r2 = stobj.simple_rename(cr, self.context.uid, self, new_name, self.context.context)
1349             ret.update(r2)
1350
1351         del dbro
1352
1353         if not in_write:
1354             # We have to update the data ourselves
1355             if ret:
1356                 ctx = self.context.context.copy()
1357                 ctx['__from_node'] = True
1358                 doc_obj.write(cr, self.context.uid, [self.file_id,], ret, ctx )
1359             ret = True
1360
1361         return ret
1362
1363 class node_content(node_class):
1364     our_type = 'content'
1365     def __init__(self, path, parent, context, cnt, dctx = None, act_id=None):
1366         super(node_content,self).__init__(path, parent,context)
1367         self.cnt_id = cnt.id
1368         self.create_date = False
1369         self.write_date = False
1370         self.content_length = False
1371         self.unixperms = 0640
1372         if parent:
1373             self.uidperms = parent.uidperms & 14
1374             self.uuser = parent.uuser
1375             self.ugroup = parent.ugroup
1376         
1377         self.extension = cnt.extension
1378         self.report_id = cnt.report_id and cnt.report_id.id
1379         #self.mimetype = cnt.extension.
1380         self.displayname = path
1381         if dctx:
1382            self.dctx.update(dctx)
1383         self.act_id = act_id
1384
1385     def fill_fields(self, cr, dctx = None):
1386         """ Try to read the object and fill missing fields, like mimetype,
1387             dates etc.
1388             This function must be different from the constructor, because
1389             it uses the db cursor.
1390         """
1391
1392         cr.execute('SELECT DISTINCT mimetype FROM document_directory_content_type WHERE active AND code = %s;',
1393                 (self.extension,))
1394         res = cr.fetchall()
1395         if res and res[0][0]:
1396             self.mimetype = str(res[0][0])
1397
1398
1399     def get_data(self, cr, fil_obj = None):
1400         cntobj = self.context._dirobj.pool.get('document.directory.content')
1401         if not self.check_perms(4):
1402             raise IOError(errno.EPERM, "Permission denied")
1403
1404         ctx = self.context.context.copy()
1405         ctx.update(self.dctx)
1406         data = cntobj.process_read(cr, self.context.uid, self, ctx)
1407         if data:
1408             self.content_length = len(data)
1409         return data
1410
1411     def open_data(self, cr, mode):
1412         if mode.endswith('b'):
1413             mode = mode[:-1]
1414         if mode in ('r', 'w'):
1415             cperms = mode[:1]
1416         elif mode in ('r+', 'w+'):
1417             cperms = 'rw'
1418         else:
1419             raise IOError(errno.EINVAL, "Cannot open at mode %s" % mode)
1420         
1421         if not self.check_perms(cperms):
1422             raise IOError(errno.EPERM, "Permission denied")
1423
1424         ctx = self.context.context.copy()
1425         ctx.update(self.dctx)
1426         
1427         return nodefd_content(self, cr, mode, ctx)
1428
1429     def get_data_len(self, cr, fil_obj = None):
1430         # FIXME : here, we actually generate the content twice!!
1431         # we should have cached the generated content, but it is
1432         # not advisable to do keep it in memory, until we have a cache
1433         # expiration logic.
1434         if not self.content_length:
1435             self.get_data(cr,fil_obj)
1436         return self.content_length
1437
1438     def set_data(self, cr, data, fil_obj = None):
1439         cntobj = self.context._dirobj.pool.get('document.directory.content')
1440         if not self.check_perms(2):
1441             raise IOError(errno.EPERM, "Permission denied")
1442
1443         ctx = self.context.context.copy()
1444         ctx.update(self.dctx)
1445         return cntobj.process_write(cr, self.context.uid, self, data, ctx)
1446
1447     def _get_ttag(self,cr):
1448         return 'cnt-%d%s' % (self.cnt_id,(self.act_id and ('-' + str(self.act_id))) or '')
1449
1450     def get_dav_resourcetype(self, cr):
1451         return ''
1452
1453 class nodefd_content(StringIO, node_descriptor):
1454     """ A descriptor to content nodes
1455     """
1456     def __init__(self, parent, cr, mode, ctx):
1457         node_descriptor.__init__(self, parent)
1458         self._context=ctx
1459         self._size = 0L
1460
1461         if mode in ('r', 'r+'):
1462             cntobj = parent.context._dirobj.pool.get('document.directory.content')
1463             data = cntobj.process_read(cr, parent.context.uid, parent, ctx)
1464             if data:
1465                 self._size = len(data)
1466                 parent.content_length = len(data)
1467             StringIO.__init__(self, data)
1468         elif mode in ('w', 'w+'):
1469             StringIO.__init__(self, None)
1470             # at write, we start at 0 (= overwrite), but have the original
1471             # data available, in case of a seek()
1472         elif mode == 'a':
1473             StringIO.__init__(self, None)
1474         else:
1475             logging.getLogger('document.content').error("Incorrect mode %s specified", mode)
1476             raise IOError(errno.EINVAL, "Invalid file mode")
1477         self.mode = mode
1478
1479     def size(self):
1480         return self._size
1481
1482     def close(self):
1483         # we now open a *separate* cursor, to update the data.
1484         # FIXME: this may be improved, for concurrency handling
1485         if self.mode == 'r':
1486             StringIO.close(self)
1487             return
1488
1489         par = self._get_parent()
1490         uid = par.context.uid
1491         cr = pooler.get_db(par.context.dbname).cursor()
1492         try:
1493             if self.mode in ('w', 'w+', 'r+'):
1494                 data = self.getvalue()
1495                 cntobj = par.context._dirobj.pool.get('document.directory.content')
1496                 cntobj.process_write(cr, uid, par, data, par.context.context)
1497             elif self.mode == 'a':
1498                 raise NotImplementedError
1499             cr.commit()
1500         except Exception:
1501             logging.getLogger('document.content').exception('Cannot update db content #%d for close:', par.cnt_id)
1502             raise
1503         finally:
1504             cr.close()
1505         StringIO.close(self)
1506
1507 class nodefd_static(StringIO, node_descriptor):
1508     """ A descriptor to nodes with static data.
1509     """
1510     def __init__(self, parent, cr, mode, ctx=None):
1511         node_descriptor.__init__(self, parent)
1512         self._context=ctx
1513         self._size = 0L
1514
1515         if mode in ('r', 'r+'):
1516             data = parent.get_data(cr)
1517             if data:
1518                 self._size = len(data)
1519                 parent.content_length = len(data)
1520             StringIO.__init__(self, data)
1521         elif mode in ('w', 'w+'):
1522             StringIO.__init__(self, None)
1523             # at write, we start at 0 (= overwrite), but have the original
1524             # data available, in case of a seek()
1525         elif mode == 'a':
1526             StringIO.__init__(self, None)
1527         else:
1528             logging.getLogger('document.nodes').error("Incorrect mode %s specified", mode)
1529             raise IOError(errno.EINVAL, "Invalid file mode")
1530         self.mode = mode
1531
1532     def size(self):
1533         return self._size
1534
1535     def close(self):
1536         # we now open a *separate* cursor, to update the data.
1537         # FIXME: this may be improved, for concurrency handling
1538         if self.mode == 'r':
1539             StringIO.close(self)
1540             return
1541
1542         par = self._get_parent()
1543         # uid = par.context.uid
1544         cr = pooler.get_db(par.context.dbname).cursor()
1545         try:
1546             if self.mode in ('w', 'w+', 'r+'):
1547                 data = self.getvalue()
1548                 par.set_data(cr, data)
1549             elif self.mode == 'a':
1550                 raise NotImplementedError
1551             cr.commit()
1552         except Exception:
1553             logging.getLogger('document.nodes').exception('Cannot update db content #%d for close:', par.cnt_id)
1554             raise
1555         finally:
1556             cr.close()
1557         StringIO.close(self)
1558
1559 #eof