mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-04-27 14:57:41 +00:00
Unify terminology obj => item
This commit is contained in:
parent
2b81ba4ffd
commit
7f01b22642
7 changed files with 75 additions and 73 deletions
|
|
@ -44,9 +44,9 @@ class StorageTests(object):
|
||||||
assert isinstance(href, (str, unicode))
|
assert isinstance(href, (str, unicode))
|
||||||
assert isinstance(etag, (str, unicode))
|
assert isinstance(etag, (str, unicode))
|
||||||
assert s.has(href)
|
assert s.has(href)
|
||||||
obj, etag2 = s.get(href)
|
item, etag2 = s.get(href)
|
||||||
assert etag == etag2
|
assert etag == etag2
|
||||||
assert 'UID:{}'.format(obj.uid) in obj.raw
|
assert 'UID:{}'.format(item.uid) in item.raw
|
||||||
|
|
||||||
def test_upload_already_existing(self):
|
def test_upload_already_existing(self):
|
||||||
s = self._get_storage()
|
s = self._get_storage()
|
||||||
|
|
@ -81,10 +81,10 @@ class StorageTests(object):
|
||||||
|
|
||||||
def test_wrong_etag(self):
|
def test_wrong_etag(self):
|
||||||
s = self._get_storage()
|
s = self._get_storage()
|
||||||
obj = self._create_bogus_item(1)
|
item = self._create_bogus_item(1)
|
||||||
href, etag = s.upload(obj)
|
href, etag = s.upload(item)
|
||||||
with pytest.raises(exceptions.PreconditionFailed):
|
with pytest.raises(exceptions.PreconditionFailed):
|
||||||
s.update(href, obj, '"lolnope"')
|
s.update(href, item, '"lolnope"')
|
||||||
with pytest.raises(exceptions.PreconditionFailed):
|
with pytest.raises(exceptions.PreconditionFailed):
|
||||||
s.delete(href, '"lolnope"')
|
s.delete(href, '"lolnope"')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -135,10 +135,10 @@ def test_conflict_resolution_both_etags_new():
|
||||||
with pytest.raises(exceptions.SyncConflict):
|
with pytest.raises(exceptions.SyncConflict):
|
||||||
sync(a, b, status)
|
sync(a, b, status)
|
||||||
sync(a, b, status, conflict_resolution='a wins')
|
sync(a, b, status, conflict_resolution='a wins')
|
||||||
obj_a, _ = a.get(href_a)
|
item_a, _ = a.get(href_a)
|
||||||
obj_b, _ = b.get(href_b)
|
item_b, _ = b.get(href_b)
|
||||||
assert_item_equals(obj_a, obj_b)
|
assert_item_equals(item_a, item_b)
|
||||||
n = normalize_item(obj_a)
|
n = normalize_item(item_a)
|
||||||
assert u'UID:1' in n
|
assert u'UID:1' in n
|
||||||
assert u'ASDASD' in n
|
assert u'ASDASD' in n
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,15 +29,17 @@ class Storage(object):
|
||||||
implement.
|
implement.
|
||||||
|
|
||||||
Terminology:
|
Terminology:
|
||||||
- UID: Global identifier of the item, across storages.
|
- ITEM: Instance of the Item class, represents a calendar event, task or
|
||||||
- HREF: Per-storage identifier of item, might be UID. The reason items
|
contact.
|
||||||
aren't just referenced by their UID is because the CalDAV and CardDAV
|
- UID: String; Global identifier of the item, across storages.
|
||||||
specifications make this imperformant to implement.
|
- HREF: String; Per-storage identifier of item, might be UID. The reason
|
||||||
- ETAG: Checksum of item, or something similar that changes when the
|
items aren't just referenced by their UID is because the CalDAV and
|
||||||
object does.
|
CardDAV specifications make this imperformant to implement.
|
||||||
|
- ETAG: String; Checksum of item, or something similar that changes when the
|
||||||
|
item does.
|
||||||
|
|
||||||
All of the above properties should be strings. If bytestrings, an ASCII
|
Strings can be either unicode strings or bytestrings. If bytestrings, an
|
||||||
encoding is assumed.
|
ASCII encoding is assumed.
|
||||||
|
|
||||||
:param collection: If None, the given URL or path is already directly
|
:param collection: If None, the given URL or path is already directly
|
||||||
referring to a collection. Otherwise it will be treated as a basepath
|
referring to a collection. Otherwise it will be treated as a basepath
|
||||||
|
|
@ -77,7 +79,7 @@ class Storage(object):
|
||||||
def get(self, href):
|
def get(self, href):
|
||||||
'''
|
'''
|
||||||
:param href: href to fetch
|
:param href: href to fetch
|
||||||
:returns: (object, etag)
|
:returns: (item, etag)
|
||||||
'''
|
'''
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -86,11 +88,11 @@ class Storage(object):
|
||||||
:param hrefs: list of hrefs to fetch
|
:param hrefs: list of hrefs to fetch
|
||||||
:raises: :exc:`vdirsyncer.exceptions.PreconditionFailed` if one of the
|
:raises: :exc:`vdirsyncer.exceptions.PreconditionFailed` if one of the
|
||||||
items couldn't be found.
|
items couldn't be found.
|
||||||
:returns: iterable of (href, obj, etag)
|
:returns: iterable of (href, item, etag)
|
||||||
'''
|
'''
|
||||||
for href in hrefs:
|
for href in hrefs:
|
||||||
obj, etag = self.get(href)
|
item, etag = self.get(href)
|
||||||
yield href, obj, etag
|
yield href, item, etag
|
||||||
|
|
||||||
def has(self, href):
|
def has(self, href):
|
||||||
'''
|
'''
|
||||||
|
|
@ -99,17 +101,17 @@ class Storage(object):
|
||||||
'''
|
'''
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def upload(self, obj):
|
def upload(self, item):
|
||||||
'''
|
'''
|
||||||
Upload a new object, raise
|
Upload a new item, raise
|
||||||
:exc:`vdirsyncer.exceptions.PreconditionFailed` if it already exists.
|
:exc:`vdirsyncer.exceptions.PreconditionFailed` if it already exists.
|
||||||
:returns: (href, etag)
|
:returns: (href, etag)
|
||||||
'''
|
'''
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def update(self, href, obj, etag):
|
def update(self, href, item, etag):
|
||||||
'''
|
'''
|
||||||
Update the object, raise
|
Update the item, raise
|
||||||
:exc:`vdirsyncer.exceptions.PreconditionFailed` if the etag on the
|
:exc:`vdirsyncer.exceptions.PreconditionFailed` if the etag on the
|
||||||
server doesn't match the given etag or if the item doesn't exist.
|
server doesn't match the given etag or if the item doesn't exist.
|
||||||
|
|
||||||
|
|
@ -119,7 +121,7 @@ class Storage(object):
|
||||||
|
|
||||||
def delete(self, href, etag):
|
def delete(self, href, etag):
|
||||||
'''
|
'''
|
||||||
Delete the object by href, raise
|
Delete the item by href, raise
|
||||||
:exc:`vdirsyncer.exceptions.PreconditionFailed` when item has a
|
:exc:`vdirsyncer.exceptions.PreconditionFailed` when item has a
|
||||||
different etag or doesn't exist.
|
different etag or doesn't exist.
|
||||||
'''
|
'''
|
||||||
|
|
|
||||||
|
|
@ -103,9 +103,9 @@ class DavStorage(HttpStorageBase):
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
def get(self, href):
|
def get(self, href):
|
||||||
((actual_href, obj, etag),) = self.get_multi([href])
|
((actual_href, item, etag),) = self.get_multi([href])
|
||||||
assert href == actual_href
|
assert href == actual_href
|
||||||
return obj, etag
|
return item, etag
|
||||||
|
|
||||||
def get_multi(self, hrefs):
|
def get_multi(self, hrefs):
|
||||||
if not hrefs:
|
if not hrefs:
|
||||||
|
|
@ -129,7 +129,7 @@ class DavStorage(HttpStorageBase):
|
||||||
for element in root.iter('{DAV:}response'):
|
for element in root.iter('{DAV:}response'):
|
||||||
href = self._normalize_href(
|
href = self._normalize_href(
|
||||||
element.find('{DAV:}href').text.decode(response.encoding))
|
element.find('{DAV:}href').text.decode(response.encoding))
|
||||||
obj = element \
|
raw = element \
|
||||||
.find('{DAV:}propstat') \
|
.find('{DAV:}propstat') \
|
||||||
.find('{DAV:}prop') \
|
.find('{DAV:}prop') \
|
||||||
.find(self.get_multi_data_query).text
|
.find(self.get_multi_data_query).text
|
||||||
|
|
@ -137,11 +137,11 @@ class DavStorage(HttpStorageBase):
|
||||||
.find('{DAV:}propstat') \
|
.find('{DAV:}propstat') \
|
||||||
.find('{DAV:}prop') \
|
.find('{DAV:}prop') \
|
||||||
.find('{DAV:}getetag').text
|
.find('{DAV:}getetag').text
|
||||||
if isinstance(obj, bytes):
|
if isinstance(raw, bytes):
|
||||||
obj = obj.decode(response.encoding)
|
raw = raw.decode(response.encoding)
|
||||||
if isinstance(etag, bytes):
|
if isinstance(etag, bytes):
|
||||||
etag = etag.decode(response.encoding)
|
etag = etag.decode(response.encoding)
|
||||||
rv.append((href, Item(obj), etag))
|
rv.append((href, Item(raw), etag))
|
||||||
try:
|
try:
|
||||||
hrefs_left.remove(href)
|
hrefs_left.remove(href)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
@ -159,7 +159,7 @@ class DavStorage(HttpStorageBase):
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _put(self, href, obj, etag):
|
def _put(self, href, item, etag):
|
||||||
headers = self._default_headers()
|
headers = self._default_headers()
|
||||||
headers['Content-Type'] = self.item_mimetype
|
headers['Content-Type'] = self.item_mimetype
|
||||||
if etag is None:
|
if etag is None:
|
||||||
|
|
@ -171,25 +171,25 @@ class DavStorage(HttpStorageBase):
|
||||||
response = self._request(
|
response = self._request(
|
||||||
'PUT',
|
'PUT',
|
||||||
href,
|
href,
|
||||||
data=obj.raw.encode('utf-8'),
|
data=item.raw.encode('utf-8'),
|
||||||
headers=headers
|
headers=headers
|
||||||
)
|
)
|
||||||
self._check_response(response)
|
self._check_response(response)
|
||||||
etag = response.headers.get('etag', None)
|
etag = response.headers.get('etag', None)
|
||||||
if not etag:
|
if not etag:
|
||||||
obj2, etag = self.get(href)
|
item2, etag = self.get(href)
|
||||||
assert obj2.uid == obj.uid
|
assert item2.uid == item.uid
|
||||||
return href, etag
|
return href, etag
|
||||||
|
|
||||||
def update(self, href, obj, etag):
|
def update(self, href, item, etag):
|
||||||
href = self._normalize_href(href)
|
href = self._normalize_href(href)
|
||||||
if etag is None:
|
if etag is None:
|
||||||
raise ValueError('etag must be given and must not be None.')
|
raise ValueError('etag must be given and must not be None.')
|
||||||
return self._put(href, obj, etag)
|
return self._put(href, item, etag)
|
||||||
|
|
||||||
def upload(self, obj):
|
def upload(self, item):
|
||||||
href = self._get_href(obj.uid)
|
href = self._get_href(item.uid)
|
||||||
return self._put(href, obj, None)
|
return self._put(href, item, None)
|
||||||
|
|
||||||
def delete(self, href, etag):
|
def delete(self, href, etag):
|
||||||
href = self._normalize_href(href)
|
href = self._normalize_href(href)
|
||||||
|
|
|
||||||
|
|
@ -76,28 +76,28 @@ class FilesystemStorage(Storage):
|
||||||
def has(self, href):
|
def has(self, href):
|
||||||
return os.path.isfile(self._get_filepath(href))
|
return os.path.isfile(self._get_filepath(href))
|
||||||
|
|
||||||
def upload(self, obj):
|
def upload(self, item):
|
||||||
href = self._get_href(obj.uid)
|
href = self._get_href(item.uid)
|
||||||
fpath = self._get_filepath(href)
|
fpath = self._get_filepath(href)
|
||||||
if os.path.exists(fpath):
|
if os.path.exists(fpath):
|
||||||
raise exceptions.AlreadyExistingError(obj.uid)
|
raise exceptions.AlreadyExistingError(item.uid)
|
||||||
with open(fpath, 'wb+') as f:
|
with open(fpath, 'wb+') as f:
|
||||||
f.write(obj.raw.encode(self.encoding))
|
f.write(item.raw.encode(self.encoding))
|
||||||
return href, _get_etag(fpath)
|
return href, _get_etag(fpath)
|
||||||
|
|
||||||
def update(self, href, obj, etag):
|
def update(self, href, item, etag):
|
||||||
fpath = self._get_filepath(href)
|
fpath = self._get_filepath(href)
|
||||||
if href != self._get_href(obj.uid):
|
if href != self._get_href(item.uid):
|
||||||
logger.warning('href != uid + fileext: href={}; uid={}'
|
logger.warning('href != uid + fileext: href={}; uid={}'
|
||||||
.format(href, obj.uid))
|
.format(href, item.uid))
|
||||||
if not os.path.exists(fpath):
|
if not os.path.exists(fpath):
|
||||||
raise exceptions.NotFoundError(obj.uid)
|
raise exceptions.NotFoundError(item.uid)
|
||||||
actual_etag = _get_etag(fpath)
|
actual_etag = _get_etag(fpath)
|
||||||
if etag != actual_etag:
|
if etag != actual_etag:
|
||||||
raise exceptions.WrongEtagError(etag, actual_etag)
|
raise exceptions.WrongEtagError(etag, actual_etag)
|
||||||
|
|
||||||
with open(fpath, 'wb') as f:
|
with open(fpath, 'wb') as f:
|
||||||
f.write(obj.raw.encode('utf-8'))
|
f.write(item.raw.encode('utf-8'))
|
||||||
return _get_etag(fpath)
|
return _get_etag(fpath)
|
||||||
|
|
||||||
def delete(self, href, etag):
|
def delete(self, href, etag):
|
||||||
|
|
|
||||||
|
|
@ -23,37 +23,37 @@ class MemoryStorage(Storage):
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.items = {} # href => (etag, object)
|
self.items = {} # href => (etag, item)
|
||||||
super(MemoryStorage, self).__init__(**kwargs)
|
super(MemoryStorage, self).__init__(**kwargs)
|
||||||
|
|
||||||
def list(self):
|
def list(self):
|
||||||
for href, (etag, obj) in self.items.items():
|
for href, (etag, item) in self.items.items():
|
||||||
yield href, etag
|
yield href, etag
|
||||||
|
|
||||||
def get(self, href):
|
def get(self, href):
|
||||||
etag, obj = self.items[href]
|
etag, item = self.items[href]
|
||||||
return obj, etag
|
return item, etag
|
||||||
|
|
||||||
def has(self, href):
|
def has(self, href):
|
||||||
return href in self.items
|
return href in self.items
|
||||||
|
|
||||||
def upload(self, obj):
|
def upload(self, item):
|
||||||
href = self._get_href(obj.uid)
|
href = self._get_href(item.uid)
|
||||||
if href in self.items:
|
if href in self.items:
|
||||||
raise exceptions.AlreadyExistingError(obj.uid)
|
raise exceptions.AlreadyExistingError(item.uid)
|
||||||
etag = _get_etag()
|
etag = _get_etag()
|
||||||
self.items[href] = (etag, obj)
|
self.items[href] = (etag, item)
|
||||||
return href, etag
|
return href, etag
|
||||||
|
|
||||||
def update(self, href, obj, etag):
|
def update(self, href, item, etag):
|
||||||
if href != self._get_href(obj.uid) or href not in self.items:
|
if href != self._get_href(item.uid) or href not in self.items:
|
||||||
raise exceptions.NotFoundError(href)
|
raise exceptions.NotFoundError(href)
|
||||||
actual_etag, _ = self.items[href]
|
actual_etag, _ = self.items[href]
|
||||||
if etag != actual_etag:
|
if etag != actual_etag:
|
||||||
raise exceptions.WrongEtagError(etag, actual_etag)
|
raise exceptions.WrongEtagError(etag, actual_etag)
|
||||||
|
|
||||||
new_etag = _get_etag()
|
new_etag = _get_etag()
|
||||||
self.items[href] = (new_etag, obj)
|
self.items[href] = (new_etag, item)
|
||||||
return new_etag
|
return new_etag
|
||||||
|
|
||||||
def delete(self, href, etag):
|
def delete(self, href, etag):
|
||||||
|
|
|
||||||
|
|
@ -23,21 +23,21 @@ def prepare_list(storage, href_to_uid):
|
||||||
if href in href_to_uid:
|
if href in href_to_uid:
|
||||||
props['uid'] = href_to_uid[href]
|
props['uid'] = href_to_uid[href]
|
||||||
else:
|
else:
|
||||||
obj, new_etag = storage.get(href)
|
item, new_etag = storage.get(href)
|
||||||
assert etag == new_etag
|
assert etag == new_etag
|
||||||
props['uid'] = obj.uid
|
props['uid'] = item.uid
|
||||||
props['obj'] = obj
|
props['item'] = item
|
||||||
yield href, props
|
yield href, props
|
||||||
|
|
||||||
|
|
||||||
def prefetch(storage, item_list, hrefs):
|
def prefetch(storage, item_list, hrefs):
|
||||||
hrefs_to_prefetch = []
|
hrefs_to_prefetch = []
|
||||||
for href in hrefs:
|
for href in hrefs:
|
||||||
if 'obj' not in item_list[href]:
|
if 'item' not in item_list[href]:
|
||||||
hrefs_to_prefetch.append(href)
|
hrefs_to_prefetch.append(href)
|
||||||
for href, obj, etag in storage.get_multi(hrefs_to_prefetch):
|
for href, item, etag in storage.get_multi(hrefs_to_prefetch):
|
||||||
assert item_list[href]['etag'] == etag
|
assert item_list[href]['etag'] == etag
|
||||||
item_list[href]['obj'] = obj
|
item_list[href]['item'] = item
|
||||||
|
|
||||||
|
|
||||||
def sync(storage_a, storage_b, status, conflict_resolution=None):
|
def sync(storage_a, storage_b, status, conflict_resolution=None):
|
||||||
|
|
@ -63,7 +63,7 @@ def sync(storage_a, storage_b, status, conflict_resolution=None):
|
||||||
(href_b, uid)
|
(href_b, uid)
|
||||||
for uid, (href_a, etag_a, href_b, etag_b) in status.iteritems()
|
for uid, (href_a, etag_a, href_b, etag_b) in status.iteritems()
|
||||||
)
|
)
|
||||||
# href => {'etag': etag, 'obj': optional object, 'uid': uid}
|
# href => {'etag': etag, 'item': optional item, 'uid': uid}
|
||||||
list_a = dict(prepare_list(storage_a, a_href_to_uid))
|
list_a = dict(prepare_list(storage_a, a_href_to_uid))
|
||||||
list_b = dict(prepare_list(storage_b, b_href_to_uid))
|
list_b = dict(prepare_list(storage_b, b_href_to_uid))
|
||||||
|
|
||||||
|
|
@ -97,8 +97,8 @@ def action_upload(uid, source, dest):
|
||||||
source_href = source_uid_to_href[uid]
|
source_href = source_uid_to_href[uid]
|
||||||
source_etag = source_list[source_href]['etag']
|
source_etag = source_list[source_href]['etag']
|
||||||
|
|
||||||
obj = source_list[source_href]['obj']
|
item = source_list[source_href]['item']
|
||||||
dest_href, dest_etag = dest_storage.upload(obj)
|
dest_href, dest_etag = dest_storage.upload(item)
|
||||||
|
|
||||||
source_status = (source_href, source_etag)
|
source_status = (source_href, source_etag)
|
||||||
dest_status = (dest_href, dest_etag)
|
dest_status = (dest_href, dest_etag)
|
||||||
|
|
@ -119,8 +119,8 @@ def action_update(uid, source, dest):
|
||||||
|
|
||||||
dest_href = dest_uid_to_href[uid]
|
dest_href = dest_uid_to_href[uid]
|
||||||
old_etag = dest_list[dest_href]['etag']
|
old_etag = dest_list[dest_href]['etag']
|
||||||
obj = source_list[source_href]['obj']
|
item = source_list[source_href]['item']
|
||||||
dest_etag = dest_storage.update(dest_href, obj, old_etag)
|
dest_etag = dest_storage.update(dest_href, item, old_etag)
|
||||||
|
|
||||||
source_status = (source_href, source_etag)
|
source_status = (source_href, source_etag)
|
||||||
dest_status = (dest_href, dest_etag)
|
dest_status = (dest_href, dest_etag)
|
||||||
|
|
@ -157,7 +157,7 @@ def action_conflict_resolve(uid):
|
||||||
b_href = b_uid_to_href[uid]
|
b_href = b_uid_to_href[uid]
|
||||||
a_meta = list_a[a_href]
|
a_meta = list_a[a_href]
|
||||||
b_meta = list_b[b_href]
|
b_meta = list_b[b_href]
|
||||||
if a_meta['obj'].raw == b_meta['obj'].raw:
|
if a_meta['item'].raw == b_meta['item'].raw:
|
||||||
sync_logger.info('...same content on both sides.')
|
sync_logger.info('...same content on both sides.')
|
||||||
status[uid] = a_href, a_meta['etag'], b_href, b_meta['etag']
|
status[uid] = a_href, a_meta['etag'], b_href, b_meta['etag']
|
||||||
elif conflict_resolution is None:
|
elif conflict_resolution is None:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue