# Copyright 2018 Therp BV # Copyright 2019-2020 initOS GmbH # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). import base64 import os import time from odoo.http import request try: from radicale.storage import BaseCollection, BaseStorage from radicale.item import Item, get_etag from radicale import types except ImportError: BaseCollection = None Item = None get_etag = None class BytesPretendingToBeString(bytes): # radicale expects a string as file content, so we provide the str # functions needed def encode(self, encoding): return self class FileItem(Item): """this item tricks radicalev into serving a plain file""" @property def name(self): return 'VCARD' def serialize(self): return BytesPretendingToBeString(base64.b64decode(self.item.datas)) @property def etag(self): return get_etag(self.item.datas.decode('ascii')) class Storage(BaseStorage): @classmethod @types.contextmanager def acquire_lock(cls, mode, user=None): """We have a database for that""" yield @classmethod def discover(cls, path, depth=None): depth = int(depth or "0") components = cls._split_path(path) collection = Collection(path) collection.logger = collection.collection.get_logger() if len(components) > 2: # TODO: this probably better should happen in some dav.collection # function if collection.collection.dav_type == 'files' and depth: for href in collection.list(): yield collection.get(href) return yield collection.get(path) return yield collection if depth and len(components) == 1: for collection in request.env['dav.collection'].search([]): yield cls('/'.join(components + ['/%d' % collection.id])) if depth and len(components) == 2: for href in collection.list(): yield collection.get(href) @classmethod def _split_path(cls, path): return list(filter( None, os.path.normpath(path or '').strip('/').split('/') )) @classmethod def create_collection(cls, href, collection=None, props=None): return Collection(href) class Collection(BaseCollection): @classmethod def _split_path(cls, path): return list(filter( None, os.path.normpath(path or '').strip('/').split('/') )) @property def env(self): return request.env @property def last_modified(self): return self._odoo_to_http_datetime(self.collection.create_date) @property def path(self): return '/'.join(self.path_components) or '/' def __init__(self, path): self.path_components = self._split_path(path) self._path = '/'.join(self.path_components) or '/' self.collection = self.env['dav.collection'] if len(self.path_components) >= 2 and str( self.path_components[1] ).isdigit(): self.collection = self.env['dav.collection'].browse(int( self.path_components[1] )) def _odoo_to_http_datetime(self, value): return time.strftime( '%a, %d %b %Y %H:%M:%S GMT', value.timetuple(), ) def get_meta(self, key=None): if key is None: return {} elif key == 'tag': return self.collection.tag elif key == 'D:displayname': return self.collection.display_name elif key == 'C:supported-calendar-component-set': return 'VTODO,VEVENT,VJOURNAL' elif key == 'C:calendar-home-set': return None elif key == 'D:principal-URL': return None elif key == 'ICAL:calendar-color': # TODO: set in dav.collection return '#48c9f4' elif key == 'C:calendar-description': return self.collection.name self.logger.warning('unsupported metadata %s', key) def get_multi(self, hrefs): return [self.collection.dav_get(self, href) for href in hrefs] def upload(self, href, vobject_item): return self.collection.dav_upload(self, href, vobject_item) def delete(self, href): return self.collection.dav_delete(self, self._split_path(href)) def get_all(self): return self.collection.dav_list(self, self.path_components)