Index: chrome/browser/sync/engine/conflict_resolver.cc |
diff --git a/chrome/browser/sync/engine/conflict_resolver.cc b/chrome/browser/sync/engine/conflict_resolver.cc |
deleted file mode 100644 |
index b91e8d17740d004b11151bbed6545d6c9a014b6d..0000000000000000000000000000000000000000 |
--- a/chrome/browser/sync/engine/conflict_resolver.cc |
+++ /dev/null |
@@ -1,404 +0,0 @@ |
-// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
-// Use of this source code is governed by a BSD-style license that can be |
-// found in the LICENSE file. |
- |
-#include "chrome/browser/sync/engine/conflict_resolver.h" |
- |
-#include <algorithm> |
-#include <list> |
-#include <map> |
-#include <set> |
- |
-#include "base/location.h" |
-#include "base/metrics/histogram.h" |
-#include "chrome/browser/sync/engine/syncer.h" |
-#include "chrome/browser/sync/engine/syncer_util.h" |
-#include "chrome/browser/sync/protocol/service_constants.h" |
-#include "chrome/browser/sync/sessions/status_controller.h" |
-#include "chrome/browser/sync/syncable/syncable.h" |
-#include "chrome/browser/sync/util/cryptographer.h" |
-#include "sync/protocol/nigori_specifics.pb.h" |
- |
-using std::list; |
-using std::map; |
-using std::set; |
-using syncable::BaseTransaction; |
-using syncable::Directory; |
-using syncable::Entry; |
-using syncable::GetModelTypeFromSpecifics; |
-using syncable::Id; |
-using syncable::IsRealDataType; |
-using syncable::MutableEntry; |
-using syncable::WriteTransaction; |
- |
-namespace browser_sync { |
- |
-using sessions::ConflictProgress; |
-using sessions::StatusController; |
- |
-namespace { |
- |
-const int SYNC_CYCLES_BEFORE_ADMITTING_DEFEAT = 8; |
- |
-} // namespace |
- |
-ConflictResolver::ConflictResolver() { |
-} |
- |
-ConflictResolver::~ConflictResolver() { |
-} |
- |
-void ConflictResolver::IgnoreLocalChanges(MutableEntry* entry) { |
- // An update matches local actions, merge the changes. |
- // This is a little fishy because we don't actually merge them. |
- // In the future we should do a 3-way merge. |
- // With IS_UNSYNCED false, changes should be merged. |
- entry->Put(syncable::IS_UNSYNCED, false); |
-} |
- |
-void ConflictResolver::OverwriteServerChanges(WriteTransaction* trans, |
- MutableEntry * entry) { |
- // This is similar to an overwrite from the old client. |
- // This is equivalent to a scenario where we got the update before we'd |
- // made our local client changes. |
- // TODO(chron): This is really a general property clobber. We clobber |
- // the server side property. Perhaps we should actually do property merging. |
- entry->Put(syncable::BASE_VERSION, entry->Get(syncable::SERVER_VERSION)); |
- entry->Put(syncable::IS_UNAPPLIED_UPDATE, false); |
-} |
- |
-ConflictResolver::ProcessSimpleConflictResult |
-ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans, |
- const Id& id, |
- const Cryptographer* cryptographer, |
- StatusController* status) { |
- MutableEntry entry(trans, syncable::GET_BY_ID, id); |
- // Must be good as the entry won't have been cleaned up. |
- CHECK(entry.good()); |
- |
- // This function can only resolve simple conflicts. Simple conflicts have |
- // both IS_UNSYNCED and IS_UNAPPLIED_UDPATE set. |
- if (!entry.Get(syncable::IS_UNAPPLIED_UPDATE) || |
- !entry.Get(syncable::IS_UNSYNCED)) { |
- // This is very unusual, but it can happen in tests. We may be able to |
- // assert NOTREACHED() here when those tests are updated. |
- return NO_SYNC_PROGRESS; |
- } |
- |
- if (entry.Get(syncable::IS_DEL) && entry.Get(syncable::SERVER_IS_DEL)) { |
- // we've both deleted it, so lets just drop the need to commit/update this |
- // entry. |
- entry.Put(syncable::IS_UNSYNCED, false); |
- entry.Put(syncable::IS_UNAPPLIED_UPDATE, false); |
- // we've made changes, but they won't help syncing progress. |
- // METRIC simple conflict resolved by merge. |
- return NO_SYNC_PROGRESS; |
- } |
- |
- // This logic determines "client wins" vs. "server wins" strategy picking. |
- // By the time we get to this point, we rely on the following to be true: |
- // a) We can decrypt both the local and server data (else we'd be in |
- // conflict encryption and not attempting to resolve). |
- // b) All unsynced changes have been re-encrypted with the default key ( |
- // occurs either in AttemptToUpdateEntry, SetPassphrase, or |
- // RefreshEncryption). |
- // c) Base_server_specifics having a valid datatype means that we received |
- // an undecryptable update that only changed specifics, and since then have |
- // not received any further non-specifics-only or decryptable updates. |
- // d) If the server_specifics match specifics, server_specifics are |
- // encrypted with the default key, and all other visible properties match, |
- // then we can safely ignore the local changes as redundant. |
- // e) Otherwise if the base_server_specifics match the server_specifics, no |
- // functional change must have been made server-side (else |
- // base_server_specifics would have been cleared), and we can therefore |
- // safely ignore the server changes as redundant. |
- // f) Otherwise, it's in general safer to ignore local changes, with the |
- // exception of deletion conflicts (choose to undelete) and conflicts |
- // where the non_unique_name or parent don't match. |
- if (!entry.Get(syncable::SERVER_IS_DEL)) { |
- // TODO(nick): The current logic is arbitrary; instead, it ought to be made |
- // consistent with the ModelAssociator behavior for a datatype. It would |
- // be nice if we could route this back to ModelAssociator code to pick one |
- // of three options: CLIENT, SERVER, or MERGE. Some datatypes (autofill) |
- // are easily mergeable. |
- // See http://crbug.com/77339. |
- bool name_matches = entry.Get(syncable::NON_UNIQUE_NAME) == |
- entry.Get(syncable::SERVER_NON_UNIQUE_NAME); |
- bool parent_matches = entry.Get(syncable::PARENT_ID) == |
- entry.Get(syncable::SERVER_PARENT_ID); |
- bool entry_deleted = entry.Get(syncable::IS_DEL); |
- |
- // This positional check is meant to be necessary but not sufficient. As a |
- // result, it may be false even when the position hasn't changed, possibly |
- // resulting in unnecessary commits, but if it's true the position has |
- // definitely not changed. The check works by verifying that the prev id |
- // as calculated from the server position (which will ignore any |
- // unsynced/unapplied predecessors and be root for non-bookmark datatypes) |
- // matches the client prev id. Because we traverse chains of conflicting |
- // items in predecessor -> successor order, we don't need to also verify the |
- // successor matches (If it's in conflict, we'll verify it next. If it's |
- // not, then it should be taken into account already in the |
- // ComputePrevIdFromServerPosition calculation). This works even when there |
- // are chains of conflicting items. |
- // |
- // Example: Original sequence was abcde. Server changes to aCDbe, while |
- // client changes to aDCbe (C and D are in conflict). Locally, D's prev id |
- // is a, while C's prev id is D. On the other hand, the server prev id will |
- // ignore unsynced/unapplied items, so D's server prev id will also be a, |
- // just like C's. Because we traverse in client predecessor->successor |
- // order, we evaluate D first. Since prev id and server id match, we |
- // consider the position to have remained the same for D, and will unset |
- // it's UNSYNCED/UNAPPLIED bits. When we evaluate C though, we'll see that |
- // the prev id is D locally while the server's prev id is a. C will |
- // therefore count as a positional conflict (and the local data will be |
- // overwritten by the server data typically). The final result will be |
- // aCDbe (the same as the server's view). Even though both C and D were |
- // modified, only one counted as being in actual conflict and was resolved |
- // with local/server wins. |
- // |
- // In general, when there are chains of positional conflicts, only the first |
- // item in chain (based on the clients point of view) will have both it's |
- // server prev id and local prev id match. For all the rest the server prev |
- // id will be the predecessor of the first item in the chain, and therefore |
- // not match the local prev id. |
- // |
- // Similarly, chains of conflicts where the server and client info are the |
- // same are supported due to the predecessor->successor ordering. In this |
- // case, from the first item onward, we unset the UNSYNCED/UNAPPLIED bits as |
- // we decide that nothing changed. The subsequent item's server prev id will |
- // accurately match the local prev id because the predecessor is no longer |
- // UNSYNCED/UNAPPLIED. |
- // TODO(zea): simplify all this once we can directly compare server position |
- // to client position. |
- syncable::Id server_prev_id = entry.ComputePrevIdFromServerPosition( |
- entry.Get(syncable::SERVER_PARENT_ID)); |
- bool needs_reinsertion = !parent_matches || |
- server_prev_id != entry.Get(syncable::PREV_ID); |
- DVLOG_IF(1, needs_reinsertion) << "Insertion needed, server prev id " |
- << " is " << server_prev_id << ", local prev id is " |
- << entry.Get(syncable::PREV_ID); |
- const sync_pb::EntitySpecifics& specifics = |
- entry.Get(syncable::SPECIFICS); |
- const sync_pb::EntitySpecifics& server_specifics = |
- entry.Get(syncable::SERVER_SPECIFICS); |
- const sync_pb::EntitySpecifics& base_server_specifics = |
- entry.Get(syncable::BASE_SERVER_SPECIFICS); |
- std::string decrypted_specifics, decrypted_server_specifics; |
- bool specifics_match = false; |
- bool server_encrypted_with_default_key = false; |
- if (specifics.has_encrypted()) { |
- DCHECK(cryptographer->CanDecryptUsingDefaultKey(specifics.encrypted())); |
- decrypted_specifics = cryptographer->DecryptToString( |
- specifics.encrypted()); |
- } else { |
- decrypted_specifics = specifics.SerializeAsString(); |
- } |
- if (server_specifics.has_encrypted()) { |
- server_encrypted_with_default_key = |
- cryptographer->CanDecryptUsingDefaultKey( |
- server_specifics.encrypted()); |
- decrypted_server_specifics = cryptographer->DecryptToString( |
- server_specifics.encrypted()); |
- } else { |
- decrypted_server_specifics = server_specifics.SerializeAsString(); |
- } |
- if (decrypted_server_specifics == decrypted_specifics && |
- server_encrypted_with_default_key == specifics.has_encrypted()) { |
- specifics_match = true; |
- } |
- bool base_server_specifics_match = false; |
- if (server_specifics.has_encrypted() && |
- IsRealDataType(GetModelTypeFromSpecifics(base_server_specifics))) { |
- std::string decrypted_base_server_specifics; |
- if (!base_server_specifics.has_encrypted()) { |
- decrypted_base_server_specifics = |
- base_server_specifics.SerializeAsString(); |
- } else { |
- decrypted_base_server_specifics = cryptographer->DecryptToString( |
- base_server_specifics.encrypted()); |
- } |
- if (decrypted_server_specifics == decrypted_base_server_specifics) |
- base_server_specifics_match = true; |
- } |
- |
- // We manually merge nigori data. |
- if (entry.GetModelType() == syncable::NIGORI) { |
- // Create a new set of specifics based on the server specifics (which |
- // preserves their encryption keys). |
- sync_pb::EntitySpecifics specifics = |
- entry.Get(syncable::SERVER_SPECIFICS); |
- sync_pb::NigoriSpecifics* server_nigori = specifics.mutable_nigori(); |
- // Store the merged set of encrypted types (cryptographer->Update(..) will |
- // have merged the local types already). |
- cryptographer->UpdateNigoriFromEncryptedTypes(server_nigori); |
- // The local set of keys is already merged with the server's set within |
- // the cryptographer. If we don't have pending keys we can store the |
- // merged set back immediately. Else we preserve the server keys and will |
- // update the nigori when the user provides the pending passphrase via |
- // SetPassphrase(..). |
- if (cryptographer->is_ready()) { |
- cryptographer->GetKeys(server_nigori->mutable_encrypted()); |
- } |
- // TODO(zea): Find a better way of doing this. As it stands, we have to |
- // update this code whenever we add a new non-cryptographer related field |
- // to the nigori node. |
- if (entry.Get(syncable::SPECIFICS).nigori().sync_tabs()) { |
- server_nigori->set_sync_tabs(true); |
- } |
- // We deliberately leave the server's device information. This client will |
- // add it's own device information on restart. |
- entry.Put(syncable::SPECIFICS, specifics); |
- DVLOG(1) << "Resovling simple conflict, merging nigori nodes: " << entry; |
- status->increment_num_server_overwrites(); |
- OverwriteServerChanges(trans, &entry); |
- UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
- NIGORI_MERGE, |
- CONFLICT_RESOLUTION_SIZE); |
- } else if (!entry_deleted && name_matches && parent_matches && |
- specifics_match && !needs_reinsertion) { |
- DVLOG(1) << "Resolving simple conflict, everything matches, ignoring " |
- << "changes for: " << entry; |
- // This unsets both IS_UNSYNCED and IS_UNAPPLIED_UPDATE, and sets the |
- // BASE_VERSION to match the SERVER_VERSION. If we didn't also unset |
- // IS_UNAPPLIED_UPDATE, then we would lose unsynced positional data from |
- // adjacent entries when the server update gets applied and the item is |
- // re-inserted into the PREV_ID/NEXT_ID linked list. This is primarily |
- // an issue because we commit after applying updates, and is most |
- // commonly seen when positional changes are made while a passphrase |
- // is required (and hence there will be many encryption conflicts). |
- OverwriteServerChanges(trans, &entry); |
- IgnoreLocalChanges(&entry); |
- UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
- CHANGES_MATCH, |
- CONFLICT_RESOLUTION_SIZE); |
- } else if (base_server_specifics_match) { |
- DVLOG(1) << "Resolving simple conflict, ignoring server encryption " |
- << " changes for: " << entry; |
- status->increment_num_server_overwrites(); |
- OverwriteServerChanges(trans, &entry); |
- UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
- IGNORE_ENCRYPTION, |
- CONFLICT_RESOLUTION_SIZE); |
- } else if (entry_deleted || !name_matches || !parent_matches) { |
- OverwriteServerChanges(trans, &entry); |
- status->increment_num_server_overwrites(); |
- DVLOG(1) << "Resolving simple conflict, overwriting server changes " |
- << "for: " << entry; |
- UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
- OVERWRITE_SERVER, |
- CONFLICT_RESOLUTION_SIZE); |
- } else { |
- DVLOG(1) << "Resolving simple conflict, ignoring local changes for: " |
- << entry; |
- IgnoreLocalChanges(&entry); |
- status->increment_num_local_overwrites(); |
- UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
- OVERWRITE_LOCAL, |
- CONFLICT_RESOLUTION_SIZE); |
- } |
- // Now that we've resolved the conflict, clear the prev server |
- // specifics. |
- entry.Put(syncable::BASE_SERVER_SPECIFICS, sync_pb::EntitySpecifics()); |
- return SYNC_PROGRESS; |
- } else { // SERVER_IS_DEL is true |
- // If a server deleted folder has local contents it should be a hierarchy |
- // conflict. Hierarchy conflicts should not be processed by this function. |
- // We could end up here if a change was made since we last tried to detect |
- // conflicts, which was during update application. |
- if (entry.Get(syncable::IS_DIR)) { |
- Directory::ChildHandles children; |
- trans->directory()->GetChildHandlesById(trans, |
- entry.Get(syncable::ID), |
- &children); |
- if (0 != children.size()) { |
- DVLOG(1) << "Entry is a server deleted directory with local contents, " |
- << "should be a hierarchy conflict. (race condition)."; |
- return NO_SYNC_PROGRESS; |
- } |
- } |
- |
- // The entry is deleted on the server but still exists locally. |
- if (!entry.Get(syncable::UNIQUE_CLIENT_TAG).empty()) { |
- // If we've got a client-unique tag, we can undelete while retaining |
- // our present ID. |
- DCHECK_EQ(entry.Get(syncable::SERVER_VERSION), 0) << "For the server to " |
- "know to re-create, client-tagged items should revert to version 0 " |
- "when server-deleted."; |
- OverwriteServerChanges(trans, &entry); |
- status->increment_num_server_overwrites(); |
- DVLOG(1) << "Resolving simple conflict, undeleting server entry: " |
- << entry; |
- UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
- OVERWRITE_SERVER, |
- CONFLICT_RESOLUTION_SIZE); |
- // Clobber the versions, just in case the above DCHECK is violated. |
- entry.Put(syncable::SERVER_VERSION, 0); |
- entry.Put(syncable::BASE_VERSION, 0); |
- } else { |
- // Otherwise, we've got to undelete by creating a new locally |
- // uncommitted entry. |
- SyncerUtil::SplitServerInformationIntoNewEntry(trans, &entry); |
- |
- MutableEntry server_update(trans, syncable::GET_BY_ID, id); |
- CHECK(server_update.good()); |
- CHECK(server_update.Get(syncable::META_HANDLE) != |
- entry.Get(syncable::META_HANDLE)) |
- << server_update << entry; |
- UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
- UNDELETE, |
- CONFLICT_RESOLUTION_SIZE); |
- } |
- return SYNC_PROGRESS; |
- } |
-} |
- |
-bool ConflictResolver::ResolveConflicts(syncable::WriteTransaction* trans, |
- const Cryptographer* cryptographer, |
- const ConflictProgress& progress, |
- sessions::StatusController* status) { |
- bool forward_progress = false; |
- // Iterate over simple conflict items. |
- set<Id>::const_iterator conflicting_item_it; |
- set<Id> processed_items; |
- for (conflicting_item_it = progress.SimpleConflictingItemsBegin(); |
- conflicting_item_it != progress.SimpleConflictingItemsEnd(); |
- ++conflicting_item_it) { |
- Id id = *conflicting_item_it; |
- if (processed_items.count(id) > 0) |
- continue; |
- |
- // We have a simple conflict. In order check if positions have changed, |
- // we need to process conflicting predecessors before successors. Traverse |
- // backwards through all continuous conflicting predecessors, building a |
- // stack of items to resolve in predecessor->successor order, then process |
- // each item individually. |
- list<Id> predecessors; |
- Id prev_id = id; |
- do { |
- predecessors.push_back(prev_id); |
- Entry entry(trans, syncable::GET_BY_ID, prev_id); |
- // Any entry in conflict must be valid. |
- CHECK(entry.good()); |
- Id new_prev_id = entry.Get(syncable::PREV_ID); |
- if (new_prev_id == prev_id) |
- break; |
- prev_id = new_prev_id; |
- } while (processed_items.count(prev_id) == 0 && |
- progress.HasSimpleConflictItem(prev_id)); // Excludes root. |
- while (!predecessors.empty()) { |
- id = predecessors.back(); |
- predecessors.pop_back(); |
- switch (ProcessSimpleConflict(trans, id, cryptographer, status)) { |
- case NO_SYNC_PROGRESS: |
- break; |
- case SYNC_PROGRESS: |
- forward_progress = true; |
- break; |
- } |
- processed_items.insert(id); |
- } |
- } |
- return forward_progress; |
-} |
- |
-} // namespace browser_sync |