Synchronizes two storages. :param storage_a: The first storage :type storage_a: :class:`vdirsyncer.storage.base.Storage` :param storage_b: The second storage :type storage_b: :class:`vdirsyncer.storage.base.Storage` :param status: {ident: (href_a, etag_a, href_b, etag_b)}
(storage_a, storage_b, status, conflict_resolution=None,
force_delete=False, error_callback=None, partial_sync='revert')
| 88 | |
| 89 | |
| 90 | def sync(storage_a, storage_b, status, conflict_resolution=None, |
| 91 | force_delete=False, error_callback=None, partial_sync='revert'): |
| 92 | '''Synchronizes two storages. |
| 93 | |
| 94 | :param storage_a: The first storage |
| 95 | :type storage_a: :class:`vdirsyncer.storage.base.Storage` |
| 96 | :param storage_b: The second storage |
| 97 | :type storage_b: :class:`vdirsyncer.storage.base.Storage` |
| 98 | :param status: {ident: (href_a, etag_a, href_b, etag_b)} |
| 99 | metadata about the two storages for detection of changes. Will be |
| 100 | modified by the function and should be passed to it at the next sync. |
| 101 | If this is the first sync, an empty dictionary should be provided. |
| 102 | :param conflict_resolution: A function that, given two conflicting item |
| 103 | versions A and B, returns a new item with conflicts resolved. The UID |
| 104 | must be the same. The strings `"a wins"` and `"b wins"` are also |
| 105 | accepted to mean that side's version will always be taken. If none |
| 106 | is provided, the sync function will raise :py:exc:`SyncConflict`. |
| 107 | :param force_delete: When one storage got completely emptied between two |
| 108 | syncs, :py:exc:`StorageEmpty` is raised for |
| 109 | safety. Setting this parameter to ``True`` disables this safety |
| 110 | measure. |
| 111 | :param error_callback: Instead of raising errors when executing actions, |
| 112 | call the given function with an `Exception` as the only argument. |
| 113 | :param partial_sync: What to do when doing sync actions on read-only |
| 114 | storages. |
| 115 | |
| 116 | - ``error``: Raise an error. |
| 117 | - ``ignore``: Those actions are simply skipped. |
| 118 | - ``revert`` (default): Revert changes on other side. |
| 119 | ''' |
| 120 | if storage_a.read_only and storage_b.read_only: |
| 121 | raise BothReadOnly() |
| 122 | |
| 123 | if conflict_resolution == 'a wins': |
| 124 | conflict_resolution = lambda a, b: a |
| 125 | elif conflict_resolution == 'b wins': |
| 126 | conflict_resolution = lambda a, b: b |
| 127 | |
| 128 | status_nonempty = bool(next(status.iter_old(), None)) |
| 129 | |
| 130 | with status.transaction(): |
| 131 | a_info = _StorageInfo(storage_a, SubStatus(status, 'a')) |
| 132 | b_info = _StorageInfo(storage_b, SubStatus(status, 'b')) |
| 133 | |
| 134 | a_nonempty = a_info.prepare_new_status() |
| 135 | b_nonempty = b_info.prepare_new_status() |
| 136 | |
| 137 | if status_nonempty and not force_delete: |
| 138 | if a_nonempty and not b_nonempty: |
| 139 | raise StorageEmpty(empty_storage=storage_b) |
| 140 | elif not a_nonempty and b_nonempty: |
| 141 | raise StorageEmpty(empty_storage=storage_a) |
| 142 | |
| 143 | actions = list(_get_actions(a_info, b_info)) |
| 144 | |
| 145 | with storage_a.at_once(), storage_b.at_once(): |
| 146 | for action in actions: |
| 147 | try: |
nothing calls this directly
no test coverage detected