diff --git a/tests/storage/dav/__init__.py b/tests/storage/dav/__init__.py index b3e8c45..4fdb23f 100644 --- a/tests/storage/dav/__init__.py +++ b/tests/storage/dav/__init__.py @@ -1,4 +1,3 @@ - # -*- coding: utf-8 -*- ''' vdirsyncer.tests.storage.dav @@ -7,43 +6,3 @@ :copyright: (c) 2014 Markus Unterwaditzer :license: MIT, see LICENSE for more details. ''' - -import os -import pytest - -from .. import StorageTests -import vdirsyncer.exceptions as exceptions -from vdirsyncer.storage.base import Item -import requests.exceptions - - -dav_server = os.environ.get('DAV_SERVER', '').strip() or 'radicale_filesystem' -if dav_server.startswith('radicale_'): - from ._radicale import ServerMixin -elif dav_server == 'owncloud': - from ._owncloud import ServerMixin -else: - raise RuntimeError('{} is not a known DAV server.'.format(dav_server)) - -try: - import radicale - radicale_version = radicale.VERSION - del radicale -except ImportError: - radicale_version = None - - -pytestmark = pytest.mark.xfail( - dav_server == 'radicale_database' and radicale_version == '0.8', - reason='Database storage of Radicale 0.8 is broken.') - - -class DavStorageTests(ServerMixin, StorageTests): - def test_dav_broken_item(self): - item = Item(u'UID:1') - s = self._get_storage() - try: - s.upload(item) - except (exceptions.Error, requests.exceptions.HTTPError): - pass - assert not list(s.list()) diff --git a/tests/storage/dav/test_carddav.py b/tests/storage/dav/test_carddav.py deleted file mode 100644 index 85bf63d..0000000 --- a/tests/storage/dav/test_carddav.py +++ /dev/null @@ -1,34 +0,0 @@ - -# -*- coding: utf-8 -*- -''' - vdirsyncer.tests.storage.test_carddav - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - :copyright: (c) 2014 Markus Unterwaditzer - :license: MIT, see LICENSE for more details. -''' - -from vdirsyncer.storage.dav.carddav import CarddavStorage -from . import DavStorageTests, pytestmark - - -VCARD_TEMPLATE = u'''BEGIN:VCARD -VERSION:3.0 -FN:Cyrus Daboo -N:Daboo;Cyrus -ADR;TYPE=POSTAL:;2822 Email HQ;Suite 2821;RFCVille;PA;15213;USA -EMAIL;TYPE=INTERNET;TYPE=PREF:cyrus@example.com -NICKNAME:me -NOTE:Example VCard. -ORG:Self Employed -TEL;TYPE=WORK;TYPE=VOICE:412 605 0499 -TEL;TYPE=FAX:412 605 0705 -URL:http://www.example.com -UID:{uid} -X-SOMETHING:{r} -END:VCARD''' - - -class TestCarddavStorage(DavStorageTests): - storage_class = CarddavStorage - item_template = VCARD_TEMPLATE diff --git a/tests/storage/dav/test_caldav.py b/tests/storage/dav/test_main.py similarity index 59% rename from tests/storage/dav/test_caldav.py rename to tests/storage/dav/test_main.py index 9c2dd25..0aa7594 100644 --- a/tests/storage/dav/test_caldav.py +++ b/tests/storage/dav/test_main.py @@ -1,19 +1,59 @@ # -*- coding: utf-8 -*- ''' - vdirsyncer.tests.storage.test_caldav - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + vdirsyncer.tests.storage.dav.test_main + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: (c) 2014 Markus Unterwaditzer :license: MIT, see LICENSE for more details. ''' - +import os import pytest -import requests.exceptions -from vdirsyncer.storage.dav.caldav import CaldavStorage + +from .. import StorageTests import vdirsyncer.exceptions as exceptions -from . import DavStorageTests, pytestmark +from vdirsyncer.storage.base import Item +from vdirsyncer.storage.dav import CaldavStorage, CarddavStorage +import requests.exceptions + + +dav_server = os.environ.get('DAV_SERVER', '').strip() or 'radicale_filesystem' +if dav_server.startswith('radicale_'): + from ._radicale import ServerMixin +elif dav_server == 'owncloud': + from ._owncloud import ServerMixin +else: + raise RuntimeError('{} is not a known DAV server.'.format(dav_server)) + +try: + import radicale + radicale_version = radicale.VERSION + del radicale +except ImportError: + radicale_version = None + + +pytestmark = pytest.mark.xfail( + dav_server == 'radicale_database' and radicale_version == '0.8', + reason='Database storage of Radicale 0.8 is broken.') + + +VCARD_TEMPLATE = u'''BEGIN:VCARD +VERSION:3.0 +FN:Cyrus Daboo +N:Daboo;Cyrus +ADR;TYPE=POSTAL:;2822 Email HQ;Suite 2821;RFCVille;PA;15213;USA +EMAIL;TYPE=INTERNET;TYPE=PREF:cyrus@example.com +NICKNAME:me +NOTE:Example VCard. +ORG:Self Employed +TEL;TYPE=WORK;TYPE=VOICE:412 605 0499 +TEL;TYPE=FAX:412 605 0705 +URL:http://www.example.com +UID:{uid} +X-SOMETHING:{r} +END:VCARD''' TASK_TEMPLATE = u'''BEGIN:VCALENDAR @@ -43,13 +83,24 @@ UID:{uid} END:VEVENT END:VCALENDAR''' - templates = { + 'VCARD': VCARD_TEMPLATE, 'VEVENT': EVENT_TEMPLATE, 'VTODO': TASK_TEMPLATE } +class DavStorageTests(ServerMixin, StorageTests): + def test_dav_broken_item(self): + item = Item(u'UID:1') + s = self._get_storage() + try: + s.upload(item) + except (exceptions.Error, requests.exceptions.HTTPError): + pass + assert not list(s.list()) + + class TestCaldavStorage(DavStorageTests): storage_class = CaldavStorage @@ -90,3 +141,8 @@ class TestCaldavStorage(DavStorageTests): a = self.storage_class(item_types='VTODO,VEVENT', **kw) b = self.storage_class(item_types=('VTODO', 'VEVENT'), **kw) assert a.item_types == b.item_types == ('VTODO', 'VEVENT') + + +class TestCarddavStorage(DavStorageTests): + storage_class = CarddavStorage + item_template = VCARD_TEMPLATE diff --git a/vdirsyncer/storage/__init__.py b/vdirsyncer/storage/__init__.py index 9f6c3a8..e915f5b 100644 --- a/vdirsyncer/storage/__init__.py +++ b/vdirsyncer/storage/__init__.py @@ -12,8 +12,8 @@ :license: MIT, see LICENSE for more details. ''' -from .dav.caldav import CaldavStorage -from .dav.carddav import CarddavStorage +from .dav import CaldavStorage +from .dav import CarddavStorage from .filesystem import FilesystemStorage from .http import HttpStorage diff --git a/vdirsyncer/storage/dav/base.py b/vdirsyncer/storage/dav.py similarity index 62% rename from vdirsyncer/storage/dav/base.py rename to vdirsyncer/storage/dav.py index eeb17be..574c513 100644 --- a/vdirsyncer/storage/dav/base.py +++ b/vdirsyncer/storage/dav.py @@ -1,14 +1,14 @@ # -*- coding: utf-8 -*- ''' - vdirsyncer.storage.dav.base + vdirsyncer.storage.dav ~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: (c) 2014 Markus Unterwaditzer, Christian Geier and contributors :license: MIT, see LICENSE for more details. ''' -from ..base import Item -from ..http import HttpStorageBase +from .base import Item +from .http import HttpStorageBase import vdirsyncer.exceptions as exceptions import vdirsyncer.log as log import requests @@ -18,6 +18,9 @@ from lxml import etree dav_logger = log.get('storage.dav') +CALDAV_DT_FORMAT = '%Y%m%dT%H%M%SZ' +CONFIG_DT_FORMAT = '%Y-%m-%d' + class DavStorage(HttpStorageBase): @@ -228,3 +231,122 @@ class DavStorage(HttpStorageBase): '{DAV:}prop').find('{DAV:}getetag').text href = self._normalize_href(element.find('{DAV:}href').text) yield href, etag + + + + +class CaldavStorage(DavStorage): + + fileext = '.ics' + item_mimetype = 'text/calendar' + dav_header = 'calendar-access' + leif_class = 'CalDiscover' + + start_date = None + end_date = None + + get_multi_template = ''' + + + + + + {hrefs} + ''' + + get_multi_data_query = '{urn:ietf:params:xml:ns:caldav}calendar-data' + + def __init__(self, start_date=None, end_date=None, + item_types=('VTODO', 'VEVENT'), **kwargs): + ''' + :param start_date: Start date of timerange to show, default -inf. + :param end_date: End date of timerange to show, default +inf. + :param item_types: The item types to show from the server. Dependent on + server functionality, no clientside validation of results. This + currently only affects the `list` method, but this shouldn't cause + problems in the normal usecase. + ''' + super(CaldavStorage, self).__init__(**kwargs) + if isinstance(item_types, str): + item_types = [x.strip() for x in item_types.split(',')] + self.item_types = tuple(item_types) + if (start_date is None) != (end_date is None): + raise ValueError('If start_date is given, ' + 'end_date has to be given too.') + elif start_date is not None and end_date is not None: + namespace = dict(datetime.__dict__) + namespace['start_date'] = self.start_date = \ + (eval(start_date, namespace) if isinstance(start_date, str) + else start_date) + self.end_date = \ + (eval(end_date, namespace) if isinstance(end_date, str) + else end_date) + + self._list_template = self._get_list_template() + + def _get_list_template(self): + data = ''' + + + + + + + + {caldavfilter} + + + + ''' + start = self.start_date + end = self.end_date + caldavfilter = '' + if start is not None and end is not None: + start = start.strftime(CALDAV_DT_FORMAT) + end = end.strftime(CALDAV_DT_FORMAT) + caldavfilter = ('' + .format(start=start, end=end)) + return data.format(caldavfilter=caldavfilter, component='{item_type}') + + def list(self): + hrefs = set() + for t in self.item_types: + xml = self._list_template.format(item_type=t) + for href, etag in self._list(xml): + assert href not in hrefs + hrefs.add(href) + yield href, etag + + +class CarddavStorage(DavStorage): + + fileext = '.vcf' + item_mimetype = 'text/vcard' + dav_header = 'addressbook' + leif_class = 'CardDiscover' + + get_multi_template = ''' + + + + + + {hrefs} + ''' + + get_multi_data_query = '{urn:ietf:params:xml:ns:carddav}address-data' + + def list(self): + return self._list(''' + + + + + + + + ''') diff --git a/vdirsyncer/storage/dav/__init__.py b/vdirsyncer/storage/dav/__init__.py deleted file mode 100644 index dd12acd..0000000 --- a/vdirsyncer/storage/dav/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# -*- coding: utf-8 -*- -''' - vdirsyncer.storage.dav - ~~~~~~~~~~~~~~~~~~~~~~ - - :copyright: (c) 2014 Markus Unterwaditzer - :license: MIT, see LICENSE for more details. -''' diff --git a/vdirsyncer/storage/dav/caldav.py b/vdirsyncer/storage/dav/caldav.py deleted file mode 100644 index 90a6306..0000000 --- a/vdirsyncer/storage/dav/caldav.py +++ /dev/null @@ -1,101 +0,0 @@ -# -*- coding: utf-8 -*- -''' - vdirsyncer.storage.dav.caldav - ~~~~~~~~~~~~~~~~~~~~~~~~~ - - Original version from khal: https://github.com/geier/khal - - :copyright: (c) 2014 Markus Unterwaditzer, Christian Geier and contributors - :license: MIT, see LICENSE for more details. -''' - -from .base import DavStorage -import datetime - -CALDAV_DT_FORMAT = '%Y%m%dT%H%M%SZ' -CONFIG_DT_FORMAT = '%Y-%m-%d' - - -class CaldavStorage(DavStorage): - - fileext = '.ics' - item_mimetype = 'text/calendar' - dav_header = 'calendar-access' - leif_class = 'CalDiscover' - - start_date = None - end_date = None - - get_multi_template = ''' - - - - - - {hrefs} - ''' - - get_multi_data_query = '{urn:ietf:params:xml:ns:caldav}calendar-data' - - def __init__(self, start_date=None, end_date=None, - item_types=('VTODO', 'VEVENT'), **kwargs): - ''' - :param start_date: Start date of timerange to show, default -inf. - :param end_date: End date of timerange to show, default +inf. - :param item_types: The item types to show from the server. Dependent on - server functionality, no clientside validation of results. This - currently only affects the `list` method, but this shouldn't cause - problems in the normal usecase. - ''' - super(CaldavStorage, self).__init__(**kwargs) - if isinstance(item_types, str): - item_types = [x.strip() for x in item_types.split(',')] - self.item_types = tuple(item_types) - if (start_date is None) != (end_date is None): - raise ValueError('If start_date is given, ' - 'end_date has to be given too.') - elif start_date is not None and end_date is not None: - namespace = dict(datetime.__dict__) - namespace['start_date'] = self.start_date = \ - (eval(start_date, namespace) if isinstance(start_date, str) - else start_date) - self.end_date = \ - (eval(end_date, namespace) if isinstance(end_date, str) - else end_date) - - self._list_template = self._get_list_template() - - def _get_list_template(self): - data = ''' - - - - - - - - {caldavfilter} - - - - ''' - start = self.start_date - end = self.end_date - caldavfilter = '' - if start is not None and end is not None: - start = start.strftime(CALDAV_DT_FORMAT) - end = end.strftime(CALDAV_DT_FORMAT) - caldavfilter = ('' - .format(start=start, end=end)) - return data.format(caldavfilter=caldavfilter, component='{item_type}') - - def list(self): - hrefs = set() - for t in self.item_types: - xml = self._list_template.format(item_type=t) - for href, etag in self._list(xml): - assert href not in hrefs - hrefs.add(href) - yield href, etag diff --git a/vdirsyncer/storage/dav/carddav.py b/vdirsyncer/storage/dav/carddav.py deleted file mode 100644 index 2194b6b..0000000 --- a/vdirsyncer/storage/dav/carddav.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- -''' - vdirsyncer.storage.dav.carddav - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Original version from pycarddav: https://github.com/geier/pycarddav - - :copyright: (c) 2014 Markus Unterwaditzer, Christian Geier and contributors - :license: MIT, see LICENSE for more details. -''' - -from .base import DavStorage - - -class CarddavStorage(DavStorage): - - fileext = '.vcf' - item_mimetype = 'text/vcard' - dav_header = 'addressbook' - leif_class = 'CardDiscover' - - get_multi_template = ''' - - - - - - {hrefs} - ''' - - get_multi_data_query = '{urn:ietf:params:xml:ns:carddav}address-data' - - def list(self): - return self._list(''' - - - - - - - - ''')