mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-04-27 14:57:41 +00:00
parent
3c9ef726df
commit
727ce250cf
8 changed files with 74 additions and 26 deletions
|
|
@ -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
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"]
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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':
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue