def handle_conflict(key, action_1, client_1, action_2, client_2): print( "\n" 'Conflict for "{}". Which version would you like to keep?\n' " (1) {}{} updated at {} ({})\n" " (2) {}{} updated at {} ({})\n" " (d) View difference (requires the diff command)\n" " (X) Skip this file\n".format( key, client_1.get_uri(), key, action_1.get_remote_datetime(), action_1.state, client_2.get_uri(), key, action_2.get_remote_datetime(), action_2.state, ), file=sys.stderr, ) while True: choice = utils.get_input("Choice (default=skip): ") print("", file=sys.stderr) if choice == "d": show_diff(client_1, client_2, key) else: break if choice == "1": return Resolution.get_resolution(key, action_1, client_2, client_1) elif choice == "2": return Resolution.get_resolution(key, action_2, client_1, client_2)
def test_repr(self): s3_client = s3.S3SyncClient(None, 'mortybucket', 'dimensional/portals') local_client = local.LocalSyncClient('/home/picklerick') resolution = Resolution(Resolution.CREATE, s3_client, local_client, 'foo', 20023) expected_repr = ("Resolution<action=CREATE, " "to=s3://mortybucket/dimensional/portals/, " "from=/home/picklerick/, " "key=foo, timestamp=20023>") assert repr(resolution) == expected_repr
def sync(self, conflict_choice=None, keys=None, dry_run=False): self.client_1.lock() self.client_2.lock() try: resolutions, unhandled_events = self.get_sync_states(keys) self.logger.debug( 'There are %s unhandled events for the user to solve', len(unhandled_events) ) self.logger.debug( 'There are %s automatically resolved calls', len(resolutions) ) for key in sorted(unhandled_events.keys()): action_1, action_2 = unhandled_events[key] if conflict_choice == '1': resolutions[key] = Resolution.get_resolution( key, action_1, self.client_2, self.client_1 ) elif conflict_choice == '2': resolutions[key] = Resolution.get_resolution( key, action_2, self.client_1, self.client_2 ) if self.conflict_handler is not None: resolution = self.conflict_handler( key, action_1, self.client_1, action_2, self.client_2 ) if resolution is not None: resolutions[key] = resolution else: self.logger.info('Ignoring sync conflict for %s', key) else: self.logger.info('Unable to resolve conflict for %s', key) self.run_resolutions(resolutions, dry_run) except KeyboardInterrupt: self.logger.warning('Session interrupted by Keyboard Interrupt. Aborting....') finally: self.client_1.unlock() self.client_2.unlock()
def test_get_resolution_updated(self, state, action, s3_client, local_client): sync_state = SyncState(state, 1234, 4567) resolution = Resolution.get_resolution('foo/bar', sync_state, s3_client, local_client) assert resolution.action == action assert resolution.to_client == s3_client assert resolution.key == 'foo/bar' if state != SyncState.DELETED: assert resolution.timestamp == 1234 assert resolution.from_client == local_client else: assert resolution.timestamp == 4567 assert resolution.from_client is None
def sync(self, conflict_choice=None, keys=None, dry_run=False): self.client_1.lock() self.client_2.lock() try: resolutions, unhandled_events = self.get_sync_states(keys) self.logger.debug( "There are %s unhandled events for the user to solve", len(unhandled_events), ) self.logger.debug("There are %s automatically resolved calls", len(resolutions)) for key in sorted(unhandled_events.keys()): action_1, action_2 = unhandled_events[key] if conflict_choice == "1": resolutions[key] = Resolution.get_resolution( key, action_1, self.client_2, self.client_1) elif conflict_choice == "2": resolutions[key] = Resolution.get_resolution( key, action_2, self.client_1, self.client_2) if self.conflict_handler is not None: resolution = self.conflict_handler(key, action_1, self.client_1, action_2, self.client_2) if resolution is not None: resolutions[key] = resolution else: self.logger.info("Ignoring sync conflict for %s", key) else: self.logger.info("Unable to resolve conflict for %s", key) self.run_resolutions(resolutions, dry_run) finally: self.client_1.unlock() self.client_2.unlock()
def get_sync_states(self, keys=None): # we store a list of resolutions to make sure we can handle everything before # running any updates on the file system and indexes resolutions = {} # list of unhandled events which cannot be solved automatically (or alternatively the # the automated solution has not yet been implemented) unhandled_events = {} self.logger.debug("Generating deferred calls based on client states") for key, state_1, state_2 in self.get_states(keys): self.logger.debug("%s: %s %s", key, state_1, state_2) if (state_1.state == SyncState.NOCHANGES and state_2.state == SyncState.NOCHANGES): if state_1.remote_timestamp == state_2.remote_timestamp: continue elif state_1.remote_timestamp > state_2.remote_timestamp: resolutions[key] = Resolution( Resolution.UPDATE, self.client_2, self.client_1, key, state_1.remote_timestamp, ) elif state_2.remote_timestamp > state_1.remote_timestamp: resolutions[key] = Resolution( Resolution.UPDATE, self.client_1, self.client_2, key, state_2.remote_timestamp, ) elif (state_1.state == SyncState.CREATED and state_2.state == SyncState.DOESNOTEXIST): resolutions[key] = Resolution( Resolution.CREATE, self.client_2, self.client_1, key, state_1.local_timestamp, ) elif (state_2.state == SyncState.CREATED and state_1.state == SyncState.DOESNOTEXIST): resolutions[key] = Resolution( Resolution.CREATE, self.client_1, self.client_2, key, state_2.local_timestamp, ) elif (state_1.state == SyncState.NOCHANGES and state_2.state == SyncState.DOESNOTEXIST): resolutions[key] = Resolution( Resolution.CREATE, self.client_2, self.client_1, key, state_1.remote_timestamp, ) elif (state_2.state == SyncState.NOCHANGES and state_1.state == SyncState.DOESNOTEXIST): resolutions[key] = Resolution( Resolution.CREATE, self.client_1, self.client_2, key, state_2.remote_timestamp, ) elif (state_1.state == SyncState.UPDATED and state_2.state == SyncState.DOESNOTEXIST): resolutions[key] = Resolution( Resolution.CREATE, self.client_2, self.client_1, key, state_1.local_timestamp, ) elif (state_2.state == SyncState.UPDATED and state_1.state == SyncState.DOESNOTEXIST): resolutions[key] = Resolution( Resolution.CREATE, self.client_2, self.client_1, key, state_1.local_timestamp, ) elif state_1.state in ( SyncState.DELETED, SyncState.DOESNOTEXIST, ) and state_2.state in (SyncState.DELETED, SyncState.DOESNOTEXIST): # nothing to do, they have already both been deleted/do not exist continue elif (state_1.state == SyncState.UPDATED and state_2.state == SyncState.NOCHANGES and state_1.remote_timestamp == state_2.remote_timestamp): resolutions[key] = Resolution( Resolution.UPDATE, self.client_2, self.client_1, key, state_1.local_timestamp, ) elif (state_2.state == SyncState.UPDATED and state_1.state == SyncState.NOCHANGES and state_1.remote_timestamp == state_2.remote_timestamp): resolutions[key] = Resolution( Resolution.UPDATE, self.client_1, self.client_2, key, state_2.local_timestamp, ) elif (state_1.state == SyncState.DELETED and state_2.state == SyncState.NOCHANGES and state_1.remote_timestamp == state_2.remote_timestamp): resolutions[key] = Resolution( Resolution.DELETE, self.client_2, None, key, state_1.remote_timestamp, ) elif (state_2.state == SyncState.DELETED and state_1.state == SyncState.NOCHANGES and state_1.remote_timestamp == state_2.remote_timestamp): resolutions[key] = Resolution( Resolution.DELETE, self.client_1, None, key, state_2.remote_timestamp, ) elif (state_1.state == SyncState.DELETED and state_2.state == SyncState.CREATED and state_1.remote_timestamp == state_2.remote_timestamp): resolutions[key] = Resolution( Resolution.CREATE, self.client_1, self.client_2, key, state_2.local_timestamp, ) elif (state_2.state == SyncState.DELETED and state_1.state == SyncState.CREATED and state_1.remote_timestamp == state_2.remote_timestamp): resolutions[key] = Resolution( Resolution.CREATE, self.client_2, self.client_1, key, state_1.local_timestamp, ) else: unhandled_events[key] = (state_1, state_2) self.logger.debug("Action=%s", resolutions.get(key)) return resolutions, unhandled_events
def test_equal_wrong_instance(self): resolution = Resolution(Resolution.CREATE, None, None, 'bar', 23232) assert resolution != "Not a Resolution"
def test_invalid_sync_state(self): with pytest.raises(ValueError) as exc: Resolution.get_resolution('', SyncState('Invalid', None, None), None, None) assert exc.value
def test_not_equal(self): resolution_1 = Resolution(Resolution.UPDATE, None, None, 'fffff', 232323) resolution_2 = Resolution(Resolution.DELETE, None, None, 'wew', 3823) assert resolution_1 != resolution_2
def test_equal(self): resolution_1 = Resolution(Resolution.UPDATE, None, None, 'fffff', 232323) resolution_2 = Resolution(Resolution.UPDATE, None, None, 'fffff', 232323) assert resolution_1 == resolution_2
def test_equal_to_self(self): resolution = Resolution(Resolution.UPDATE, None, None, 'fffff', 232323) assert resolution == resolution
def test_not_equal(self): resolution_1 = Resolution(Resolution.UPDATE, None, None, "fffff", 232323) resolution_2 = Resolution(Resolution.DELETE, None, None, "wew", 3823) assert resolution_1 != resolution_2