1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
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.
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.
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/>.
20 ##############################################################################
23 from document_webdav import nodes
27 # TODO: implement DAV-aware errors, inherit from IOError
29 def dict_merge(*dicts):
30 """ Return a dict with all values of dicts
37 def dict_merge2(*dicts):
38 """ Return a dict with all values of dicts.
39 If some key appears twice and contains iterable objects, the values
40 are merged (instead of overwritten).
45 if k in res and isinstance(res[k], (list, tuple)):
46 res[k] = res[k] + d[k]
51 # Assuming that we have set global properties right, we mark *all*
52 # directories as having calendar-access.
53 nodes.node_dir.http_options = dict_merge2(nodes.node_dir.http_options,
54 { 'DAV': ['calendar-access',] })
56 class node_calendar_collection(nodes.node_dir):
57 DAV_PROPS = dict_merge2(nodes.node_dir.DAV_PROPS,
58 { "http://calendarserver.org/ns/" : ('getctag',), } )
59 DAV_M_NS = dict_merge2(nodes.node_dir.DAV_M_NS,
60 { "http://calendarserver.org/ns/" : '_get_dav', } )
62 def _file_get(self,cr, nodename=False):
65 def _child_get(self, cr, name=False, parent_id=False, domain=None):
66 dirobj = self.context._dirobj
67 uid = self.context.uid
68 ctx = self.context.context.copy()
70 where = [('collection_id','=',self.dir_id)]
72 if name and name.endswith('.ics'):
76 where.append(('name','=',name))
79 where = where + domain
80 fil_obj = dirobj.pool.get('basic.calendar')
81 ids = fil_obj.search(cr,uid,where,context=ctx)
83 for cal in fil_obj.browse(cr, uid, ids, context=ctx):
84 if (not name) or not ext:
85 res.append(node_calendar(cal.name, self, self.context, cal))
87 res.append(res_node_calendar(cal.name+'.ics', self, self.context, cal))
88 # May be both of them!
91 def _get_dav_owner(self, cr):
95 def _get_ttag(self, cr):
96 return 'calen-dir-%d' % self.dir_id
98 def _get_dav_getctag(self, cr):
99 result = self.get_etag(cr)
102 class node_calendar_res_col(nodes.node_res_obj):
103 """ Calendar collection, as a dynamically created node
105 This class shall be used instead of node_calendar_collection, when the
106 node is under dynamic ones.
108 DAV_PROPS = dict_merge2(nodes.node_res_obj.DAV_PROPS,
109 { "http://calendarserver.org/ns/" : ('getctag',), } )
110 DAV_M_NS = dict_merge2(nodes.node_res_obj.DAV_M_NS,
111 { "http://calendarserver.org/ns/" : '_get_dav', } )
113 def _file_get(self,cr, nodename=False):
116 def _child_get(self, cr, name=False, parent_id=False, domain=None):
117 dirobj = self.context._dirobj
118 uid = self.context.uid
119 ctx = self.context.context.copy()
120 ctx.update(self.dctx)
121 where = [('collection_id','=',self.dir_id)]
123 if name and name.endswith('.ics'):
127 where.append(('name','=',name))
130 where = where + domain
131 fil_obj = dirobj.pool.get('basic.calendar')
132 ids = fil_obj.search(cr,uid,where,context=ctx)
134 # TODO: shall we use any of our dynamic information??
135 for cal in fil_obj.browse(cr, uid, ids, context=ctx):
136 if (not name) or not ext:
137 res.append(node_calendar(cal.name, self, self.context, cal))
138 if (not name) or ext:
139 res.append(res_node_calendar(cal.name+'.ics', self, self.context, cal))
140 # May be both of them!
143 def _get_ttag(self, cr):
144 return 'calen-dir-%d' % self.dir_id
146 def _get_dav_getctag(self, cr):
147 result = self.get_etag(cr)
150 class node_calendar(nodes.node_class):
151 our_type = 'collection'
153 "DAV:": ('supported-report-set',),
154 # "http://cal.me.com/_namespace/" : ('user-state',),
155 "http://calendarserver.org/ns/" : ( 'getctag',),
156 'http://groupdav.org/': ('resourcetype',),
157 "urn:ietf:params:xml:ns:caldav" : (
158 'calendar-description',
159 'supported-calendar-component-set',
162 "urn:ietf:params:xml:ns:caldav" : (
165 'supported-calendar-data',
173 # "http://cal.me.com/_namespace/": '_get_dav',
174 'http://groupdav.org/': '_get_gdav',
175 "http://calendarserver.org/ns/" : '_get_dav',
176 "urn:ietf:params:xml:ns:caldav" : '_get_caldav'}
178 http_options = { 'DAV': ['calendar-access'] }
180 def __init__(self,path, parent, context, calendar):
181 super(node_calendar,self).__init__(path, parent,context)
182 self.calendar_id = calendar.id
183 self.mimetype = 'application/x-directory'
184 self.create_date = calendar.create_date
185 self.write_date = calendar.write_date or calendar.create_date
186 self.content_length = 0
187 self.displayname = calendar.name
188 self.cal_type = calendar.type
190 def _get_dav_getctag(self, cr):
191 result = self._get_ttag(cr) + ':' + str(time.time())
194 def _get_dav_user_state(self, cr):
198 def get_dav_resourcetype(self, cr):
199 res = [ ('collection', 'DAV:'),
200 (str(self.cal_type + '-collection'), 'http://groupdav.org/'),
201 ('calendar', 'urn:ietf:params:xml:ns:caldav') ]
204 def get_domain(self, cr, filters):
209 _log = logging.getLogger('caldav.query')
210 if filters.localName == 'calendar-query':
212 for filter_child in filters.childNodes:
213 if filter_child.nodeType == filter_child.TEXT_NODE:
215 if filter_child.localName == 'filter':
216 for vcalendar_filter in filter_child.childNodes:
217 if vcalendar_filter.nodeType == vcalendar_filter.TEXT_NODE:
219 if vcalendar_filter.localName == 'comp-filter':
220 if vcalendar_filter.getAttribute('name') == 'VCALENDAR':
221 for vevent_filter in vcalendar_filter.childNodes:
222 if vevent_filter.nodeType == vevent_filter.TEXT_NODE:
224 if vevent_filter.localName == 'comp-filter':
225 if vevent_filter.getAttribute('name'):
226 res = [('type','=',vevent_filter.getAttribute('name').lower() )]
228 for cfe in vevent_filter.childNodes:
229 if cfe.localName == 'time-range':
230 if cfe.getAttribute('start'):
231 _log.warning("Ignore start.. ")
232 # No, it won't work in this API
233 #val = cfe.getAttribute('start')
234 #res += [('dtstart','=', cfe)]
235 elif cfe.getAttribute('end'):
236 _log.warning("Ignore end.. ")
238 _log.debug("Unknown comp-filter: %s", cfe.localName)
240 _log.debug("Unknown comp-filter: %s", vevent_filter.localName)
242 _log.debug("Unknown filter element: %s", vcalendar_filter.localName)
244 _log.debug("Unknown calendar-query element: %s", filter_child.localName)
246 elif filters.localName == 'calendar-multiget':
248 for filter_child in filters.childNodes:
249 if filter_child.nodeType == filter_child.TEXT_NODE:
251 if filter_child.localName == 'href':
252 if not filter_child.firstChild:
254 uri = filter_child.firstChild.data
255 caluri = uri.split('/')
258 if caluri not in names : names.append(caluri)
260 _log.debug("Unknonwn multiget element: %s", filter_child.localName)
261 res = [('name','in',names)]
264 _log.debug("Unknown element in REPORT: %s", filters.localName)
267 def children(self, cr, domain=None):
268 return self._child_get(cr, domain=domain)
270 def child(self,cr, name, domain=None):
271 res = self._child_get(cr, name, domain=domain)
277 def _child_get(self, cr, name=False, parent_id=False, domain=None):
278 dirobj = self.context._dirobj
279 uid = self.context.uid
280 ctx = self.context.context.copy()
281 ctx.update(self.dctx)
284 if name.endswith('.ics'):
287 where.append(('id','=',int(name)))
289 # if somebody requests any other name than the ones we
290 # generate (non-numeric), it just won't exist
291 # FIXME: however, this confuses Evolution (at least), which
292 # thinks the .ics node hadn't been saved.
298 fil_obj = dirobj.pool.get('basic.calendar')
299 ids = fil_obj.search(cr, uid, domain)
301 if self.calendar_id in ids:
302 res = fil_obj.get_calendar_objects(cr, uid, [self.calendar_id], self, domain=where, context=ctx)
305 def create_child(self, cr, path, data):
306 """ API function to create a child file object and node
307 Return the node_* created
309 # we ignore the path, it will be re-generated automatically
310 fil_obj = self.context._dirobj.pool.get('basic.calendar')
311 ctx = self.context.context.copy()
312 ctx.update(self.dctx)
313 uid = self.context.uid
315 res = self.set_data(cr, data)
318 # We arbitrarily construct only the first node of the data
319 # that have been imported. ICS may have had more elements,
320 # but only one node can be returned here.
321 assert isinstance(res[0], (int, long))
322 fnodes = fil_obj.get_calendar_objects(cr, uid, [self.calendar_id], self,
323 domain=[('id','=',res[0])], context=ctx)
325 # If we reach this line, it means that we couldn't import any useful
326 # (and matching type vs. our node kind) data from the iCal content.
330 def set_data(self, cr, data, fil_obj = None):
331 uid = self.context.uid
332 calendar_obj = self.context._dirobj.pool.get('basic.calendar')
333 res = calendar_obj.import_cal(cr, uid, data, self.calendar_id)
336 def get_data_len(self, cr, fil_obj = None):
337 return self.content_length
339 def _get_ttag(self,cr):
340 return 'calendar-%d' % (self.calendar_id,)
345 def _get_caldav_calendar_data(self, cr):
347 for child in self.children(cr):
348 res.append(child._get_caldav_calendar_data(cr))
351 def _get_caldav_calendar_description(self, cr):
352 uid = self.context.uid
353 calendar_obj = self.context._dirobj.pool.get('basic.calendar')
354 ctx = self.context.context.copy()
355 ctx.update(self.dctx)
357 calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
358 return calendar.description or calendar.name
362 def _get_dav_supported_report_set(self, cr):
364 return ('supported-report', 'DAV:',
366 ('principal-match','DAV:')
370 def _get_caldav_supported_calendar_component_set(self, cr):
371 return ('comp', 'urn:ietf:params:xml:ns:caldav', None,
372 {'name': self.cal_type.upper()} )
374 def _get_caldav_calendar_timezone(self, cr):
377 def _get_caldav_supported_calendar_data(self, cr):
378 return ('calendar-data', 'urn:ietf:params:xml:ns:caldav', None,
379 {'content-type': "text/calendar", 'version': "2.0" } )
381 def _get_caldav_max_resource_size(self, cr):
384 def _get_caldav_min_date_time(self, cr):
385 return "19700101T000000Z"
387 def _get_caldav_max_date_time(self, cr):
388 return "21001231T235959Z" # I will be dead by then
390 class res_node_calendar(nodes.node_class):
393 "http://calendarserver.org/ns/" : ('getctag',),
394 "urn:ietf:params:xml:ns:caldav" : (
395 'calendar-description',
399 "http://calendarserver.org/ns/" : '_get_dav',
400 "urn:ietf:params:xml:ns:caldav" : '_get_caldav'}
402 http_options = { 'DAV': ['calendar-access'] }
404 def __init__(self,path, parent, context, res_obj, res_model=None, res_id=None):
405 super(res_node_calendar,self).__init__(path, parent, context)
406 self.mimetype = 'text/calendar'
407 self.create_date = parent.create_date
408 self.write_date = parent.write_date or parent.create_date
409 self.calendar_id = hasattr(parent, 'calendar_id') and parent.calendar_id or False
411 if not self.calendar_id: self.calendar_id = res_obj.id
412 pr = res_obj.perm_read()[0]
413 self.create_date = pr.get('create_date')
414 self.write_date = pr.get('write_date') or pr.get('create_date')
415 self.displayname = res_obj.name
417 self.content_length = 0
419 self.model = res_model
422 def open(self, cr, mode=False):
423 if self.type in ('collection','database'):
425 s = StringIO.StringIO(self.get_data(cr))
429 def get_data(self, cr, fil_obj = None):
430 uid = self.context.uid
431 calendar_obj = self.context._dirobj.pool.get('basic.calendar')
432 context = self.context.context.copy()
433 context.update(self.dctx)
434 context.update({'model': self.model, 'res_id':self.res_id})
435 res = calendar_obj.export_cal(cr, uid, [self.calendar_id], context=context)
438 def _get_caldav_calendar_data(self, cr):
439 return self.get_data(cr)
441 def get_data_len(self, cr, fil_obj = None):
442 return self.content_length
444 def set_data(self, cr, data, fil_obj = None):
445 uid = self.context.uid
446 context = self.context.context.copy()
447 context.update(self.dctx)
448 context.update({'model': self.model, 'res_id':self.res_id})
449 calendar_obj = self.context._dirobj.pool.get('basic.calendar')
450 res = calendar_obj.import_cal(cr, uid, data, self.calendar_id, context=context)
453 def _get_ttag(self,cr):
455 if self.model and self.res_id:
456 res = '%s_%d' % (self.model, self.res_id)
457 elif self.calendar_id:
458 res = '%d' % (self.calendar_id)
463 uid = self.context.uid
465 if self.type in ('collection','database'):
467 if self.model and self.res_id:
468 document_obj = self.context._dirobj.pool.get(self.model)
470 res = document_obj.unlink(cr, uid, [self.res_id])
476 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4