diff --git a/tests/storage/__init__.py b/tests/storage/__init__.py index 141212c..b72655a 100644 --- a/tests/storage/__init__.py +++ b/tests/storage/__init__.py @@ -236,6 +236,8 @@ class StorageTests(object): def test_metadata(self, requires_metadata, s): try: + s.set_meta('color', None) + assert s.get_meta('color') is None s.set_meta('color', u'#ff0000') assert s.get_meta('color') == u'#ff0000' except exceptions.UnsupportedMetadataError: diff --git a/tests/test_metasync.py b/tests/test_metasync.py new file mode 100644 index 0000000..e0bbaec --- /dev/null +++ b/tests/test_metasync.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- + +import pytest + +import vdirsyncer.exceptions as exceptions +from vdirsyncer.storage.base import Item +from vdirsyncer.storage.memory import MemoryStorage +from vdirsyncer.metasync import metasync, MetaSyncConflict + +from . import assert_item_equals, blow_up, normalize_item + +def test_irrelevant_status(): + a = MemoryStorage() + b = MemoryStorage() + status = {'foo': 'bar'} + + metasync(a, b, status, keys=()) + assert not status + +def test_basic(): + a = MemoryStorage() + b = MemoryStorage() + status = {} + + a.set_meta('foo', 'bar') + metasync(a, b, status, keys=['foo']) + assert a.get_meta('foo') == b.get_meta('foo') == 'bar' + + a.set_meta('foo', 'baz') + metasync(a, b, status, keys=['foo']) + assert a.get_meta('foo') == b.get_meta('foo') == 'baz' + + b.set_meta('foo', None) + metasync(a, b, status, keys=['foo']) + assert a.get_meta('foo') is b.get_meta('foo') is None + + +def test_conflict(): + a = MemoryStorage() + b = MemoryStorage() + status = {} + a.set_meta('foo', 'bar') + b.set_meta('foo', 'baz') + + with pytest.raises(MetaSyncConflict): + metasync(a, b, status, keys=['foo']) + + assert a.get_meta('foo') == 'bar' + assert b.get_meta('foo') == 'baz' + assert not status + + +def test_conflict_same_content(): + a = MemoryStorage() + b = MemoryStorage() + status = {} + a.set_meta('foo', 'bar') + b.set_meta('foo', 'bar') + + metasync(a, b, status, keys=['foo']) + assert a.get_meta('foo') == b.get_meta('foo') == status['foo'] == 'bar' + + +@pytest.mark.parametrize('wins', 'ab') +def test_conflict_x_wins(wins): + a = MemoryStorage() + b = MemoryStorage() + status = {} + a.set_meta('foo', 'bar') + b.set_meta('foo', 'baz') + + metasync(a, b, status, keys=['foo'], + conflict_resolution='a wins' if wins == 'a' else 'b wins') + + assert a.get_meta('foo') == b.get_meta('foo') == status['foo'] == ( + 'bar' if wins == 'a' else 'baz' + ) diff --git a/vdirsyncer/metasync.py b/vdirsyncer/metasync.py index c0d88b2..db7ce74 100644 --- a/vdirsyncer/metasync.py +++ b/vdirsyncer/metasync.py @@ -11,7 +11,7 @@ class MetaSyncConflict(MetaSyncError): key = None -def metasync(storage_a, storage_b, status, keys, conflict_resolution): +def metasync(storage_a, storage_b, status, keys, conflict_resolution=None): def _a_to_b(): logger.info(u'Copying {} to {}'.format(key, storage_b)) storage_b.set_meta(key, a) @@ -24,7 +24,7 @@ def metasync(storage_a, storage_b, status, keys, conflict_resolution): def _resolve_conflict(): if a == b: - pass + status[key] = a elif conflict_resolution is None: raise MetaSyncConflict(key=key) elif conflict_resolution == 'a wins': diff --git a/vdirsyncer/storage/filesystem.py b/vdirsyncer/storage/filesystem.py index 543d863..7b844ae 100644 --- a/vdirsyncer/storage/filesystem.py +++ b/vdirsyncer/storage/filesystem.py @@ -185,7 +185,7 @@ class FilesystemStorage(Storage): fpath = os.path.join(self.path, key) try: with open(fpath, 'rb') as f: - return f.read().decode(self.encoding) + return f.read().decode(self.encoding) or None except IOError as e: if e.errno == errno.ENOENT: return None @@ -193,6 +193,7 @@ class FilesystemStorage(Storage): raise def set_meta(self, key, value): + value = value or u'' assert isinstance(value, text_type) fpath = os.path.join(self.path, key) with atomic_write(fpath, mode='wb', overwrite=True) as f: diff --git a/vdirsyncer/storage/memory.py b/vdirsyncer/storage/memory.py index 2775745..6a01c00 100644 --- a/vdirsyncer/storage/memory.py +++ b/vdirsyncer/storage/memory.py @@ -66,7 +66,7 @@ class MemoryStorage(Storage): del self.items[href] def get_meta(self, key): - return self.metadata[key] + return self.metadata.get(key) def set_meta(self, key, value): self.metadata[key] = value