mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-04-27 14:57:41 +00:00
Ability to sync differently named collections with each other (#423)
* Ability to sync differently named collections * Fixes * Fixes * Add example
This commit is contained in:
parent
777eb35898
commit
be8df955e9
6 changed files with 94 additions and 20 deletions
|
|
@ -16,6 +16,8 @@ Version 0.10.0
|
||||||
have been added.
|
have been added.
|
||||||
- New global command line option `--config`, to specify an alternative config
|
- New global command line option `--config`, to specify an alternative config
|
||||||
file. See :gh:`409`.
|
file. See :gh:`409`.
|
||||||
|
- The ``collections`` parameter can now be used to synchronize
|
||||||
|
differently-named collections with each other.
|
||||||
|
|
||||||
Version 0.9.3
|
Version 0.9.3
|
||||||
=============
|
=============
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,12 @@ Pair Section
|
||||||
The special values ``"from a"`` and ``"from b"``, tell vdirsyncer to try
|
The special values ``"from a"`` and ``"from b"``, tell vdirsyncer to try
|
||||||
autodiscovery on a specific storage.
|
autodiscovery on a specific storage.
|
||||||
|
|
||||||
|
If the collection you want to sync doesn't have the same name on each side,
|
||||||
|
you may also use a value of the form ``["config_name", "name_a", "name_b"]``.
|
||||||
|
This will synchronize the collection ``name_a`` on side A with the collection
|
||||||
|
``name_b`` on side B. The ``config_name`` will be used for representation in
|
||||||
|
CLI arguments and logging.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- ``collections = ["from b", "foo", "bar"]`` makes vdirsyncer synchronize the
|
- ``collections = ["from b", "foo", "bar"]`` makes vdirsyncer synchronize the
|
||||||
|
|
@ -72,6 +78,10 @@ Pair Section
|
||||||
- ``collections = ["from b", from a"]`` makes vdirsyncer synchronize all
|
- ``collections = ["from b", from a"]`` makes vdirsyncer synchronize all
|
||||||
existing collections on either side.
|
existing collections on either side.
|
||||||
|
|
||||||
|
- ``collections = [["bar", "bar_a", "bar_b"], "foo"]`` makes vdirsyncer
|
||||||
|
synchronize ``bar_a`` from side A with ``bar_b`` from side B, and also
|
||||||
|
synchronize ``foo`` on both sides with each other.
|
||||||
|
|
||||||
- ``conflict_resolution``: Optional, define how conflicts should be handled. A
|
- ``conflict_resolution``: Optional, define how conflicts should be handled. A
|
||||||
conflict occurs when one item (event, task) changed on both sides since the
|
conflict occurs when one item (event, task) changed on both sides since the
|
||||||
last sync.
|
last sync.
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,4 @@ def test_invalid_collections_arg():
|
||||||
with pytest.raises(exceptions.UserError) as excinfo:
|
with pytest.raises(exceptions.UserError) as excinfo:
|
||||||
_read_config(f)
|
_read_config(f)
|
||||||
|
|
||||||
assert (
|
assert 'Expected string' in str(excinfo.value)
|
||||||
'Section `pair foobar`: `collections` parameter must be a list of '
|
|
||||||
'collection names (strings!) or `null`.'
|
|
||||||
) in str(excinfo.value)
|
|
||||||
|
|
|
||||||
|
|
@ -81,3 +81,48 @@ def test_discover_on_unsupported_storage(tmpdir, runner):
|
||||||
result = runner.invoke(['discover'])
|
result = runner.invoke(['discover'])
|
||||||
assert result.exception
|
assert result.exception
|
||||||
assert 'doesn\'t support collection discovery' in result.output
|
assert 'doesn\'t support collection discovery' in result.output
|
||||||
|
|
||||||
|
|
||||||
|
def test_discover_different_collection_names(tmpdir, runner):
|
||||||
|
foo = tmpdir.mkdir('foo')
|
||||||
|
bar = tmpdir.mkdir('bar')
|
||||||
|
runner.write_with_general(dedent('''
|
||||||
|
[storage foo]
|
||||||
|
type = filesystem
|
||||||
|
fileext = .txt
|
||||||
|
path = {foo}
|
||||||
|
|
||||||
|
[storage bar]
|
||||||
|
type = filesystem
|
||||||
|
fileext = .txt
|
||||||
|
path = {bar}
|
||||||
|
|
||||||
|
[pair foobar]
|
||||||
|
a = foo
|
||||||
|
b = bar
|
||||||
|
collections = [
|
||||||
|
["coll1", "coll_a1", "coll_b1"],
|
||||||
|
"coll2"
|
||||||
|
]
|
||||||
|
''').format(foo=str(foo), bar=str(bar)))
|
||||||
|
|
||||||
|
result = runner.invoke(['discover'], input='y\n' * 6)
|
||||||
|
assert not result.exception
|
||||||
|
|
||||||
|
coll_a1 = foo.join('coll_a1')
|
||||||
|
coll_b1 = bar.join('coll_b1')
|
||||||
|
|
||||||
|
assert coll_a1.exists()
|
||||||
|
assert coll_b1.exists()
|
||||||
|
|
||||||
|
result = runner.invoke(['sync'])
|
||||||
|
assert not result.exception
|
||||||
|
|
||||||
|
foo_txt = coll_a1.join('foo.txt')
|
||||||
|
foo_txt.write('BEGIN:VCALENDAR\nUID:foo\nEND:VCALENDAR')
|
||||||
|
|
||||||
|
result = runner.invoke(['sync'])
|
||||||
|
assert not result.exception
|
||||||
|
|
||||||
|
assert foo_txt.exists()
|
||||||
|
assert coll_b1.join('foo.txt').exists()
|
||||||
|
|
|
||||||
|
|
@ -66,17 +66,26 @@ def _validate_pair_section(pair_config):
|
||||||
if collections is None:
|
if collections is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
e = ValueError('`collections` parameter must be a list of collection '
|
|
||||||
'names (strings!) or `null`.')
|
|
||||||
|
|
||||||
if not isinstance(collections, list):
|
if not isinstance(collections, list):
|
||||||
raise e
|
raise ValueError('`collections` parameter must be a list or `null`.')
|
||||||
|
|
||||||
if any(not isinstance(x, (text_type, bytes)) for x in collections):
|
collection_names = set()
|
||||||
raise e
|
|
||||||
|
|
||||||
if len(set(collections)) != len(collections):
|
for i, collection in enumerate(collections):
|
||||||
raise ValueError('Duplicate values in collections parameter.')
|
if isinstance(collection, (text_type, bytes)):
|
||||||
|
collection_name = collection
|
||||||
|
elif isinstance(collection, list) and \
|
||||||
|
len(collection) == 3 and \
|
||||||
|
all(isinstance(x, (text_type, bytes)) for x in collection):
|
||||||
|
collection_name = collection[0]
|
||||||
|
else:
|
||||||
|
raise ValueError('`collections` parameter, position {i}:'
|
||||||
|
'Expected string or list of three strings.'
|
||||||
|
.format(i=i))
|
||||||
|
|
||||||
|
if collection_name in collection_names:
|
||||||
|
raise ValueError('Duplicate values in collections parameter.')
|
||||||
|
collection_names.add(collection_name)
|
||||||
|
|
||||||
|
|
||||||
def load_config(fname=None):
|
def load_config(fname=None):
|
||||||
|
|
|
||||||
|
|
@ -276,7 +276,7 @@ def _collections_for_pair_impl(status_path, pair):
|
||||||
a_discovered = _discover_from_config(pair.config_a)
|
a_discovered = _discover_from_config(pair.config_a)
|
||||||
b_discovered = _discover_from_config(pair.config_b)
|
b_discovered = _discover_from_config(pair.config_b)
|
||||||
|
|
||||||
for shortcut in set(shortcuts):
|
for shortcut in shortcuts:
|
||||||
if shortcut == 'from a':
|
if shortcut == 'from a':
|
||||||
collections = a_discovered
|
collections = a_discovered
|
||||||
elif shortcut == 'from b':
|
elif shortcut == 'from b':
|
||||||
|
|
@ -285,17 +285,28 @@ def _collections_for_pair_impl(status_path, pair):
|
||||||
collections = [shortcut]
|
collections = [shortcut]
|
||||||
|
|
||||||
for collection in collections:
|
for collection in collections:
|
||||||
try:
|
if isinstance(collection, list):
|
||||||
a_args = a_discovered[collection]
|
try:
|
||||||
except KeyError:
|
collection, collection_a, collection_b = collection
|
||||||
a_args = _handle_collection_not_found(pair.config_a,
|
except ValueError:
|
||||||
collection)
|
raise exceptions.UserError(
|
||||||
|
'Expected string or list of length 3, '
|
||||||
|
'{} found instead.'
|
||||||
|
.format(collection))
|
||||||
|
else:
|
||||||
|
collection_a = collection_b = collection
|
||||||
|
|
||||||
try:
|
try:
|
||||||
b_args = b_discovered[collection]
|
a_args = a_discovered[collection_a]
|
||||||
|
except KeyError:
|
||||||
|
a_args = _handle_collection_not_found(pair.config_a,
|
||||||
|
collection_a)
|
||||||
|
|
||||||
|
try:
|
||||||
|
b_args = b_discovered[collection_b]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
b_args = _handle_collection_not_found(pair.config_b,
|
b_args = _handle_collection_not_found(pair.config_b,
|
||||||
collection)
|
collection_b)
|
||||||
|
|
||||||
yield collection, (a_args, b_args)
|
yield collection, (a_args, b_args)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue