Remove the default of collections to autodiscovery

See #328
This commit is contained in:
Markus Unterwaditzer 2016-02-11 23:32:32 +01:00
parent 3c9ef726df
commit 727ce250cf
8 changed files with 74 additions and 26 deletions

View file

@ -9,6 +9,12 @@ Package maintainers and users who have to manually update their installation
may want to subscribe to `GitHub's tag feed may want to subscribe to `GitHub's tag feed
<https://github.com/untitaker/vdirsyncer/tags.atom>`_. <https://github.com/untitaker/vdirsyncer/tags.atom>`_.
Version 0.9.0
=============
- The ``collections`` parameter is now required in pair configurations.
Vdirsyncer will tell you what to do in its error message. See :gh:`328`.
Version 0.8.1 Version 0.8.1
============= =============

View file

@ -19,16 +19,11 @@ status_path = ~/.vdirsyncer/status/
a = bob_contacts_local a = bob_contacts_local
b = bob_contacts_remote b = bob_contacts_remote
# Synchronize all collections that can be found.
# Synchronize all collections available on "side B" (in this case the server).
# You need to run `vdirsyncer discover` if new calendars/addressbooks are added # You need to run `vdirsyncer discover` if new calendars/addressbooks are added
# on the server. # on the server.
# Omitting this parameter implies that the given path and URL in the collections = ["from a", "from b"]
# corresponding `[storage <name>]` blocks are already directly pointing to a
# collection each.
collections = ["from b"]
# Synchronize the "display name" property into a local file (~/.contacts/displayname). # Synchronize the "display name" property into a local file (~/.contacts/displayname).
metadata = ["displayname"] metadata = ["displayname"]
@ -58,7 +53,7 @@ url = https://owncloud.example.com/remote.php/carddav/
[pair bob_calendar] [pair bob_calendar]
a = bob_calendar_local a = bob_calendar_local
b = bob_calendar_remote b = bob_calendar_remote
collections = ["private", "work"] collections = ["from a", "from b"]
# Calendars also have a color property # Calendars also have a color property
metadata = ["displayname", "color"] metadata = ["displayname", "color"]

View file

@ -52,14 +52,11 @@ Pair Section
- ``a`` and ``b`` reference the storages to sync by their names. - ``a`` and ``b`` reference the storages to sync by their names.
- ``collections``: Optional, a list of collections to synchronize when - ``collections``: A list of collections to synchronize when
``vdirsyncer sync`` is executed. If this parameter is omitted, it is assumed ``vdirsyncer sync`` is executed.
the storages are already directly pointing to one collection each. Specifying
a collection multiple times won't make vdirsyncer sync that collection more
than once.
Furthermore, there are the special values ``"from a"`` and ``"from b"``, The special values ``"from a"`` and ``"from b"``, tell vdirsyncer to try
which tell vdirsyncer to try autodiscovery on a specific storage. autodiscovery on a specific storage.
Examples: Examples:

View file

@ -72,6 +72,7 @@ default addressbook to ``~/.contacts/``::
[pair my_contacts] [pair my_contacts]
a = my_contacts_local a = my_contacts_local
b = my_contacts_remote b = my_contacts_remote
collections = null
[storage my_contacts_local] [storage my_contacts_local]
type = filesystem type = filesystem
@ -122,14 +123,14 @@ value.
Collection discovery Collection discovery
-------------------- --------------------
Configuring each collection (=addressbook/calendar) becomes extremely The above configuration only syncs a single addressbook. This is denoted by
repetitive if they are all on the same server. Vdirsyncer can do this for you ``collections = null`` (collection = addressbook/calendar). We can change this
by automatically downloading a list of the configured user's collections:: line to let vdirsyncer automatically sync all addressbooks it can find::
[pair my_contacts] [pair my_contacts]
a = my_contacts_local a = my_contacts_local
b = my_contacts_remote b = my_contacts_remote
collections = ["from b"] collections = ["from a", "from b"] # changed from `null`
[storage my_contacts_local] [storage my_contacts_local]
type = filesystem type = filesystem
@ -138,6 +139,8 @@ by automatically downloading a list of the configured user's collections::
[storage my_contacts_remote] [storage my_contacts_remote]
type = carddav type = carddav
# We can simplify this URL here as well. In theory it shouldn't matter.
url = https://owncloud.example.com/remote.php/carddav/ url = https://owncloud.example.com/remote.php/carddav/
username = bob username = bob
password = asdf password = asdf

View file

