[IMP] caldav
[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     headers = None
62     def get_dav_props(self, cr):                
63         return self.PROPS
64
65     def match_dav_eprop(self, cr, match, ns, prop):
66         if ns == "DAV:" and prop == "getetag":
67             dirobj = self.context._dirobj
68             uid = self.context.uid
69             ctx = self.context.context.copy()            
70             tem, dav_time = tuple(match.split(':'))
71             title, cal_id, model, res_id = tuple(tem.split('-'))
72             model_obj = dirobj.pool.get(model)
73             model = model_obj.browse(cr, uid, res_id, context=ctx)
74             write_time = model.write_date or model.create_date
75             wtime = time.mktime(time.strptime(write_time,'%Y-%m-%d %H:%M:%S'))            
76             if dav_time <= wtime:
77                 return True
78             return False
79         res = super(node_calendar_collection, self).match_dav_eprop(cr, match, ns, prop)
80         return res
81
82     def get_dav_eprop(self,cr, ns, propname):  
83         if self.M_NS.has_key(ns):
84             prefix = self.M_NS[ns]
85         else:
86             print "No namespace:",ns, "( for prop:", propname,")"
87             return None
88
89         mname = prefix + "_" + propname
90
91         if not hasattr(self, mname):
92             return None
93
94         try:
95             m = getattr(self, mname)
96             r = m(cr)
97             return r
98         except AttributeError, e:
99             print 'Property %s not supported' % propname
100             print "Exception:", e            
101         return None
102
103     def _file_get(self,cr, nodename=False):
104         return []   
105
106     def set_data(self, cr, data, fil_obj = None):
107         res = None
108         uid = self.context.uid
109         calendar_obj = self.context._dirobj.pool.get('basic.calendar')        
110         if self.headers and self.headers.has_key('If-Match'):
111             for match in self.headers['If-Match'].split(','):                
112                 if match != '*':                    
113                    tem, dav_time = tuple(match.split(':'))
114                    title, cal_id, model, res_id = tuple(tem.split('-'))
115                    res = calendar_obj.import_cal(cr, uid, base64.encodestring(data), cal_id)
116         
117         return res
118
119
120     def get_domain(self, cr, filters):        
121         res = []
122         dirobj = self.context._dirobj
123         uid = self.context.uid
124         ctx = self.context.context.copy()
125         ctx.update(self.dctx)
126         calendar_obj = dirobj.pool.get('basic.calendar')
127         if not filters:
128             return res
129         if filters.localName == 'calendar-query':      
130             res = []
131             for filter_child in filters.childNodes:
132                 if filter_child.nodeType == filter_child.TEXT_NODE:
133                     continue                
134                 if filter_child.localName == 'filter':                    
135                     for vcalendar_filter in filter_child.childNodes:
136                         if vcalendar_filter.nodeType == vcalendar_filter.TEXT_NODE:                            
137                             continue
138                         if vcalendar_filter.localName == 'comp-filter':
139                             if vcalendar_filter.getAttribute('name') == 'VCALENDAR':
140                                 for vevent_filter in vcalendar_filter.childNodes:
141                                     if vevent_filter.nodeType == vevent_filter.TEXT_NODE:
142                                         continue
143                                     if vevent_filter.localName == 'comp-filter':
144                                         if vevent_filter.getAttribute('name') == 'VEVENT':
145                                             res = [('type','=','vevent')]
146                                         if vevent_filter.getAttribute('name') == 'VTODO':
147                                             res = [('type','=','vtodo')]                               
148             
149             return res
150         elif filters.localName == 'calendar-multiget':
151             ids = []
152             for filter_child in filters.childNodes:
153                 if filter_child.nodeType == filter_child.TEXT_NODE:
154                     continue
155                 if filter_child.localName == 'href':
156                     if not filter_child.firstChild:
157                         continue
158                     uri = filter_child.firstChild.data  
159                     caluri = uri.split('/')
160                     if len(caluri):
161                         caluri = caluri[-1]
162                         caluri, res_id = tuple(caluri.split('_'))
163                         calendar = calendar_obj.name_search(cr, uid, caluri)
164                         if calendar:                            
165                             calendar_id, calendar_name = calendar[0]                            
166                             if calendar_id not in ids: ids.append(calendar_id)
167             return [('id', 'in', ids)]
168         return res
169
170     def _child_get(self, cr, name=False, parent_id=False, domain=None):
171         dirobj = self.context._dirobj
172         uid = self.context.uid
173         ctx = self.context.context.copy()
174         ctx.update(self.dctx)
175         where = [('collection_id','=',self.dir_id)]                              
176         if name:
177             name, res_id = tuple(name.split('_'))
178             ctx.update({'res_id':res_id})
179             where.append(('name','=',name))
180         if not domain:
181             domain = []       
182         where = where + domain
183         fil_obj = dirobj.pool.get('basic.calendar')        
184         ids = fil_obj.search(cr,uid,where,context=ctx)
185         res = fil_obj.get_calendar_object(cr, uid, ids, parent=self, context=ctx)        
186         return res
187
188     def _get_dav_owner(self, cr):
189         return False
190
191     
192     def get_etag(self, cr):
193         """ Get a tag, unique per object + modification.
194
195             see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """
196         return self._get_ttag(cr) + ':' + self._get_wtag(cr)
197
198     def _get_wtag(self, cr):
199         """ Return the modification time as a unique, compact string """
200         if self.write_date:
201             wtime = time.mktime(time.strptime(self.write_date, '%Y-%m-%d %H:%M:%S'))
202         else: wtime = time.time()
203         return str(wtime)
204
205     def _get_ttag(self, cr):
206         return 'calendar collection-%d' % self.dir_id
207
208     def _get_dav_getctag(self, cr):
209         result = self.get_etag(cr)        
210         return str(result)   
211         
212
213 class node_calendar(nodes.node_class):
214     our_type = 'file'
215     PROPS = {
216             "http://calendarserver.org/ns/" : ('getctag'),
217             "urn:ietf:params:xml:ns:caldav" : (
218                     'calendar-description',
219                     'calendar-data',
220                     'calendar-home-set',
221                     'calendar-user-address-set',
222                     'schedule-inbox-URL',
223                     'schedule-outbox-URL',)}
224     M_NS = { 
225            "http://calendarserver.org/ns/" : '_get_dav',
226            "urn:ietf:params:xml:ns:caldav" : '_get_caldav'} 
227
228     def __init__(self,path, parent, context, calendar):
229         super(node_calendar,self).__init__(path, parent,context)
230         self.calendar_id = calendar.id
231         self.mimetype = 'text/calendar'
232         self.create_date = calendar.create_date
233         self.write_date = calendar.write_date or calendar.create_date
234         self.content_length = 0
235         self.displayname = calendar.name
236         self.model = False
237         self.res_id = False
238          
239     def open(self, cr, mode=False):
240         uid = self.context.uid        
241         if self.type in ('collection','database'):
242             return False            
243         fobj = self.context._dirobj.pool.get('basic.calendar').browse(cr, uid, self.calendar_id, context=self.context.context)        
244         s = StringIO.StringIO(self.get_data(cr, fobj))        
245         s.name = self
246         return s           
247
248        
249    
250     def get_dav_props(self, cr):        
251         return self.PROPS
252
253     def get_dav_eprop(self,cr, ns, propname): 
254         if self.M_NS.has_key(ns):
255             prefix = self.M_NS[ns]
256         else:
257             print "No namespace:",ns, "( for prop:", propname,")"
258             return None
259         propname = propname.replace('-','_')
260         mname = prefix + "_" + propname
261         if not hasattr(self, mname):
262             return None
263
264         try:
265             m = getattr(self, mname)
266             r = m(cr)            
267             return r
268         except AttributeError, e:
269             print 'Property %s not supported' % propname
270             print "Exception:", e            
271         return None
272
273
274     def get_data(self, cr, fil_obj = None):         
275         uid = self.context.uid
276         calendar_obj = self.context._dirobj.pool.get('basic.calendar')
277         context = self.context.context.copy()   
278         context.update({'model': self.model, 'res_id':self.res_id})     
279         res = calendar_obj.export_cal(cr, uid, [self.calendar_id], context=context)        
280         return res
281
282     def get_data_len(self, cr, fil_obj = None):        
283         return self.content_length
284
285     def set_data(self, cr, data, fil_obj = None):
286         uid = self.context.uid
287         calendar_obj = self.context._dirobj.pool.get('basic.calendar')
288         return calendar_obj.import_cal(cr, uid, base64.encodestring(data), self.calendar_id)
289
290     def _get_ttag(self,cr):
291         return 'calendar-%d-%s-%d' % (self.calendar_id, self.model, self.res_id)
292
293
294     
295     def _get_caldav_calendar_data(self, cr):        
296         return self.get_data(cr)
297         
298
299     def _get_caldav_calendar_description(self, cr):
300         uid = self.context.uid
301         calendar_obj = self.context._dirobj.pool.get('basic.calendar')
302         ctx = self.context.context.copy()
303         ctx.update(self.dctx)        
304         calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
305         return calendar.description
306     
307
308     def _get_caldav_calendar_home_set(self, cr):
309         import xml.dom.minidom
310         import urllib       
311         uid = self.context.uid
312         ctx = self.context.context.copy()
313         ctx.update(self.dctx)
314         doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None)        
315         
316         calendar_obj = self.context._dirobj.pool.get('basic.calendar')             
317         calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
318         huri = doc.createTextNode(urllib.quote('/%s/%s' % (cr.dbname, calendar.collection_id.name)))
319         href = doc.documentElement
320         href.tagName = 'D:href'
321         href.appendChild(huri)
322         return href
323
324     def _get_caldav_calendar_user_address_set(self, cr):
325         import xml.dom.minidom
326         dirobj = self.context._dirobj
327         uid = self.context.uid
328         ctx = self.context.context.copy()
329         ctx.update(self.dctx)
330         user_obj = self.context._dirobj.pool.get('res.users')
331         user = user_obj.browse(cr, uid, uid, context=ctx)        
332         doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None)
333         href = doc.documentElement
334         href.tagName = 'D:href'
335         huri = doc.createTextNode('MAILTO:' + user.email)
336         href.appendChild(huri)
337         return href
338
339
340     def _get_caldav_schedule_inbox_URL(self, cr):
341         import xml.dom.minidom
342         import urllib        
343         uid = self.context.uid
344         ctx = self.context.context.copy()
345         ctx.update(self.dctx)
346         calendar_obj = self.context._dirobj.pool.get('basic.calendar')             
347         calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
348         res = '%s/%s' %(calendar.name, calendar.collection_id.name)
349         doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None)
350         href = doc.documentElement
351         href.tagName = 'D:href'
352         huri = doc.createTextNode(urllib.quote('/%s/%s' % (cr.dbname, res)))
353         href.appendChild(huri)
354         return href
355
356
357
358     def _get_caldav_schedule_outbox_URL(self, cr):
359         return self._get_caldav_schedule_inbox_URL(cr)
360     
361
362     def get_etag(self, cr):
363         """ Get a tag, unique per object + modification.
364
365             see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """
366         return self._get_ttag(cr) + ':' + self._get_wtag(cr)
367
368     def _get_wtag(self, cr):
369         """ Return the modification time as a unique, compact string """
370         if self.write_date:
371             wtime = time.mktime(time.strptime(self.write_date, '%Y-%m-%d %H:%M:%S'))
372         else: wtime = time.time()
373         return str(wtime)             
374 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4