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