| 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
|
| old mode 100644
|
| new mode 100755
|
| index f67ea522372cca564dd4f5fffb711e3612de8366..1f7896ef8c69814d6c9d709dbcec1c66d83c2210
|
| --- a/chrome/browser/sync/engine/conflict_resolver.cc
|
| +++ b/chrome/browser/sync/engine/conflict_resolver.cc
|
| @@ -23,9 +23,7 @@ using syncable::Directory;
|
| using syncable::Entry;
|
| using syncable::Id;
|
| using syncable::MutableEntry;
|
| -using syncable::Name;
|
| using syncable::ScopedDirLookup;
|
| -using syncable::SyncName;
|
| using syncable::WriteTransaction;
|
|
|
| namespace browser_sync {
|
| @@ -38,88 +36,6 @@ ConflictResolver::ConflictResolver() {
|
| ConflictResolver::~ConflictResolver() {
|
| }
|
|
|
| -namespace {
|
| -// TODO(ncarter): Remove title/path conflicts and the code to resolve them.
|
| -// This is historical cruft that seems to be actually reached by some users.
|
| -inline PathString GetConflictPathnameBase(PathString base) {
|
| - time_t time_since = time(NULL);
|
| - struct tm* now = localtime(&time_since);
|
| - // Use a fixed format as the locale's format may include '/' characters or
|
| - // other illegal characters.
|
| - PathString date = IntToPathString(now->tm_year + 1900);
|
| - date.append(PSTR("-"));
|
| - ++now->tm_mon; // tm_mon is 0-based.
|
| - if (now->tm_mon < 10)
|
| - date.append(PSTR("0"));
|
| - date.append(IntToPathString(now->tm_mon));
|
| - date.append(PSTR("-"));
|
| - if (now->tm_mday < 10)
|
| - date.append(PSTR("0"));
|
| - date.append(IntToPathString(now->tm_mday));
|
| - return base + PSTR(" (Edited on ") + date + PSTR(")");
|
| -}
|
| -
|
| -// TODO(ncarter): Remove title/path conflicts and the code to resolve them.
|
| -Name FindNewName(BaseTransaction* trans,
|
| - Id parent_id,
|
| - const SyncName& original_name) {
|
| - const PathString name = original_name.value();
|
| - // 255 is defined in our spec.
|
| - const size_t allowed_length = kSyncProtocolMaxNameLengthBytes;
|
| - // TODO(sync): How do we get length on other platforms? The limit is
|
| - // checked in java on the server, so it's not the number of glyphs its the
|
| - // number of 16 bit characters in the UTF-16 representation.
|
| -
|
| - // 10 characters for 32 bit numbers + 2 characters for brackets means 12
|
| - // characters should be more than enough for the name. Doubling this ensures
|
| - // that we will have enough space.
|
| - COMPILE_ASSERT(kSyncProtocolMaxNameLengthBytes >= 24,
|
| - maximum_name_too_short);
|
| - CHECK(name.length() <= allowed_length);
|
| -
|
| - if (!Entry(trans,
|
| - syncable::GET_BY_PARENTID_AND_DBNAME,
|
| - parent_id,
|
| - name).good())
|
| - return Name::FromSyncName(original_name);
|
| - PathString base = name;
|
| - PathString ext;
|
| - PathString::size_type ext_index = name.rfind('.');
|
| - if (PathString::npos != ext_index && 0 != ext_index &&
|
| - name.length() - ext_index < allowed_length / 2) {
|
| - base = name.substr(0, ext_index);
|
| - ext = name.substr(ext_index);
|
| - }
|
| -
|
| - PathString name_base = GetConflictPathnameBase(base);
|
| - if (name_base.length() + ext.length() > allowed_length) {
|
| - name_base.resize(allowed_length - ext.length());
|
| - TrimPathStringToValidCharacter(&name_base);
|
| - }
|
| - PathString new_name = name_base + ext;
|
| - int n = 2;
|
| - while (Entry(trans,
|
| - syncable::GET_BY_PARENTID_AND_DBNAME,
|
| - parent_id,
|
| - new_name).good()) {
|
| - PathString local_ext = PSTR("(");
|
| - local_ext.append(IntToPathString(n));
|
| - local_ext.append(PSTR(")"));
|
| - local_ext.append(ext);
|
| - if (name_base.length() + local_ext.length() > allowed_length) {
|
| - name_base.resize(allowed_length - local_ext.length());
|
| - TrimPathStringToValidCharacter(&name_base);
|
| - }
|
| - new_name = name_base + local_ext;
|
| - n++;
|
| - }
|
| -
|
| - CHECK(new_name.length() <= kSyncProtocolMaxNameLengthBytes);
|
| - return Name(new_name);
|
| -}
|
| -
|
| -} // namespace
|
| -
|
| 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.
|
| @@ -151,8 +67,10 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
|
| CHECK(entry.good());
|
| // If an update fails, locally we have to be in a set or unsynced. We're not
|
| // in a set here, so we must be unsynced.
|
| - if (!entry.Get(syncable::IS_UNSYNCED))
|
| + if (!entry.Get(syncable::IS_UNSYNCED)) {
|
| return NO_SYNC_PROGRESS;
|
| + }
|
| +
|
| if (!entry.Get(syncable::IS_UNAPPLIED_UPDATE)) {
|
| if (!entry.Get(syncable::PARENT_ID).ServerKnows()) {
|
| LOG(INFO) << "Item conflicting because its parent not yet committed. "
|
| @@ -164,6 +82,7 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
|
| }
|
| 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.
|
| @@ -180,7 +99,8 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
|
| // Check if there's no changes.
|
|
|
| // Verbose but easier to debug.
|
| - bool name_matches = entry.SyncNameMatchesServerName();
|
| + 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);
|
| @@ -208,7 +128,6 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
|
| return NO_SYNC_PROGRESS;
|
| }
|
| }
|
| - // METRIC conflict resolved by entry split;
|
|
|
| // If the entry's deleted on the server, we can have a directory here.
|
| entry.Put(syncable::IS_UNSYNCED, true);
|
| @@ -225,162 +144,6 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
|
| }
|
| }
|
|
|
| -namespace {
|
| -
|
| -bool NamesCollideWithChildrenOfFolder(BaseTransaction* trans,
|
| - const Directory::ChildHandles& children,
|
| - Id folder_id) {
|
| - Directory::ChildHandles::const_iterator i = children.begin();
|
| - while (i != children.end()) {
|
| - Entry child(trans, syncable::GET_BY_HANDLE, *i);
|
| - CHECK(child.good());
|
| - if (Entry(trans,
|
| - syncable::GET_BY_PARENTID_AND_DBNAME,
|
| - folder_id,
|
| - child.GetName().db_value()).good())
|
| - return true;
|
| - ++i;
|
| - }
|
| - return false;
|
| -}
|
| -
|
| -void GiveEntryNewName(WriteTransaction* trans, MutableEntry* entry) {
|
| - using namespace syncable;
|
| - Name new_name =
|
| - FindNewName(trans, entry->Get(syncable::PARENT_ID), entry->GetName());
|
| - LOG(INFO) << "Resolving name clash, renaming " << *entry << " to "
|
| - << new_name.db_value();
|
| - entry->PutName(new_name);
|
| - CHECK(entry->Get(syncable::IS_UNSYNCED));
|
| -}
|
| -
|
| -} // namespace
|
| -
|
| -bool ConflictResolver::AttemptItemMerge(WriteTransaction* trans,
|
| - MutableEntry* locally_named,
|
| - MutableEntry* server_named) {
|
| - // To avoid complications we only merge new entries with server entries.
|
| - if (locally_named->Get(syncable::IS_DIR) !=
|
| - server_named->Get(syncable::SERVER_IS_DIR) ||
|
| - locally_named->Get(syncable::ID).ServerKnows() ||
|
| - locally_named->Get(syncable::IS_UNAPPLIED_UPDATE) ||
|
| - server_named->Get(syncable::IS_UNSYNCED))
|
| - return false;
|
| - Id local_id = locally_named->Get(syncable::ID);
|
| - Id desired_id = server_named->Get(syncable::ID);
|
| - if (locally_named->Get(syncable::IS_DIR)) {
|
| - // Extra work for directory name clash. We have to make sure we don't have
|
| - // clashing child items, and update the parent id the children of the new
|
| - // entry.
|
| - Directory::ChildHandles children;
|
| - trans->directory()->GetChildHandles(trans, local_id, &children);
|
| - if (NamesCollideWithChildrenOfFolder(trans, children, desired_id))
|
| - return false;
|
| -
|
| - LOG(INFO) << "Merging local changes to: " << desired_id << ". "
|
| - << *locally_named;
|
| -
|
| - server_named->Put(syncable::ID, trans->directory()->NextId());
|
| - Directory::ChildHandles::iterator i;
|
| - for (i = children.begin() ; i != children.end() ; ++i) {
|
| - MutableEntry child_entry(trans, syncable::GET_BY_HANDLE, *i);
|
| - CHECK(child_entry.good());
|
| - CHECK(child_entry.Put(syncable::PARENT_ID, desired_id));
|
| - CHECK(child_entry.Put(syncable::IS_UNSYNCED, true));
|
| - Id id = child_entry.Get(syncable::ID);
|
| - // We only note new entries for quicker merging next round.
|
| - if (!id.ServerKnows())
|
| - children_of_merged_dirs_.insert(id);
|
| - }
|
| - } else {
|
| - if (!server_named->Get(syncable::IS_DEL))
|
| - return false;
|
| - }
|
| -
|
| - LOG(INFO) << "Identical client and server items merging server changes. " <<
|
| - *locally_named << " server: " << *server_named;
|
| -
|
| - // Clear server_named's server data and mark it deleted so it goes away
|
| - // quietly because it's now identical to a deleted local entry.
|
| - // locally_named takes on the ID of the server entry.
|
| - server_named->Put(syncable::ID, trans->directory()->NextId());
|
| - locally_named->Put(syncable::ID, desired_id);
|
| - locally_named->Put(syncable::IS_UNSYNCED, false);
|
| - CopyServerFields(server_named, locally_named);
|
| - ClearServerData(server_named);
|
| - server_named->Put(syncable::IS_DEL, true);
|
| - server_named->Put(syncable::BASE_VERSION, 0);
|
| - CHECK(SUCCESS ==
|
| - SyncerUtil::AttemptToUpdateEntryWithoutMerge(
|
| - trans, locally_named, NULL));
|
| - return true;
|
| -}
|
| -
|
| -ConflictResolver::ServerClientNameClashReturn
|
| -ConflictResolver::ProcessServerClientNameClash(WriteTransaction* trans,
|
| - MutableEntry* locally_named,
|
| - MutableEntry* server_named,
|
| - SyncerSession* session) {
|
| - if (!locally_named->ExistsOnClientBecauseDatabaseNameIsNonEmpty())
|
| - return NO_CLASH; // Locally_named is a server update.
|
| - if (locally_named->Get(syncable::IS_DEL) ||
|
| - server_named->Get(syncable::SERVER_IS_DEL)) {
|
| - return NO_CLASH;
|
| - }
|
| - if (locally_named->Get(syncable::PARENT_ID) !=
|
| - server_named->Get(syncable::SERVER_PARENT_ID)) {
|
| - return NO_CLASH; // Different parents.
|
| - }
|
| -
|
| - PathString name = locally_named->GetSyncNameValue();
|
| - if (0 != syncable::ComparePathNames(name,
|
| - server_named->Get(syncable::SERVER_NAME))) {
|
| - return NO_CLASH; // Different names.
|
| - }
|
| -
|
| - // First try to merge.
|
| - if (AttemptItemMerge(trans, locally_named, server_named)) {
|
| - // METRIC conflict resolved by merge
|
| - return SOLVED;
|
| - }
|
| - // We need to rename.
|
| - if (!locally_named->Get(syncable::IS_UNSYNCED)) {
|
| - LOG(ERROR) << "Locally named part of a name conflict not unsynced?";
|
| - locally_named->Put(syncable::IS_UNSYNCED, true);
|
| - }
|
| - if (!server_named->Get(syncable::IS_UNAPPLIED_UPDATE)) {
|
| - LOG(ERROR) << "Server named part of a name conflict not an update?";
|
| - }
|
| - GiveEntryNewName(trans, locally_named);
|
| -
|
| - // METRIC conflict resolved by rename
|
| - return SOLVED;
|
| -}
|
| -
|
| -ConflictResolver::ServerClientNameClashReturn
|
| -ConflictResolver::ProcessNameClashesInSet(WriteTransaction* trans,
|
| - ConflictSet* conflict_set,
|
| - SyncerSession* session) {
|
| - ConflictSet::const_iterator i,j;
|
| - for (i = conflict_set->begin() ; i != conflict_set->end() ; ++i) {
|
| - MutableEntry entryi(trans, syncable::GET_BY_ID, *i);
|
| - if (!entryi.Get(syncable::IS_UNSYNCED) &&
|
| - !entryi.Get(syncable::IS_UNAPPLIED_UPDATE))
|
| - // This set is broken / doesn't make sense, this may be transient.
|
| - return BOGUS_SET;
|
| - for (j = conflict_set->begin() ; *i != *j ; ++j) {
|
| - MutableEntry entryj(trans, syncable::GET_BY_ID, *j);
|
| - ServerClientNameClashReturn rv =
|
| - ProcessServerClientNameClash(trans, &entryi, &entryj, session);
|
| - if (NO_CLASH == rv)
|
| - rv = ProcessServerClientNameClash(trans, &entryj, &entryi, session);
|
| - if (NO_CLASH != rv)
|
| - return rv;
|
| - }
|
| - }
|
| - return NO_CLASH;
|
| -}
|
| -
|
| ConflictResolver::ConflictSetCountMapKey ConflictResolver::GetSetKey(
|
| ConflictSet* set) {
|
| // TODO(sync): Come up with a better scheme for set hashing. This scheme
|
| @@ -481,6 +244,8 @@ bool AttemptToFixUnsyncedEntryInDeletedServerTree(WriteTransaction* trans,
|
| return true;
|
| }
|
|
|
| +
|
| +// TODO(chron): needs unit test badly
|
| bool AttemptToFixUpdateEntryInDeletedLocalTree(WriteTransaction* trans,
|
| ConflictSet* conflict_set,
|
| const Entry& entry) {
|
| @@ -540,18 +305,19 @@ bool AttemptToFixUpdateEntryInDeletedLocalTree(WriteTransaction* trans,
|
| // Now we fix things up by undeleting all the folders in the item's path.
|
| id = parent_id;
|
| while (!id.IsRoot() && id != reroot_id) {
|
| - if (!binary_search(conflict_set->begin(), conflict_set->end(), id))
|
| + if (!binary_search(conflict_set->begin(), conflict_set->end(), id)) {
|
| break;
|
| + }
|
| MutableEntry entry(trans, syncable::GET_BY_ID, id);
|
| +
|
| + LOG(INFO) << "Undoing our deletion of " << entry
|
| + << ", will have name " << entry.Get(syncable::NON_UNIQUE_NAME);
|
| +
|
| Id parent_id = entry.Get(syncable::PARENT_ID);
|
| - if (parent_id == reroot_id)
|
| + if (parent_id == reroot_id) {
|
| parent_id = trans->root_id();
|
| - Name old_name = entry.GetName();
|
| - Name new_name = FindNewName(trans, parent_id, old_name);
|
| - LOG(INFO) << "Undoing our deletion of " << entry <<
|
| - ", will have name " << new_name.db_value();
|
| - if (new_name != old_name || parent_id != entry.Get(syncable::PARENT_ID))
|
| - CHECK(entry.PutParentIdAndName(parent_id, new_name));
|
| + }
|
| + entry.Put(syncable::PARENT_ID, parent_id);
|
| entry.Put(syncable::IS_DEL, false);
|
| id = entry.Get(syncable::PARENT_ID);
|
| // METRIC conflict resolved by recreating dir tree.
|
| @@ -576,6 +342,7 @@ bool AttemptToFixRemovedDirectoriesWithContent(WriteTransaction* trans,
|
|
|
| } // namespace
|
|
|
| +// TODO(sync): Eliminate conflict sets. They're not necessary.
|
| bool ConflictResolver::ProcessConflictSet(WriteTransaction* trans,
|
| ConflictSet* conflict_set,
|
| int conflict_count,
|
| @@ -597,13 +364,6 @@ bool ConflictResolver::ProcessConflictSet(WriteTransaction* trans,
|
|
|
| LOG(INFO) << "Fixing a set containing " << set_size << " items";
|
|
|
| - ServerClientNameClashReturn rv = ProcessNameClashesInSet(trans, conflict_set,
|
| - session);
|
| - if (SOLVED == rv)
|
| - return true;
|
| - if (NO_CLASH != rv)
|
| - return false;
|
| -
|
| // Fix circular conflicts.
|
| if (AttemptToFixCircularConflict(trans, conflict_set))
|
| return true;
|
|
|