mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-03-29 09:35:50 +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.
|
||||
- The old config values ``True``, ``False``, ``on``, ``off`` and ``None`` are
|
||||
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
|
||||
=============
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ import pytest
|
|||
import vdirsyncer.exceptions as exceptions
|
||||
from vdirsyncer.storage.base import Item
|
||||
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
|
||||
|
||||
|
|
@ -264,3 +265,20 @@ def test_readonly():
|
|||
assert len(status) == 2 and a.has(href_a) and not b.has(href_a)
|
||||
sync(a, b, status)
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
'''
|
||||
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 = {}
|
||||
download = []
|
||||
for href, etag in storage.list():
|
||||
|
|
@ -75,21 +112,18 @@ def _prepare_idents(storage, other_storage, href_to_status):
|
|||
download.append(href)
|
||||
|
||||
_prefetch(storage, hrefs, download)
|
||||
return dict((x['ident'], x) for href, x in iteritems(hrefs))
|
||||
return hrefs
|
||||
|
||||
|
||||
def _prefetch(storage, rv, hrefs):
|
||||
if rv is None:
|
||||
rv = {}
|
||||
if not hrefs:
|
||||
return rv
|
||||
def _prepare_idents(storage, other_storage, href_to_status):
|
||||
hrefs = _prepare_hrefs(storage, other_storage, href_to_status)
|
||||
|
||||
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.')
|
||||
rv = {}
|
||||
for href, props in iteritems(hrefs):
|
||||
other_props = rv.setdefault(props['ident'], props)
|
||||
if other_props != props:
|
||||
raise IdentConflict(storage=storage,
|
||||
hrefs=[props['href'], other_props['href']])
|
||||
|
||||
return rv
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue