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
24 from document.nodes import _str2time
28 from tools.dict_tools import dict_merge, dict_merge2
30 # TODO: implement DAV-aware errors, inherit from IOError
32 # Assuming that we have set global properties right, we mark *all*
33 # directories as having calendar-access.
34 nodes.node_dir.http_options = dict_merge2(nodes.node_dir.http_options,
35 { 'DAV': ['calendar-access',] })
37 class node_calendar_collection(nodes.node_dir):
38 DAV_PROPS = dict_merge2(nodes.node_dir.DAV_PROPS,
39 { "http://calendarserver.org/ns/" : ('getctag',), } )
40 DAV_M_NS = dict_merge2(nodes.node_dir.DAV_M_NS,
41 { "http://calendarserver.org/ns/" : '_get_dav', } )
43 def _file_get(self,cr, nodename=False):
46 def _child_get(self, cr, name=False, parent_id=False, domain=None):
47 dirobj = self.context._dirobj
48 uid = self.context.uid
49 ctx = self.context.context.copy()
51 where = [('collection_id','=',self.dir_id)]
53 if name and name.endswith('.ics'):
57 where.append(('name','=',name))
60 where = where + domain
61 fil_obj = dirobj.pool.get('basic.calendar')
62 ids = fil_obj.search(cr,uid,where,context=ctx)
64 for cal in fil_obj.browse(cr, uid, ids, context=ctx):
65 if (not name) or not ext:
66 res.append(node_calendar(cal.name, self, self.context, cal))
68 res.append(res_node_calendar(cal.name+'.ics', self, self.context, cal))
69 # May be both of them!
72 def _get_dav_owner(self, cr):
76 def _get_ttag(self, cr):
77 return 'calen-dir-%d' % self.dir_id
79 def _get_dav_getctag(self, cr):
80 result = self.get_etag(cr)
83 class node_calendar_res_col(nodes.node_res_obj):
84 """ Calendar collection, as a dynamically created node
86 This class shall be used instead of node_calendar_collection, when the
87 node is under dynamic ones.
89 DAV_PROPS = dict_merge2(nodes.node_res_obj.DAV_PROPS,
90 { "http://calendarserver.org/ns/" : ('getctag',), } )
91 DAV_M_NS = dict_merge2(nodes.node_res_obj.DAV_M_NS,
92 { "http://calendarserver.org/ns/" : '_get_dav', } )
94 def _file_get(self,cr, nodename=False):
97 def _child_get(self, cr, name=False, parent_id=False, domain=None):
98 dirobj = self.context._dirobj
99 uid = self.context.uid
100 ctx = self.context.context.copy()
101 ctx.update(self.dctx)
102 where = [('collection_id','=',self.dir_id)]
104 if name and name.endswith('.ics'):
108 where.append(('name','=',name))
111 where = where + domain
112 fil_obj = dirobj.pool.get('basic.calendar')
113 ids = fil_obj.search(cr,uid,where,context=ctx)
115 # TODO: shall we use any of our dynamic information??
116 for cal in fil_obj.browse(cr, uid, ids, context=ctx):
117 if (not name) or not ext:
118 res.append(node_calendar(cal.name, self, self.context, cal))
119 if (not name) or ext:
120 res.append(res_node_calendar(cal.name+'.ics', self, self.context, cal))
121 # May be both of them!
124 def _get_ttag(self, cr):
125 return 'calen-dir-%d' % self.dir_id
127 def _get_dav_getctag(self, cr):
128 result = self.get_etag(cr)
131 class node_calendar(nodes.node_class):
132 our_type = 'collection'
134 "DAV:": ('supported-report-set',),
135 # "http://cal.me.com/_namespace/" : ('user-state',),
136 "http://calendarserver.org/ns/" : ( 'getctag',),
137 'http://groupdav.org/': ('resourcetype',),
138 "urn:ietf:params:xml:ns:caldav" : (
139 'calendar-description',
140 'supported-calendar-component-set',
143 "urn:ietf:params:xml:ns:caldav" : (
146 'supported-calendar-data',
154 # "http://cal.me.com/_namespace/": '_get_dav',
155 'http://groupdav.org/': '_get_gdav',
156 "http://calendarserver.org/ns/" : '_get_dav',
157 "urn:ietf:params:xml:ns:caldav" : '_get_caldav'}
159 http_options = { 'DAV': ['calendar-access'] }
161 def __init__(self,path, parent, context, calendar):
162 super(node_calendar,self).__init__(path, parent,context)
163 self.calendar_id = calendar.id
164 self.mimetype = 'application/x-directory'
165 self.create_date = calendar.create_date
166 self.write_date = calendar.write_date or calendar.create_date
167 self.content_length = 0
168 self.displayname = calendar.name
169 self.cal_type = calendar.type
173 def _get_dav_getctag(self, cr):
174 dirobj = self.context._dirobj
175 uid = self.context.uid
176 ctx = self.context.context.copy()
177 ctx.update(self.dctx)
179 bc_obj = dirobj.pool.get('basic.calendar')
180 res = bc_obj.get_cal_max_modified(cr, uid, [self.calendar_id], self, domain=[], context=ctx)
181 return _str2time(res)
183 def _get_dav_user_state(self, cr):
187 def get_dav_resourcetype(self, cr):
188 res = [ ('collection', 'DAV:'),
189 (str(self.cal_type + '-collection'), 'http://groupdav.org/'),
190 ('calendar', 'urn:ietf:params:xml:ns:caldav') ]
193 def get_domain(self, cr, filters):
198 _log = logging.getLogger('caldav.query')
199 if filters.localName == 'calendar-query':
201 for filter_child in filters.childNodes:
202 if filter_child.nodeType == filter_child.TEXT_NODE:
204 if filter_child.localName == 'filter':
205 for vcalendar_filter in filter_child.childNodes:
206 if vcalendar_filter.nodeType == vcalendar_filter.TEXT_NODE:
208 if vcalendar_filter.localName == 'comp-filter':
209 if vcalendar_filter.getAttribute('name') == 'VCALENDAR':
210 for vevent_filter in vcalendar_filter.childNodes:
211 if vevent_filter.nodeType == vevent_filter.TEXT_NODE:
213 if vevent_filter.localName == 'comp-filter':
214 if vevent_filter.getAttribute('name'):
215 res = [('type','=',vevent_filter.getAttribute('name').lower() )]
217 for cfe in vevent_filter.childNodes:
218 if cfe.localName == 'time-range':
219 if cfe.getAttribute('start'):
220 _log.warning("Ignore start.. ")
221 # No, it won't work in this API
222 #val = cfe.getAttribute('start')
223 #res += [('dtstart','=', cfe)]
224 elif cfe.getAttribute('end'):
225 _log.warning("Ignore end.. ")
227 _log.debug("Unknown comp-filter: %s", cfe.localName)
229 _log.debug("Unknown comp-filter: %s", vevent_filter.localName)
231 _log.debug("Unknown filter element: %s", vcalendar_filter.localName)
233 _log.debug("Unknown calendar-query element: %s", filter_child.localName)
235 elif filters.localName == 'calendar-multiget':
237 for filter_child in filters.childNodes:
238 if filter_child.nodeType == filter_child.TEXT_NODE:
240 if filter_child.localName == 'href':
241 if not filter_child.firstChild:
243 uri = filter_child.firstChild.data
244 caluri = uri.split('/')
247 if caluri not in names : names.append(caluri)
249 _log.debug("Unknonwn multiget element: %s", filter_child.localName)
250 res = [('name','in',names)]
253 _log.debug("Unknown element in REPORT: %s", filters.localName)
256 def children(self, cr, domain=None):
257 return self._child_get(cr, domain=domain)
259 def child(self,cr, name, domain=None):
260 res = self._child_get(cr, name, domain=domain)
266 def _child_get(self, cr, name=False, parent_id=False, domain=None):
267 dirobj = self.context._dirobj
268 uid = self.context.uid
269 ctx = self.context.context.copy()
270 ctx.update(self.dctx)
273 if name.endswith('.ics'):
276 where.append(('id','=',int(name)))
278 # if somebody requests any other name than the ones we
279 # generate (non-numeric), it just won't exist
280 # FIXME: however, this confuses Evolution (at least), which
281 # thinks the .ics node hadn't been saved.
287 bc_obj = dirobj.pool.get('basic.calendar')
288 # we /could/ be supplying an invalid calendar id to bc_obj, it has to check
289 res = bc_obj.get_calendar_objects(cr, uid, [self.calendar_id], self, domain=where, context=ctx)
292 def create_child(self, cr, path, data):
293 """ API function to create a child file object and node
294 Return the node_* created
296 # we ignore the path, it will be re-generated automatically
297 fil_obj = self.context._dirobj.pool.get('basic.calendar')
298 ctx = self.context.context.copy()
299 ctx.update(self.dctx)
300 uid = self.context.uid
302 res = self.set_data(cr, data)
305 # We arbitrarily construct only the first node of the data
306 # that have been imported. ICS may have had more elements,
307 # but only one node can be returned here.
308 assert isinstance(res[0], (int, long))
309 fnodes = fil_obj.get_calendar_objects(cr, uid, [self.calendar_id], self,
310 domain=[('id','=',res[0])], context=ctx)
312 # If we reach this line, it means that we couldn't import any useful
313 # (and matching type vs. our node kind) data from the iCal content.
317 def set_data(self, cr, data, fil_obj = None):
318 uid = self.context.uid
319 calendar_obj = self.context._dirobj.pool.get('basic.calendar')
320 res = calendar_obj.import_cal(cr, uid, data, self.calendar_id)
323 def get_data_len(self, cr, fil_obj = None):
324 return self.content_length
326 def _get_ttag(self,cr):
327 return 'calendar-%d' % (self.calendar_id,)
332 def _get_caldav_calendar_data(self, cr):
334 for child in self.children(cr):
335 res.append(child._get_caldav_calendar_data(cr))
338 def _get_caldav_calendar_description(self, cr):
339 uid = self.context.uid
340 calendar_obj = self.context._dirobj.pool.get('basic.calendar')
341 ctx = self.context.context.copy()
342 ctx.update(self.dctx)
344 calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
345 return calendar.description or calendar.name
349 def _get_dav_supported_report_set(self, cr):
351 return ('supported-report', 'DAV:',
353 ('principal-match','DAV:')
357 def _get_caldav_supported_calendar_component_set(self, cr):
358 return ('comp', 'urn:ietf:params:xml:ns:caldav', None,
359 {'name': self.cal_type.upper()} )
361 def _get_caldav_calendar_timezone(self, cr):
364 def _get_caldav_supported_calendar_data(self, cr):
365 return ('calendar-data', 'urn:ietf:params:xml:ns:caldav', None,
366 {'content-type': "text/calendar", 'version': "2.0" } )
368 def _get_caldav_max_resource_size(self, cr):
371 def _get_caldav_min_date_time(self, cr):
372 return "19700101T000000Z"
374 def _get_caldav_max_date_time(self, cr):
375 return "21001231T235959Z" # I will be dead by then
377 class res_node_calendar(nodes.node_class):
380 "http://calendarserver.org/ns/" : ('getctag',),
381 "urn:ietf:params:xml:ns:caldav" : (
382 'calendar-description',
386 "http://calendarserver.org/ns/" : '_get_dav',
387 "urn:ietf:params:xml:ns:caldav" : '_get_caldav'}
389 http_options = { 'DAV': ['calendar-access'] }
391 def __init__(self,path, parent, context, res_obj, res_model=None, res_id=None):
392 super(res_node_calendar,self).__init__(path, parent, context)
393 self.mimetype = 'text/calendar'
394 self.create_date = parent.create_date
395 self.write_date = parent.write_date or parent.create_date
396 self.calendar_id = hasattr(parent, 'calendar_id') and parent.calendar_id or False
398 if not self.calendar_id: self.calendar_id = res_obj.id
399 pr = res_obj.perm_read()[0]
400 self.create_date = pr.get('create_date')
401 self.write_date = pr.get('write_date') or pr.get('create_date')
402 self.displayname = res_obj.name
404 self.content_length = 0
406 self.model = res_model
409 def open(self, cr, mode=False):
410 if self.type in ('collection','database'):
412 s = StringIO.StringIO(self.get_data(cr))
416 def get_data(self, cr, fil_obj = None):
417 uid = self.context.uid
418 calendar_obj = self.context._dirobj.pool.get('basic.calendar')
419 context = self.context.context.copy()
420 context.update(self.dctx)
421 context.update({'model': self.model, 'res_id':self.res_id})
422 res = calendar_obj.export_cal(cr, uid, [self.calendar_id], context=context)
425 def _get_caldav_calendar_data(self, cr):
426 return self.get_data(cr)
428 def get_data_len(self, cr, fil_obj = None):
429 return self.content_length
431 def set_data(self, cr, data, fil_obj = None):
432 uid = self.context.uid
433 context = self.context.context.copy()
434 context.update(self.dctx)
435 context.update({'model': self.model, 'res_id':self.res_id})
436 calendar_obj = self.context._dirobj.pool.get('basic.calendar')
437 res = calendar_obj.import_cal(cr, uid, data, self.calendar_id, context=context)
440 def _get_ttag(self,cr):
442 if self.model and self.res_id:
443 res = '%s_%d' % (self.model, self.res_id)
444 elif self.calendar_id:
445 res = '%d' % (self.calendar_id)
450 uid = self.context.uid
452 if self.type in ('collection','database'):
454 if self.model and self.res_id:
455 document_obj = self.context._dirobj.pool.get(self.model)
457 res = document_obj.unlink(cr, uid, [self.res_id])
463 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4