diff --git a/tests/storage/__init__.py b/tests/storage/__init__.py index 751c933..9a09dd1 100644 --- a/tests/storage/__init__.py +++ b/tests/storage/__init__.py @@ -88,3 +88,7 @@ class StorageTests(object): assert not list(s.list()) s.upload(self._create_bogus_item('1')) assert list(s.list()) + + def test_discover(self): + # Too storage specific to implement in an abstract way + raise NotImplementedError() diff --git a/tests/storage/conftest.py b/tests/storage/conftest.py new file mode 100644 index 0000000..800e837 --- /dev/null +++ b/tests/storage/conftest.py @@ -0,0 +1,8 @@ +import pytest +import tempfile +import shutil + +@pytest.fixture +def class_tmpdir(request): + request.cls.tmpdir = x = tempfile.mkdtemp() + request.addfinalizer(lambda: shutil.rmtree(x)) diff --git a/tests/storage/dav/__init__.py b/tests/storage/dav/__init__.py index 9a61eb7..0290c63 100644 --- a/tests/storage/dav/__init__.py +++ b/tests/storage/dav/__init__.py @@ -18,6 +18,7 @@ import shutil import sys import os import urlparse +import pytest import mock from werkzeug.test import Client @@ -71,16 +72,14 @@ class Response(object): raise HTTPError(str(self.status_code)) +@pytest.mark.usefixtures('class_tmpdir') class DavStorageTests(StorageTests): '''hrefs are paths without scheme or netloc''' - tmpdir = None storage_class = None radicale_path = None patcher = None def _get_storage(self, **kwargs): - self.tmpdir = tempfile.mkdtemp() - do_the_radicale_dance(self.tmpdir) from radicale import Application app = Application() @@ -103,9 +102,6 @@ class DavStorageTests(StorageTests): def teardown_method(self, method): self.app = None - if self.tmpdir is not None: - shutil.rmtree(self.tmpdir) - self.tmpdir = None if self.patcher is not None: self.patcher.stop() self.patcher = None diff --git a/tests/storage/test_filesystem.py b/tests/storage/test_filesystem.py index 56e70a4..75e0761 100644 --- a/tests/storage/test_filesystem.py +++ b/tests/storage/test_filesystem.py @@ -10,19 +10,30 @@ from unittest import TestCase import tempfile +import pytest import shutil +import os from vdirsyncer.storage.filesystem import FilesystemStorage from . import StorageTests +@pytest.mark.usefixtures('class_tmpdir') class FilesystemStorageTests(TestCase, StorageTests): - tmpdir = None def _get_storage(self, **kwargs): - path = self.tmpdir = tempfile.mkdtemp() - return FilesystemStorage(path=path, fileext='.txt', **kwargs) + return FilesystemStorage(path=self.tmpdir, fileext='.txt', **kwargs) - def teardown_method(self, method): - if self.tmpdir is not None: - shutil.rmtree(self.tmpdir) - self.tmpdir = None + def test_discover(self): + paths = set() + for i, collection in enumerate('abcd'): + p = os.path.join(self.tmpdir, collection) + os.makedirs(os.path.join(self.tmpdir, collection)) + fname = os.path.join(p, 'asdf.txt') + with open(fname, 'w+') as f: + f.write(self._create_bogus_item(i).raw) + paths.add(p) + + storages = list(FilesystemStorage.discover(path=self.tmpdir, fileext='.txt')) + assert len(storages) == 4 + for s in storages: + assert s.path in paths diff --git a/tests/storage/test_memory.py b/tests/storage/test_memory.py index d52e7e7..5d42937 100644 --- a/tests/storage/test_memory.py +++ b/tests/storage/test_memory.py @@ -17,3 +17,6 @@ class MemoryStorageTests(TestCase, StorageTests): def _get_storage(self, **kwargs): return MemoryStorage(**kwargs) + + def test_discover(self): + '''This test doesn't make any sense here.''' diff --git a/vdirsyncer/storage/base.py b/vdirsyncer/storage/base.py index 24b1178..229b65c 100644 --- a/vdirsyncer/storage/base.py +++ b/vdirsyncer/storage/base.py @@ -33,6 +33,11 @@ class Storage(object): - HREF: Per-storage identifier of item, might be UID. - ETAG: Checksum of item, or something similar that changes when the object does. + + :param collection: If None, the given URL or path is already directly + referring to a collection. Otherwise it will be treated as a basepath + to many collections (e.g. a vdir) and the given collection name will be + looked for. ''' fileext = '.txt' _repr_attributes = () @@ -40,6 +45,12 @@ class Storage(object): def __init__(self, item_class=Item): self.item_class = item_class + @classmethod + def discover(cls, **kwargs): + '''Discover collections given a basepath to many collections. + :returns: Iterable of storages.''' + raise NotImplementedError() + def _get_href(self, uid): return uid + self.fileext diff --git a/vdirsyncer/storage/filesystem.py b/vdirsyncer/storage/filesystem.py index 913925c..494534b 100644 --- a/vdirsyncer/storage/filesystem.py +++ b/vdirsyncer/storage/filesystem.py @@ -25,12 +25,19 @@ class FilesystemStorage(Storage): ''' :param path: Absolute path to a *collection* inside a vdir. ''' + super(FilesystemStorage, self).__init__(**kwargs) self.path = expand_path(path) if collection is not None: self.path = os.path.join(self.path, collection) self.encoding = encoding self.fileext = fileext - super(FilesystemStorage, self).__init__(**kwargs) + + @classmethod + def discover(cls, path, **kwargs): + for collection in os.listdir(path): + s = cls(path=path, collection=collection, **kwargs) + if next(s.list(), None) is not None: + yield s def _get_filepath(self, href): return os.path.join(self.path, href)