mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-03-25 08:55:50 +00:00
commit
5788544839
8 changed files with 97 additions and 18 deletions
|
|
@ -74,6 +74,10 @@ Storage Section
|
|||
|
||||
- ``type`` defines which kind of storage is defined. See :ref:`storages`.
|
||||
|
||||
- ``read_only`` defines whether the storage should be regarded as a read-only
|
||||
storage, defaulting to ``False``. Setting this to ``True`` effectively means
|
||||
synchronization will discard any changes made to the other side.
|
||||
|
||||
- Any further parameters are passed on to the storage class.
|
||||
|
||||
.. _storages:
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ Version 0.1.6
|
|||
values ``from a`` and ``from b`` for automatically discovering collections.
|
||||
See :ref:`pair_config`.
|
||||
|
||||
- The ``read_only`` parameter was added to storage sections. See
|
||||
:ref:`storage_config`.
|
||||
|
||||
.. _`#48`: https://github.com/untitaker/vdirsyncer/issues/48
|
||||
|
||||
Version 0.1.5
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
:license: MIT, see LICENSE for more details.
|
||||
'''
|
||||
|
||||
import pytest
|
||||
|
||||
from requests import Response
|
||||
|
||||
from tests import normalize_item
|
||||
|
|
@ -71,3 +73,13 @@ def test_list(monkeypatch):
|
|||
assert item.uid is None
|
||||
assert etag2 == etag
|
||||
assert found_items[normalize_item(item)] == href
|
||||
|
||||
|
||||
def test_readonly_param():
|
||||
url = u'http://example.com/'
|
||||
with pytest.raises(ValueError):
|
||||
HttpStorage(url=url, read_only=False)
|
||||
|
||||
a = HttpStorage(url=url, read_only=True).read_only
|
||||
b = HttpStorage(url=url, read_only=None).read_only
|
||||
assert a is b is True
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import pytest
|
|||
from . import assert_item_equals, normalize_item
|
||||
from vdirsyncer.storage.base import Item
|
||||
from vdirsyncer.storage.memory import MemoryStorage
|
||||
from vdirsyncer.sync import sync, SyncConflict, StorageEmpty
|
||||
from vdirsyncer.sync import sync, SyncConflict, StorageEmpty, BothReadOnly
|
||||
|
||||
|
||||
def empty_storage(x):
|
||||
|
|
@ -221,3 +221,25 @@ def test_no_uids():
|
|||
b_items = set(b.get(href)[0].raw for href, etag in b.list())
|
||||
|
||||
assert a_items == b_items == {u'ASDF', u'FOOBAR'}
|
||||
|
||||
|
||||
def test_both_readonly():
|
||||
a = MemoryStorage(read_only=True)
|
||||
b = MemoryStorage(read_only=True)
|
||||
assert a.read_only
|
||||
assert b.read_only
|
||||
status = {}
|
||||
with pytest.raises(BothReadOnly):
|
||||
sync(a, b, status)
|
||||
|
||||
|
||||
def test_readonly():
|
||||
a = MemoryStorage()
|
||||
b = MemoryStorage(read_only=True)
|
||||
status = {}
|
||||
href_a, _ = a.upload(Item(u'UID:1'))
|
||||
href_b, _ = b.upload(Item(u'UID:2'))
|
||||
sync(a, b, status)
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -122,5 +122,5 @@ def test_get_class_init_args_on_storage():
|
|||
from vdirsyncer.storage.memory import MemoryStorage
|
||||
|
||||
all, required = utils.get_class_init_args(MemoryStorage)
|
||||
assert not all
|
||||
assert all == set(['read_only'])
|
||||
assert not required
|
||||
|
|
|
|||
|
|
@ -36,8 +36,17 @@ class Storage(object):
|
|||
'''
|
||||
fileext = '.txt'
|
||||
storage_name = None # the name used in the config file
|
||||
read_only = None
|
||||
_repr_attributes = ()
|
||||
|
||||
def __init__(self, read_only=None):
|
||||
if read_only is None:
|
||||
read_only = self.read_only
|
||||
if self.read_only is not None and read_only != self.read_only:
|
||||
raise ValueError('read_only must be {}'
|
||||
.format(repr(self.read_only)))
|
||||
self.read_only = bool(read_only)
|
||||
|
||||
@classmethod
|
||||
def discover(cls, **kwargs):
|
||||
'''
|
||||
|
|
|
|||
|
|
@ -52,8 +52,8 @@ class HttpStorage(Storage):
|
|||
.. note::
|
||||
|
||||
This is a read-only storage. If you sync this with
|
||||
read-and-write-storages (such as CalDAV), make sure not to change
|
||||
anything on the other side, otherwise vdirsyncer will crash.
|
||||
read-and-write-storages (such as CalDAV), any changes on the other side
|
||||
will get reverted.
|
||||
|
||||
:param url: URL to the ``.ics`` file.
|
||||
:param username: Username for authentication.
|
||||
|
|
@ -82,6 +82,7 @@ class HttpStorage(Storage):
|
|||
'''
|
||||
|
||||
storage_name = 'http'
|
||||
read_only = True
|
||||
_repr_attributes = ('username', 'url')
|
||||
_items = None
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,13 @@ class StorageEmpty(SyncError):
|
|||
'''
|
||||
|
||||
|
||||
class BothReadOnly(SyncError):
|
||||
'''
|
||||
Both storages are marked as read-only. Synchronization is therefore not
|
||||
possible.
|
||||
'''
|
||||
|
||||
|
||||
def prepare_list(storage, href_to_status):
|
||||
rv = {}
|
||||
download = []
|
||||
|
|
@ -88,6 +95,8 @@ def sync(storage_a, storage_b, status, conflict_resolution=None,
|
|||
safety. Setting this parameter to ``True`` disables this safety
|
||||
measure.
|
||||
'''
|
||||
if False not in (storage_a.read_only, storage_b.read_only):
|
||||
raise BothReadOnly()
|
||||
a_href_to_status = dict(
|
||||
(href_a, (ident, etag_a))
|
||||
for ident, (href_a, etag_a, href_b, etag_b) in iteritems(status)
|
||||
|
|
@ -127,12 +136,18 @@ def action_upload(ident, source, dest):
|
|||
|
||||
source_href = source_ident_to_href[ident]
|
||||
source_etag = source_list[source_href]['etag']
|
||||
|
||||
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)
|
||||
|
||||
dest_status = (None, None)
|
||||
|
||||
if dest_storage.read_only:
|
||||
sync_logger.warning('{dest} is read-only. Skipping update...'
|
||||
.format(dest=dest_storage))
|
||||
else:
|
||||
item = source_list[source_href]['item']
|
||||
dest_href, dest_etag = dest_storage.upload(item)
|
||||
dest_status = (dest_href, dest_etag)
|
||||
|
||||
status[ident] = source_status + dest_status if source == 'a' else \
|
||||
dest_status + source_status
|
||||
|
||||
|
|
@ -145,17 +160,25 @@ def action_update(ident, source, dest):
|
|||
dest_storage, dest_list, dest_ident_to_href = storages[dest]
|
||||
sync_logger.info('Copying (updating) item {} to {}'
|
||||
.format(ident, dest_storage))
|
||||
|
||||
source_href = source_ident_to_href[ident]
|
||||
source_etag = source_list[source_href]['etag']
|
||||
source_status = (source_href, source_etag)
|
||||
|
||||
dest_href = dest_ident_to_href[ident]
|
||||
old_etag = dest_list[dest_href]['etag']
|
||||
item = source_list[source_href]['item']
|
||||
dest_etag = dest_storage.update(dest_href, item, old_etag)
|
||||
assert isinstance(dest_etag, (bytes, text_type))
|
||||
|
||||
source_status = (source_href, source_etag)
|
||||
dest_etag = dest_list[dest_href]['etag']
|
||||
dest_status = (dest_href, dest_etag)
|
||||
|
||||
if dest_storage.read_only:
|
||||
sync_logger.info('{dest} is read-only. Skipping update...'
|
||||
.format(dest=dest_storage))
|
||||
else:
|
||||
item = source_list[source_href]['item']
|
||||
dest_etag = dest_storage.update(dest_href, item, dest_etag)
|
||||
assert isinstance(dest_etag, (bytes, text_type))
|
||||
|
||||
dest_status = (dest_href, dest_etag)
|
||||
|
||||
status[ident] = source_status + dest_status if source == 'a' else \
|
||||
dest_status + source_status
|
||||
|
||||
|
|
@ -168,12 +191,17 @@ def action_delete(ident, dest):
|
|||
dest_storage, dest_list, dest_ident_to_href = storages[dest]
|
||||
sync_logger.info('Deleting item {} from {}'
|
||||
.format(ident, dest_storage))
|
||||
dest_href = dest_ident_to_href[ident]
|
||||
dest_etag = dest_list[dest_href]['etag']
|
||||
dest_storage.delete(dest_href, dest_etag)
|
||||
if dest_storage.read_only:
|
||||
sync_logger.warning('{dest} is read-only, skipping deletion...'
|
||||
.format(dest=dest_storage))
|
||||
else:
|
||||
dest_href = dest_ident_to_href[ident]
|
||||
dest_etag = dest_list[dest_href]['etag']
|
||||
dest_storage.delete(dest_href, dest_etag)
|
||||
else:
|
||||
sync_logger.info('Deleting status info for nonexisting item {}'
|
||||
.format(ident))
|
||||
|
||||
del status[ident]
|
||||
|
||||
return inner
|
||||
|
|
|
|||
Loading…
Reference in a new issue