From 5c3cf27f2fa72cd64c54e6241f10b86a089d357a Mon Sep 17 00:00:00 2001 From: "Harry (Open ERP)" Date: Wed, 21 Apr 2010 19:04:02 +0530 Subject: [PATCH] [IMP] caldav bzr revid: hmo@tinyerp.com-20100421133402-191nwwj3nohx6zas --- addons/caldav/caldav_node.py | 62 ++++++++++++++---- addons/caldav/calendar.py | 86 ++++++++++++++++--------- addons/document/nodes.py | 10 ++- addons/document_webdav/dav_fs.py | 19 +++++- addons/document_webdav/webdav_server.py | 82 ++++++++++++++++++++++- 5 files changed, 208 insertions(+), 51 deletions(-) diff --git a/addons/caldav/caldav_node.py b/addons/caldav/caldav_node.py index d747078081d..81135751464 100644 --- a/addons/caldav/caldav_node.py +++ b/addons/caldav/caldav_node.py @@ -58,11 +58,28 @@ class node_calendar_collection(nodes.node_dir): M_NS = { "http://calendarserver.org/ns/" : '_get_dav', } - + headers = None def get_dav_props(self, cr): return self.PROPS - def get_dav_eprop(self,cr, ns, propname): + def match_dav_eprop(self, cr, match, ns, prop): + if ns == "DAV:" and prop == "getetag": + dirobj = self.context._dirobj + uid = self.context.uid + ctx = self.context.context.copy() + tem, dav_time = tuple(match.split(':')) + title, cal_id, model, res_id = tuple(tem.split('-')) + model_obj = dirobj.pool.get(model) + model = model_obj.browse(cr, uid, res_id, context=ctx) + write_time = model.write_date or model.create_date + wtime = time.mktime(time.strptime(write_time,'%Y-%m-%d %H:%M:%S')) + if dav_time <= wtime: + return True + return False + res = super(node_calendar_collection, self).match_dav_eprop(cr, match, ns, prop) + return res + + def get_dav_eprop(self,cr, ns, propname): if self.M_NS.has_key(ns): prefix = self.M_NS[ns] else: @@ -86,6 +103,19 @@ class node_calendar_collection(nodes.node_dir): def _file_get(self,cr, nodename=False): return [] + def set_data(self, cr, data, fil_obj = None): + res = None + uid = self.context.uid + calendar_obj = self.context._dirobj.pool.get('basic.calendar') + if self.headers and self.headers.has_key('If-Match'): + for match in self.headers['If-Match'].split(','): + if match != '*': + tem, dav_time = tuple(match.split(':')) + title, cal_id, model, res_id = tuple(tem.split('-')) + res = calendar_obj.import_cal(cr, uid, base64.encodestring(data), cal_id) + + return res + def get_domain(self, cr, filters): res = [] @@ -129,10 +159,11 @@ class node_calendar_collection(nodes.node_dir): caluri = uri.split('/') if len(caluri): caluri = caluri[-1] + caluri, res_id = tuple(caluri.split('_')) calendar = calendar_obj.name_search(cr, uid, caluri) if calendar: calendar_id, calendar_name = calendar[0] - ids.append(calendar_id) + if calendar_id not in ids: ids.append(calendar_id) return [('id', 'in', ids)] return res @@ -143,16 +174,15 @@ class node_calendar_collection(nodes.node_dir): ctx.update(self.dctx) where = [('collection_id','=',self.dir_id)] if name: + name, res_id = tuple(name.split('_')) + ctx.update({'res_id':res_id}) where.append(('name','=',name)) if not domain: domain = [] where = where + domain fil_obj = dirobj.pool.get('basic.calendar') ids = fil_obj.search(cr,uid,where,context=ctx) - res = [] - if ids: - for fil in fil_obj.browse(cr,uid,ids,context=ctx): - res.append(node_calendar(fil.name,self,self.context,fil)) + res = fil_obj.get_calendar_object(cr, uid, ids, parent=self, context=ctx) return res def _get_dav_owner(self, cr): @@ -198,11 +228,13 @@ class node_calendar(nodes.node_class): def __init__(self,path, parent, context, calendar): super(node_calendar,self).__init__(path, parent,context) self.calendar_id = calendar.id - self.mimetype = 'ics' + self.mimetype = 'text/calendar' self.create_date = calendar.create_date self.write_date = calendar.write_date or calendar.create_date self.content_length = 0 - self.displayname = calendar.name + self.displayname = calendar.name + self.model = False + self.res_id = False def open(self, cr, mode=False): uid = self.context.uid @@ -218,7 +250,7 @@ class node_calendar(nodes.node_class): def get_dav_props(self, cr): return self.PROPS - def get_dav_eprop(self,cr, ns, propname): + def get_dav_eprop(self,cr, ns, propname): if self.M_NS.has_key(ns): prefix = self.M_NS[ns] else: @@ -239,10 +271,12 @@ class node_calendar(nodes.node_class): return None - def get_data(self, cr, fil_obj = None): + def get_data(self, cr, fil_obj = None): uid = self.context.uid calendar_obj = self.context._dirobj.pool.get('basic.calendar') - res = calendar_obj.export_cal(cr, uid, [self.calendar_id]) + context = self.context.context.copy() + context.update({'model': self.model, 'res_id':self.res_id}) + res = calendar_obj.export_cal(cr, uid, [self.calendar_id], context=context) return res def get_data_len(self, cr, fil_obj = None): @@ -254,7 +288,7 @@ class node_calendar(nodes.node_class): return calendar_obj.import_cal(cr, uid, base64.encodestring(data), self.calendar_id) def _get_ttag(self,cr): - return 'calendar-%d' % self.calendar_id + return 'calendar-%d-%s-%d' % (self.calendar_id, self.model, self.res_id) @@ -322,7 +356,7 @@ class node_calendar(nodes.node_class): def _get_caldav_schedule_outbox_URL(self, cr): - return _get_caldav_schedule_inbox_URL(cr) + return self._get_caldav_schedule_inbox_URL(cr) def get_etag(self, cr): diff --git a/addons/caldav/calendar.py b/addons/caldav/calendar.py index 58a51c11c85..d657cc4e619 100644 --- a/addons/caldav/calendar.py +++ b/addons/caldav/calendar.py @@ -31,6 +31,7 @@ import pytz import re import tools import time +from caldav_node import node_calendar try: import vobject @@ -435,22 +436,34 @@ class Calendar(CalDAV, osv.osv): 'line_ids': fields.one2many('basic.calendar.lines', 'calendar_id', 'Calendar Lines'), 'create_date': fields.datetime('Created Date'), 'write_date': fields.datetime('Modifided Date'), - } + } - def get_calendar_object(self, cr, uid, uri, context=None): - if not uri: - return None - if len(uri) > 1: - return None - name, file_type = tuple(uri[0].split('.')) - res = self.name_search(cr, uid, name) - if not res: - return None - calendar_id, calendar_name = res[0] - calendar = self.browse(cr, uid, calendar_id) - return node_calendar(uri, context, calendar) + def get_calendar_object(self, cr, uid, ids, parent=None, context=None): + if not context: + context = {} + res = [] + ctx_res_id = context.get('res_id', None) + ctx_model = context.get('model', None) + for cal in self.browse(cr, uid, ids): + for line in cal.line_ids: + if ctx_model and ctx_model != line.object_id.model: + continue + if line.name in ('valarm', 'attendee'): + continue + domain = eval(line.domain) + if ctx_res_id: + domain += [('id','=',ctx_res_id)] + mod_obj = self.pool.get(line.object_id.model) + data_ids = mod_obj.search(cr, uid, domain, context=context) + for data_id in data_ids: + ctx = parent and parent.context or None + node = node_calendar('%s_%s' %(cal.name, data_id), parent, ctx, cal) + node.model = line.object_id.model + node.res_id = data_id + res.append(node) + return res - def export_cal(self, cr, uid, ids, vobj='vevent', context={}): + def export_cal(self, cr, uid, ids, vobj='vevent', context=None): """ Export Calendar @param self: The object pointer @param cr: the current row, from the database cursor, @@ -458,20 +471,28 @@ class Calendar(CalDAV, osv.osv): @param ids: List of calendar’s IDs @param context: A standard dictionary for contextual values """ - - cal = self.browse(cr, uid, ids[0]) + if not context: + context = {} + ctx_model = context.get('model', None) + ctx_res_id = context.get('res_id', None) ical = vobject.iCalendar() - for line in cal.line_ids: - if line.name in ('valarm', 'attendee'): - continue - mod_obj = self.pool.get(line.object_id.model) - data_ids = mod_obj.search(cr, uid, eval(line.domain), context=context) - datas = mod_obj.read(cr, uid, data_ids, context=context) - context.update({'model': line.object_id.model, - 'calendar_id': cal.id - }) - self.__attribute__ = get_attribute_mapping(cr, uid, line.name, context) - self.create_ics(cr, uid, datas, line.name, ical, context=context) + for cal in self.browse(cr, uid, ids): + for line in cal.line_ids: + if ctx_model and ctx_model != line.object_id.model: + continue + if line.name in ('valarm', 'attendee'): + continue + domain = eval(line.domain) + if ctx_res_id: + domain += [('id','=',ctx_res_id)] + mod_obj = self.pool.get(line.object_id.model) + data_ids = mod_obj.search(cr, uid, domain, context=context) + datas = mod_obj.read(cr, uid, data_ids, context=context) + context.update({'model': line.object_id.model, + 'calendar_id': cal.id + }) + self.__attribute__ = get_attribute_mapping(cr, uid, line.name, context) + self.create_ics(cr, uid, datas, line.name, ical, context=context) return ical.serialize() def import_cal(self, cr, uid, content, data_id=None, context=None): @@ -485,20 +506,21 @@ class Calendar(CalDAV, osv.osv): if not context: context = {} - vals = [] + vals = [] ical_data = base64.decodestring(content) parsedCal = vobject.readOne(ical_data) if not data_id: - data_id = self.search(cr, uid, [])[0] - cal = self.browse(cr, uid, data_id) + data_id = self.search(cr, uid, [])[0] + + cal = self.read(cr, uid, data_id, context=context)[0] cal_children = {} count = 0 - for line in cal.line_ids: + for line in self.pool.get('basic.calendar.lines').browse(cr, uid, cal['line_ids']): cal_children[line.name] = line.object_id.model for child in parsedCal.getChildren(): if child.name.lower() in cal_children: context.update({'model': cal_children[child.name.lower()], - 'calendar_id': cal.id + 'calendar_id': cal['id'] }) self.__attribute__ = get_attribute_mapping(cr, uid, child.name.lower(), context=context) val = self.parse_ics(cr, uid, child, cal_children=cal_children, context=context) diff --git a/addons/document/nodes.py b/addons/document/nodes.py index 26f6671f218..e74389dd0d4 100644 --- a/addons/document/nodes.py +++ b/addons/document/nodes.py @@ -152,6 +152,12 @@ class node_class(object): its capabilities """ return {} + def match_dav_eprop(self, cr, match, ns, prop): + res = self.get_dav_eprop(cr, ns, prop) + if res == match: + return True + return False + def get_dav_eprop(self,cr,ns,prop): return None @@ -259,8 +265,8 @@ class node_dir(node_database): def get_data(self,cr): res = '' - for child in self.children(cr): - res += child.get_data(cr) + for child in self.children(cr): + res += child.get_data(cr) return res diff --git a/addons/document_webdav/dav_fs.py b/addons/document_webdav/dav_fs.py index d8b369b5ecf..d8e654728e2 100644 --- a/addons/document_webdav/dav_fs.py +++ b/addons/document_webdav/dav_fs.py @@ -82,6 +82,20 @@ class openerp_dav_handler(dav_interface): def _get_dav_supportedlock(self, uri): raise DAV_NotFound + def match_prop(self, uri, match, ns, propname): + if uri[-1]=='/':uri=uri[:-1] + cr, uid, pool, dbname, uri2 = self.get_cr(uri) + if not dbname: + if cr: cr.close() + raise DAV_NotFound + node = self.uri2object(cr, uid, pool, uri2) + if not node: + cr.close() + raise DAV_NotFound + res = node.match_dav_eprop(cr, match, ns, propname) + cr.close() + return res + def get_prop(self, uri, ns, propname): """ return the value of a given property @@ -209,14 +223,14 @@ class openerp_dav_handler(dav_interface): return None return pool.get('document.directory').get_object(cr, uid, uri) - def get_data(self,uri): + def get_data(self,uri): self.parent.log_message('GET: %s' % uri) if uri[-1]=='/':uri=uri[:-1] cr, uid, pool, dbname, uri2 = self.get_cr(uri) try: if not dbname: raise DAV_Error, 409 - node = self.uri2object(cr,uid,pool, uri2) + node = self.uri2object(cr,uid,pool, uri2) if not node: raise DAV_NotFound(uri2) try: @@ -390,6 +404,7 @@ class openerp_dav_handler(dav_interface): node = self.uri2object(cr,uid,pool, uri2[:]) except: node = False + node.headers = self.parent.headers objname = uri2[-1] ext = objname.find('.') >0 and objname.split('.')[1] or False diff --git a/addons/document_webdav/webdav_server.py b/addons/document_webdav/webdav_server.py index 6cda2d172ef..5f88f90cf11 100644 --- a/addons/document_webdav/webdav_server.py +++ b/addons/document_webdav/webdav_server.py @@ -33,7 +33,10 @@ from dav_fs import openerp_dav_handler from tools.config import config from DAV.WebDAVServer import DAVRequestHandler from service.websrv_lib import HTTPDir,FixSendError - +import urlparse +import urllib +from string import atoi,split +from DAV.errors import * def OpenDAVConfig(**kw): class OpenDAV: @@ -81,6 +84,83 @@ class DAVHandler(FixSendError,DAVRequestHandler): def log_error(self, format, *args): netsvc.Logger().notifyChannel('xmlrpc',netsvc.LOG_WARNING,format % args) + def do_PUT(self): + dc=self.IFACE_CLASS + uri=urlparse.urljoin(self.get_baseuri(dc), self.path) + uri=urllib.unquote(uri) + # Handle If-Match + if self.headers.has_key('If-Match'): + test = False + etag = None + + for match in self.headers['If-Match'].split(','): + if match == '*': + if dc.exists(uri): + test = True + break + else: + if dc.match_prop(uri, match, "DAV:", "getetag"): + test = True + break + if not test: + self.send_status(412) + return + + # Handle If-None-Match + if self.headers.has_key('If-None-Match'): + test = True + etag = None + for match in self.headers['If-None-Match'].split(','): + if match == '*': + if dc.exists(uri): + test = False + break + else: + if dc.match_prop(uri, match, "DAV:", "getetag"): + test = False + break + if not test: + self.send_status(412) + return + + # Handle expect + expect = self.headers.get('Expect', '') + if (expect.lower() == '100-continue' and + self.protocol_version >= 'HTTP/1.1' and + self.request_version >= 'HTTP/1.1'): + self.send_status(100) + self._flush() + + # read the body + body=None + if self.headers.has_key("Content-Length"): + l=self.headers['Content-Length'] + body=self.rfile.read(atoi(l)) + + # locked resources are not allowed to be overwritten + if self._l_isLocked(uri): + return self.send_body(None, '423', 'Locked', 'Locked') + + ct=None + if self.headers.has_key("Content-Type"): + ct=self.headers['Content-Type'] + try: + location = dc.put(uri,body,ct) + except DAV_Error, (ec,dd): + return self.send_status(ec) + + headers = {} + if location: + headers['Location'] = location + + try: + etag = dc.get_prop(location or uri, "DAV:", "getetag") + headers['ETag'] = etag + except: + pass + + self.send_body(None, '201', 'Created', '', headers=headers) + try: from service.http_server import reg_http_service,OpenERPAuthProvider