diff --git a/tests/storage/__init__.py b/tests/storage/__init__.py index 66bffd5..cbbb6a5 100644 --- a/tests/storage/__init__.py +++ b/tests/storage/__init__.py @@ -63,6 +63,12 @@ class BaseStorageTests(object): def test_empty_get_multi(self, s): assert list(s.get_multi([])) == [] + def test_get_multi_duplicates(self, s, get_item): + href, etag = s.upload(get_item()) + (href2, item, etag2), = s.get_multi([href] * 2) + assert href2 == href + assert etag2 == etag + def test_upload_already_existing(self, s, get_item): item = get_item() s.upload(item) diff --git a/vdirsyncer/storage/base.py b/vdirsyncer/storage/base.py index 723584a..7a693de 100644 --- a/vdirsyncer/storage/base.py +++ b/vdirsyncer/storage/base.py @@ -9,7 +9,8 @@ from .. import exceptions -from vdirsyncer.utils.vobject import Item # noqa +from ..utils import uniq +from ..utils.vobject import Item # noqa class Storage(object): @@ -101,7 +102,7 @@ class Storage(object): raise NotImplementedError() def get_multi(self, hrefs): - '''Fetch multiple items. + '''Fetch multiple items. Duplicate hrefs must be ignored. Functionally similar to :py:meth:`get`, but might bring performance benefits on some storages when used cleverly. @@ -111,7 +112,7 @@ class Storage(object): items couldn't be found. :returns: iterable of (href, item, etag) ''' - for href in hrefs: + for href in uniq(hrefs): item, etag = self.get(href) yield href, item, etag diff --git a/vdirsyncer/storage/dav.py b/vdirsyncer/storage/dav.py index 08e8490..4882a55 100644 --- a/vdirsyncer/storage/dav.py +++ b/vdirsyncer/storage/dav.py @@ -16,6 +16,7 @@ from requests import session as requests_session from .base import Item, Storage from .http import USERAGENT, prepare_auth, prepare_verify from .. import exceptions, log, utils +from ..utils import uniq dav_logger = log.get(__name__) @@ -300,13 +301,12 @@ class DavStorage(Storage): return item, etag def get_multi(self, hrefs): - if not hrefs: - return () - hrefs = [self._normalize_href(href) for href in hrefs] - href_xml = [] - for href in hrefs: + for href in uniq(hrefs): href_xml.append('{}'.format(href)) + if not href_xml: + return () + data = self.get_multi_template.format(hrefs='\n'.join(href_xml)) response = self.session.request( 'REPORT', diff --git a/vdirsyncer/utils/__init__.py b/vdirsyncer/utils/__init__.py index 07cdc64..977d197 100644 --- a/vdirsyncer/utils/__init__.py +++ b/vdirsyncer/utils/__init__.py @@ -57,6 +57,14 @@ def split_sequence(s, f): return a, b +def uniq(s): + d = set() + for x in s: + if x not in d: + d.add(x) + yield x + + def parse_config_value(value): if value in ('on', 'yes'): logger.warning('{} is deprecated for the config, please use true.\n'