# Xandikos # Copyright (C) 2016-2017 Jelmer Vernooij , et al. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; version 3 # of the License or (at your option) any later version of # the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. """Calendar synchronisation. See https://tools.ietf.org/html/rfc6578 """ import itertools import urllib.parse from xandikos import webdav ET = webdav.ET class SyncToken(object): """A sync token wrapper.""" def __init__(self, token): self.token = token def aselement(self): ret = ET.Element('{DAV:}sync-token') ret.text = self.token return ret class SyncCollectionReporter(webdav.Reporter): """sync-collection reporter implementation. See https://tools.ietf.org/html/rfc6578, section 3.2. """ name = '{DAV:}sync-collection' @webdav.multistatus def report(self, environ, request_body, resources_by_hrefs, properties, href, resource, depth): old_token = None sync_level = None limit = None requested = None for el in request_body: if el.tag == '{DAV:}sync-token': old_token = el.text elif el.tag == '{DAV:}sync-level': sync_level = el.text elif el.tag == '{DAV:}limit': limit = el.text elif el.tag == '{DAV:}prop': requested = list(el) else: raise webdav.BadRequestError('unknown tag %s' % el.tag) # TODO(jelmer): Implement sync_level infinite if sync_level not in ("1", ): raise webdav.BadRequestError( "sync level %r unsupported" % sync_level) new_token = resource.get_sync_token() try: diff_iter = resource.iter_differences_since(old_token, new_token) except NotImplementedError: yield webdav.Status( href, '403 Forbidden', error=ET.Element('{DAV:}sync-traversal-supported')) return if limit is not None: try: [nresults_el] = list(limit) except ValueError: raise webdav.BadRequestError( 'Invalid number of subelements in limit') try: nresults = int(nresults_el.text) except ValueError: raise webdav.BadRequestError( 'nresults not a number') diff_iter = itertools.islice(diff_iter, nresults) for (name, old_resource, new_resource) in diff_iter: propstat = [] if new_resource is None: for prop in requested: propstat.append( webdav.PropStatus('404 Not Found', None, ET.Element(prop.tag))) else: for prop in requested: if old_resource is not None: old_propstat = webdav.get_property_from_element( href, old_resource, properties, prop) else: old_propstat = None new_propstat = webdav.get_property_from_element( href, new_resource, properties, prop) if old_propstat != new_propstat: propstat.append(new_propstat) yield webdav.Status( urllib.parse.urljoin(webdav.ensure_trailing_slash(href), name), propstat=propstat) yield SyncToken(new_token) class SyncTokenProperty(webdav.Property): """sync-token property. See https://tools.ietf.org/html/rfc6578, section 4 """ name = '{DAV:}sync-token' resource_type = webdav.COLLECTION_RESOURCE_TYPE in_allprops = False live = True def get_value(self, href, resource, el): el.text = resource.get_sync_token()