CalDAV: Enable the calendar-access option, as required by RFC4791
[odoo/odoo.git] / addons / caldav / caldav_node.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 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 from osv import osv, fields
23 from tools.translate import _
24 import pooler
25 import tools
26 import time
27 import base64
28 from document import nodes
29 import StringIO
30
31 class node_database(nodes.node_database):
32     def _child_get(self, cr, name=False, parent_id=False, domain=None):
33         dirobj = self.context._dirobj
34         uid = self.context.uid
35         ctx = self.context.context.copy()
36         ctx.update(self.dctx)
37         if not domain:
38             domain = []
39         domain2 = domain + [('calendar_collection','=', False)]
40         res = super(node_database, self)._child_get(cr, name=name, parent_id=parent_id, domain=domain2)
41         where = [('parent_id','=',parent_id)]
42         domain2 = domain + [('calendar_collection','=', True)]
43         if name:
44             where.append(('name','=',name))
45         if domain2:
46             where += domain2
47
48         where2 = where + [('type', '=', 'directory')]
49         ids = dirobj.search(cr, uid, where2, context=ctx)
50         for dirr in dirobj.browse(cr,uid,ids,context=ctx):
51             res.append(node_calendar_collection(dirr.name,self,self.context,dirr))
52         return res
53
54 class node_calendar_collection(nodes.node_dir):
55     PROPS = {
56             "http://calendarserver.org/ns/" : ('getctag'),
57             }
58     M_NS = {
59            "http://calendarserver.org/ns/" : '_get_dav',
60            }
61
62     http_options = { 'DAV': ['calendar-access'] }
63
64     def get_dav_props(self, cr):
65         return self.PROPS
66
67
68
69     def get_dav_eprop(self,cr, ns, propname):
70         if self.M_NS.has_key(ns):
71             prefix = self.M_NS[ns]
72         else:
73             print "No namespace:",ns, "( for prop:", propname,")"
74             return None
75
76         mname = prefix + "_" + propname
77
78         if not hasattr(self, mname):
79             return None
80
81         try:
82             m = getattr(self, mname)
83             r = m(cr)
84             return r
85         except AttributeError, e:
86             print 'Property %s not supported' % propname
87             print "Exception:", e
88         return None
89
90     def _file_get(self,cr, nodename=False):
91         return []
92
93
94
95
96     def _child_get(self, cr, name=False, parent_id=False, domain=None):
97         dirobj = self.context._dirobj
98         uid = self.context.uid
99         ctx = self.context.context.copy()
100         ctx.update(self.dctx)
101         where = [('collection_id','=',self.dir_id)]
102         ext = False
103         if name:
104             res = name.split('.ics')
105             if len(res) > 1:
106                 name = res[0]
107                 ext = '.ics'
108         if name:
109             where.append(('name','=',name))
110         if not domain:
111             domain = []
112         where = where + domain
113         fil_obj = dirobj.pool.get('basic.calendar')
114         ids = fil_obj.search(cr,uid,where,context=ctx)
115         res = []
116         for calender in fil_obj.browse(cr, uid, ids, context=ctx):
117             if not ext:
118                 res.append(node_calendar(calender.name, self, self.context, calender))
119             else:
120                 res.append(res_node_calendar(name, self, self.context, calender))
121         return res
122
123     def _get_dav_owner(self, cr):
124         return False
125
126
127     def get_etag(self, cr):
128         """ Get a tag, unique per object + modification.
129
130             see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """
131         return self._get_ttag(cr) + ':' + self._get_wtag(cr)
132
133     def _get_wtag(self, cr):
134         """ Return the modification time as a unique, compact string """
135         if self.write_date:
136             wtime = time.mktime(time.strptime(self.write_date, '%Y-%m-%d %H:%M:%S'))
137         else: wtime = time.time()
138         return str(wtime)
139
140     def _get_ttag(self, cr):
141         return 'calendar collection-%d' % self.dir_id
142
143     def _get_dav_getctag(self, cr):
144         result = self.get_etag(cr)
145         return str(result)
146
147
148 class node_calendar(nodes.node_class):
149     our_type = 'collection'
150     PROPS = {
151             "http://calendarserver.org/ns/" : ('getctag'),
152             "urn:ietf:params:xml:ns:caldav" : (
153                     'calendar-description',
154                     'calendar-data',
155                     'calendar-home-set',
156                     'calendar-user-address-set',
157                     'schedule-inbox-URL',
158                     'schedule-outbox-URL',)}
159     M_NS = {
160            "DAV:" : '_get_dav',
161            "http://calendarserver.org/ns/" : '_get_dav',
162            "urn:ietf:params:xml:ns:caldav" : '_get_caldav'}
163
164     http_options = { 'DAV': ['calendar-access'] }
165
166     def __init__(self,path, parent, context, calendar):
167         super(node_calendar,self).__init__(path, parent,context)
168         self.calendar_id = calendar.id
169         self.mimetype = 'application/x-directory'
170         self.create_date = calendar.create_date
171         self.write_date = calendar.write_date or calendar.create_date
172         self.content_length = 0
173         self.displayname = calendar.name
174         self.cal_type = calendar.type
175
176     def _get_dav_getctag(self, cr):
177         result = self._get_ttag(cr) + ':' + str(time.time())
178         return str(result)
179
180     def match_dav_eprop(self, cr, match, ns, prop):
181         if ns == "DAV:" and prop == "getetag":
182             dirobj = self.context._dirobj
183             uid = self.context.uid
184             ctx = self.context.context.copy()
185             tem, dav_time = tuple(match.split(':'))
186             model, res_id = tuple(tem.split('_'))
187             model_obj = dirobj.pool.get(model)
188             model = model_obj.browse(cr, uid, res_id, context=ctx)
189             write_time = model.write_date or model.create_date
190             wtime = time.mktime(time.strptime(write_time,'%Y-%m-%d %H:%M:%S'))
191             if float(dav_time) == float(wtime):
192                 return True
193             return False
194         res = super(node_calendar, self).match_dav_eprop(cr, match, ns, prop)
195         return res
196
197
198     def get_domain(self, cr, filters):
199         res = []
200         dirobj = self.context._dirobj
201         uid = self.context.uid
202         ctx = self.context.context.copy()
203         ctx.update(self.dctx)
204         calendar_obj = dirobj.pool.get('basic.calendar')
205         if not filters:
206             return res
207         if filters.localName == 'calendar-query':
208             res = []
209             for filter_child in filters.childNodes:
210                 if filter_child.nodeType == filter_child.TEXT_NODE:
211                     continue
212                 if filter_child.localName == 'filter':
213                     for vcalendar_filter in filter_child.childNodes:
214                         if vcalendar_filter.nodeType == vcalendar_filter.TEXT_NODE:
215                             continue
216                         if vcalendar_filter.localName == 'comp-filter':
217                             if vcalendar_filter.getAttribute('name') == 'VCALENDAR':
218                                 for vevent_filter in vcalendar_filter.childNodes:
219                                     if vevent_filter.nodeType == vevent_filter.TEXT_NODE:
220                                         continue
221                                     if vevent_filter.localName == 'comp-filter':
222                                         if vevent_filter.getAttribute('name') == 'VEVENT':
223                                             res = [('type','=','vevent')]
224                                         if vevent_filter.getAttribute('name') == 'VTODO':
225                                             res = [('type','=','vtodo')]
226             return res
227         elif filters.localName == 'calendar-multiget':
228             names = []
229             for filter_child in filters.childNodes:
230                 if filter_child.nodeType == filter_child.TEXT_NODE:
231                     continue
232                 if filter_child.localName == 'href':
233                     if not filter_child.firstChild:
234                         continue
235                     uri = filter_child.firstChild.data
236                     caluri = uri.split('/')
237                     if len(caluri):
238                         caluri = caluri[-2]
239                         if caluri not in names : names.append(caluri)
240             res = [('name','in',names)]
241             return res
242         return res
243
244     def children(self, cr, domain=None):
245         return self._child_get(cr, domain=domain)
246
247     def child(self,cr, name, domain=None):
248         res = self._child_get(cr, name, domain=domain)
249         if res:
250             return res[0]
251         return None
252
253
254     def _child_get(self, cr, name=False, parent_id=False, domain=None):
255         dirobj = self.context._dirobj
256         uid = self.context.uid
257         ctx = self.context.context.copy()
258         ctx.update(self.dctx)
259         where = []
260         if name:
261             where.append(('id','=',int(name)))
262         if not domain:
263             domain = []
264         #for opr1, opt, opr2 in domain:
265         #    if opr1 == 'type' and opr2 != self.cal_type:
266         #        return []
267
268         fil_obj = dirobj.pool.get('basic.calendar')
269         ids = fil_obj.search(cr, uid, domain)
270         res = []
271         if self.calendar_id in ids:
272             res = fil_obj.get_calendar_objects(cr, uid, [self.calendar_id], self, domain=where, context=ctx)
273         return res
274
275
276
277     def get_dav_props(self, cr):
278         return self.PROPS
279
280     def get_dav_eprop(self,cr, ns, propname):
281         if self.M_NS.has_key(ns):
282             prefix = self.M_NS[ns]
283         else:
284             print "No namespace:",ns, "( for prop:", propname,")"
285             return None
286         propname = propname.replace('-','_')
287         mname = prefix + "_" + propname
288         if not hasattr(self, mname):
289             return None
290
291         try:
292             m = getattr(self, mname)
293             r = m(cr)
294             return r
295         except AttributeError, e:
296             print 'Property %s not supported' % propname
297             print "Exception:", e
298         return None
299
300
301     def create_child(self,cr,path,data):
302         """ API function to create a child file object and node
303             Return the node_* created
304         """
305         return self.set_data(cr, data)
306
307
308     def set_data(self, cr, data, fil_obj = None):
309         uid = self.context.uid
310         calendar_obj = self.context._dirobj.pool.get('basic.calendar')
311         return calendar_obj.import_cal(cr, uid, base64.encodestring(data), self.calendar_id)
312
313     def get_data_len(self, cr, fil_obj = None):
314         return self.content_length
315
316
317     def _get_ttag(self,cr):
318         return 'calendar-%d' % (self.calendar_id,)
319
320
321
322     def get_etag(self, cr):
323         """ Get a tag, unique per object + modification.
324
325             see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """
326         return self._get_ttag(cr) + ':' + self._get_wtag(cr)
327
328     def _get_wtag(self, cr):
329         """ Return the modification time as a unique, compact string """
330         if self.write_date:
331             wtime = time.mktime(time.strptime(self.write_date, '%Y-%m-%d %H:%M:%S'))
332         else: wtime = time.time()
333         return str(wtime)
334
335     def rmcol(self, cr):
336         return False
337
338
339 class res_node_calendar(nodes.node_class):
340     our_type = 'file'
341     PROPS = {
342             "http://calendarserver.org/ns/" : ('getctag'),
343             "urn:ietf:params:xml:ns:caldav" : (
344                     'calendar-description',
345                     'calendar-data',
346                     'calendar-home-set',
347                     'calendar-user-address-set',
348                     'schedule-inbox-URL',
349                     'schedule-outbox-URL',)}
350     M_NS = {
351            "http://calendarserver.org/ns/" : '_get_dav',
352            "urn:ietf:params:xml:ns:caldav" : '_get_caldav'}
353
354     http_options = { 'DAV': ['calendar-access'] }
355
356     def __init__(self,path, parent, context, res_obj, res_model=None, res_id=None):
357         super(res_node_calendar,self).__init__(path, parent, context)
358         self.mimetype = 'text/calendar'
359         self.create_date = parent.create_date
360         self.write_date = parent.write_date or parent.create_date
361         self.calendar_id = hasattr(parent, 'calendar_id') and parent.calendar_id or False
362         if res_obj:
363             if not self.calendar_id: self.calendar_id = res_obj.id
364             self.create_date = res_obj.create_date
365             self.write_date = res_obj.write_date or res_obj.create_date
366             self.displayname = res_obj.name
367
368         self.content_length = 0
369
370         self.model = res_model
371         self.res_id = res_id
372
373     def open(self, cr, mode=False):
374         uid = self.context.uid
375         if self.type in ('collection','database'):
376             return False
377         s = StringIO.StringIO(self.get_data(cr))
378         s.name = self
379         return s
380
381
382
383     def get_dav_props(self, cr):
384         return self.PROPS
385
386     def get_dav_eprop(self,cr, ns, propname):
387         if self.M_NS.has_key(ns):
388             prefix = self.M_NS[ns]
389         else:
390             print "No namespace:",ns, "( for prop:", propname,")"
391             return None
392         propname = propname.replace('-','_')
393         mname = prefix + "_" + propname
394         if not hasattr(self, mname):
395             return None
396
397         try:
398             m = getattr(self, mname)
399             r = m(cr)
400             return r
401         except AttributeError, e:
402             print 'Property %s not supported' % propname
403             print "Exception:", e
404         return None
405
406
407     def get_data(self, cr, fil_obj = None):
408         uid = self.context.uid
409         calendar_obj = self.context._dirobj.pool.get('basic.calendar')
410         context = self.context.context.copy()
411         context.update({'model': self.model, 'res_id':self.res_id})
412         res = calendar_obj.export_cal(cr, uid, [self.calendar_id], context=context)
413         return res
414
415     def get_data_len(self, cr, fil_obj = None):
416         return self.content_length
417
418     def set_data(self, cr, data, fil_obj = None):
419         uid = self.context.uid
420         calendar_obj = self.context._dirobj.pool.get('basic.calendar')
421         return calendar_obj.import_cal(cr, uid, base64.encodestring(data), self.calendar_id)
422
423     def _get_ttag(self,cr):
424         res = False
425         if self.model and self.res_id:
426             res = '%s_%d' % (self.model, self.res_id)
427         elif self.calendar_id:
428             res = '%d' % (self.calendar_id)
429         return res
430
431
432
433     def _get_caldav_calendar_data(self, cr):
434         return self.get_data(cr)
435
436
437     def _get_caldav_calendar_description(self, cr):
438         uid = self.context.uid
439         calendar_obj = self.context._dirobj.pool.get('basic.calendar')
440         ctx = self.context.context.copy()
441         ctx.update(self.dctx)
442         calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
443         return calendar.description
444
445
446     def _get_caldav_calendar_home_set(self, cr):
447         import xml.dom.minidom
448         import urllib
449         uid = self.context.uid
450         ctx = self.context.context.copy()
451         ctx.update(self.dctx)
452         doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None)
453
454         calendar_obj = self.context._dirobj.pool.get('basic.calendar')
455         calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
456         huri = doc.createTextNode(urllib.quote('/%s/%s' % (cr.dbname, calendar.collection_id.name)))
457         href = doc.documentElement
458         href.tagName = 'D:href'
459         href.appendChild(huri)
460         return href
461
462     def _get_caldav_calendar_user_address_set(self, cr):
463         import xml.dom.minidom
464         dirobj = self.context._dirobj
465         uid = self.context.uid
466         ctx = self.context.context.copy()
467         ctx.update(self.dctx)
468         user_obj = self.context._dirobj.pool.get('res.users')
469         user = user_obj.browse(cr, uid, uid, context=ctx)
470         doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None)
471         href = doc.documentElement
472         href.tagName = 'D:href'
473         huri = doc.createTextNode('MAILTO:' + user.email)
474         href.appendChild(huri)
475         return href
476
477
478     def _get_caldav_schedule_inbox_URL(self, cr):
479         import xml.dom.minidom
480         import urllib
481         uid = self.context.uid
482         ctx = self.context.context.copy()
483         ctx.update(self.dctx)
484         calendar_obj = self.context._dirobj.pool.get('basic.calendar')
485         calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
486         res = '%s/%s' %(calendar.name, calendar.collection_id.name)
487         doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None)
488         href = doc.documentElement
489         href.tagName = 'D:href'
490         huri = doc.createTextNode(urllib.quote('/%s/%s' % (cr.dbname, res)))
491         href.appendChild(huri)
492         return href
493
494
495     def rm(self, cr):
496         uid = self.context.uid
497         res = False
498         if self.type in ('collection','database'):
499             return False
500         if self.model and self.res_id:
501             document_obj = self.context._dirobj.pool.get(self.model)
502             if document_obj:
503                 res = False
504                 #res = document_obj.unlink(cr, uid, [self.res_id]) #TOFIX
505
506         return res
507
508
509
510     def _get_caldav_schedule_outbox_URL(self, cr):
511         return self._get_caldav_schedule_inbox_URL(cr)
512
513
514     def get_etag(self, cr):
515         """ Get a tag, unique per object + modification.
516
517             see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """
518         return self._get_ttag(cr) + ':' + self._get_wtag(cr)
519
520     def _get_wtag(self, cr):
521         """ Return the modification time as a unique, compact string """
522         if self.write_date:
523             wtime = time.mktime(time.strptime(self.write_date, '%Y-%m-%d %H:%M:%S'))
524         else: wtime = time.time()
525         return str(wtime)
526
527 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4