[ADD]: Images: caldav, crm_caldav, document_webdav, project_caldav
[odoo/odoo.git] / addons / caldav / caldav_node.py
index 510e43e..5272d0f 100644 (file)
 #
 ##############################################################################
 
-import time
 from document_webdav import nodes
+from document.nodes import _str2time, nodefd_static
 import logging
-import StringIO
+from orm_utils import get_last_modified
 
-# TODO: implement DAV-aware errors, inherit from IOError
+try:
+    from tools.dict_tools import  dict_merge2
+except ImportError:
+    from document.dict_tools import  dict_merge2
 
-def dict_merge(*dicts):
-    """ Return a dict with all values of dicts
-    """
-    res = {}
-    for d in dicts:
-        res.update(d)
-    return res
-
-def dict_merge2(*dicts):
-    """ Return a dict with all values of dicts.
-        If some key appears twice and contains iterable objects, the values
-        are merged (instead of overwritten).
-    """
-    res = {}
-    for d in dicts:
-        for k in d.keys():
-            if k in res and isinstance(res[k], (list, tuple)):
-                res[k] = res[k] + d[k]
-            else:
-                res[k] = d[k]
-    return res
+# TODO: implement DAV-aware errors, inherit from IOError
 
 # Assuming that we have set global properties right, we mark *all* 
 # directories as having calendar-access.
@@ -83,21 +66,27 @@ class node_calendar_collection(nodes.node_dir):
         for cal in fil_obj.browse(cr, uid, ids, context=ctx):
             if (not name) or not ext:
                 res.append(node_calendar(cal.name, self, self.context, cal))
-            if (not name) or ext:
+            if self.context.get('DAV-client', '') in ('iPhone', 'iCalendar'):
+                # these ones must not see the webcal entry.
+                continue
+            if cal.has_webcal and (not name) or ext:
                 res.append(res_node_calendar(cal.name+'.ics', self, self.context, cal))
             # May be both of them!
         return res
 
-    def _get_dav_owner(self, cr):
-        # Todo?
-        return False
-
     def _get_ttag(self, cr):
         return 'calen-dir-%d' % self.dir_id
 
     def _get_dav_getctag(self, cr):
