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