From 1f85b4db6fd72e84c241d359ec815a123136ede7 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 23 May 2014 15:14:38 +0200 Subject: [PATCH] Fix #63 --- vdirsyncer/cli.py | 31 +++++++++++++++++++++++-------- vdirsyncer/sync.py | 26 +++++++++++++------------- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/vdirsyncer/cli.py b/vdirsyncer/cli.py index 1786cf0..e530c78 100644 --- a/vdirsyncer/cli.py +++ b/vdirsyncer/cli.py @@ -14,7 +14,7 @@ import sys import argvard from .storage import storage_names -from .sync import sync, StorageEmpty +from .sync import sync, StorageEmpty, SyncConflict from .utils import expand_path, parse_options, split_dict, get_class_init_args import vdirsyncer.log as log @@ -29,6 +29,7 @@ except ImportError: cli_logger = log.get(__name__) PROJECT_HOME = 'https://github.com/untitaker/vdirsyncer' +DOCS_HOME = 'https://vdirsyncer.readthedocs.org/en/latest' class CliError(RuntimeError): @@ -296,16 +297,30 @@ def sync_collection(config_a, config_b, pair_name, collection, pair_options, force_delete=status_name in force_delete ) except StorageEmpty as e: - raise CliError( - '{collection_description}: Storage "{side}" ({storage}) was ' - 'completely emptied. Use "--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( - collection_description=collection_description, + cli_logger.critical( + '{collection}: Storage "{side}" ({storage}) was completely ' + 'emptied. Use "--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( + collection=collection_description, side='a' if e.empty_storage is a else 'b', storage=e.empty_storage, status_name=status_name ) ) + except SyncConflict as e: + cli_logger.critical( + '{collection}: 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(collection=collection_description, e=e, docs=DOCS_HOME) + ) + except Exception: + cli_logger.exception('Unhandled exception occured while syncing {}.' + .format(collection_description)) + save_status(general['status_path'], status_name, status) diff --git a/vdirsyncer/sync.py b/vdirsyncer/sync.py index ed65a96..7a85590 100644 --- a/vdirsyncer/sync.py +++ b/vdirsyncer/sync.py @@ -25,6 +25,10 @@ sync_logger = log.get(__name__) class SyncError(exceptions.Error): '''Errors related to synchronization.''' + def __init__(self, *args, **kwargs): + self.__dict__.update(kwargs) + super(SyncError, self).__init__(*args) + class SyncConflict(SyncError): ''' @@ -39,10 +43,6 @@ class StorageEmpty(SyncError): The first argument is the empty storage. ''' - @property - def empty_storage(self): - return self.args[0] - def prepare_list(storage, href_to_status): rv = {} @@ -63,7 +63,7 @@ def prepare_list(storage, href_to_status): props['item'] = item props['ident'] = item.ident if props['etag'] != etag: - raise SyncConflict('Etag changed during sync.') + raise SyncError('Etag changed during sync.') return rv @@ -101,7 +101,7 @@ def sync(storage_a, storage_b, status, conflict_resolution=None, list_b = prepare_list(storage_b, b_href_to_status) if bool(list_a) != bool(list_b) and status and not force_delete: - raise StorageEmpty(storage_b if list_a else storage_a) + raise StorageEmpty(empty_storage=(storage_b if list_a else storage_a)) a_ident_to_href = dict((x['ident'], href) for href, x in iteritems(list_a)) b_ident_to_href = dict((x['ident'], href) for href, x in iteritems(list_b)) @@ -185,15 +185,15 @@ def action_conflict_resolve(ident): .format(ident)) a_storage, list_a, a_ident_to_href = storages['a'] b_storage, list_b, b_ident_to_href = storages['b'] - a_href = a_ident_to_href[ident] - b_href = b_ident_to_href[ident] - a_meta = list_a[a_href] - b_meta = list_b[b_href] - if a_meta['item'].raw == b_meta['item'].raw: + href_a = a_ident_to_href[ident] + href_b = b_ident_to_href[ident] + meta_a = list_a[href_a] + meta_b = list_b[href_b] + if meta_a['item'].raw == meta_b['item'].raw: sync_logger.info('...same content on both sides.') - status[ident] = a_href, a_meta['etag'], b_href, b_meta['etag'] + status[ident] = href_a, meta_a['etag'], href_b, meta_b['etag'] elif conflict_resolution is None: - raise SyncConflict() + raise SyncConflict(ident=ident, href_a=href_a, href_b=href_b) elif conflict_resolution == 'a wins': sync_logger.info('...{} wins.'.format(a_storage)) action_update(ident, 'a', 'b')(storages, status,