@ -29,6 +29,7 @@ def test_read_config(read_config, monkeypatch):
b = bob_b b = bob_b
foo = bar foo = bar
bam = true bam = true
collections = null
[storage bob_a] [storage bob_a]
type = filesystem type = filesystem
@ -45,7 +46,8 @@ def test_read_config(read_config, monkeypatch):
''') ''')
assert general == {'status_path': '/tmp/status/'} assert general == {'status_path': '/tmp/status/'}
assert pairs == {'bob': ('bob_a', 'bob_b', {'bam': True, 'foo': 'bar'})} assert pairs == {'bob': ('bob_a', 'bob_b',
{'collections': None, 'bam': True, 'foo': 'bar'})}
assert storages == { assert storages == {
'bob_a': {'type': 'filesystem', 'path': '/tmp/contacts/', 'fileext': 'bob_a': {'type': 'filesystem', 'path': '/tmp/contacts/', 'fileext':
'.vcf', 'yesno': False, 'number': 42, '.vcf', 'yesno': False, 'number': 42,
@ -58,6 +60,30 @@ def test_read_config(read_config, monkeypatch):
assert 'bogus' in errors[0] assert 'bogus' in errors[0]
def test_missing_collections_param(read_config, monkeypatch):
errorlog = []
monkeypatch.setattr('vdirsyncer.cli.cli_logger.error', errorlog.append)
with pytest.raises(exceptions.UserError) as excinfo:
read_config(u'''
[general]
status_path = /tmp/status/
[pair bob]
a = bob_a
b = bob_b
[storage bob_a]
type = lmao
[storage bob_b]
type = lmao
''')
assert 'collections parameter missing' in str(excinfo.value)
assert not errorlog
def test_storage_instance_from_config(monkeypatch): def test_storage_instance_from_config(monkeypatch):
def lol(**kw): def lol(**kw):
assert kw == {'foo': 'bar', 'baz': 1} assert kw == {'foo': 'bar', 'baz': 1}
@ -75,6 +101,7 @@ def test_missing_general_section(read_config):
[pair my_pair] [pair my_pair]
a = my_a a = my_a
b = my_b b = my_b
collections = null
[storage my_a] [storage my_a]
type = filesystem type = filesystem

View file

@ -19,6 +19,7 @@ def test_simple_run(tmpdir, runner):
[pair my_pair] [pair my_pair]
a = my_a a = my_a
b = my_b b = my_b
collections = null
[storage my_a] [storage my_a]
type = filesystem type = filesystem
@ -48,6 +49,7 @@ def test_debug_connections(tmpdir, runner):
[pair my_pair] [pair my_pair]
a = my_a a = my_a
b = my_b b = my_b
collections = null
[storage my_a] [storage my_a]
type = filesystem type = filesystem
@ -75,6 +77,7 @@ def test_empty_storage(tmpdir, runner):
[pair my_pair] [pair my_pair]
a = my_a a = my_a
b = my_b b = my_b
collections = null
[storage my_a] [storage my_a]
type = filesystem type = filesystem
@ -241,6 +244,7 @@ def test_multiple_pairs(tmpdir, runner):
[pair {a}{b}] [pair {a}{b}]
a = {a} a = {a}
b = {b} b = {b}
collections = null
''').format(a=name_a, b=name_b) ''').format(a=name_a, b=name_b)
for name in name_a, name_b: for name in name_a, name_b:
@ -326,6 +330,7 @@ def test_ident_conflict(tmpdir, runner):
[pair foobar] [pair foobar]
a = foo a = foo
b = bar b = bar
collections = null
[storage foo] [storage foo]
type = filesystem type = filesystem
@ -365,6 +370,7 @@ def test_unknown_storage(tmpdir, runner, existing, missing):
[pair foobar] [pair foobar]
a = foo a = foo
b = bar b = bar
collections = null
[storage {existing}] [storage {existing}]
type = filesystem type = filesystem

View file

@ -55,15 +55,29 @@ def _validate_general_section(general_config):
def _validate_pair_section(pair_config): def _validate_pair_section(pair_config):
collections = pair_config.get('collections', None) try:
collections = pair_config['collections']
except KeyError:
raise ValueError('collections parameter missing.\n\n'
'As of 0.9.0 this parameter has no default anymore. '
'Set `collections = null` explicitly in your pair '
'config.')
if collections is None: if collections is None:
return return
e = ValueError('`collections` parameter must be a list of collection ' e = ValueError('`collections` parameter must be a list of collection '
'names (strings!) or `null`.') 'names (strings!) or `null`.')
if not isinstance(collections, list) or \
any(not isinstance(x, (text_type, bytes)) for x in collections): if not isinstance(collections, list):
raise e raise e
if any(not isinstance(x, (text_type, bytes)) for x in collections):
raise e
if len(set(collections)) != len(collections):
raise ValueError('Duplicate values in collections parameter.')
def load_config(): def load_config():
fname = os.environ.get('VDIRSYNCER_CONFIG', None) fname = os.environ.get('VDIRSYNCER_CONFIG', None)

View file

@ -252,14 +252,14 @@ def _handle_collection_not_found(config, collection, e=None):
def _collections_for_pair_impl(status_path, pair): def _collections_for_pair_impl(status_path, pair):
shortcuts = set(pair.options.get('collections', ())) shortcuts = pair.options['collections']
if not shortcuts: if shortcuts is None:
yield None, (pair.config_a, pair.config_b) yield None, (pair.config_a, pair.config_b)
else: else:
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 shortcuts: for shortcut in set(shortcuts):
if shortcut == 'from a': if shortcut == 'from a':
collections = a_discovered collections = a_discovered
elif shortcut == 'from b': elif shortcut == 'from b':