Merge pull request #77 from untitaker/readonly

Add read_only parameter
This commit is contained in:
Markus Unterwaditzer 2014-06-12 15:46:57 +02:00
commit 5788544839
8 changed files with 97 additions and 18 deletions

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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):
'''

View file

@ -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

View file

@ -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