#
##############################################################################
-import time
from document_webdav import nodes
-import StringIO
+from document.nodes import _str2time, nodefd_static
+import logging
+from orm_utils import get_last_modified
-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
+try:
+ from tools.dict_tools import dict_merge2
+except ImportError:
+ from document.dict_tools import dict_merge2
+
+# TODO: implement DAV-aware errors, inherit from IOError
# Assuming that we have set global properties right, we mark *all*
# directories as having calendar-access.
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
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
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'
DAV_PROPS = {
- "olDAV:": ('principal-collection-set',
- 'principal-URL',
- 'supported-report-set'),
+ "DAV:": ('supported-report-set',),
# "http://cal.me.com/_namespace/" : ('user-state',),
- "http://calendarserver.org/ns/" : (
- # 'dropbox-home-URL',
- # 'notification-URL',
- 'getctag',),
+ "http://calendarserver.org/ns/" : ( 'getctag',),
'http://groupdav.org/': ('resourcetype',),
"urn:ietf:params:xml:ns:caldav" : (
- 'calendar-description',
+ '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',
- # 'calendar-home-set',
- # 'calendar-user-address-set',
- # 'schedule-inbox-URL',
- #'schedule-outbox-URL',
+ 'calendar-timezone',
+ 'supported-calendar-data',
+ 'max-resource-size',
+ 'min-date-time',
+ 'max-date-time',
)}
+
DAV_M_NS = {
"DAV:" : '_get_dav',
# "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'] }
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)
-
- def _get_dav_dropbox_home_URL(self, cr): #Depr
- import urllib
- uid = self.context.uid
- ctx = self.context.context.copy()
- ctx.update(self.dctx)
- calendar_obj = self.context._dirobj.pool.get('basic.calendar')
- calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
- res = '%s/%s' %(calendar.collection_id.name, calendar.name)
- url = urllib.quote('/%s/%s' % (cr.dbname, res))
- return url
-
- def _get_dav_notification_URL(self, cr): # Depr
- import urllib
+ dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
- calendar_obj = self.context._dirobj.pool.get('basic.calendar')
- calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
- res = '%s/%s' %(calendar.collection_id.name, calendar.name)
- url = urllib.quote('/%s/%s' % (cr.dbname, res))
- return url
+
+ 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
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):
res = []
if not filters:
return res
+ _log = logging.getLogger('caldav.query')
if filters.localName == 'calendar-query':
res = []
for filter_child in filters.childNodes:
if vevent_filter.nodeType == vevent_filter.TEXT_NODE:
continue
if vevent_filter.localName == 'comp-filter':
- if vevent_filter.getAttribute('name') == 'VEVENT':
- res = [('type','=','vevent')]
- if vevent_filter.getAttribute('name') == 'VTODO':
- res = [('type','=','vtodo')]
+ if vevent_filter.getAttribute('name'):
+ res = [('type','=',vevent_filter.getAttribute('name').lower() )]
+
+ for cfe in vevent_filter.childNodes:
+ if cfe.localName == 'time-range':
+ if cfe.getAttribute('start'):
+ _log.warning("Ignore start.. ")
+ # No, it won't work in this API
+ #val = cfe.getAttribute('start')
+ #res += [('dtstart','=', cfe)]
+ elif cfe.getAttribute('end'):
+ _log.warning("Ignore end.. ")
+ else:
+ _log.debug("Unknown comp-filter: %s", cfe.localName)
+ else:
+ _log.debug("Unknown comp-filter: %s", vevent_filter.localName)
+ else:
+ _log.debug("Unknown filter element: %s", vcalendar_filter.localName)
+ else:
+ _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)
- 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
def children(self, cr, domain=None):
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):
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.
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')
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_principal_collection_set(self, cr):
- import urllib
- uid = self.context.uid
- ctx = self.context.context.copy()
- ctx.update(self.dctx)
- calendar_obj = self.context._dirobj.pool.get('basic.calendar')
- calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
- res = '%s/%s' %(calendar.collection_id.name, calendar.name)
- return ('href', 'DAV:', urllib.quote('/%s/%s' % (cr.dbname, res)))
-
- def _get_caldav_calendar_user_address_set(self, cr):
- uid = self.context.uid
- ctx = self.context.context.copy()
- ctx.update(self.dctx)
- user_obj = self.context._dirobj.pool.get('res.users')
- user = user_obj.browse(cr, uid, uid, context=ctx)
- return ('href', 'DAV:', 'MAILTO:' + str(user.email) or str(user.name))
+ def _get_dav_supported_report_set(self, cr):
+
+ return ('supported-report', 'DAV:',
+ ('report','DAV:',
+ ('principal-match','DAV:')
+ )
+ )
+
+ def _get_caldav_supported_calendar_component_set(self, cr):
+ return ('comp', 'urn:ietf:params:xml:ns:caldav', None,
+ {'name': self.cal_type.upper()} )
+
+ def _get_caldav_calendar_timezone(self, cr):
+ return None #TODO
+
+ def _get_caldav_supported_calendar_data(self, cr):
+ return ('calendar-data', 'urn:ietf:params:xml:ns:caldav', None,
+ {'content-type': "text/calendar", 'version': "2.0" } )
+
+ def _get_caldav_max_resource_size(self, cr):
+ return 65535
- def _get_caldav_schedule_outbox_URL(self, cr):
- return self._get_caldav_schedule_inbox_URL(cr)
+ def _get_caldav_min_date_time(self, cr):
+ return "19700101T000000Z"
- def _get_caldav_schedule_inbox_URL(self, cr):
- import urllib
- uid = self.context.uid
- ctx = self.context.context.copy()
- ctx.update(self.dctx)
- calendar_obj = self.context._dirobj.pool.get('basic.calendar')
- calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
- res = '/webdav/%s/%s/%s' %(cr.dbname, calendar.collection_id.name, calendar.name)
-
- return ('href', 'DAV:', res)
+ 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_dav_supported_report_set(self, cr):
- return '' # TODO
+ def _get_apple_cal_calendar_order(self, cr):
+ return self.cal_order
class res_node_calendar(nodes.node_class):
our_type = 'file'
"urn:ietf:params:xml:ns:caldav" : (
'calendar-description',
'calendar-data',
- # 'calendar-home-set',
- # 'calendar-user-address-set',
- #'schedule-inbox-URL',
- # 'schedule-outbox-URL',
)}
DAV_M_NS = {
"http://calendarserver.org/ns/" : '_get_dav',
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
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()
res = '%d' % (self.calendar_id)
return res
-
def rm(self, cr):
uid = self.context.uid
res = False