mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-03-29 09:35:50 +00:00
Redraw API borders
This commit is contained in:
parent
cfe252d458
commit
e717be9681
1 changed files with 96 additions and 96 deletions
|
|
@ -51,6 +51,41 @@ class JobFailed(RuntimeError):
|
|||
pass
|
||||
|
||||
|
||||
def handle_cli_error(status_name='sync'):
|
||||
try:
|
||||
raise
|
||||
except CliError as e:
|
||||
cli_logger.critical(str(e))
|
||||
except StorageEmpty as e:
|
||||
cli_logger.error(
|
||||
'{status_name}: Storage "{name}" was completely emptied. Use '
|
||||
'`vdirsyncer sync --force-delete {status_name}` to synchronize '
|
||||
'that emptyness to the other side, or delete the status by '
|
||||
'yourself to restore the items from the non-empty side.'.format(
|
||||
name=e.empty_storage.instance_name,
|
||||
status_name=status_name
|
||||
)
|
||||
)
|
||||
except SyncConflict as e:
|
||||
cli_logger.error(
|
||||
'{status_name}: One item changed on both sides. Resolve this '
|
||||
'conflict manually, or by setting the `conflict_resolution` '
|
||||
'parameter in your config file.\n'
|
||||
'See also {docs}/api.html#pair-section\n'
|
||||
'Item ID: {e.ident}\n'
|
||||
'Item href on side A: {e.href_a}\n'
|
||||
'Item href on side B: {e.href_b}\n'
|
||||
.format(status_name=status_name, e=e, docs=DOCS_HOME)
|
||||
)
|
||||
except (click.Abort, KeyboardInterrupt, JobFailed):
|
||||
pass
|
||||
except Exception as e:
|
||||
cli_logger.exception('Unhandled exception occured while syncing {}.'
|
||||
.format(status_name))
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def validate_section_name(name, section_type):
|
||||
invalid = set(name) - SECTION_NAME_CHARS
|
||||
if invalid:
|
||||
|
|
@ -74,13 +109,13 @@ def _parse_old_config_list_value(d, key):
|
|||
return value
|
||||
|
||||
|
||||
def get_status_name(pair, collection):
|
||||
def _get_status_name(pair, collection):
|
||||
if collection is None:
|
||||
return pair
|
||||
return pair + '/' + collection
|
||||
|
||||
|
||||
def get_collections_cache_key(pair_options, config_a, config_b):
|
||||
def _get_collections_cache_key(pair_options, config_a, config_b):
|
||||
m = hashlib.sha256()
|
||||
j = json.dumps([pair_options, config_a, config_b], sort_keys=True)
|
||||
m.update(j.encode('utf-8'))
|
||||
|
|
@ -105,7 +140,7 @@ def collections_for_pair(status_path, name_a, name_b, pair_name, config_a,
|
|||
:returns: iterable of (collection, a_args, b_args)
|
||||
'''
|
||||
rv = load_status(status_path, pair_name, data_type='collections')
|
||||
cache_key = get_collections_cache_key(pair_options, config_a, config_b)
|
||||
cache_key = _get_collections_cache_key(pair_options, config_a, config_b)
|
||||
if rv and not skip_cache:
|
||||
if rv.get('cache_key', None) == cache_key:
|
||||
return rv.get('collections', rv)
|
||||
|
|
@ -126,47 +161,44 @@ def collections_for_pair(status_path, name_a, name_b, pair_name, config_a,
|
|||
return rv
|
||||
|
||||
|
||||
def _discover_from_config(config):
|
||||
storage_type = config['type']
|
||||
cls, config = _storage_class_from_config(config)
|
||||
|
||||
try:
|
||||
discovered = list(cls.discover(**config))
|
||||
except Exception:
|
||||
return handle_storage_init_error(cls, config)
|
||||
else:
|
||||
rv = {}
|
||||
for args in discovered:
|
||||
args['type'] = storage_type
|
||||
rv[args['collection']] = args
|
||||
return rv
|
||||
|
||||
|
||||
def _get_coll(pair_name, storage_name, collection, discovered, config):
|
||||
try:
|
||||
return discovered[collection]
|
||||
except KeyError:
|
||||
storage_type = config['type']
|
||||
cls, config = _storage_class_from_config(config)
|
||||
try:
|
||||
args = cls.join_collection(collection=collection, **config)
|
||||
args['type'] = storage_type
|
||||
return args
|
||||
except NotImplementedError:
|
||||
raise CliError(
|
||||
'{pair}: Unable to find collection {collection!r} for storage '
|
||||
'{storage!r}. A same-named collection was found for the other '
|
||||
'storage, and vdirsyncer is configured to synchronize '
|
||||
'those.'.format(pair=pair_name, collection=collection,
|
||||
storage=storage_name))
|
||||
|
||||
|
||||
def _collections_for_pair_impl(status_path, name_a, name_b, pair_name,
|
||||
config_a, config_b, pair_options):
|
||||
|
||||
def _discover_from_config(config):
|
||||
storage_type = config['type']
|
||||
cls, config = storage_class_from_config(config)
|
||||
|
||||
try:
|
||||
discovered = list(cls.discover(**config))
|
||||
except Exception:
|
||||
return handle_storage_init_error(cls, config)
|
||||
else:
|
||||
rv = {}
|
||||
for args in discovered:
|
||||
args['type'] = storage_type
|
||||
rv[args['collection']] = args
|
||||
return rv
|
||||
|
||||
def _get_coll(discovered, collection, storage_name, config):
|
||||
try:
|
||||
return discovered[collection]
|
||||
except KeyError:
|
||||
storage_type = config['type']
|
||||
cls, config = storage_class_from_config(config)
|
||||
try:
|
||||
rv = cls.join_collection(collection=collection, **config)
|
||||
rv['type'] = storage_type
|
||||
return rv
|
||||
except NotImplementedError:
|
||||
raise CliError(
|
||||
'Unable to find collection {collection!r} for storage '
|
||||
'{storage_name!r}.\n For pair {pair_name!r}, you wanted '
|
||||
'to use the collections {shortcut!r}, which yielded '
|
||||
'{collection!r} (amongst others). Vdirsyncer was unable '
|
||||
'to find an equivalent collection for the other '
|
||||
'storage.'.format(
|
||||
collection=collection, shortcut=shortcut,
|
||||
storage_name=storage_name, pair_name=pair_name
|
||||
)
|
||||
)
|
||||
|
||||
shortcuts = set(_parse_old_config_list_value(pair_options, 'collections'))
|
||||
if not shortcuts:
|
||||
yield None, (config_a, config_b)
|
||||
|
|
@ -174,20 +206,23 @@ def _collections_for_pair_impl(status_path, name_a, name_b, pair_name,
|
|||
a_discovered = _discover_from_config(config_a)
|
||||
b_discovered = _discover_from_config(config_b)
|
||||
|
||||
for shortcut in shortcuts:
|
||||
if shortcut in ('from a', 'from b'):
|
||||
collections = (a_discovered if shortcut == 'from a'
|
||||
else b_discovered)
|
||||
else:
|
||||
collections = [shortcut]
|
||||
for shortcut in shortcuts:
|
||||
if shortcut == 'from a':
|
||||
collections = a_discovered
|
||||
elif shortcut == 'from b':
|
||||
collections = b_discovered
|
||||
else:
|
||||
collections = [shortcut]
|
||||
|
||||
for collection in collections:
|
||||
a_args = _get_coll(a_discovered, collection, name_a, config_a)
|
||||
b_args = _get_coll(b_discovered, collection, name_b, config_b)
|
||||
yield collection, (a_args, b_args)
|
||||
for collection in collections:
|
||||
a_args = _get_coll(pair_name, name_a, collection, a_discovered,
|
||||
config_a)
|
||||
b_args = _get_coll(pair_name, name_b, collection, b_discovered,
|
||||
config_b)
|
||||
yield collection, (a_args, b_args)
|
||||
|
||||
|
||||
def validate_general_section(general_config):
|
||||
def _validate_general_section(general_config):
|
||||
if general_config is None:
|
||||
raise CliError(
|
||||
'Unable to find general section. You should copy the example '
|
||||
|
|
@ -252,13 +287,13 @@ def load_config(f):
|
|||
else:
|
||||
handlers.get(section_type, bad_section)(name, get_options(section))
|
||||
|
||||
validate_general_section(general)
|
||||
_validate_general_section(general)
|
||||
return general, pairs, storages
|
||||
|
||||
|
||||
def load_status(base_path, pair, collection=None, data_type=None):
|
||||
assert data_type is not None
|
||||
status_name = get_status_name(pair, collection)
|
||||
status_name = _get_status_name(pair, collection)
|
||||
path = expand_path(os.path.join(base_path, status_name))
|
||||
if os.path.isfile(path) and data_type == 'items':
|
||||
new_path = path + '.items'
|
||||
|
|
@ -288,7 +323,7 @@ def load_status(base_path, pair, collection=None, data_type=None):
|
|||
def save_status(base_path, pair, collection=None, data_type=None, data=None):
|
||||
assert data_type is not None
|
||||
assert data is not None
|
||||
status_name = get_status_name(pair, collection)
|
||||
status_name = _get_status_name(pair, collection)
|
||||
path = expand_path(os.path.join(base_path, status_name)) + '.' + data_type
|
||||
base_path = os.path.dirname(path)
|
||||
|
||||
|
|
@ -308,7 +343,7 @@ def save_status(base_path, pair, collection=None, data_type=None, data=None):
|
|||
json.dump(data, f)
|
||||
|
||||
|
||||
def storage_class_from_config(config):
|
||||
def _storage_class_from_config(config):
|
||||
config = dict(config)
|
||||
storage_name = config.pop('type')
|
||||
cls = storage_names.get(storage_name, None)
|
||||
|
|
@ -317,14 +352,14 @@ def storage_class_from_config(config):
|
|||
return cls, config
|
||||
|
||||
|
||||
def storage_instance_from_config(config):
|
||||
def _storage_instance_from_config(config):
|
||||
'''
|
||||
:param config: A configuration dictionary to pass as kwargs to the class
|
||||
corresponding to config['type']
|
||||
:param description: A name for the storage for debugging purposes
|
||||
'''
|
||||
|
||||
cls, config = storage_class_from_config(config)
|
||||
cls, config = _storage_class_from_config(config)
|
||||
|
||||
try:
|
||||
return cls(**config)
|
||||
|
|
@ -416,44 +451,9 @@ def sync_pair(wq, pair_name, collections_to_sync, general, all_pairs,
|
|||
wq.spawn_worker()
|
||||
|
||||
|
||||
def handle_cli_error(status_name='sync'):
|
||||
try:
|
||||
raise
|
||||
except CliError as e:
|
||||
cli_logger.critical(str(e))
|
||||
except StorageEmpty as e:
|
||||
cli_logger.error(
|
||||
'{status_name}: Storage "{name}" was completely emptied. Use '
|
||||
'`vdirsyncer sync --force-delete {status_name}` to synchronize '
|
||||
'that emptyness to the other side, or delete the status by '
|
||||
'yourself to restore the items from the non-empty side.'.format(
|
||||
name=e.empty_storage.instance_name,
|
||||
status_name=status_name
|
||||
)
|
||||
)
|
||||
except SyncConflict as e:
|
||||
cli_logger.error(
|
||||
'{status_name}: One item changed on both sides. Resolve this '
|
||||
'conflict manually, or by setting the `conflict_resolution` '
|
||||
'parameter in your config file.\n'
|
||||
'See also {docs}/api.html#pair-section\n'
|
||||
'Item ID: {e.ident}\n'
|
||||
'Item href on side A: {e.href_a}\n'
|
||||
'Item href on side B: {e.href_b}\n'
|
||||
.format(status_name=status_name, e=e, docs=DOCS_HOME)
|
||||
)
|
||||
except (click.Abort, KeyboardInterrupt, JobFailed):
|
||||
pass
|
||||
except Exception as e:
|
||||
cli_logger.exception('Unhandled exception occured while syncing {}.'
|
||||
.format(status_name))
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def sync_collection(wq, pair_name, collection, config_a, config_b,
|
||||
pair_options, general, force_delete):
|
||||
status_name = get_status_name(pair_name, collection)
|
||||
status_name = _get_status_name(pair_name, collection)
|
||||
|
||||
key = ('sync', pair_name, collection)
|
||||
if key in wq.handled_jobs:
|
||||
|
|
@ -468,8 +468,8 @@ def sync_collection(wq, pair_name, collection, config_a, config_b,
|
|||
collection, data_type='items') or {}
|
||||
cli_logger.debug('Loaded status for {}'.format(status_name))
|
||||
|
||||
a = storage_instance_from_config(config_a)
|
||||
b = storage_instance_from_config(config_b)
|
||||
a = _storage_instance_from_config(config_a)
|
||||
b = _storage_instance_from_config(config_b)
|
||||
sync(
|
||||
a, b, status,
|
||||
conflict_resolution=pair_options.get('conflict_resolution', None),
|
||||
|
|
|
|||
Loading…
Reference in a new issue