diff options
author | albertb@google.com <albertb@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-14 23:14:03 +0000 |
---|---|---|
committer | albertb@google.com <albertb@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-14 23:14:03 +0000 |
commit | 62fe438df3ae845738db67dfbfcb5e1be8660cc8 (patch) | |
tree | a228130d9d47c0d6502ff75d50b65a1c36fa3011 /chrome/browser | |
parent | 3a5a6733f6226297c35dbdff21a5821921753bed (diff) | |
download | chromium_src-62fe438df3ae845738db67dfbfcb5e1be8660cc8.zip chromium_src-62fe438df3ae845738db67dfbfcb5e1be8660cc8.tar.gz chromium_src-62fe438df3ae845738db67dfbfcb5e1be8660cc8.tar.bz2 |
I refactored ChangeProcessor so that the common stuff can be reused by other
data types. For ModelAssociator, I just extracted an interface. There's
probably more that can be reused, but I thought we would get to it once we
know more about what kind of associations the other data types will
require. In particular, I didn't use templates because none of the methods that
ProfileSyncService calls on ModelAssociator require a data-type specific type.
I didn't invest too much time refactoring the unit tests, so they're pretty
hacky. I believe the right thing to do would be to test PSS, CP and MA
seperately instead of having a giant PSS test that assumes we only care
about bookmarks.
BUG=29831,29832
TEST=Unit test
Review URL: http://codereview.chromium.org/477007
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@34510 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r-- | chrome/browser/sync/glue/bookmark_change_processor.cc | 539 | ||||
-rw-r--r-- | chrome/browser/sync/glue/bookmark_change_processor.h | 172 | ||||
-rw-r--r-- | chrome/browser/sync/glue/bookmark_model_associator.cc (renamed from chrome/browser/sync/glue/model_associator.cc) | 75 | ||||
-rw-r--r-- | chrome/browser/sync/glue/bookmark_model_associator.h | 135 | ||||
-rw-r--r-- | chrome/browser/sync/glue/change_processor.cc | 534 | ||||
-rw-r--r-- | chrome/browser/sync/glue/change_processor.h | 173 | ||||
-rw-r--r-- | chrome/browser/sync/glue/model_associator.h | 130 | ||||
-rw-r--r-- | chrome/browser/sync/glue/sync_backend_host.cc | 11 | ||||
-rw-r--r-- | chrome/browser/sync/glue/sync_backend_host.h | 23 | ||||
-rw-r--r-- | chrome/browser/sync/profile_sync_service.cc | 97 | ||||
-rw-r--r-- | chrome/browser/sync/profile_sync_service.h | 9 | ||||
-rw-r--r-- | chrome/browser/sync/profile_sync_service_unittest.cc | 46 |
12 files changed, 1058 insertions, 886 deletions
diff --git a/chrome/browser/sync/glue/bookmark_change_processor.cc b/chrome/browser/sync/glue/bookmark_change_processor.cc new file mode 100644 index 0000000..916669f --- /dev/null +++ b/chrome/browser/sync/glue/bookmark_change_processor.cc @@ -0,0 +1,539 @@ +// Copyright (c) 2006-2009 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/glue/bookmark_change_processor.h" + +#include <stack> +#include <vector> + +#include "app/gfx/codec/png_codec.h" +#include "base/string_util.h" +#include "chrome/browser/bookmarks/bookmark_utils.h" +#include "chrome/browser/favicon_service.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "third_party/skia/include/core/SkBitmap.h" + +namespace browser_sync { + +BookmarkChangeProcessor::BookmarkChangeProcessor( + UnrecoverableErrorHandler* error_handler) + : ChangeProcessor(error_handler), + bookmark_model_(NULL), + model_associator_(NULL) { +} + +void BookmarkChangeProcessor::StartImpl(Profile* profile) { + DCHECK(!bookmark_model_); + bookmark_model_ = profile->GetBookmarkModel(); + DCHECK(bookmark_model_->IsLoaded()); + bookmark_model_->AddObserver(this); +} + +void BookmarkChangeProcessor::StopImpl() { + DCHECK(bookmark_model_); + bookmark_model_->RemoveObserver(this); + bookmark_model_ = NULL; +} + +void BookmarkChangeProcessor::UpdateSyncNodeProperties( + const BookmarkNode* src, BookmarkModel* model, sync_api::WriteNode* dst) { + // Set the properties of the item. + dst->SetIsFolder(src->is_folder()); + dst->SetTitle(src->GetTitle()); + dst->SetURL(src->GetURL()); + SetSyncNodeFavicon(src, model, dst); +} + +// static +void BookmarkChangeProcessor::EncodeFavicon(const BookmarkNode* src, + BookmarkModel* model, + std::vector<unsigned char>* dst) { + const SkBitmap& favicon = model->GetFavIcon(src); + + dst->clear(); + + // Check for zero-dimension images. This can happen if the favicon is + // still being loaded. + if (favicon.empty()) + return; + + // Re-encode the BookmarkNode's favicon as a PNG, and pass the data to the + // sync subsystem. + if (!gfx::PNGCodec::EncodeBGRASkBitmap(favicon, false, dst)) + return; +} + +void BookmarkChangeProcessor::RemoveOneSyncNode( + sync_api::WriteTransaction* trans, const BookmarkNode* node) { + sync_api::WriteNode sync_node(trans); + if (!model_associator_->InitSyncNodeFromBookmarkId(node->id(), &sync_node)) { + error_handler()->OnUnrecoverableError(); + return; + } + // This node should have no children. + DCHECK(sync_node.GetFirstChildId() == sync_api::kInvalidId); + // Remove association and delete the sync node. + model_associator_->Disassociate(sync_node.GetId()); + sync_node.Remove(); +} + +void BookmarkChangeProcessor::RemoveSyncNodeHierarchy( + const BookmarkNode* topmost) { + sync_api::WriteTransaction trans(share_handle()); + + // Later logic assumes that |topmost| has been unlinked. + DCHECK(!topmost->GetParent()); + + // A BookmarkModel deletion event means that |node| and all its children were + // deleted. Sync backend expects children to be deleted individually, so we do + // a depth-first-search here. At each step, we consider the |index|-th child + // of |node|. |index_stack| stores index values for the parent levels. + std::stack<int> index_stack; + index_stack.push(0); // For the final pop. It's never used. + const BookmarkNode* node = topmost; + int index = 0; + while (node) { + // The top of |index_stack| should always be |node|'s index. + DCHECK(!node->GetParent() || (node->GetParent()->IndexOfChild(node) == + index_stack.top())); + if (index == node->GetChildCount()) { + // If we've processed all of |node|'s children, delete |node| and move + // on to its successor. + RemoveOneSyncNode(&trans, node); + node = node->GetParent(); + index = index_stack.top() + 1; // (top() + 0) was what we removed. + index_stack.pop(); + } else { + // If |node| has an unprocessed child, process it next after pushing the + // current state onto the stack. + DCHECK_LT(index, node->GetChildCount()); + index_stack.push(index); + node = node->GetChild(index); + index = 0; + } + } + DCHECK(index_stack.empty()); // Nothing should be left on the stack. +} + +void BookmarkChangeProcessor::BookmarkModelBeingDeleted( + BookmarkModel* model) { + DCHECK(!running()) << "BookmarkModel deleted while ChangeProcessor running."; + bookmark_model_ = NULL; +} + +void BookmarkChangeProcessor::BookmarkNodeAdded(BookmarkModel* model, + const BookmarkNode* parent, + int index) { + DCHECK(running()); + DCHECK(share_handle()); + + // Acquire a scoped write lock via a transaction. + sync_api::WriteTransaction trans(share_handle()); + + CreateSyncNode(parent, model, index, &trans, model_associator_.get(), + error_handler()); +} + +// static +int64 BookmarkChangeProcessor::CreateSyncNode(const BookmarkNode* parent, + BookmarkModel* model, int index, sync_api::WriteTransaction* trans, + BookmarkModelAssociator* associator, + UnrecoverableErrorHandler* error_handler) { + const BookmarkNode* child = parent->GetChild(index); + DCHECK(child); + + // Create a WriteNode container to hold the new node. + sync_api::WriteNode sync_child(trans); + + // Actually create the node with the appropriate initial position. + if (!PlaceSyncNode(CREATE, parent, index, trans, &sync_child, associator, + error_handler)) { + LOG(WARNING) << "Sync node creation failed; recovery unlikely"; + error_handler->OnUnrecoverableError(); + return sync_api::kInvalidId; + } + + UpdateSyncNodeProperties(child, model, &sync_child); + + // Associate the ID from the sync domain with the bookmark node, so that we + // can refer back to this item later. + associator->Associate(child, sync_child.GetId()); + + return sync_child.GetId(); +} + + +void BookmarkChangeProcessor::BookmarkNodeRemoved(BookmarkModel* model, + const BookmarkNode* parent, + int index, + const BookmarkNode* node) { + DCHECK(running()); + RemoveSyncNodeHierarchy(node); +} + +void BookmarkChangeProcessor::BookmarkNodeChanged(BookmarkModel* model, + const BookmarkNode* node) { + DCHECK(running()); + // We shouldn't see changes to the top-level nodes. + if (node == model->GetBookmarkBarNode() || node == model->other_node()) { + NOTREACHED() << "Saw update to permanent node!"; + return; + } + + // Acquire a scoped write lock via a transaction. + sync_api::WriteTransaction trans(share_handle()); + + // Lookup the sync node that's associated with |node|. + sync_api::WriteNode sync_node(&trans); + if (!model_associator_->InitSyncNodeFromBookmarkId(node->id(), &sync_node)) { + error_handler()->OnUnrecoverableError(); + return; + } + + UpdateSyncNodeProperties(node, model, &sync_node); + + DCHECK_EQ(sync_node.GetIsFolder(), node->is_folder()); + DCHECK_EQ(model_associator_->GetBookmarkNodeFromSyncId( + sync_node.GetParentId()), + node->GetParent()); + // This node's index should be one more than the predecessor's index. + DCHECK_EQ(node->GetParent()->IndexOfChild(node), + CalculateBookmarkModelInsertionIndex(node->GetParent(), + &sync_node)); +} + + +void BookmarkChangeProcessor::BookmarkNodeMoved(BookmarkModel* model, + const BookmarkNode* old_parent, int old_index, + const BookmarkNode* new_parent, int new_index) { + DCHECK(running()); + const BookmarkNode* child = new_parent->GetChild(new_index); + // We shouldn't see changes to the top-level nodes. + if (child == model->GetBookmarkBarNode() || child == model->other_node()) { + NOTREACHED() << "Saw update to permanent node!"; + return; + } + + // Acquire a scoped write lock via a transaction. + sync_api::WriteTransaction trans(share_handle()); + + // Lookup the sync node that's associated with |child|. + sync_api::WriteNode sync_node(&trans); + if (!model_associator_->InitSyncNodeFromBookmarkId(child->id(), &sync_node)) { + error_handler()->OnUnrecoverableError(); + return; + } + + if (!PlaceSyncNode(MOVE, new_parent, new_index, &trans, &sync_node, + model_associator_.get(), error_handler())) { + error_handler()->OnUnrecoverableError(); + return; + } +} + +void BookmarkChangeProcessor::BookmarkNodeFavIconLoaded(BookmarkModel* model, + const BookmarkNode* node) { + DCHECK(running()); + BookmarkNodeChanged(model, node); +} + +void BookmarkChangeProcessor::BookmarkNodeChildrenReordered( + BookmarkModel* model, const BookmarkNode* node) { + + // Acquire a scoped write lock via a transaction. + sync_api::WriteTransaction trans(share_handle()); + + // The given node's children got reordered. We need to reorder all the + // children of the corresponding sync node. + for (int i = 0; i < node->GetChildCount(); ++i) { + sync_api::WriteNode sync_child(&trans); + if (!model_associator_->InitSyncNodeFromBookmarkId(node->GetChild(i)->id(), + &sync_child)) { + error_handler()->OnUnrecoverableError(); + return; + } + DCHECK_EQ(sync_child.GetParentId(), + model_associator_->GetSyncIdFromBookmarkId(node->id())); + + if (!PlaceSyncNode(MOVE, node, i, &trans, &sync_child, + model_associator_.get(), error_handler())) { + error_handler()->OnUnrecoverableError(); + return; + } + } +} + +// static +bool BookmarkChangeProcessor::PlaceSyncNode(MoveOrCreate operation, + const BookmarkNode* parent, int index, sync_api::WriteTransaction* trans, + sync_api::WriteNode* dst, BookmarkModelAssociator* associator, + UnrecoverableErrorHandler* error_handler) { + sync_api::ReadNode sync_parent(trans); + if (!associator->InitSyncNodeFromBookmarkId(parent->id(), &sync_parent)) { + LOG(WARNING) << "Parent lookup failed"; + error_handler->OnUnrecoverableError(); + return false; + } + + bool success = false; + if (index == 0) { + // Insert into first position. + success = (operation == CREATE) ? dst->InitByCreation(sync_parent, NULL) : + dst->SetPosition(sync_parent, NULL); + if (success) { + DCHECK_EQ(dst->GetParentId(), sync_parent.GetId()); + DCHECK_EQ(dst->GetId(), sync_parent.GetFirstChildId()); + DCHECK_EQ(dst->GetPredecessorId(), sync_api::kInvalidId); + } + } else { + // Find the bookmark model predecessor, and insert after it. + const BookmarkNode* prev = parent->GetChild(index - 1); + sync_api::ReadNode sync_prev(trans); + if (!associator->InitSyncNodeFromBookmarkId(prev->id(), &sync_prev)) { + LOG(WARNING) << "Predecessor lookup failed"; + return false; + } + success = (operation == CREATE) ? + dst->InitByCreation(sync_parent, &sync_prev) : + dst->SetPosition(sync_parent, &sync_prev); + if (success) { + DCHECK_EQ(dst->GetParentId(), sync_parent.GetId()); + DCHECK_EQ(dst->GetPredecessorId(), sync_prev.GetId()); + DCHECK_EQ(dst->GetId(), sync_prev.GetSuccessorId()); + } + } + return success; +} + +// Determine the bookmark model index to which a node must be moved so that +// predecessor of the node (in the bookmark model) matches the predecessor of +// |source| (in the sync model). +// As a precondition, this assumes that the predecessor of |source| has been +// updated and is already in the correct position in the bookmark model. +int BookmarkChangeProcessor::CalculateBookmarkModelInsertionIndex( + const BookmarkNode* parent, + const sync_api::BaseNode* child_info) const { + DCHECK(parent); + DCHECK(child_info); + int64 predecessor_id = child_info->GetPredecessorId(); + // A return ID of kInvalidId indicates no predecessor. + if (predecessor_id == sync_api::kInvalidId) + return 0; + + // Otherwise, insert after the predecessor bookmark node. + const BookmarkNode* predecessor = + model_associator_->GetBookmarkNodeFromSyncId(predecessor_id); + DCHECK(predecessor); + DCHECK_EQ(predecessor->GetParent(), parent); + return parent->IndexOfChild(predecessor) + 1; +} + +// ApplyModelChanges is called by the sync backend after changes have been made +// to the sync engine's model. Apply these changes to the browser bookmark +// model. +void BookmarkChangeProcessor::ApplyChangesFromSyncModel( + const sync_api::BaseTransaction* trans, + const sync_api::SyncManager::ChangeRecord* changes, + int change_count) { + if (!running()) + return; + // A note about ordering. Sync backend is responsible for ordering the change + // records in the following order: + // + // 1. Deletions, from leaves up to parents. + // 2. Existing items with synced parents & predecessors. + // 3. New items with synced parents & predecessors. + // 4. Items with parents & predecessors in the list. + // 5. Repeat #4 until all items are in the list. + // + // "Predecessor" here means the previous item within a given folder; an item + // in the first position is always said to have a synced predecessor. + // For the most part, applying these changes in the order given will yield + // the correct result. There is one exception, however: for items that are + // moved away from a folder that is being deleted, we will process the delete + // before the move. Since deletions in the bookmark model propagate from + // parent to child, we must move them to a temporary location. + BookmarkModel* model = bookmark_model_; + + // We are going to make changes to the bookmarks model, but don't want to end + // up in a feedback loop, so remove ourselves as an observer while applying + // changes. + model->RemoveObserver(this); + + // A parent to hold nodes temporarily orphaned by parent deletion. It is + // lazily created inside the loop. + const BookmarkNode* foster_parent = NULL; + for (int i = 0; i < change_count; ++i) { + const BookmarkNode* dst = + model_associator_->GetBookmarkNodeFromSyncId(changes[i].id); + // Ignore changes to the permanent top-level nodes. We only care about + // their children. + if ((dst == model->GetBookmarkBarNode()) || (dst == model->other_node())) + continue; + if (changes[i].action == + sync_api::SyncManager::ChangeRecord::ACTION_DELETE) { + // Deletions should always be at the front of the list. + DCHECK(i == 0 || changes[i-1].action == changes[i].action); + // Children of a deleted node should not be deleted; they may be + // reparented by a later change record. Move them to a temporary place. + DCHECK(dst) << "Could not find node to be deleted"; + const BookmarkNode* parent = dst->GetParent(); + if (dst->GetChildCount()) { + if (!foster_parent) { + foster_parent = model->AddGroup(model->other_node(), + model->other_node()->GetChildCount(), + std::wstring()); + } + for (int i = dst->GetChildCount() - 1; i >= 0; --i) { + model->Move(dst->GetChild(i), foster_parent, + foster_parent->GetChildCount()); + } + } + DCHECK_EQ(dst->GetChildCount(), 0) << "Node being deleted has children"; + model_associator_->Disassociate(changes[i].id); + model->Remove(parent, parent->IndexOfChild(dst)); + dst = NULL; + } else { + DCHECK_EQ((changes[i].action == + sync_api::SyncManager::ChangeRecord::ACTION_ADD), (dst == NULL)) + << "ACTION_ADD should be seen if and only if the node is unknown."; + + sync_api::ReadNode src(trans); + if (!src.InitByIdLookup(changes[i].id)) { + LOG(ERROR) << "ApplyModelChanges was passed a bad ID"; + error_handler()->OnUnrecoverableError(); + return; + } + + CreateOrUpdateBookmarkNode(&src, model); + } + } + // Clean up the temporary node. + if (foster_parent) { + // There should be no nodes left under the foster parent. + DCHECK_EQ(foster_parent->GetChildCount(), 0); + model->Remove(foster_parent->GetParent(), + foster_parent->GetParent()->IndexOfChild(foster_parent)); + foster_parent = NULL; + } + + // We are now ready to hear about bookmarks changes again. + model->AddObserver(this); +} + +// Create a bookmark node corresponding to |src| if one is not already +// associated with |src|. +const BookmarkNode* BookmarkChangeProcessor::CreateOrUpdateBookmarkNode( + sync_api::BaseNode* src, + BookmarkModel* model) { + const BookmarkNode* parent = + model_associator_->GetBookmarkNodeFromSyncId(src->GetParentId()); + if (!parent) { + DLOG(WARNING) << "Could not find parent of node being added/updated." + << " Node title: " << src->GetTitle() + << ", parent id = " << src->GetParentId(); + return NULL; + } + int index = CalculateBookmarkModelInsertionIndex(parent, src); + const BookmarkNode* dst = model_associator_->GetBookmarkNodeFromSyncId( + src->GetId()); + if (!dst) { + dst = CreateBookmarkNode(src, parent, model, index); + model_associator_->Associate(dst, src->GetId()); + } else { + // URL and is_folder are not expected to change. + // TODO(ncarter): Determine if such changes should be legal or not. + DCHECK_EQ(src->GetIsFolder(), dst->is_folder()); + + // Handle reparenting and/or repositioning. + model->Move(dst, parent, index); + + // Handle title update and URL changes due to possible conflict resolution + // that can happen if both a local user change and server change occur + // within a sufficiently small time interval. + const BookmarkNode* old_dst = dst; + dst = bookmark_utils::ApplyEditsWithNoGroupChange(model, parent, + BookmarkEditor::EditDetails(dst), + src->GetTitle(), + src->GetIsFolder() ? GURL() : src->GetURL(), + NULL); // NULL because we don't need a BookmarkEditor::Handler. + if (dst != old_dst) { // dst was replaced with a new node with new URL. + model_associator_->Disassociate(src->GetId()); + model_associator_->Associate(dst, src->GetId()); + } + SetBookmarkFavicon(src, dst, model->profile()); + } + + return dst; +} + +// static +// Creates a bookmark node under the given parent node from the given sync +// node. Returns the newly created node. +const BookmarkNode* BookmarkChangeProcessor::CreateBookmarkNode( + sync_api::BaseNode* sync_node, + const BookmarkNode* parent, + BookmarkModel* model, + int index) { + DCHECK(parent); + DCHECK(index >= 0 && index <= parent->GetChildCount()); + + const BookmarkNode* node; + if (sync_node->GetIsFolder()) { + node = model->AddGroup(parent, index, sync_node->GetTitle()); + } else { + node = model->AddURL(parent, index, + sync_node->GetTitle(), sync_node->GetURL()); + SetBookmarkFavicon(sync_node, node, model->profile()); + } + return node; +} + +// static +// Sets the favicon of the given bookmark node from the given sync node. +bool BookmarkChangeProcessor::SetBookmarkFavicon( + sync_api::BaseNode* sync_node, + const BookmarkNode* bookmark_node, + Profile* profile) { + size_t icon_size = 0; + const unsigned char* icon_bytes = sync_node->GetFaviconBytes(&icon_size); + if (!icon_size || !icon_bytes) + return false; + + // Registering a favicon requires that we provide a source URL, but we + // don't know where these came from. Currently we just use the + // destination URL, which is not correct, but since the favicon URL + // is used as a key in the history's thumbnail DB, this gives us a value + // which does not collide with others. + GURL fake_icon_url = bookmark_node->GetURL(); + + std::vector<unsigned char> icon_bytes_vector(icon_bytes, + icon_bytes + icon_size); + + HistoryService* history = + profile->GetHistoryService(Profile::EXPLICIT_ACCESS); + FaviconService* favicon_service = + profile->GetFaviconService(Profile::EXPLICIT_ACCESS); + + history->AddPage(bookmark_node->GetURL()); + favicon_service->SetFavicon(bookmark_node->GetURL(), + fake_icon_url, + icon_bytes_vector); + + return true; +} + +// static +void BookmarkChangeProcessor::SetSyncNodeFavicon( + const BookmarkNode* bookmark_node, + BookmarkModel* model, + sync_api::WriteNode* sync_node) { + std::vector<unsigned char> favicon_bytes; + EncodeFavicon(bookmark_node, model, &favicon_bytes); + if (!favicon_bytes.empty()) + sync_node->SetFaviconBytes(&favicon_bytes[0], favicon_bytes.size()); +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/bookmark_change_processor.h b/chrome/browser/sync/glue/bookmark_change_processor.h new file mode 100644 index 0000000..496b85f --- /dev/null +++ b/chrome/browser/sync/glue/bookmark_change_processor.h @@ -0,0 +1,172 @@ +// Copyright (c) 2006-2009 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. + +#ifndef CHROME_BROWSER_SYNC_GLUE_BOOKMARK_CHANGE_PROCESSOR_H_ +#define CHROME_BROWSER_SYNC_GLUE_BOOKMARK_CHANGE_PROCESSOR_H_ + +#include <vector> + +#include "base/scoped_ptr.h" +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/browser/sync/engine/syncapi.h" +#include "chrome/browser/sync/glue/change_processor.h" +#include "chrome/browser/sync/glue/bookmark_model_associator.h" +#include "chrome/browser/sync/glue/sync_backend_host.h" + +class ProfileSyncService; + +namespace browser_sync { + +// This class is responsible for taking changes from the BookmarkModel +// and applying them to the sync_api 'syncable' model, and vice versa. +// All operations and use of this class are from the UI thread. +// This is currently bookmarks specific. +class BookmarkChangeProcessor : public BookmarkModelObserver, + public ChangeProcessor { + public: + explicit BookmarkChangeProcessor(UnrecoverableErrorHandler* error_handler); + virtual ~BookmarkChangeProcessor() {} + + void set_model_associator(BookmarkModelAssociator* model_associator) { + model_associator_.reset(model_associator); + } + virtual ModelAssociator* GetModelAssociator() { + return model_associator_.get(); + } + + // BookmarkModelObserver implementation. + // BookmarkModel -> sync_api model change application. + virtual void Loaded(BookmarkModel* model) { NOTREACHED(); } + virtual void BookmarkModelBeingDeleted(BookmarkModel* model); + virtual void BookmarkNodeMoved(BookmarkModel* model, + const BookmarkNode* old_parent, + int old_index, + const BookmarkNode* new_parent, + int new_index); + virtual void BookmarkNodeAdded(BookmarkModel* model, + const BookmarkNode* parent, + int index); + virtual void BookmarkNodeRemoved(BookmarkModel* model, + const BookmarkNode* parent, + int index, + const BookmarkNode* node); + virtual void BookmarkNodeChanged(BookmarkModel* model, + const BookmarkNode* node); + virtual void BookmarkNodeFavIconLoaded(BookmarkModel* model, + const BookmarkNode* node); + virtual void BookmarkNodeChildrenReordered(BookmarkModel* model, + const BookmarkNode* node); + + // The change processor implementation, responsible for applying changes from + // the sync model to the bookmarks model. + virtual void ApplyChangesFromSyncModel( + const sync_api::BaseTransaction* trans, + const sync_api::SyncManager::ChangeRecord* changes, + int change_count); + + // The following methods are static and hence may be invoked at any time, + // and do not depend on having a running ChangeProcessor. + // Creates a bookmark node under the given parent node from the given sync + // node. Returns the newly created node. + static const BookmarkNode* CreateBookmarkNode( + sync_api::BaseNode* sync_node, + const BookmarkNode* parent, + BookmarkModel* model, + int index); + + // Sets the favicon of the given bookmark node from the given sync node. + // Returns whether the favicon was set in the bookmark node. + // |profile| is the profile that contains the HistoryService and BookmarkModel + // for the bookmark in question. + static bool SetBookmarkFavicon(sync_api::BaseNode* sync_node, + const BookmarkNode* bookmark_node, + Profile* profile); + + // Sets the favicon of the given sync node from the given bookmark node. + static void SetSyncNodeFavicon(const BookmarkNode* bookmark_node, + BookmarkModel* model, + sync_api::WriteNode* sync_node); + + // Treat the |index|th child of |parent| as a newly added node, and create a + // corresponding node in the sync domain using |trans|. All properties + // will be transferred to the new node. A node corresponding to |parent| + // must already exist and be associated for this call to succeed. Returns + // the ID of the just-created node, or if creation fails, kInvalidID. + static int64 CreateSyncNode(const BookmarkNode* parent, + BookmarkModel* model, + int index, + sync_api::WriteTransaction* trans, + BookmarkModelAssociator* associator, + UnrecoverableErrorHandler* error_handler); + + protected: + virtual void StartImpl(Profile* profile); + virtual void StopImpl(); + + private: + enum MoveOrCreate { + MOVE, + CREATE, + }; + + // Create a bookmark node corresponding to |src| if one is not already + // associated with |src|. Returns the node that was created or updated. + const BookmarkNode* CreateOrUpdateBookmarkNode( + sync_api::BaseNode* src, + BookmarkModel* model); + + // Helper function to determine the appropriate insertion index of sync node + // |node| under the Bookmark model node |parent|, to make the positions + // match up between the two models. This presumes that the predecessor of the + // item (in the bookmark model) has already been moved into its appropriate + // position. + int CalculateBookmarkModelInsertionIndex( + const BookmarkNode* parent, + const sync_api::BaseNode* node) const; + + // Helper function used to fix the position of a sync node so that it matches + // the position of a corresponding bookmark model node. |parent| and + // |index| identify the bookmark model position. |dst| is the node whose + // position is to be fixed. If |operation| is CREATE, treat |dst| as an + // uncreated node and set its position via InitByCreation(); otherwise, + // |dst| is treated as an existing node, and its position will be set via + // SetPosition(). |trans| is the transaction to which |dst| belongs. Returns + // false on failure. + static bool PlaceSyncNode(MoveOrCreate operation, + const BookmarkNode* parent, + int index, + sync_api::WriteTransaction* trans, + sync_api::WriteNode* dst, + BookmarkModelAssociator* associator, + UnrecoverableErrorHandler* error_handler); + + // Copy properties (but not position) from |src| to |dst|. + static void UpdateSyncNodeProperties(const BookmarkNode* src, + BookmarkModel* model, + sync_api::WriteNode* dst); + + // Helper function to encode a bookmark's favicon into a PNG byte vector. + static void EncodeFavicon(const BookmarkNode* src, + BookmarkModel* model, + std::vector<unsigned char>* dst); + + // Remove the sync node corresponding to |node|. It shouldn't have + // any children. + void RemoveOneSyncNode(sync_api::WriteTransaction* trans, + const BookmarkNode* node); + + // Remove all the sync nodes associated with |node| and its children. + void RemoveSyncNodeHierarchy(const BookmarkNode* node); + + // The bookmark model we are processing changes from. Non-NULL when + // |running_| is true. + BookmarkModel* bookmark_model_; + scoped_ptr<BookmarkModelAssociator> model_associator_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkChangeProcessor); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_GLUE_BOOKMARK_CHANGE_PROCESSOR_H_ diff --git a/chrome/browser/sync/glue/model_associator.cc b/chrome/browser/sync/glue/bookmark_model_associator.cc index 1754504..7e220bf 100644 --- a/chrome/browser/sync/glue/model_associator.cc +++ b/chrome/browser/sync/glue/bookmark_model_associator.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/sync/glue/model_associator.h" +#include "chrome/browser/sync/glue/bookmark_model_associator.h" #include <stack> @@ -11,6 +11,7 @@ #include "base/task.h" #include "chrome/browser/bookmarks/bookmark_model.h" #include "chrome/browser/sync/engine/syncapi.h" +#include "chrome/browser/sync/glue/bookmark_change_processor.h" #include "chrome/browser/sync/profile_sync_service.h" namespace browser_sync { @@ -150,29 +151,32 @@ const BookmarkNode* BookmarkNodeIdIndex::Find(int64 id) const { return iter == node_index_.end() ? NULL : iter->second; } -ModelAssociator::ModelAssociator(ProfileSyncService* sync_service) +BookmarkModelAssociator::BookmarkModelAssociator( + ProfileSyncService* sync_service) : sync_service_(sync_service), - task_pending_(false) { + ALLOW_THIS_IN_INITIALIZER_LIST(persist_associations_(this)) { DCHECK(sync_service_); } -void ModelAssociator::ClearAll() { +bool BookmarkModelAssociator::DisassociateModels() { id_map_.clear(); id_map_inverse_.clear(); dirty_associations_sync_ids_.clear(); + return true; } -int64 ModelAssociator::GetSyncIdFromBookmarkId(int64 node_id) const { +int64 BookmarkModelAssociator::GetSyncIdFromBookmarkId(int64 node_id) const { BookmarkIdToSyncIdMap::const_iterator iter = id_map_.find(node_id); return iter == id_map_.end() ? sync_api::kInvalidId : iter->second; } -const BookmarkNode* ModelAssociator::GetBookmarkNodeFromSyncId(int64 sync_id) { +const BookmarkNode* BookmarkModelAssociator::GetBookmarkNodeFromSyncId( + int64 sync_id) { SyncIdToBookmarkNodeMap::const_iterator iter = id_map_inverse_.find(sync_id); return iter == id_map_inverse_.end() ? NULL : iter->second; } -bool ModelAssociator::InitSyncNodeFromBookmarkId( +bool BookmarkModelAssociator::InitSyncNodeFromBookmarkId( int64 node_id, sync_api::BaseNode* sync_node) { DCHECK(sync_node); @@ -185,7 +189,8 @@ bool ModelAssociator::InitSyncNodeFromBookmarkId( return true; } -void ModelAssociator::Associate(const BookmarkNode* node, int64 sync_id) { +void BookmarkModelAssociator::Associate(const BookmarkNode* node, + int64 sync_id) { int64 node_id = node->id(); DCHECK_NE(sync_id, sync_api::kInvalidId); DCHECK(id_map_.find(node_id) == id_map_.end()); @@ -196,7 +201,7 @@ void ModelAssociator::Associate(const BookmarkNode* node, int64 sync_id) { PostPersistAssociationsTask(); } -void ModelAssociator::Disassociate(int64 sync_id) { +void BookmarkModelAssociator::Disassociate(int64 sync_id) { SyncIdToBookmarkNodeMap::iterator iter = id_map_inverse_.find(sync_id); if (iter == id_map_inverse_.end()) return; @@ -205,16 +210,16 @@ void ModelAssociator::Disassociate(int64 sync_id) { dirty_associations_sync_ids_.erase(sync_id); } -bool ModelAssociator::BookmarkModelHasUserCreatedNodes() const { +bool BookmarkModelAssociator::ChromeModelHasUserCreatedNodes() { BookmarkModel* model = sync_service_->profile()->GetBookmarkModel(); DCHECK(model->IsLoaded()); return model->GetBookmarkBarNode()->GetChildCount() > 0 || model->other_node()->GetChildCount() > 0; } -bool ModelAssociator::SyncModelHasUserCreatedNodes() { +bool BookmarkModelAssociator::SyncModelHasUserCreatedNodes() { int64 bookmark_bar_sync_id; - if (!GetSyncIdForTaggedNode(kBookmarkBarTag,&bookmark_bar_sync_id)) { + if (!GetSyncIdForTaggedNode(kBookmarkBarTag, &bookmark_bar_sync_id)) { sync_service_->OnUnrecoverableError(); return false; } @@ -245,8 +250,9 @@ bool ModelAssociator::SyncModelHasUserCreatedNodes() { other_bookmarks_node.GetFirstChildId() != sync_api::kInvalidId; } -bool ModelAssociator::NodesMatch(const BookmarkNode* bookmark, - const sync_api::BaseNode* sync_node) const { +bool BookmarkModelAssociator::NodesMatch(const BookmarkNode* bookmark, + const sync_api::BaseNode* sync_node) + const { if (bookmark->GetTitle() != sync_node->GetTitle()) return false; if (bookmark->is_folder() != sync_node->GetIsFolder()) @@ -261,7 +267,7 @@ bool ModelAssociator::NodesMatch(const BookmarkNode* bookmark, return true; } -bool ModelAssociator::AssociateTaggedPermanentNode( +bool BookmarkModelAssociator::AssociateTaggedPermanentNode( const BookmarkNode* permanent_node, const std::string&tag) { // Do nothing if |permanent_node| is already initialized and associated. int64 sync_id = GetSyncIdFromBookmarkId(permanent_node->id()); @@ -274,8 +280,8 @@ bool ModelAssociator::AssociateTaggedPermanentNode( return true; } -bool ModelAssociator::GetSyncIdForTaggedNode(const std::string& tag, - int64* sync_id) { +bool BookmarkModelAssociator::GetSyncIdForTaggedNode(const std::string& tag, + int64* sync_id) { sync_api::ReadTransaction trans( sync_service_->backend()->GetUserShareHandle()); sync_api::ReadNode sync_node(&trans); @@ -285,20 +291,20 @@ bool ModelAssociator::GetSyncIdForTaggedNode(const std::string& tag, return true; } -bool ModelAssociator::AssociateModels() { +bool BookmarkModelAssociator::AssociateModels() { // Try to load model associations from persisted associations first. If that // succeeds, we don't need to run the complex model matching algorithm. if (LoadAssociations()) return true; - ClearAll(); + DisassociateModels(); // We couldn't load model associations from persisted associations. So build // them. return BuildAssociations(); } -bool ModelAssociator::BuildAssociations() { +bool BookmarkModelAssociator::BuildAssociations() { // Algorithm description: // Match up the roots and recursively do the following: // * For each sync node for the current sync parent node, find the best @@ -379,15 +385,15 @@ bool ModelAssociator::BuildAssociations() { if (child_node) { model->Move(child_node, parent_node, index); // Set the favicon for bookmark node from sync node or vice versa. - if (ChangeProcessor::SetBookmarkFavicon(&sync_child_node, + if (BookmarkChangeProcessor::SetBookmarkFavicon(&sync_child_node, child_node, sync_service_->profile())) { - ChangeProcessor::SetSyncNodeFavicon(child_node, model, - &sync_child_node); + BookmarkChangeProcessor::SetSyncNodeFavicon(child_node, model, + &sync_child_node); } } else { // Create a new bookmark node for the sync node. - child_node = ChangeProcessor::CreateBookmarkNode(&sync_child_node, - parent_node, model, index); + child_node = BookmarkChangeProcessor::CreateBookmarkNode( + &sync_child_node, parent_node, model, index); } Associate(child_node, sync_child_id); if (sync_child_node.GetIsFolder()) @@ -403,8 +409,8 @@ bool ModelAssociator::BuildAssociations() { // So the children starting from index in the parent bookmark node are the // ones that are not present in the parent sync node. So create them. for (int i = index; i < parent_node->GetChildCount(); ++i) { - sync_child_id = ChangeProcessor::CreateSyncNode(parent_node, model, i, - &trans, this, sync_service_); + sync_child_id = BookmarkChangeProcessor::CreateSyncNode(parent_node, + model, i, &trans, this, sync_service_); if (parent_node->GetChild(i)->is_folder()) dfs_stack.push(sync_child_id); } @@ -412,20 +418,17 @@ bool ModelAssociator::BuildAssociations() { return true; } -void ModelAssociator::PostPersistAssociationsTask() { +void BookmarkModelAssociator::PostPersistAssociationsTask() { // No need to post a task if a task is already pending. - if (task_pending_) + if (!persist_associations_.empty()) return; - task_pending_ = true; MessageLoop::current()->PostTask( FROM_HERE, - NewRunnableMethod(this, &ModelAssociator::PersistAssociations)); + persist_associations_.NewRunnableMethod( + &BookmarkModelAssociator::PersistAssociations)); } -void ModelAssociator::PersistAssociations() { - DCHECK(task_pending_); - task_pending_ = false; - +void BookmarkModelAssociator::PersistAssociations() { // If there are no dirty associations we have nothing to do. We handle this // explicity instead of letting the for loop do it to avoid creating a write // transaction in this case. @@ -456,7 +459,7 @@ void ModelAssociator::PersistAssociations() { dirty_associations_sync_ids_.clear(); } -bool ModelAssociator::LoadAssociations() { +bool BookmarkModelAssociator::LoadAssociations() { BookmarkModel* model = sync_service_->profile()->GetBookmarkModel(); DCHECK(model->IsLoaded()); // If the bookmarks changed externally, our previous associations may not be diff --git a/chrome/browser/sync/glue/bookmark_model_associator.h b/chrome/browser/sync/glue/bookmark_model_associator.h new file mode 100644 index 0000000..d722367 --- /dev/null +++ b/chrome/browser/sync/glue/bookmark_model_associator.h @@ -0,0 +1,135 @@ +// Copyright (c) 2006-2009 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. + +#ifndef CHROME_BROWSER_SYNC_GLUE_BOOKMARK_MODEL_ASSOCIATOR_H_ +#define CHROME_BROWSER_SYNC_GLUE_BOOKMARK_MODEL_ASSOCIATOR_H_ + +#include <map> +#include <set> +#include <string> + +#include "base/basictypes.h" +#include "base/task.h" +#include "chrome/browser/sync/glue/model_associator.h" + +class BookmarkNode; + +namespace sync_api { +class BaseNode; +class BaseTransaction; +class ReadNode; +} + +class ProfileSyncService; + +namespace browser_sync { + +class BookmarkChangeProcessor; + +// Contains all model association related logic: +// * Algorithm to associate bookmark model and sync model. +// * Methods to get a bookmark node for a given sync node and vice versa. +// * Persisting model associations and loading them back. +class BookmarkModelAssociator : public ModelAssociator { + public: + explicit BookmarkModelAssociator(ProfileSyncService* sync_service); + virtual ~BookmarkModelAssociator() { } + + // AssociateModels iterates through both the sync and the browser + // bookmark model, looking for matched pairs of items. For any pairs it + // finds, it will call AssociateSyncID. For any unmatched items, + // MergeAndAssociateModels will try to repair the match, e.g. by adding a new + // node. After successful completion, the models should be identical and + // corresponding. Returns true on success. On failure of this step, we + // should abort the sync operation and report an error to the user. + virtual bool AssociateModels(); + + // Clears all associations. + virtual bool DisassociateModels(); + + // Returns whether the sync model has nodes other than the permanent tagged + // nodes. + virtual bool SyncModelHasUserCreatedNodes(); + + // Returns whether the bookmark model has user created nodes or not. That is, + // whether there are nodes in the bookmark model except the bookmark bar and + // other bookmarks. + virtual bool ChromeModelHasUserCreatedNodes(); + + // Returns sync id for the given bookmark node id. + // Returns sync_api::kInvalidId if the sync node is not found for the given + // bookmark node id. + int64 GetSyncIdFromBookmarkId(int64 node_id) const; + + // Returns the bookmark node for the given sync id. + // Returns NULL if no bookmark node is found for the given sync id. + const BookmarkNode* GetBookmarkNodeFromSyncId(int64 sync_id); + + // Initializes the given sync node from the given bookmark node id. + // Returns false if no sync node was found for the given bookmark node id or + // if the initialization of sync node fails. + bool InitSyncNodeFromBookmarkId(int64 node_id, sync_api::BaseNode* sync_node); + + // Associates the given bookmark node with the given sync id. + void Associate(const BookmarkNode* node, int64 sync_id); + // Remove the association that corresponds to the given sync id. + void Disassociate(int64 sync_id); + + protected: + // Stores the id of the node with the given tag in |sync_id|. + // Returns of that node was found successfully. + // Tests override this. + virtual bool GetSyncIdForTaggedNode(const std::string& tag, int64* sync_id); + + // Returns sync service instance. + ProfileSyncService* sync_service() { return sync_service_; } + + private: + typedef std::map<int64, int64> BookmarkIdToSyncIdMap; + typedef std::map<int64, const BookmarkNode*> SyncIdToBookmarkNodeMap; + typedef std::set<int64> DirtyAssociationsSyncIds; + + // Posts a task to persist dirty associations. + void PostPersistAssociationsTask(); + // Persists all dirty associations. + void PersistAssociations(); + + // Loads the persisted associations into in-memory maps. + // If the persisted associations are out-of-date due to some reason, returns + // false; otherwise returns true. + bool LoadAssociations(); + + // Matches up the bookmark model and the sync model to build model + // associations. + bool BuildAssociations(); + + // Associate a top-level node of the bookmark model with a permanent node in + // the sync domain. Such permanent nodes are identified by a tag that is + // well known to the server and the client, and is unique within a particular + // user's share. For example, "other_bookmarks" is the tag for the Other + // Bookmarks folder. The sync nodes are server-created. + bool AssociateTaggedPermanentNode(const BookmarkNode* permanent_node, + const std::string& tag); + + // Compare the properties of a pair of nodes from either domain. + bool NodesMatch(const BookmarkNode* bookmark, + const sync_api::BaseNode* sync_node) const; + + ProfileSyncService* sync_service_; + BookmarkIdToSyncIdMap id_map_; + SyncIdToBookmarkNodeMap id_map_inverse_; + // Stores sync ids for dirty associations. + DirtyAssociationsSyncIds dirty_associations_sync_ids_; + + // Used to post PersistAssociation tasks to the current message loop and + // guarantees no invocations can occur if |this| has been deleted. (This + // allows this class to be non-refcounted). + ScopedRunnableMethodFactory<BookmarkModelAssociator> persist_associations_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkModelAssociator); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_GLUE_BOOKMARK_MODEL_ASSOCIATOR_H_ diff --git a/chrome/browser/sync/glue/change_processor.cc b/chrome/browser/sync/glue/change_processor.cc index 6b4e9f7..0c608b8 100644 --- a/chrome/browser/sync/glue/change_processor.cc +++ b/chrome/browser/sync/glue/change_processor.cc @@ -1,542 +1,30 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Copyright (c) 2006-2009 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/glue/change_processor.h" - -#include <stack> - -#include "app/gfx/codec/png_codec.h" -#include "base/string_util.h" -#include "chrome/browser/bookmarks/bookmark_utils.h" -#include "chrome/browser/favicon_service.h" #include "chrome/browser/profile.h" -#include "chrome/browser/sync/profile_sync_service.h" -#include "third_party/skia/include/core/SkBitmap.h" namespace browser_sync { -void ChangeProcessor::Start(BookmarkModel* model, sync_api::UserShare* handle) { - DCHECK(error_handler_ && model_associator_); - DCHECK(!share_handle_ && !bookmark_model_); - share_handle_ = handle; - bookmark_model_ = model; - DCHECK(model->IsLoaded()); - bookmark_model_->AddObserver(this); +ChangeProcessor::~ChangeProcessor() { + Stop(); +} + +void ChangeProcessor::Start(Profile* profile, + sync_api::UserShare* share_handle) { + DCHECK(error_handler_ && !share_handle_); + share_handle_ = share_handle; + StartImpl(profile); running_ = true; } void ChangeProcessor::Stop() { if (!running_) return; - DCHECK(bookmark_model_); - bookmark_model_->RemoveObserver(this); - bookmark_model_ = NULL; + StopImpl(); share_handle_ = NULL; - model_associator_ = NULL; running_ = false; } -void ChangeProcessor::UpdateSyncNodeProperties(const BookmarkNode* src, - BookmarkModel* model, - sync_api::WriteNode* dst) { - // Set the properties of the item. - dst->SetIsFolder(src->is_folder()); - dst->SetTitle(src->GetTitle()); - dst->SetURL(src->GetURL()); - SetSyncNodeFavicon(src, model, dst); -} - -// static -void ChangeProcessor::EncodeFavicon(const BookmarkNode* src, - BookmarkModel* model, - std::vector<unsigned char>* dst) { - const SkBitmap& favicon = model->GetFavIcon(src); - - dst->clear(); - - // Check for zero-dimension images. This can happen if the favicon is - // still being loaded. - if (favicon.empty()) - return; - - // Re-encode the BookmarkNode's favicon as a PNG, and pass the data to the - // sync subsystem. - if (!gfx::PNGCodec::EncodeBGRASkBitmap(favicon, false, dst)) - return; -} - -void ChangeProcessor::RemoveOneSyncNode(sync_api::WriteTransaction* trans, - const BookmarkNode* node) { - sync_api::WriteNode sync_node(trans); - if (!model_associator_->InitSyncNodeFromBookmarkId(node->id(), &sync_node)) { - error_handler_->OnUnrecoverableError(); - return; - } - // This node should have no children. - DCHECK(sync_node.GetFirstChildId() == sync_api::kInvalidId); - // Remove association and delete the sync node. - model_associator_->Disassociate(sync_node.GetId()); - sync_node.Remove(); -} - -void ChangeProcessor::RemoveSyncNodeHierarchy(const BookmarkNode* topmost) { - sync_api::WriteTransaction trans(share_handle_); - - // Later logic assumes that |topmost| has been unlinked. - DCHECK(!topmost->GetParent()); - - // A BookmarkModel deletion event means that |node| and all its children were - // deleted. Sync backend expects children to be deleted individually, so we do - // a depth-first-search here. At each step, we consider the |index|-th child - // of |node|. |index_stack| stores index values for the parent levels. - std::stack<int> index_stack; - index_stack.push(0); // For the final pop. It's never used. - const BookmarkNode* node = topmost; - int index = 0; - while (node) { - // The top of |index_stack| should always be |node|'s index. - DCHECK(!node->GetParent() || (node->GetParent()->IndexOfChild(node) == - index_stack.top())); - if (index == node->GetChildCount()) { - // If we've processed all of |node|'s children, delete |node| and move - // on to its successor. - RemoveOneSyncNode(&trans, node); - node = node->GetParent(); - index = index_stack.top() + 1; // (top() + 0) was what we removed. - index_stack.pop(); - } else { - // If |node| has an unprocessed child, process it next after pushing the - // current state onto the stack. - DCHECK_LT(index, node->GetChildCount()); - index_stack.push(index); - node = node->GetChild(index); - index = 0; - } - } - DCHECK(index_stack.empty()); // Nothing should be left on the stack. -} - -void ChangeProcessor::BookmarkModelBeingDeleted(BookmarkModel* model) { - DCHECK(!running_) << "BookmarkModel deleted while ChangeProcessor running."; - bookmark_model_ = NULL; -} - -void ChangeProcessor::BookmarkNodeAdded(BookmarkModel* model, - const BookmarkNode* parent, - int index) { - DCHECK(running_); - DCHECK(share_handle_); - - // Acquire a scoped write lock via a transaction. - sync_api::WriteTransaction trans(share_handle_); - - CreateSyncNode(parent, model, index, &trans, model_associator_, - error_handler_); -} - -// static -int64 ChangeProcessor::CreateSyncNode(const BookmarkNode* parent, - BookmarkModel* model, int index, sync_api::WriteTransaction* trans, - ModelAssociator* associator, UnrecoverableErrorHandler* error_handler) { - const BookmarkNode* child = parent->GetChild(index); - DCHECK(child); - - // Create a WriteNode container to hold the new node. - sync_api::WriteNode sync_child(trans); - - // Actually create the node with the appropriate initial position. - if (!PlaceSyncNode(CREATE, parent, index, trans, &sync_child, associator, - error_handler)) { - LOG(WARNING) << "Sync node creation failed; recovery unlikely"; - error_handler->OnUnrecoverableError(); - return sync_api::kInvalidId; - } - - UpdateSyncNodeProperties(child, model, &sync_child); - - // Associate the ID from the sync domain with the bookmark node, so that we - // can refer back to this item later. - associator->Associate(child, sync_child.GetId()); - - return sync_child.GetId(); -} - - -void ChangeProcessor::BookmarkNodeRemoved(BookmarkModel* model, - const BookmarkNode* parent, - int index, - const BookmarkNode* node) { - DCHECK(running_); - RemoveSyncNodeHierarchy(node); -} - -void ChangeProcessor::BookmarkNodeChanged(BookmarkModel* model, - const BookmarkNode* node) { - DCHECK(running_); - // We shouldn't see changes to the top-level nodes. - if (node == model->GetBookmarkBarNode() || node == model->other_node()) { - NOTREACHED() << "Saw update to permanent node!"; - return; - } - - // Acquire a scoped write lock via a transaction. - sync_api::WriteTransaction trans(share_handle_); - - // Lookup the sync node that's associated with |node|. - sync_api::WriteNode sync_node(&trans); - if (!model_associator_->InitSyncNodeFromBookmarkId(node->id(), &sync_node)) { - error_handler_->OnUnrecoverableError(); - return; - } - - UpdateSyncNodeProperties(node, model, &sync_node); - - DCHECK_EQ(sync_node.GetIsFolder(), node->is_folder()); - DCHECK_EQ(model_associator_->GetBookmarkNodeFromSyncId( - sync_node.GetParentId()), - node->GetParent()); - // This node's index should be one more than the predecessor's index. - DCHECK_EQ(node->GetParent()->IndexOfChild(node), - CalculateBookmarkModelInsertionIndex(node->GetParent(), - &sync_node)); -} - - -void ChangeProcessor::BookmarkNodeMoved(BookmarkModel* model, - const BookmarkNode* old_parent, - int old_index, - const BookmarkNode* new_parent, - int new_index) { - DCHECK(running_); - const BookmarkNode* child = new_parent->GetChild(new_index); - // We shouldn't see changes to the top-level nodes. - if (child == model->GetBookmarkBarNode() || child == model->other_node()) { - NOTREACHED() << "Saw update to permanent node!"; - return; - } - - // Acquire a scoped write lock via a transaction. - sync_api::WriteTransaction trans(share_handle_); - - // Lookup the sync node that's associated with |child|. - sync_api::WriteNode sync_node(&trans); - if (!model_associator_->InitSyncNodeFromBookmarkId(child->id(), &sync_node)) { - error_handler_->OnUnrecoverableError(); - return; - } - - if (!PlaceSyncNode(MOVE, new_parent, new_index, &trans, &sync_node, - model_associator_, error_handler_)) { - error_handler_->OnUnrecoverableError(); - return; - } -} - -void ChangeProcessor::BookmarkNodeFavIconLoaded(BookmarkModel* model, - const BookmarkNode* node) { - DCHECK(running_); - BookmarkNodeChanged(model, node); -} - -void ChangeProcessor::BookmarkNodeChildrenReordered( - BookmarkModel* model, const BookmarkNode* node) { - - // Acquire a scoped write lock via a transaction. - sync_api::WriteTransaction trans(share_handle_); - - // The given node's children got reordered. We need to reorder all the - // children of the corresponding sync node. - for (int i = 0; i < node->GetChildCount(); ++i) { - sync_api::WriteNode sync_child(&trans); - if (!model_associator_->InitSyncNodeFromBookmarkId(node->GetChild(i)->id(), - &sync_child)) { - error_handler_->OnUnrecoverableError(); - return; - } - DCHECK_EQ(sync_child.GetParentId(), - model_associator_->GetSyncIdFromBookmarkId(node->id())); - - if (!PlaceSyncNode(MOVE, node, i, &trans, &sync_child, model_associator_, - error_handler_)) { - error_handler_->OnUnrecoverableError(); - return; - } - } -} - -// static -bool ChangeProcessor::PlaceSyncNode(MoveOrCreate operation, - const BookmarkNode* parent, - int index, - sync_api::WriteTransaction* trans, - sync_api::WriteNode* dst, - ModelAssociator* associator, - UnrecoverableErrorHandler* error_handler) { - sync_api::ReadNode sync_parent(trans); - if (!associator->InitSyncNodeFromBookmarkId(parent->id(), &sync_parent)) { - LOG(WARNING) << "Parent lookup failed"; - error_handler->OnUnrecoverableError(); - return false; - } - - bool success = false; - if (index == 0) { - // Insert into first position. - success = (operation == CREATE) ? dst->InitByCreation(sync_parent, NULL) : - dst->SetPosition(sync_parent, NULL); - if (success) { - DCHECK_EQ(dst->GetParentId(), sync_parent.GetId()); - DCHECK_EQ(dst->GetId(), sync_parent.GetFirstChildId()); - DCHECK_EQ(dst->GetPredecessorId(), sync_api::kInvalidId); - } - } else { - // Find the bookmark model predecessor, and insert after it. - const BookmarkNode* prev = parent->GetChild(index - 1); - sync_api::ReadNode sync_prev(trans); - if (!associator->InitSyncNodeFromBookmarkId(prev->id(), &sync_prev)) { - LOG(WARNING) << "Predecessor lookup failed"; - return false; - } - success = (operation == CREATE) ? - dst->InitByCreation(sync_parent, &sync_prev) : - dst->SetPosition(sync_parent, &sync_prev); - if (success) { - DCHECK_EQ(dst->GetParentId(), sync_parent.GetId()); - DCHECK_EQ(dst->GetPredecessorId(), sync_prev.GetId()); - DCHECK_EQ(dst->GetId(), sync_prev.GetSuccessorId()); - } - } - return success; -} - -// Determine the bookmark model index to which a node must be moved so that -// predecessor of the node (in the bookmark model) matches the predecessor of -// |source| (in the sync model). -// As a precondition, this assumes that the predecessor of |source| has been -// updated and is already in the correct position in the bookmark model. -int ChangeProcessor::CalculateBookmarkModelInsertionIndex( - const BookmarkNode* parent, - const sync_api::BaseNode* child_info) const { - DCHECK(parent); - DCHECK(child_info); - int64 predecessor_id = child_info->GetPredecessorId(); - // A return ID of kInvalidId indicates no predecessor. - if (predecessor_id == sync_api::kInvalidId) - return 0; - - // Otherwise, insert after the predecessor bookmark node. - const BookmarkNode* predecessor = - model_associator_->GetBookmarkNodeFromSyncId(predecessor_id); - DCHECK(predecessor); - DCHECK_EQ(predecessor->GetParent(), parent); - return parent->IndexOfChild(predecessor) + 1; -} - -// ApplyModelChanges is called by the sync backend after changes have been made -// to the sync engine's model. Apply these changes to the browser bookmark -// model. -void ChangeProcessor::ApplyChangesFromSyncModel( - const sync_api::BaseTransaction* trans, - const sync_api::SyncManager::ChangeRecord* changes, - int change_count) { - if (!running_) - return; - // A note about ordering. Sync backend is responsible for ordering the change - // records in the following order: - // - // 1. Deletions, from leaves up to parents. - // 2. Existing items with synced parents & predecessors. - // 3. New items with synced parents & predecessors. - // 4. Items with parents & predecessors in the list. - // 5. Repeat #4 until all items are in the list. - // - // "Predecessor" here means the previous item within a given folder; an item - // in the first position is always said to have a synced predecessor. - // For the most part, applying these changes in the order given will yield - // the correct result. There is one exception, however: for items that are - // moved away from a folder that is being deleted, we will process the delete - // before the move. Since deletions in the bookmark model propagate from - // parent to child, we must move them to a temporary location. - BookmarkModel* model = bookmark_model_; - - // We are going to make changes to the bookmarks model, but don't want to end - // up in a feedback loop, so remove ourselves as an observer while applying - // changes. - model->RemoveObserver(this); - - // A parent to hold nodes temporarily orphaned by parent deletion. It is - // lazily created inside the loop. - const BookmarkNode* foster_parent = NULL; - for (int i = 0; i < change_count; ++i) { - const BookmarkNode* dst = - model_associator_->GetBookmarkNodeFromSyncId(changes[i].id); - // Ignore changes to the permanent top-level nodes. We only care about - // their children. - if ((dst == model->GetBookmarkBarNode()) || (dst == model->other_node())) - continue; - if (changes[i].action == - sync_api::SyncManager::ChangeRecord::ACTION_DELETE) { - // Deletions should always be at the front of the list. - DCHECK(i == 0 || changes[i-1].action == changes[i].action); - // Children of a deleted node should not be deleted; they may be - // reparented by a later change record. Move them to a temporary place. - DCHECK(dst) << "Could not find node to be deleted"; - const BookmarkNode* parent = dst->GetParent(); - if (dst->GetChildCount()) { - if (!foster_parent) { - foster_parent = model->AddGroup(model->other_node(), - model->other_node()->GetChildCount(), - std::wstring()); - } - for (int i = dst->GetChildCount() - 1; i >= 0; --i) { - model->Move(dst->GetChild(i), foster_parent, - foster_parent->GetChildCount()); - } - } - DCHECK_EQ(dst->GetChildCount(), 0) << "Node being deleted has children"; - model_associator_->Disassociate(changes[i].id); - model->Remove(parent, parent->IndexOfChild(dst)); - dst = NULL; - } else { - DCHECK_EQ((changes[i].action == - sync_api::SyncManager::ChangeRecord::ACTION_ADD), (dst == NULL)) - << "ACTION_ADD should be seen if and only if the node is unknown."; - - sync_api::ReadNode src(trans); - if (!src.InitByIdLookup(changes[i].id)) { - LOG(ERROR) << "ApplyModelChanges was passed a bad ID"; - error_handler_->OnUnrecoverableError(); - return; - } - - CreateOrUpdateBookmarkNode(&src, model); - } - } - // Clean up the temporary node. - if (foster_parent) { - // There should be no nodes left under the foster parent. - DCHECK_EQ(foster_parent->GetChildCount(), 0); - model->Remove(foster_parent->GetParent(), - foster_parent->GetParent()->IndexOfChild(foster_parent)); - foster_parent = NULL; - } - - // We are now ready to hear about bookmarks changes again. - model->AddObserver(this); -} - -// Create a bookmark node corresponding to |src| if one is not already -// associated with |src|. -const BookmarkNode* ChangeProcessor::CreateOrUpdateBookmarkNode( - sync_api::BaseNode* src, - BookmarkModel* model) { - const BookmarkNode* parent = - model_associator_->GetBookmarkNodeFromSyncId(src->GetParentId()); - if (!parent) { - DLOG(WARNING) << "Could not find parent of node being added/updated." - << " Node title: " << src->GetTitle() - << ", parent id = " << src->GetParentId(); - return NULL; - } - int index = CalculateBookmarkModelInsertionIndex(parent, src); - const BookmarkNode* dst = model_associator_->GetBookmarkNodeFromSyncId( - src->GetId()); - if (!dst) { - dst = CreateBookmarkNode(src, parent, model, index); - model_associator_->Associate(dst, src->GetId()); - } else { - // URL and is_folder are not expected to change. - // TODO(ncarter): Determine if such changes should be legal or not. - DCHECK_EQ(src->GetIsFolder(), dst->is_folder()); - - // Handle reparenting and/or repositioning. - model->Move(dst, parent, index); - - // Handle title update and URL changes due to possible conflict resolution - // that can happen if both a local user change and server change occur - // within a sufficiently small time interval. - const BookmarkNode* old_dst = dst; - dst = bookmark_utils::ApplyEditsWithNoGroupChange(model, parent, - BookmarkEditor::EditDetails(dst), - src->GetTitle(), - src->GetIsFolder() ? GURL() : src->GetURL(), - NULL); // NULL because we don't need a BookmarkEditor::Handler. - if (dst != old_dst) { // dst was replaced with a new node with new URL. - model_associator_->Disassociate(src->GetId()); - model_associator_->Associate(dst, src->GetId()); - } - SetBookmarkFavicon(src, dst, model->profile()); - } - - return dst; -} - -// static -// Creates a bookmark node under the given parent node from the given sync -// node. Returns the newly created node. -const BookmarkNode* ChangeProcessor::CreateBookmarkNode( - sync_api::BaseNode* sync_node, - const BookmarkNode* parent, - BookmarkModel* model, - int index) { - DCHECK(parent); - DCHECK(index >= 0 && index <= parent->GetChildCount()); - - const BookmarkNode* node; - if (sync_node->GetIsFolder()) { - node = model->AddGroup(parent, index, sync_node->GetTitle()); - } else { - node = model->AddURL(parent, index, - sync_node->GetTitle(), sync_node->GetURL()); - SetBookmarkFavicon(sync_node, node, model->profile()); - } - return node; -} - -// static -// Sets the favicon of the given bookmark node from the given sync node. -bool ChangeProcessor::SetBookmarkFavicon( - sync_api::BaseNode* sync_node, - const BookmarkNode* bookmark_node, - Profile* profile) { - size_t icon_size = 0; - const unsigned char* icon_bytes = sync_node->GetFaviconBytes(&icon_size); - if (!icon_size || !icon_bytes) - return false; - - // Registering a favicon requires that we provide a source URL, but we - // don't know where these came from. Currently we just use the - // destination URL, which is not correct, but since the favicon URL - // is used as a key in the history's thumbnail DB, this gives us a value - // which does not collide with others. - GURL fake_icon_url = bookmark_node->GetURL(); - - std::vector<unsigned char> icon_bytes_vector(icon_bytes, - icon_bytes + icon_size); - - HistoryService* history = - profile->GetHistoryService(Profile::EXPLICIT_ACCESS); - FaviconService* favicon_service = - profile->GetFaviconService(Profile::EXPLICIT_ACCESS); - - history->AddPage(bookmark_node->GetURL()); - favicon_service->SetFavicon(bookmark_node->GetURL(), - fake_icon_url, - icon_bytes_vector); - - return true; -} - -// static -void ChangeProcessor::SetSyncNodeFavicon( - const BookmarkNode* bookmark_node, - BookmarkModel* model, - sync_api::WriteNode* sync_node) { - std::vector<unsigned char> favicon_bytes; - EncodeFavicon(bookmark_node, model, &favicon_bytes); - if (!favicon_bytes.empty()) - sync_node->SetFaviconBytes(&favicon_bytes[0], favicon_bytes.size()); -} - } // namespace browser_sync diff --git a/chrome/browser/sync/glue/change_processor.h b/chrome/browser/sync/glue/change_processor.h index fe7719b..2537636 100644 --- a/chrome/browser/sync/glue/change_processor.h +++ b/chrome/browser/sync/glue/change_processor.h @@ -1,181 +1,68 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Copyright (c) 2006-2009 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. #ifndef CHROME_BROWSER_SYNC_GLUE_CHANGE_PROCESSOR_H_ #define CHROME_BROWSER_SYNC_GLUE_CHANGE_PROCESSOR_H_ -#include "chrome/browser/bookmarks/bookmark_model.h" #include "chrome/browser/sync/engine/syncapi.h" -#include "chrome/browser/sync/glue/model_associator.h" #include "chrome/browser/sync/glue/sync_backend_host.h" -class ProfileSyncService; +class Profile; namespace browser_sync { +class ModelAssociator; class UnrecoverableErrorHandler; -// This class is responsible for taking changes from the BookmarkModel -// and applying them to the sync_api 'syncable' model, and vice versa. -// All operations and use of this class are from the UI thread. -// This is currently bookmarks specific. -class ChangeProcessor : public BookmarkModelObserver, - public ChangeProcessingInterface { +// An interface used to apply changes from the sync model to the browser's +// native model. This does not currently distinguish between model data types. +class ChangeProcessor { public: explicit ChangeProcessor(UnrecoverableErrorHandler* error_handler) - : running_(false), error_handler_(error_handler), - bookmark_model_(NULL), share_handle_(NULL), model_associator_(NULL) {} - virtual ~ChangeProcessor() { Stop(); } + : running_(false), error_handler_(error_handler), share_handle_(NULL) {} + virtual ~ChangeProcessor(); // Call when the processor should accept changes from either provided model - // and apply them to the other. Both the BookmarkModel and sync_api are + // and apply them to the other. Both the chrome model and sync_api are // expected to be initialized and loaded. You must have set a valid // ModelAssociator and UnrecoverableErrorHandler before using this method, // and the two models should be associated w.r.t the ModelAssociator provided. // It is safe to call Start again after previously Stop()ing the processor. - void Start(BookmarkModel* model, sync_api::UserShare* handle); - - // Call when processing changes between models is no longer safe / needed. + // Subclasses can extract their associated chrome model from |profile| in + // |StartImpl|. + void Start(Profile* profile, sync_api::UserShare* share_handle); void Stop(); - bool IsRunning() const { return running_; } - // Injectors for the components required to Start the processor. - void set_model_associator(ModelAssociator* associator) { - model_associator_ = associator; - } - - // BookmarkModelObserver implementation. - // BookmarkModel -> sync_api model change application. - virtual void Loaded(BookmarkModel* model) { NOTREACHED(); } - virtual void BookmarkModelBeingDeleted(BookmarkModel* model); - virtual void BookmarkNodeMoved(BookmarkModel* model, - const BookmarkNode* old_parent, - int old_index, - const BookmarkNode* new_parent, - int new_index); - virtual void BookmarkNodeAdded(BookmarkModel* model, - const BookmarkNode* parent, - int index); - virtual void BookmarkNodeRemoved(BookmarkModel* model, - const BookmarkNode* parent, - int index, - const BookmarkNode* node); - virtual void BookmarkNodeChanged(BookmarkModel* model, - const BookmarkNode* node); - virtual void BookmarkNodeFavIconLoaded(BookmarkModel* model, - const BookmarkNode* node); - virtual void BookmarkNodeChildrenReordered(BookmarkModel* model, - const BookmarkNode* node); - - // sync_api model -> BookmarkModel change application. + virtual ModelAssociator* GetModelAssociator() = 0; + + // Changes have been applied to the backend model and are ready to be + // applied to the frontend model. See syncapi.h for detailed instructions on + // how to interpret and process |changes|. virtual void ApplyChangesFromSyncModel( const sync_api::BaseTransaction* trans, const sync_api::SyncManager::ChangeRecord* changes, - int change_count); - - // The following methods are static and hence may be invoked at any time, - // and do not depend on having a running ChangeProcessor. - // Creates a bookmark node under the given parent node from the given sync - // node. Returns the newly created node. - static const BookmarkNode* CreateBookmarkNode( - sync_api::BaseNode* sync_node, - const BookmarkNode* parent, - BookmarkModel* model, - int index); - - // Sets the favicon of the given bookmark node from the given sync node. - // Returns whether the favicon was set in the bookmark node. - // |profile| is the profile that contains the HistoryService and BookmarkModel - // for the bookmark in question. - static bool SetBookmarkFavicon(sync_api::BaseNode* sync_node, - const BookmarkNode* bookmark_node, - Profile* profile); - - // Sets the favicon of the given sync node from the given bookmark node. - static void SetSyncNodeFavicon(const BookmarkNode* bookmark_node, - BookmarkModel* model, - sync_api::WriteNode* sync_node); - - // Treat the |index|th child of |parent| as a newly added node, and create a - // corresponding node in the sync domain using |trans|. All properties - // will be transferred to the new node. A node corresponding to |parent| - // must already exist and be associated for this call to succeed. Returns - // the ID of the just-created node, or if creation fails, kInvalidID. - static int64 CreateSyncNode(const BookmarkNode* parent, - BookmarkModel* model, - int index, - sync_api::WriteTransaction* trans, - ModelAssociator* associator, - UnrecoverableErrorHandler* error_handler); + int change_count) = 0; - private: - enum MoveOrCreate { - MOVE, - CREATE, - }; - - // Create a bookmark node corresponding to |src| if one is not already - // associated with |src|. Returns the node that was created or updated. - const BookmarkNode* CreateOrUpdateBookmarkNode( - sync_api::BaseNode* src, - BookmarkModel* model); - - // Helper function to determine the appropriate insertion index of sync node - // |node| under the Bookmark model node |parent|, to make the positions - // match up between the two models. This presumes that the predecessor of the - // item (in the bookmark model) has already been moved into its appropriate - // position. - int CalculateBookmarkModelInsertionIndex( - const BookmarkNode* parent, - const sync_api::BaseNode* node) const; - - // Helper function used to fix the position of a sync node so that it matches - // the position of a corresponding bookmark model node. |parent| and - // |index| identify the bookmark model position. |dst| is the node whose - // position is to be fixed. If |operation| is CREATE, treat |dst| as an - // uncreated node and set its position via InitByCreation(); otherwise, - // |dst| is treated as an existing node, and its position will be set via - // SetPosition(). |trans| is the transaction to which |dst| belongs. Returns - // false on failure. - static bool PlaceSyncNode(MoveOrCreate operation, - const BookmarkNode* parent, - int index, - sync_api::WriteTransaction* trans, - sync_api::WriteNode* dst, - ModelAssociator* associator, - UnrecoverableErrorHandler* error_handler); - - // Copy properties (but not position) from |src| to |dst|. - static void UpdateSyncNodeProperties(const BookmarkNode* src, - BookmarkModel* model, - sync_api::WriteNode* dst); - - // Helper function to encode a bookmark's favicon into a PNG byte vector. - static void EncodeFavicon(const BookmarkNode* src, - BookmarkModel* model, - std::vector<unsigned char>* dst); - - // Remove the sync node corresponding to |node|. It shouldn't have - // any children. - void RemoveOneSyncNode(sync_api::WriteTransaction* trans, - const BookmarkNode* node); - - // Remove all the sync nodes associated with |node| and its children. - void RemoveSyncNodeHierarchy(const BookmarkNode* node); + protected: + // These methods are invoked by Start() and Stop() to do + // implementation-specific work. + virtual void StartImpl(Profile* profile) = 0; + virtual void StopImpl() = 0; + bool running() { return running_; } + UnrecoverableErrorHandler* error_handler() { return error_handler_; } + sync_api::UserShare* share_handle() { return share_handle_; } + + private: bool running_; // True if we have been told it is safe to process changes. UnrecoverableErrorHandler* error_handler_; // Guaranteed to outlive us. - // The two models we are processing changes between. Non-NULL when - // |running_| is true. - BookmarkModel* bookmark_model_; + // The sync model we are processing changes from. Non-NULL when |running_| is + // true. sync_api::UserShare* share_handle_; - // The two models should be associated according to this ModelAssociator. - scoped_refptr<ModelAssociator> model_associator_; - DISALLOW_COPY_AND_ASSIGN(ChangeProcessor); }; diff --git a/chrome/browser/sync/glue/model_associator.h b/chrome/browser/sync/glue/model_associator.h index e7d5c32..7e09be2 100644 --- a/chrome/browser/sync/glue/model_associator.h +++ b/chrome/browser/sync/glue/model_associator.h @@ -5,131 +5,31 @@ #ifndef CHROME_BROWSER_SYNC_GLUE_MODEL_ASSOCIATOR_H_ #define CHROME_BROWSER_SYNC_GLUE_MODEL_ASSOCIATOR_H_ -#include <map> -#include <set> -#include <string> - -#include "base/basictypes.h" -#include "base/ref_counted.h" -#include "base/scoped_ptr.h" - -class BookmarkNode; - -namespace sync_api { -class BaseNode; -class BaseTransaction; -class ReadNode; -} - -class ProfileSyncService; +#include "chrome/browser/sync/engine/syncapi.h" namespace browser_sync { -class ChangeProcessor; - -// Contains all model association related logic: -// * Algorithm to associate bookmark model and sync model. -// * Methods to get a bookmark node for a given sync node and vice versa. -// * Persisting model associations and loading them back. -class ModelAssociator - : public base::RefCountedThreadSafe<ModelAssociator> { +// Contains all model association related logic to associate the chrome model +// with the sync model. +class ModelAssociator { public: - explicit ModelAssociator(ProfileSyncService* sync_service); - - // Clears all associations. - void ClearAll(); + virtual ~ModelAssociator() {} - // Returns sync id for the given bookmark node id. - // Returns sync_api::kInvalidId if the sync node is not found for the given - // bookmark node id. - int64 GetSyncIdFromBookmarkId(int64 node_id) const; - - // Returns the bookmark node for the given sync id. - // Returns NULL if no bookmark node is found for the given sync id. - const BookmarkNode* GetBookmarkNodeFromSyncId(int64 sync_id); - - // Initializes the given sync node from the given bookmark node id. - // Returns false if no sync node was found for the given bookmark node id or - // if the initialization of sync node fails. - bool InitSyncNodeFromBookmarkId(int64 node_id, sync_api::BaseNode* sync_node); - - // Associates the given bookmark node with the given sync id. - void Associate(const BookmarkNode* node, int64 sync_id); - // Remove the association that corresponds to the given sync id. - void Disassociate(int64 sync_id); + // Iterates through both the sync and the chrome model looking for matched + // pairs of items. After successful completion, the models should be identical + // and corresponding. Returns true on success. On failure of this step, we + // should abort the sync operation and report an error to the user. + virtual bool AssociateModels() = 0; - // Returns whether the bookmark model has user created nodes or not. That is, - // whether there are nodes in the bookmark model except the bookmark bar and - // other bookmarks. - bool BookmarkModelHasUserCreatedNodes() const; + // Clears all the associations between the chrome and sync models. + virtual bool DisassociateModels() = 0; // Returns whether the sync model has nodes other than the permanent tagged // nodes. - bool SyncModelHasUserCreatedNodes(); - - // AssociateModels iterates through both the sync and the browser - // bookmark model, looking for matched pairs of items. For any pairs it - // finds, it will call AssociateSyncID. For any unmatched items, - // MergeAndAssociateModels will try to repair the match, e.g. by adding a new - // node. After successful completion, the models should be identical and - // corresponding. Returns true on success. On failure of this step, we - // should abort the sync operation and report an error to the user. - bool AssociateModels(); - - protected: - friend class base::RefCountedThreadSafe<ModelAssociator>; - virtual ~ModelAssociator() { } - - // Stores the id of the node with the given tag in |sync_id|. - // Returns of that node was found successfully. - // Tests override this. - virtual bool GetSyncIdForTaggedNode(const std::string& tag, int64* sync_id); - - // Returns sync service instance. - ProfileSyncService* sync_service() { return sync_service_; } - - private: - typedef std::map<int64, int64> BookmarkIdToSyncIdMap; - typedef std::map<int64, const BookmarkNode*> SyncIdToBookmarkNodeMap; - typedef std::set<int64> DirtyAssociationsSyncIds; - - // Posts a task to persist dirty associations. - void PostPersistAssociationsTask(); - // Persists all dirty associations. - void PersistAssociations(); - - // Loads the persisted associations into in-memory maps. - // If the persisted associations are out-of-date due to some reason, returns - // false; otherwise returns true. - bool LoadAssociations(); - - // Matches up the bookmark model and the sync model to build model - // associations. - bool BuildAssociations(); - - // Associate a top-level node of the bookmark model with a permanent node in - // the sync domain. Such permanent nodes are identified by a tag that is - // well known to the server and the client, and is unique within a particular - // user's share. For example, "other_bookmarks" is the tag for the Other - // Bookmarks folder. The sync nodes are server-created. - bool AssociateTaggedPermanentNode(const BookmarkNode* permanent_node, - const std::string& tag); - - // Compare the properties of a pair of nodes from either domain. - bool NodesMatch(const BookmarkNode* bookmark, - const sync_api::BaseNode* sync_node) const; - - ProfileSyncService* sync_service_; - BookmarkIdToSyncIdMap id_map_; - SyncIdToBookmarkNodeMap id_map_inverse_; - // Stores sync ids for dirty associations. - DirtyAssociationsSyncIds dirty_associations_sync_ids_; - - // Indicates whether there is already a pending task to persist dirty model - // associations. - bool task_pending_; + virtual bool SyncModelHasUserCreatedNodes() = 0; - DISALLOW_COPY_AND_ASSIGN(ModelAssociator); + // Returns whether the chrome model has user-created nodes or not. + virtual bool ChromeModelHasUserCreatedNodes() = 0; }; } // namespace browser_sync diff --git a/chrome/browser/sync/glue/sync_backend_host.cc b/chrome/browser/sync/glue/sync_backend_host.cc index d6917cd..8bff80c 100644 --- a/chrome/browser/sync/glue/sync_backend_host.cc +++ b/chrome/browser/sync/glue/sync_backend_host.cc @@ -25,12 +25,12 @@ namespace browser_sync { SyncBackendHost::SyncBackendHost(SyncFrontend* frontend, const FilePath& profile_path, - ChangeProcessingInterface* processor) + std::set<ChangeProcessor*> processors) : core_thread_("Chrome_SyncCoreThread"), frontend_loop_(MessageLoop::current()), bookmark_model_worker_(NULL), frontend_(frontend), - processor_(processor), + processors_(processors), sync_data_folder_path_(profile_path.Append(kSyncDataFolderName)), last_auth_error_(AuthError::None()) { core_ = new Core(this); @@ -243,7 +243,12 @@ void SyncBackendHost::Core::OnChangesApplied( DLOG(WARNING) << "Could not update bookmark model from non-UI thread"; return; } - host_->processor_->ApplyChangesFromSyncModel(trans, changes, change_count); + for (std::set<ChangeProcessor*>::const_iterator it = + host_->processors_.begin(); it != host_->processors_.end(); ++it) { + // TODO(sync): Filter per data-type here and apply only the relevant + // changes for each processor. + (*it)->ApplyChangesFromSyncModel(trans, changes, change_count); + } } void SyncBackendHost::Core::OnSyncCycleCompleted() { diff --git a/chrome/browser/sync/glue/sync_backend_host.h b/chrome/browser/sync/glue/sync_backend_host.h index 80696eb..59ce96c 100644 --- a/chrome/browser/sync/glue/sync_backend_host.h +++ b/chrome/browser/sync/glue/sync_backend_host.h @@ -5,6 +5,7 @@ #ifndef CHROME_BROWSER_SYNC_GLUE_SYNC_BACKEND_HOST_H_ #define CHROME_BROWSER_SYNC_GLUE_SYNC_BACKEND_HOST_H_ +#include <set> #include <string> #include "base/file_path.h" @@ -21,6 +22,8 @@ namespace browser_sync { +class ChangeProcessor; + // SyncFrontend is the interface used by SyncBackendHost to communicate with // the entity that created it and, presumably, is interested in sync-related // activity. @@ -49,21 +52,6 @@ class SyncFrontend { DISALLOW_COPY_AND_ASSIGN(SyncFrontend); }; -// An interface used to apply changes from the sync model to the browser's -// native model. This does not currently distinguish between model data types. -class ChangeProcessingInterface { - public: - // Changes have been applied to the backend model and are ready to be - // applied to the frontend model. See syncapi.h for detailed instructions on - // how to interpret and process |changes|. - virtual void ApplyChangesFromSyncModel( - const sync_api::BaseTransaction* trans, - const sync_api::SyncManager::ChangeRecord* changes, - int change_count) = 0; - protected: - virtual ~ChangeProcessingInterface() { } -}; - // A UI-thread safe API into the sync backend that "hosts" the top-level // syncapi element, the SyncManager, on its own thread. This class handles // dispatch of potentially blocking calls to appropriate threads and ensures @@ -79,7 +67,7 @@ class SyncBackendHost { // it used to call the constructor), and push changes from sync_api through // |processor|. SyncBackendHost(SyncFrontend* frontend, const FilePath& profile_path, - ChangeProcessingInterface* processor); + std::set<ChangeProcessor*> processor); ~SyncBackendHost(); // Called on |frontend_loop_| to kick off asynchronous initialization. @@ -282,7 +270,8 @@ class SyncBackendHost { // The frontend which we serve (and are owned by). SyncFrontend* frontend_; - ChangeProcessingInterface* processor_; // Guaranteed to outlive us. + // The change processors that handle the different data types. + std::set<ChangeProcessor*> processors_; // Path of the folder that stores the sync data files. FilePath sync_data_folder_path_; diff --git a/chrome/browser/sync/profile_sync_service.cc b/chrome/browser/sync/profile_sync_service.cc index 37539ef..be160fd 100644 --- a/chrome/browser/sync/profile_sync_service.cc +++ b/chrome/browser/sync/profile_sync_service.cc @@ -21,6 +21,7 @@ #include "chrome/browser/history/history_types.h" #include "chrome/browser/profile.h" #include "chrome/browser/sync/engine/syncapi.h" +#include "chrome/browser/sync/glue/bookmark_change_processor.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/notification_service.h" #include "chrome/common/notification_type.h" @@ -31,8 +32,9 @@ #include "net/base/cookie_monster.h" #include "views/window/window.h" +using browser_sync::BookmarkChangeProcessor; +using browser_sync::BookmarkModelAssociator; using browser_sync::ChangeProcessor; -using browser_sync::ModelAssociator; using browser_sync::SyncBackendHost; typedef GoogleServiceAuthError AuthError; @@ -49,17 +51,22 @@ ProfileSyncService::ProfileSyncService(Profile* profile) is_auth_in_progress_(false), ALLOW_THIS_IN_INITIALIZER_LIST(wizard_(this)), unrecoverable_error_detected_(false) { - change_processor_.reset(new ChangeProcessor(this)); + BookmarkChangeProcessor* processor = new BookmarkChangeProcessor(this); + change_processors_.insert(processor); + // TODO: Move this back to StartUp + BookmarkModelAssociator* associator = new BookmarkModelAssociator(this); + processor->set_model_associator(associator); } ProfileSyncService::~ProfileSyncService() { Shutdown(false); + STLDeleteElements(&change_processors_); } -void ProfileSyncService::set_model_associator( - browser_sync::ModelAssociator* associator) { - model_associator_ = associator; - change_processor_->set_model_associator(associator); +void ProfileSyncService::set_change_processor( + browser_sync::ChangeProcessor* change_processor) { + change_processors_.clear(); + change_processors_.insert(change_processor); } void ProfileSyncService::Initialize() { @@ -153,15 +160,11 @@ void ProfileSyncService::StartUp() { profile_->GetPrefs()->GetInt64(prefs::kSyncLastSyncedTime)); backend_.reset(new SyncBackendHost(this, profile_->GetPath(), - change_processor_.get())); + change_processors_)); registrar_.Add(this, NotificationType::BOOKMARK_MODEL_LOADED, Source<Profile>(profile_)); - // Create new model association manager and change processor. - model_associator_ = new ModelAssociator(this); - change_processor_->set_model_associator(model_associator_); - InitializeBackend(); } @@ -171,13 +174,16 @@ void ProfileSyncService::Shutdown(bool sync_disabled) { if (backend_.get()) backend_->Shutdown(sync_disabled); - change_processor_->Stop(); + for (std::set<ChangeProcessor*>::const_iterator it = + change_processors_.begin(); it != change_processors_.end(); ++it) { + (*it)->Stop(); + } backend_.reset(); // Clear all associations and throw away the association manager instance. - if (model_associator_.get()) { - model_associator_->ClearAll(); - model_associator_ = NULL; + for (std::set<ChangeProcessor*>::const_iterator it = + change_processors_.begin(); it != change_processors_.end(); ++it) { + (*it)->GetModelAssociator()->DisassociateModels(); } // Clear various flags. @@ -222,8 +228,13 @@ bool ProfileSyncService::MergeAndSyncAcceptanceNeeded() const { if (profile_->GetPrefs()->GetBoolean(prefs::kSyncHasSetupCompleted)) return false; - return model_associator_->BookmarkModelHasUserCreatedNodes() && - model_associator_->SyncModelHasUserCreatedNodes(); + for (std::set<ChangeProcessor*>::const_iterator it = + change_processors_.begin(); it != change_processors_.end(); ++it) { + if ((*it)->GetModelAssociator()->ChromeModelHasUserCreatedNodes() && + (*it)->GetModelAssociator()->SyncModelHasUserCreatedNodes()) + return true; + } + return false; } bool ProfileSyncService::HasSyncSetupCompleted() const { @@ -247,7 +258,10 @@ void ProfileSyncService::UpdateLastSyncedTime() { // to do as little work as possible, to avoid further corruption or crashes. void ProfileSyncService::OnUnrecoverableError() { unrecoverable_error_detected_ = true; - change_processor_->Stop(); + for (std::set<ChangeProcessor*>::const_iterator it = + change_processors_.begin(); it != change_processors_.end(); ++it) { + (*it)->Stop(); + } // Tell the wizard so it can inform the user only if it is already open. wizard_.Step(SyncSetupWizard::FATAL_ERROR); @@ -375,8 +389,17 @@ void ProfileSyncService::OnUserSubmittedAuth( void ProfileSyncService::OnUserAcceptedMergeAndSync() { base::TimeTicks start_time = base::TimeTicks::Now(); - bool not_first_run = model_associator_->SyncModelHasUserCreatedNodes(); - bool merge_success = model_associator_->AssociateModels(); + bool not_first_run = false; + bool merge_success = true; + for (std::set<ChangeProcessor*>::const_iterator it = + change_processors_.begin(); it != change_processors_.end(); ++it) { + not_first_run |= (*it)->GetModelAssociator()-> + SyncModelHasUserCreatedNodes(); + // TODO(sync): Figure out what do to when a single associator fails. + // http://crbug.com/30038 + merge_success &= (*it)->GetModelAssociator()-> + AssociateModels(); + } UMA_HISTOGRAM_MEDIUM_TIMES("Sync.UserPerceivedBookmarkAssociation", base::TimeTicks::Now() - start_time); if (!merge_success) { @@ -388,8 +411,10 @@ void ProfileSyncService::OnUserAcceptedMergeAndSync() { wizard_.Step(not_first_run ? SyncSetupWizard::DONE : SyncSetupWizard::DONE_FIRST_TIME); - change_processor_->Start(profile_->GetBookmarkModel(), - backend_->GetUserShareHandle()); + for (std::set<ChangeProcessor*>::const_iterator it = + change_processors_.begin(); it != change_processors_.end(); ++it) { + (*it)->Start(profile(), backend_->GetUserShareHandle()); + } FOR_EACH_OBSERVER(Observer, observers_, OnStateChanged()); } @@ -406,7 +431,10 @@ void ProfileSyncService::OnUserCancelledDialog() { void ProfileSyncService::StartProcessingChangesIfReady() { BookmarkModel* model = profile_->GetBookmarkModel(); - DCHECK(!change_processor_->IsRunning()); + for (std::set<ChangeProcessor*>::const_iterator it = + change_processors_.begin(); it != change_processors_.end(); ++it) { + DCHECK(!(*it)->IsRunning()); + } // First check if the subsystems are ready. We can't proceed until they // both have finished loading. @@ -424,8 +452,15 @@ void ProfileSyncService::StartProcessingChangesIfReady() { // We're ready to merge the models. base::TimeTicks start_time = base::TimeTicks::Now(); - bool not_first_run = model_associator_->SyncModelHasUserCreatedNodes(); - bool merge_success = model_associator_->AssociateModels(); + bool not_first_run = false; + bool merge_success = true; + for (std::set<ChangeProcessor*>::const_iterator it = + change_processors_.begin(); it != change_processors_.end(); ++it) { + not_first_run |= (*it)->GetModelAssociator()-> + SyncModelHasUserCreatedNodes(); + merge_success &= (*it)->GetModelAssociator()-> + AssociateModels(); + } UMA_HISTOGRAM_TIMES("Sync.BookmarkAssociationTime", base::TimeTicks::Now() - start_time); if (!merge_success) { @@ -437,8 +472,10 @@ void ProfileSyncService::StartProcessingChangesIfReady() { wizard_.Step(not_first_run ? SyncSetupWizard::DONE : SyncSetupWizard::DONE_FIRST_TIME); - change_processor_->Start(profile_->GetBookmarkModel(), - backend_->GetUserShareHandle()); + for (std::set<ChangeProcessor*>::const_iterator it = + change_processors_.begin(); it != change_processors_.end(); ++it) { + (*it)->Start(profile(), backend_->GetUserShareHandle()); + } FOR_EACH_OBSERVER(Observer, observers_, OnStateChanged()); } @@ -469,5 +506,9 @@ bool ProfileSyncService::ShouldPushChanges() { // True only after all bootstrapping has succeeded: the bookmark model is // loaded, the sync backend is initialized, the two domains are // consistent with one another, and no unrecoverable error has transpired. - return change_processor_->IsRunning(); + for (std::set<ChangeProcessor*>::const_iterator it = + change_processors_.begin(); it != change_processors_.end(); ++it) { + if (!(*it)->IsRunning()) return false; + } + return true; } diff --git a/chrome/browser/sync/profile_sync_service.h b/chrome/browser/sync/profile_sync_service.h index 59bc2e5..7a0dab08 100644 --- a/chrome/browser/sync/profile_sync_service.h +++ b/chrome/browser/sync/profile_sync_service.h @@ -28,7 +28,6 @@ class MessageLoop; class Profile; namespace browser_sync { -class ModelAssociator; class UnrecoverableErrorHandler { public: @@ -231,7 +230,7 @@ class ProfileSyncService : public NotificationObserver, virtual void InitializeBackend(); // Tests need this. - void set_model_associator(browser_sync::ModelAssociator* associator); + void set_change_processor(browser_sync::ChangeProcessor* change_processor); // We keep track of the last auth error observed so we can cover up the first // "expected" auth failure from observers. @@ -274,9 +273,6 @@ class ProfileSyncService : public NotificationObserver, // This specifies where to find the sync server. GURL sync_service_url_; - // Model association manager instance. - scoped_refptr<browser_sync::ModelAssociator> model_associator_; - // The last time we detected a successful transition from SYNCING state. // Our backend notifies us whenever we should take a new snapshot. base::Time last_synced_time_; @@ -285,7 +281,8 @@ class ProfileSyncService : public NotificationObserver, // other threads. scoped_ptr<browser_sync::SyncBackendHost> backend_; - scoped_ptr<browser_sync::ChangeProcessor> change_processor_; + // The change processors that handle the different data types. + std::set<browser_sync::ChangeProcessor*> change_processors_; NotificationRegistrar registrar_; diff --git a/chrome/browser/sync/profile_sync_service_unittest.cc b/chrome/browser/sync/profile_sync_service_unittest.cc index aa87866..ea575a9 100644 --- a/chrome/browser/sync/profile_sync_service_unittest.cc +++ b/chrome/browser/sync/profile_sync_service_unittest.cc @@ -15,7 +15,8 @@ #include "chrome/browser/chrome_thread.h" #include "chrome/browser/profile.h" #include "chrome/browser/sync/engine/syncapi.h" -#include "chrome/browser/sync/glue/model_associator.h" +#include "chrome/browser/sync/glue/bookmark_change_processor.h" +#include "chrome/browser/sync/glue/bookmark_model_associator.h" #include "chrome/browser/sync/glue/sync_backend_host.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/common/chrome_switches.h" @@ -23,15 +24,17 @@ #include "chrome/test/sync/test_http_bridge_factory.h" using std::vector; -using browser_sync::ChangeProcessingInterface; +using browser_sync::BookmarkChangeProcessor; +using browser_sync::BookmarkModelAssociator; +using browser_sync::ChangeProcessor; using browser_sync::ModelAssociator; using browser_sync::SyncBackendHost; using browser_sync::TestHttpBridgeFactory; -class TestModelAssociator : public ModelAssociator { +class TestModelAssociator : public BookmarkModelAssociator { public: explicit TestModelAssociator(ProfileSyncService* service) - : ModelAssociator(service) { + : BookmarkModelAssociator(service) { } virtual bool GetSyncIdForTaggedNode(const std::string& tag, int64* sync_id) { @@ -92,7 +95,10 @@ class TestProfileSyncService : public ProfileSyncService { } virtual void InitializeBackend() { - set_model_associator(new TestModelAssociator(this)); + change_processor_ = new BookmarkChangeProcessor(this); + model_associator_ = new TestModelAssociator(this); + change_processor_->set_model_associator(model_associator_); + set_change_processor(change_processor_); TestHttpBridgeFactory* factory = new TestHttpBridgeFactory(); TestHttpBridgeFactory* factory2 = new TestHttpBridgeFactory(); backend()->InitializeForTestMode(L"testuser", factory, factory2); @@ -112,6 +118,16 @@ class TestProfileSyncService : public ProfileSyncService { // Never show the dialog. return false; } + + BookmarkChangeProcessor* change_processor() { + return change_processor_; + } + BookmarkModelAssociator* model_associator() { + return model_associator_; + } + + BookmarkChangeProcessor* change_processor_; + BookmarkModelAssociator* model_associator_; }; // FakeServerChange constructs a list of sync_api::ChangeRecords while modifying @@ -228,7 +244,7 @@ class FakeServerChange { } // Pass the fake change list to |service|. - void ApplyPendingChanges(browser_sync::ChangeProcessingInterface* processor) { + void ApplyPendingChanges(browser_sync::ChangeProcessor* processor) { processor->ApplyChangesFromSyncModel(trans_, changes_.size() ? &changes_[0] : NULL, changes_.size()); } @@ -279,14 +295,14 @@ class ProfileSyncServiceTest : public testing::Test { MessageLoop::current()->RunAllPending(); } - ModelAssociator* associator() { + BookmarkModelAssociator* associator() { DCHECK(service_.get()); return service_->model_associator_; } - ChangeProcessingInterface* change_processor() { + BookmarkChangeProcessor* change_processor() { DCHECK(service_.get()); - return service_->change_processor_.get(); + return service_->change_processor_; } void StartSyncService() { @@ -468,7 +484,7 @@ class ProfileSyncServiceTest : public testing::Test { ChromeThread ui_thread_; ChromeThread file_thread_; - scoped_ptr<ProfileSyncService> service_; + scoped_ptr<TestProfileSyncService> service_; scoped_ptr<TestingProfile> profile_; BookmarkModel* model_; }; @@ -922,7 +938,7 @@ static TestData kF2Children[] = { { L"f2u1", "http://www.f2u1.com/" }, }; -static TestData kOtherBookmarksChildren[] = { +static TestData kOtherBookmarkChildren[] = { { L"f3", NULL }, { L"u4", "http://www.u4.com/" }, { L"u3", "http://www.u3.com/" }, @@ -1002,8 +1018,8 @@ void ProfileSyncServiceTestWithData::WriteTestDataToBookmarkModel() { const BookmarkNode* other_bookmarks_node = model_->other_node(); PopulateFromTestData(other_bookmarks_node, - kOtherBookmarksChildren, - arraysize(kOtherBookmarksChildren)); + kOtherBookmarkChildren, + arraysize(kOtherBookmarkChildren)); ASSERT_GE(other_bookmarks_node->GetChildCount(), 6); const BookmarkNode* f3_node = other_bookmarks_node->GetChild(0); @@ -1032,8 +1048,8 @@ void ProfileSyncServiceTestWithData::ExpectBookmarkModelMatchesTestData() { const BookmarkNode* other_bookmarks_node = model_->other_node(); CompareWithTestData(other_bookmarks_node, - kOtherBookmarksChildren, - arraysize(kOtherBookmarksChildren)); + kOtherBookmarkChildren, + arraysize(kOtherBookmarkChildren)); ASSERT_GE(other_bookmarks_node->GetChildCount(), 6); const BookmarkNode* f3_node = other_bookmarks_node->GetChild(0); |