-        result = self.get_etag(cr)
-        return str(result)
+        dirobj = self.context._dirobj
+        uid = self.context.uid
+        ctx = self.context.context.copy()
+        ctx.update(self.dctx)
+        where = [('collection_id','=',self.dir_id)]
+        bc_obj = dirobj.pool.get('basic.calendar')
+        
+        res = get_last_modified(bc_obj, cr, uid, where, context=ctx)
+        return _str2time(res)
 
 class node_calendar_res_col(nodes.node_res_obj):
     """ Calendar collection, as a dynamically created node
@@ -135,7 +124,10 @@ class node_calendar_res_col(nodes.node_res_obj):
         for cal in fil_obj.browse(cr, uid, ids, context=ctx):
             if (not name) or not ext:
                 res.append(node_calendar(cal.name, self, self.context, cal))
-            if (not name) or ext:
+            if self.context.get('DAV-client', '') in ('iPhone', 'iCalendar'):
+                # these ones must not see the webcal entry.
+                continue
+            if cal.has_webcal and (not name) or ext:
                 res.append(res_node_calendar(cal.name+'.ics', self, self.context, cal))
             # May be both of them!
         return res
@@ -144,8 +136,15 @@ class node_calendar_res_col(nodes.node_res_obj):
         return 'calen-dir-%d' % self.dir_id
 
     def _get_dav_getctag(self, cr):
-        result = self.get_etag(cr)
-        return str(result)
+        dirobj = self.context._dirobj
+        uid = self.context.uid
+        ctx = self.context.context.copy()
+        ctx.update(self.dctx)
+        where = [('collection_id','=',self.dir_id)]
+        bc_obj = dirobj.pool.get('basic.calendar')
+        
+        res = get_last_modified(bc_obj, cr, uid, where, context=ctx)
+        return _str2time(res)
 
 class node_calendar(nodes.node_class):
     our_type = 'collection'
@@ -157,7 +156,9 @@ class node_calendar(nodes.node_class):
             "urn:ietf:params:xml:ns:caldav" : (
                     'calendar-description', 
                     'supported-calendar-component-set',
-                    )}
+                    ),
+            "http://apple.com/ns/ical/": ("calendar-color", "calendar-order"),
+            }
     DAV_PROPS_HIDDEN = {
             "urn:ietf:params:xml:ns:caldav" : (
                     'calendar-data',
@@ -173,7 +174,9 @@ class node_calendar(nodes.node_class):
            # "http://cal.me.com/_namespace/": '_get_dav', 
            'http://groupdav.org/': '_get_gdav',
            "http://calendarserver.org/ns/" : '_get_dav',
-           "urn:ietf:params:xml:ns:caldav" : '_get_caldav'}
+           "urn:ietf:params:xml:ns:caldav" : '_get_caldav',
+           "http://apple.com/ns/ical/": '_get_apple_cal',
+           }
 
     http_options = { 'DAV': ['calendar-access'] }
 
@@ -186,10 +189,22 @@ class node_calendar(nodes.node_class):
         self.content_length = 0
         self.displayname = calendar.name
         self.cal_type = calendar.type
+        self.cal_color = calendar.calendar_color or None
+        self.cal_order = calendar.calendar_order or None
+        try:
+            self.uuser = (calendar.user_id and calendar.user_id.login) or 'nobody'
+        except Exception:
+            self.uuser = 'nobody'
 
     def _get_dav_getctag(self, cr):
-        result = self._get_ttag(cr) + ':' + str(time.time())
-        return str(result)
+        dirobj = self.context._dirobj
+        uid = self.context.uid
+        ctx = self.context.context.copy()
+        ctx.update(self.dctx)
+
+        bc_obj = dirobj.pool.get('basic.calendar')
+        res = bc_obj.get_cal_max_modified(cr, uid, [self.calendar_id], self, domain=[], context=ctx)
+        return _str2time(res)
 
     def _get_dav_user_state(self, cr):
         #TODO
@@ -197,8 +212,10 @@ class node_calendar(nodes.node_class):
 
     def get_dav_resourcetype(self, cr):
         res = [ ('collection', 'DAV:'),
-                (str(self.cal_type + '-collection'), 'http://groupdav.org/'),
-                ('calendar', 'urn:ietf:params:xml:ns:caldav') ]
+                ('calendar', 'urn:ietf:params:xml:ns:caldav'),
+                ]
+        if self.context.get('DAV-client', '') == 'GroupDAV':
+            res.append((str(self.cal_type + '-collection'), 'http://groupdav.org/'))
         return res
 
     def get_domain(self, cr, filters):
@@ -244,22 +261,9 @@ class node_calendar(nodes.node_class):
                     _log.debug("Unknown calendar-query element: %s", filter_child.localName)
             return res
         elif filters.localName == 'calendar-multiget':
-            names = []
-            for filter_child in filters.childNodes:
-                if filter_child.nodeType == filter_child.TEXT_NODE:
-                    continue
-                if filter_child.localName == 'href':
-                    if not filter_child.firstChild:
-                        continue
-                    uri = filter_child.firstChild.data
-                    caluri = uri.split('/')
-                    if len(caluri):
-                        caluri = caluri[-2]
-                        if caluri not in names : names.append(caluri)
-                else:
-                    _log.debug("Unknonwn multiget element: %s", filter_child.localName)
-            res = [('name','in',names)]
-            return res
+            # this is not the place to process, as it wouldn't support multi-level
+            # hrefs. So, the code is moved to document_webdav/dav_fs.py
+            pass
         else:
             _log.debug("Unknown element in REPORT: %s", filters.localName)
         return res
@@ -280,26 +284,33 @@ class node_calendar(nodes.node_class):
         ctx = self.context.context.copy()
         ctx.update(self.dctx)
         where = []
+        bc_obj = dirobj.pool.get('basic.calendar')
+
         if name:
             if name.endswith('.ics'):
                 name = name[:-4]
             try:
-                where.append(('id','=',int(name)))
+                if name.isdigit():
+                    where.append(('id','=',int(name)))
+                else:
+                    bca_obj = dirobj.pool.get('basic.calendar.alias')
+                    bc_alias = bca_obj.search(cr, uid, 
+                        [('cal_line_id.calendar_id', '=', self.calendar_id),
+                         ('name', '=', name)] )
+                    if not bc_alias:
+                        return []
+                    bc_val = bca_obj.read(cr, uid, bc_alias, ['res_id',])
+                    where.append(('id', '=', bc_val[0]['res_id']))
             except ValueError:
                 # if somebody requests any other name than the ones we
                 # generate (non-numeric), it just won't exist
-                # FIXME: however, this confuses Evolution (at least), which
-                # thinks the .ics node hadn't been saved.
                 return []
 
         if not domain:
             domain = []
 
-        fil_obj = dirobj.pool.get('basic.calendar')
-        ids = fil_obj.search(cr, uid, domain)
-        res = []
-        if self.calendar_id in ids:
-            res = fil_obj.get_calendar_objects(cr, uid, [self.calendar_id], self, domain=where, context=ctx)
+        # we /could/ be supplying an invalid calendar id to bc_obj, it has to check
+        res = bc_obj.get_calendar_objects(cr, uid, [self.calendar_id], self, domain=where, context=ctx)
         return res
 
     def create_child(self, cr, path, data):
@@ -321,6 +332,21 @@ class node_calendar(nodes.node_class):
             assert isinstance(res[0], (int, long))
             fnodes = fil_obj.get_calendar_objects(cr, uid, [self.calendar_id], self,
                     domain=[('id','=',res[0])], context=ctx)
+            if self.context.get('DAV-client','') in ('iPhone', 'iCalendar',):
+                # For those buggy clients, register the alias
+                bca_obj = fil_obj.pool.get('basic.calendar.alias')
+                ourcal = fil_obj.browse(cr, uid, self.calendar_id)
+                line_id = None
+                for line in ourcal.line_ids:
+                    if line.name == ourcal.type:
+                        line_id = line.id
+                        break
+                assert line_id, "Calendar #%d must have at least one %s line" % \
+                                    (ourcal.id, ourcal.type)
+                if path.endswith('.ics'):
+                    path = path[:-4]
+                bca_obj.create(cr, uid, { 'cal_line_id': line_id, 
+                                    'res_id': res[0], 'name': path}, context=ctx)
             return fnodes[0]
         # If we reach this line, it means that we couldn't import any useful
         # (and matching type vs. our node kind) data from the iCal content.
@@ -343,11 +369,19 @@ class node_calendar(nodes.node_class):
         return False
 
     def _get_caldav_calendar_data(self, cr):
+        if self.context.get('DAV-client', '') in ('iPhone', 'iCalendar'):
+            # Never return collective data to iClients, they get confused
+            # because they do propfind on the calendar node with Depth=1
+            # and only expect the childrens' data
+            return None
         res = []
         for child in self.children(cr):
             res.append(child._get_caldav_calendar_data(cr))
         return res
 
+    def open_data(self, cr, mode):
+        return nodefd_static(self, cr, mode)
+
     def _get_caldav_calendar_description(self, cr):
         uid = self.context.uid
         calendar_obj = self.context._dirobj.pool.get('basic.calendar')
@@ -356,7 +390,7 @@ class node_calendar(nodes.node_class):
         try:
             calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
             return calendar.description or calendar.name
-        except Exception, e:
+        except Exception:
             return None
 
     def _get_dav_supported_report_set(self, cr):
@@ -386,6 +420,12 @@ class node_calendar(nodes.node_class):
 
     def _get_caldav_max_date_time(self, cr):
         return "21001231T235959Z" # I will be dead by then
+    
+    def _get_apple_cal_calendar_color(self, cr):
+        return self.cal_color
+
+    def _get_apple_cal_calendar_order(self, cr):
+        return self.cal_order
 
 class res_node_calendar(nodes.node_class):
     our_type = 'file'
@@ -409,7 +449,7 @@ class res_node_calendar(nodes.node_class):
         self.calendar_id = hasattr(parent, 'calendar_id') and parent.calendar_id or False
         if res_obj:
             if not self.calendar_id: self.calendar_id = res_obj.id
-            pr = res_obj.perm_read()[0]
+            pr = res_obj.perm_read(context=context, details=False)[0]
             self.create_date = pr.get('create_date')
             self.write_date = pr.get('write_date') or pr.get('create_date')
             self.displayname = res_obj.name
@@ -419,14 +459,10 @@ class res_node_calendar(nodes.node_class):
         self.model = res_model
         self.res_id = res_id
 
-    def open(self, cr, mode=False):
-        if self.type in ('collection','database'):
-            return False
-        s = StringIO.StringIO(self.get_data(cr))
-        s.name = self
-        return s
+    def open_data(self, cr, mode):
+        return nodefd_static(self, cr, mode)
 
-    def get_data(self, cr, fil_obj = None):
+    def get_data(self, cr, fil_obj=None):
         uid = self.context.uid
         calendar_obj = self.context._dirobj.pool.get('basic.calendar')
         context = self.context.context.copy()
@@ -458,6 +494,16 @@ class res_node_calendar(nodes.node_class):
             res = '%d' % (self.calendar_id)
         return res
 
+    def _get_wtag(self, cr):
+        uid = self.context.uid
+        context = self.context.context
+        if self.model and self.res_id:
+            mod_obj = self.context._dirobj.pool.get(self.model)
+            pr = mod_obj.perm_read(cr, uid, [self.res_id], context=context, details=False)[0]
+            self.write_date = pr.get('write_date') or pr.get('create_date')
+        
+        # Super will use self.write_date, so we should be fine.
+        return super(res_node_calendar, self)._get_wtag(cr)
 
     def rm(self, cr):
         uid = self.context.uid