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