diff --git a/tests/cli/test_repair.py b/tests/cli/test_repair.py index 37a69a2..f91a769 100644 --- a/tests/cli/test_repair.py +++ b/tests/cli/test_repair.py @@ -23,6 +23,16 @@ def test_repair_uids(): assert uid1 != uid2 +def test_repair_nonascii_uids(): + s = MemoryStorage() + href, etag = s.upload(Item(u'BEGIN:VCARD\nUID:äää\nEND:VCARD')) + assert s.get(href)[0].uid == u'äää' + repair_storage(s) + newuid = s.get(href)[0].uid + assert newuid != u'äää' + assert newuid.encode('ascii', 'ignore').decode('ascii') == newuid + + def test_full(tmpdir, runner): runner.write_with_general(dedent(''' [storage foo] diff --git a/vdirsyncer/cli/utils.py b/vdirsyncer/cli/utils.py index 21f2f41..d168f84 100644 --- a/vdirsyncer/cli/utils.py +++ b/vdirsyncer/cli/utils.py @@ -676,27 +676,38 @@ def repair_storage(storage): .format(href)) continue - if item.uid is None or item.uid in seen_uids: - if item.uid is None: - cli_logger.warning('No UID, assigning random one.') - else: - cli_logger.warning('Duplicate UID, reassigning random one.') - - new_uid = uuid.uuid4() - stack = [parsed] - while stack: - component = stack.pop() - if component.name in ('VEVENT', 'VTODO', 'VJOURNAL', 'VCARD'): - component['UID'] = new_uid - changed = True - else: - stack.extend(component.subcomponents) + if item.uid is None: + cli_logger.warning('No UID, assigning random one.') + changed = reroll_uid(parsed) or changed + elif item.uid in seen_uids: + cli_logger.warning('Duplicate UID, assigning random one.') + changed = reroll_uid(parsed) or changed + elif item.uid.encode('ascii', 'ignore').decode('ascii') != item.uid: + cli_logger.warning('UID is non-ascii, assigning, random one.') + changed = reroll_uid(parsed) or changed new_item = Item(u'\r\n'.join(parsed.dump_lines())) assert new_item.uid seen_uids.add(new_item.uid) if changed: - storage.update(href, new_item, etag) + try: + storage.update(href, new_item, etag) + except Exception: + cli_logger.exception('Server rejected new item.') + + +def reroll_uid(component): + new_uid = uuid.uuid4() + stack = [component] + changed = False + while stack: + component = stack.pop() + if component.name in ('VEVENT', 'VTODO', 'VJOURNAL', 'VCARD'): + component['UID'] = new_uid + changed = True + else: + stack.extend(component.subcomponents) + return changed def assert_permissions(path, wanted):