[FIX] caldav: support caldav property
[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 from document import nodes
28 import StringIO
29
30 class node_database(nodes.node_database):
31     def _child_get(self, cr, name=False, parent_id=False, domain=None):
32         dirobj = self.context._dirobj
33         uid = self.context.uid
34         ctx = self.context.context.copy()
35         ctx.update(self.dctx)
36         if not domain:
37             domain = []
38         domain2 = domain + [('calendar_collection','=', False)]
39         res = super(node_database, self)._child_get(cr, name=name, parent_id=parent_id, domain=domain2)
40         where = [('parent_id','=',parent_id)]
41         domain2 = domain + [('calendar_collection','=', True)]
42         if name:
43             where.append(('name','=',name))
44         if domain2:
45             where += domain2
46
47         where2 = where + [('type', '=', 'directory')]
48         ids = dirobj.search(cr, uid, where2, context=ctx)
49         for dirr in dirobj.browse(cr,uid,ids,context=ctx):
50             res.append(node_calendar_collection(dirr.name,self,self.context,dirr))
51         return res
52
53 class node_calendar_collection(nodes.node_dir):
54     DAV_PROPS = {
55             "http://calendarserver.org/ns/" : ('getctag',),
56             }
57     DAV_M_NS = {
58            "http://calendarserver.org/ns/" : '_get_dav',
59            }
60
61     http_options = { 'DAV': ['calendar-access'] }
62
63     def _file_get(self,cr, nodename=False):
64         return []
65
66     def _child_get(self, cr, name=False, parent_id=False, domain=None):
67         dirobj = self.context._dirobj
68         uid = self.context.uid
69         ctx = self.context.context.copy()
70         ctx.update(self.dctx)
71         where = [('collection_id','=',self.dir_id)]
72         ext = False
73         if name and name.endswith('.ics'):
74             name = name[:-4]
75             ext = True
76         if name:
77             where.append(('name','=',name))
78         if not domain:
79             domain = []
80         where = where + domain
81         fil_obj = dirobj.pool.get('basic.calendar')
82         ids = fil_obj.search(cr,uid,where,context=ctx)
83         res = []
84         for cal in fil_obj.browse(cr, uid, ids, context=ctx):
85             if (not name) or not ext:
86                 res.append(node_calendar(cal.name, self, self.context, cal))
87             if (not name) or ext:
88                 res.append(res_node_calendar(cal.name+'.ics', self, self.context, cal))
89             # May be both of them!
90         return res
91
92     def _get_dav_owner(self, cr):
93         # Todo?
94         return False
95
96     def _get_ttag(self, cr):
97         return 'calen-dir-%d' % self.dir_id
98
99     def _get_dav_getctag(self, cr):
100         result = self.get_etag(cr)
101         return str(result)
102
103 class node_calendar(nodes.node_class):
104     our_type = 'collection'
105     DAV_PROPS = {
106             "DAV:": ('principal-collection-set'),
107             "http://cal.me.com/_namespace/" : ('user-state'),
108             "http://calendarserver.org/ns/" : (
109                     'dropbox-home-URL',
110                     'notification-URL',
111                     'getctag',),
112             'http://groupdav.org/': ('resourcetype',),
113             "urn:ietf:params:xml:ns:caldav" : (
114                     'calendar-description',
115                     'calendar-data',
116                     'calendar-home-set',
117                     'calendar-user-address-set',
118                     'schedule-inbox-URL',
119                     'schedule-outbox-URL',)}
120     DAV_M_NS = {
121            "DAV:" : '_get_dav',
122            "http://cal.me.com/_namespace/": '_get_dav', 
123            'http://groupdav.org/': '_get_gdav',
124            "http://calendarserver.org/ns/" : '_get_dav',
125            "urn:ietf:params:xml:ns:caldav" : '_get_caldav'}
126
127     http_options = { 'DAV': ['calendar-access'] }
128
129     def __init__(self,path, parent, context, calendar):
130         super(node_calendar,self).__init__(path, parent,context)
131         self.calendar_id = calendar.id
132         self.mimetype = 'application/x-directory'
133         self.create_date = calendar.create_date
134         self.write_date = calendar.write_date or calendar.create_date
135         self.content_length = 0
136         self.displayname = calendar.name
137         self.cal_type = calendar.type
138
139     def _get_dav_getctag(self, cr):
140         result = self._get_ttag(cr) + ':' + str(time.time())
141         return str(result)
142
143     def _get_dav_dropbox_home_URL(self, cr):
144         import xml.dom.minidom
145         import urllib
146         uid = self.context.uid
147         ctx = self.context.context.copy()
148         ctx.update(self.dctx)
149         calendar_obj = self.context._dirobj.pool.get('basic.calendar')
150         calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
151         res = '%s/%s' %(calendar.collection_id.name, calendar.name)
152         url = urllib.quote('/%s/%s' % (cr.dbname, res))
153         return url
154     
155     def _get_dav_notification_URL(self, cr):
156         import xml.dom.minidom
157         import urllib
158         uid = self.context.uid
159         ctx = self.context.context.copy()
160         ctx.update(self.dctx)
161         calendar_obj = self.context._dirobj.pool.get('basic.calendar')
162         calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
163         res = '%s/%s' %(calendar.collection_id.name, calendar.name)
164         url = urllib.quote('/%s/%s' % (cr.dbname, res))
165         return url
166
167     def _get_dav_user_state(self, cr):
168         #TODO
169         return True
170
171
172     def get_dav_resourcetype(self, cr):
173         res = [ ('collection', 'DAV:'),
174                 (str(self.cal_type + '-collection'), 'http://groupdav.org/'),
175                 ('calendar', 'urn:ietf:params:xml:ns:caldav') ]
176         return res
177
178     def get_domain(self, cr, filters):
179         # TODO: doc.
180         res = []
181         if not filters:
182             return res
183         if filters.localName == 'calendar-query':
184             res = []
185             for filter_child in filters.childNodes:
186                 if filter_child.nodeType == filter_child.TEXT_NODE:
187                     continue
188                 if filter_child.localName == 'filter':
189                     for vcalendar_filter in filter_child.childNodes:
190                         if vcalendar_filter.nodeType == vcalendar_filter.TEXT_NODE:
191                             continue
192                         if vcalendar_filter.localName == 'comp-filter':
193                             if vcalendar_filter.getAttribute('name') == 'VCALENDAR':
194                                 for vevent_filter in vcalendar_filter.childNodes:
195                                     if vevent_filter.nodeType == vevent_filter.TEXT_NODE:
196                                         continue
197                                     if vevent_filter.localName == 'comp-filter':
198                                         if vevent_filter.getAttribute('name') == 'VEVENT':
199                                             res = [('type','=','vevent')]
200                                         if vevent_filter.getAttribute('name') == 'VTODO':
201                                             res = [('type','=','vtodo')]
202             return res
203         elif filters.localName == 'calendar-multiget':
204             names = []
205             for filter_child in filters.childNodes:
206                 if filter_child.nodeType == filter_child.TEXT_NODE:
207                     continue
208                 if filter_child.localName == 'href':
209                     if not filter_child.firstChild:
210                         continue
211                     uri = filter_child.firstChild.data
212                     caluri = uri.split('/')
213                     if len(caluri):
214                         caluri = caluri[-2]
215                         if caluri not in names : names.append(caluri)
216             res = [('name','in',names)]
217             return res
218         return res
219
220     def children(self, cr, domain=None):
221         return self._child_get(cr, domain=domain)
222
223     def child(self,cr, name, domain=None):
224         res = self._child_get(cr, name, domain=domain)
225         if res:
226             return res[0]
227         return None
228
229
230     def _child_get(self, cr, name=False, parent_id=False, domain=None):
231         dirobj = self.context._dirobj
232         uid = self.context.uid
233         ctx = self.context.context.copy()
234         ctx.update(self.dctx)
235         where = []
236         if name:
237             if name.endswith('.ics'):
238                 name = name[:-4]
239             try:
240                 where.append(('id','=',int(name)))
241             except ValueError:
242                 # if somebody requests any other name than the ones we
243                 # generate (non-numeric), it just won't exist
244                 # FIXME: however, this confuses Evolution (at least), which
245                 # thinks the .ics node hadn't been saved.
246                 return []
247
248         if not domain:
249             domain = []
250
251         fil_obj = dirobj.pool.get('basic.calendar')
252         ids = fil_obj.search(cr, uid, domain)
253         res = []
254         if self.calendar_id in ids:
255             res = fil_obj.get_calendar_objects(cr, uid, [self.calendar_id], self, domain=where, context=ctx)
256         return res
257
258     def create_child(self, cr, path, data):
259         """ API function to create a child file object and node
260             Return the node_* created
261         """
262         # we ignore the path, it will be re-generated automatically
263         fil_obj = self.context._dirobj.pool.get('basic.calendar')
264         ctx = self.context.context.copy()
265         ctx.update(self.dctx)
266         uid = self.context.uid
267
268         res = self.set_data(cr, data)
269
270         if res and len(res):
271             # We arbitrarily construct only the first node of the data
272             # that have been imported. ICS may have had more elements,
273             # but only one node can be returned here.
274             assert isinstance(res[0], (int, long))
275             fnodes = fil_obj.get_calendar_objects(cr, uid, [self.calendar_id], self,
276                     domain=[('id','=',res[0])], context=ctx)
277             return fnodes[0]
278         return None
279
280
281     def set_data(self, cr, data, fil_obj = None):
282         uid = self.context.uid
283         calendar_obj = self.context._dirobj.pool.get('basic.calendar')
284         res = calendar_obj.import_cal(cr, uid, data, self.calendar_id)
285         return res
286
287     def get_data_len(self, cr, fil_obj = None):
288         return self.content_length
289
290     def _get_ttag(self,cr):
291         return 'calendar-%d' % (self.calendar_id,)
292
293     def rmcol(self, cr):
294         return False
295
296     
297     def _get_caldav_calendar_data(self, cr):
298         res = []
299         for child in self.children(cr):
300             res.append(child._get_caldav_calendar_data(cr))
301         return res
302
303     def _get_caldav_calendar_description(self, cr):
304         uid = self.context.uid
305         calendar_obj = self.context._dirobj.pool.get('basic.calendar')
306         ctx = self.context.context.copy()
307         ctx.update(self.dctx)
308         calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
309         return calendar.description
310
311     def _get_dav_principal_collection_set(self, uri):
312         import urllib
313         uid = self.context.uid
314         ctx = self.context.context.copy()
315         ctx.update(self.dctx)
316         calendar_obj = self.context._dirobj.pool.get('basic.calendar')
317         calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
318         res = '%s/%s' %(calendar.collection_id.name, calendar.name)
319         doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None)
320         href = doc.documentElement
321         href.tagName = 'D:href'
322         huri = doc.createTextNode(urllib.quote('/%s/%s' % (cr.dbname, res)))
323         href.appendChild(huri)
324         return href
325
326     def _get_caldav_calendar_home_set(self, cr):
327         import xml.dom.minidom
328         import urllib
329         uid = self.context.uid
330         ctx = self.context.context.copy()
331         ctx.update(self.dctx)
332         doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None)
333
334         calendar_obj = self.context._dirobj.pool.get('basic.calendar')
335         calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
336         huri = doc.createTextNode(urllib.quote('/%s/%s' % (cr.dbname, calendar.collection_id.name)))
337         href = doc.documentElement
338         href.tagName = 'D:href'
339         href.appendChild(huri)
340         return href
341
342     def _get_caldav_calendar_user_address_set(self, cr):
343         import xml.dom.minidom
344         dirobj = self.context._dirobj
345         uid = self.context.uid
346         ctx = self.context.context.copy()
347         ctx.update(self.dctx)
348         user_obj = self.context._dirobj.pool.get('res.users')
349         user = user_obj.browse(cr, uid, uid, context=ctx)
350         doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None)
351         href = doc.documentElement
352         href.tagName = 'D:href'
353         huri = doc.createTextNode('MAILTO:' + str(user.email) or str(user.name))
354         href.appendChild(huri)
355         return href
356
357     def _get_caldav_schedule_outbox_URL(self, cr):
358         return self._get_caldav_schedule_inbox_URL(cr)
359
360     def _get_caldav_schedule_inbox_URL(self, cr):
361         import xml.dom.minidom
362         import urllib
363         uid = self.context.uid
364         ctx = self.context.context.copy()
365         ctx.update(self.dctx)
366         calendar_obj = self.context._dirobj.pool.get('basic.calendar')
367         calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
368         res = '%s/%s' %(calendar.collection_id.name, calendar.name)
369         doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None)
370         href = doc.documentElement
371         href.tagName = 'D:href'
372         huri = doc.createTextNode(urllib.quote('/%s/%s' % (cr.dbname, res)))
373         href.appendChild(huri)
374         return href
375
376
377 class res_node_calendar(nodes.node_class):
378     our_type = 'file'
379     DAV_PROPS = {
380             "http://calendarserver.org/ns/" : ('getctag'),
381             "urn:ietf:params:xml:ns:caldav" : (
382                     'calendar-description',
383                     'calendar-data',
384                     'calendar-home-set',
385                     'calendar-user-address-set',
386                     'schedule-inbox-URL',
387                     'schedule-outbox-URL',)}
388     DAV_M_NS = {
389            "http://calendarserver.org/ns/" : '_get_dav',
390            "urn:ietf:params:xml:ns:caldav" : '_get_caldav'}
391
392     http_options = { 'DAV': ['calendar-access'] }
393
394     def __init__(self,path, parent, context, res_obj, res_model=None, res_id=None):
395         super(res_node_calendar,self).__init__(path, parent, context)
396         self.mimetype = 'text/calendar'
397         self.create_date = parent.create_date
398         self.write_date = parent.write_date or parent.create_date
399         self.calendar_id = hasattr(parent, 'calendar_id') and parent.calendar_id or False
400         if res_obj:
401             if not self.calendar_id: self.calendar_id = res_obj.id
402             pr = res_obj.perm_read()[0]
403             self.create_date = pr.get('create_date')
404             self.write_date = pr.get('write_date') or pr.get('create_date')
405             self.displayname = res_obj.name
406
407         self.content_length = 0
408
409         self.model = res_model
410         self.res_id = res_id
411
412     def open(self, cr, mode=False):
413         uid = self.context.uid
414         if self.type in ('collection','database'):
415             return False
416         s = StringIO.StringIO(self.get_data(cr))
417         s.name = self
418         return s
419
420     def get_data(self, cr, fil_obj = None):
421         uid = self.context.uid
422         calendar_obj = self.context._dirobj.pool.get('basic.calendar')
423         context = self.context.context.copy()
424         context.update({'model': self.model, 'res_id':self.res_id})
425         res = calendar_obj.export_cal(cr, uid, [self.calendar_id], context=context)
426         return res
427   
428     def _get_caldav_calendar_data(self, cr):
429         return self.get_data(cr)
430
431     def get_data_len(self, cr, fil_obj = None):
432         return self.content_length
433
434     def set_data(self, cr, data, fil_obj = None):
435         uid = self.context.uid
436         calendar_obj = self.context._dirobj.pool.get('basic.calendar')
437         res =  calendar_obj.import_cal(cr, uid, data, self.calendar_id)
438         return res
439
440     def _get_ttag(self,cr):
441         res = False
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)
446         return res
447
448
449     def rm(self, cr):
450         uid = self.context.uid
451         res = False
452         if self.type in ('collection','database'):
453             return False
454         if self.model and self.res_id:
455             document_obj = self.context._dirobj.pool.get(self.model)
456             if document_obj:
457                 res =  document_obj.unlink(cr, uid, [self.res_id])
458
459         return res
460
461    
462
463 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4