mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-04-27 14:57:41 +00:00
Handle UID conflicts during sync
This commit is contained in:
parent
aad6d23ed6
commit
ccc3dee28b
3 changed files with 68 additions and 14 deletions
|
|
@ -18,6 +18,8 @@ Version 0.4.1
|
||||||
it should try to create collections.
|
it should try to create collections.
|
||||||
- The old config values ``True``, ``False``, ``on``, ``off`` and ``None`` are
|
- The old config values ``True``, ``False``, ``on``, ``off`` and ``None`` are
|
||||||
now invalid.
|
now invalid.
|
||||||
|
- UID conflicts are now properly handled instead of ignoring one item. Card-
|
||||||
|
and CalDAV servers are already supposed to take care of those though.
|
||||||
|
|
||||||
Version 0.4.0
|
Version 0.4.0
|
||||||
=============
|
=============
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ import pytest
|
||||||
import vdirsyncer.exceptions as exceptions
|
import vdirsyncer.exceptions as exceptions
|
||||||
from vdirsyncer.storage.base import Item
|
from vdirsyncer.storage.base import Item
|
||||||
from vdirsyncer.storage.memory import MemoryStorage
|
from vdirsyncer.storage.memory import MemoryStorage
|
||||||
from vdirsyncer.sync import BothReadOnly, StorageEmpty, SyncConflict, sync
|
from vdirsyncer.sync import BothReadOnly, IdentConflict, StorageEmpty, \
|
||||||
|
SyncConflict, sync
|
||||||
|
|
||||||
from . import assert_item_equals, normalize_item
|
from . import assert_item_equals, normalize_item
|
||||||
|
|
||||||
|
|
@ -264,3 +265,20 @@ def test_readonly():
|
||||||
assert len(status) == 2 and a.has(href_a) and not b.has(href_a)
|
assert len(status) == 2 and a.has(href_a) and not b.has(href_a)
|
||||||
sync(a, b, status)
|
sync(a, b, status)
|
||||||
assert len(status) == 1 and not a.has(href_a) and not b.has(href_a)
|
assert len(status) == 1 and not a.has(href_a) and not b.has(href_a)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('sync_inbetween', (True, False))
|
||||||
|
def test_ident_conflict(sync_inbetween):
|
||||||
|
a = MemoryStorage()
|
||||||
|
b = MemoryStorage()
|
||||||
|
status = {}
|
||||||
|
href_a, etag_a = a.upload(Item(u'UID:aaa'))
|
||||||
|
href_b, etag_b = a.upload(Item(u'UID:bbb'))
|
||||||
|
if sync_inbetween:
|
||||||
|
sync(a, b, status)
|
||||||
|
|
||||||
|
a.update(href_a, Item(u'UID:xxx'), etag_a)
|
||||||
|
a.update(href_b, Item(u'UID:xxx'), etag_b)
|
||||||
|
|
||||||
|
with pytest.raises(IdentConflict):
|
||||||
|
sync(a, b, status)
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,27 @@ class SyncConflict(SyncError):
|
||||||
href_b = None
|
href_b = None
|
||||||
|
|
||||||
|
|
||||||
|
class IdentConflict(SyncError):
|
||||||
|
'''
|
||||||
|
Multiple items on the same storage have the same UID.
|
||||||
|
|
||||||
|
:param storage: The affected storage.
|
||||||
|
:param hrefs: List of affected hrefs on `storage`.
|
||||||
|
'''
|
||||||
|
storage = None
|
||||||
|
_hrefs = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hrefs(self):
|
||||||
|
return self._hrefs
|
||||||
|
|
||||||
|
@hrefs.setter
|
||||||
|
def hrefs(self, val):
|
||||||
|
val = set(val)
|
||||||
|
assert len(val) > 1
|
||||||
|
self._hrefs = val
|
||||||
|
|
||||||
|
|
||||||
class StorageEmpty(SyncError):
|
class StorageEmpty(SyncError):
|
||||||
'''
|
'''
|
||||||
One storage unexpectedly got completely empty between two synchronizations.
|
One storage unexpectedly got completely empty between two synchronizations.
|
||||||
|
|
@ -61,7 +82,23 @@ class BothReadOnly(SyncError):
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
def _prepare_idents(storage, other_storage, href_to_status):
|
def _prefetch(storage, rv, hrefs):
|
||||||
|
if rv is None:
|
||||||
|
rv = {}
|
||||||
|
if not hrefs:
|
||||||
|
return rv
|
||||||
|
|
||||||
|
for href, item, etag in storage.get_multi(hrefs):
|
||||||
|
props = rv[href]
|
||||||
|
props['item'] = item
|
||||||
|
props['ident'] = item.ident
|
||||||
|
if props['etag'] != etag:
|
||||||
|
raise SyncError('Etag changed during sync.')
|
||||||
|
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
def _prepare_hrefs(storage, other_storage, href_to_status):
|
||||||
hrefs = {}
|
hrefs = {}
|
||||||
download = []
|
download = []
|
||||||
for href, etag in storage.list():
|
for href, etag in storage.list():
|
||||||
|
|
@ -75,21 +112,18 @@ def _prepare_idents(storage, other_storage, href_to_status):
|
||||||
download.append(href)
|
download.append(href)
|
||||||
|
|
||||||
_prefetch(storage, hrefs, download)
|
_prefetch(storage, hrefs, download)
|
||||||
return dict((x['ident'], x) for href, x in iteritems(hrefs))
|
return hrefs
|
||||||
|
|
||||||
|
|
||||||
def _prefetch(storage, rv, hrefs):
|
def _prepare_idents(storage, other_storage, href_to_status):
|
||||||
if rv is None:
|
hrefs = _prepare_hrefs(storage, other_storage, href_to_status)
|
||||||
rv = {}
|
|
||||||
if not hrefs:
|
|
||||||
return rv
|
|
||||||
|
|
||||||
for href, item, etag in storage.get_multi(hrefs):
|
rv = {}
|
||||||
props = rv[href]
|
for href, props in iteritems(hrefs):
|
||||||
props['item'] = item
|
other_props = rv.setdefault(props['ident'], props)
|
||||||
props['ident'] = item.ident
|
if other_props != props:
|
||||||
if props['etag'] != etag:
|
raise IdentConflict(storage=storage,
|
||||||
raise SyncError('Etag changed during sync.')
|
hrefs=[props['href'], other_props['href']])
|
||||||
|
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue