summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authortim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-18 22:36:05 +0000
committertim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-18 22:36:05 +0000
commitdb1df0f4ac4c9897992e3674574d229845f94b25 (patch)
tree695cad509b25be65e50b913232ce5367fac72482 /chrome
parent5f3e6942ba82da6dfe02fe6f35d2919995bfcb81 (diff)
downloadchromium_src-db1df0f4ac4c9897992e3674574d229845f94b25.zip
chromium_src-db1df0f4ac4c9897992e3674574d229845f94b25.tar.gz
chromium_src-db1df0f4ac4c9897992e3674574d229845f94b25.tar.bz2
Move model operations from ProfileSyncService to ChangeProcessor.
TEST=ProfileSyncServiceTest, sync integration tests Review URL: http://codereview.chromium.org/172040 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@23669 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/sync/glue/change_processor.cc541
-rw-r--r--chrome/browser/sync/glue/change_processor.h188
-rw-r--r--chrome/browser/sync/glue/model_associator.cc17
-rw-r--r--chrome/browser/sync/glue/model_associator.h2
-rw-r--r--chrome/browser/sync/glue/sync_backend_host.cc7
-rw-r--r--chrome/browser/sync/glue/sync_backend_host.h88
-rw-r--r--chrome/browser/sync/profile_sync_service.cc553
-rw-r--r--chrome/browser/sync/profile_sync_service.h151
-rw-r--r--chrome/browser/sync/profile_sync_service_unittest.cc27
-rw-r--r--chrome/chrome.gyp2
-rw-r--r--chrome/test/live_sync/profile_sync_service_test_harness.cc2
11 files changed, 885 insertions, 693 deletions
diff --git a/chrome/browser/sync/glue/change_processor.cc b/chrome/browser/sync/glue/change_processor.cc
new file mode 100644
index 0000000..09ddb96
--- /dev/null
+++ b/chrome/browser/sync/glue/change_processor.cc
@@ -0,0 +1,541 @@
+// Copyright (c) 2006-2008 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.
+
+#ifdef CHROME_PERSONALIZATION
+
+#include "chrome/browser/sync/glue/change_processor.h"
+
+#include "base/gfx/png_encoder.h"
+#include "base/string_util.h"
+#include "chrome/browser/bookmarks/bookmark_utils.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);
+ running_ = true;
+}
+
+void ChangeProcessor::Stop() {
+ if (!running_)
+ return;
+ DCHECK(bookmark_model_);
+ bookmark_model_->RemoveObserver(this);
+ bookmark_model_ = NULL;
+ 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(WideToUTF16(src->GetTitle()).c_str());
+ // URL is passed as a C string here because this interface avoids
+ // string16. SetURL copies the data into its own memory.
+ string16 url = UTF8ToUTF16(src->GetURL().spec());
+ dst->SetURL(url.c_str());
+ 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 (!PNGEncoder::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_->DisassociateIds(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->AssociateIds(child->id(), 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.
+ DCHECK_NE(node, model->GetBookmarkBarNode());
+ DCHECK_NE(node, model->other_node());
+
+ // 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.
+ DCHECK_NE(child, model->GetBookmarkBarNode());
+ DCHECK_NE(child, model->other_node());
+
+ // 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->Remove(parent, parent->IndexOfChild(dst));
+ dst = NULL;
+ model_associator_->DisassociateIds(changes[i].id);
+ } 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_->AssociateIds(dst->id(), 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, dst,
+ UTF16ToWide(src->GetTitle()),
+ src->GetIsFolder() ? GURL() : 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_->DisassociateIds(src->GetId());
+ model_associator_->AssociateIds(dst->id(), 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, UTF16ToWide(sync_node->GetTitle()));
+ } else {
+ GURL url(sync_node->GetURL());
+ node = model->AddURL(parent, index,
+ UTF16ToWide(sync_node->GetTitle()), url);
+ 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);
+
+ history->AddPage(bookmark_node->GetURL());
+ history->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
+
+#endif // CHROME_PERSONALIZATION
diff --git a/chrome/browser/sync/glue/change_processor.h b/chrome/browser/sync/glue/change_processor.h
new file mode 100644
index 0000000..e9b15d4
--- /dev/null
+++ b/chrome/browser/sync/glue/change_processor.h
@@ -0,0 +1,188 @@
+// Copyright (c) 2006-2008 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.
+
+#ifdef CHROME_PERSONALIZATION
+
+#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;
+
+namespace browser_sync {
+
+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 {
+ public:
+ ChangeProcessor(UnrecoverableErrorHandler* error_handler)
+ : error_handler_(error_handler), model_associator_(NULL),
+ share_handle_(NULL), running_(false), bookmark_model_(NULL) {}
+ virtual ~ChangeProcessor() { Stop(); }
+
+ // Call when the processor should accept changes from either provided model
+ // and apply them to the other. Both the BookmarkModel 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.
+ 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 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);
+
+ 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);
+
+ 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_;
+ 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);
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_GLUE_CHANGE_APPLICATOR_H_
+
+#endif // CHROME_PERSONALIZATION
diff --git a/chrome/browser/sync/glue/model_associator.cc b/chrome/browser/sync/glue/model_associator.cc
index d4c4c45..47597ff 100644
--- a/chrome/browser/sync/glue/model_associator.cc
+++ b/chrome/browser/sync/glue/model_associator.cc
@@ -353,13 +353,15 @@ bool ModelAssociator::BuildAssocations() {
if (child_node) {
model->Move(child_node, parent_node, index);
// Set the favicon for bookmark node from sync node or vice versa.
- if (!sync_service_->SetBookmarkFavicon(&sync_child_node, child_node))
- sync_service_->SetSyncNodeFavicon(child_node, &sync_child_node);
+ if (ChangeProcessor::SetBookmarkFavicon(&sync_child_node,
+ child_node, sync_service_->profile())) {
+ ChangeProcessor::SetSyncNodeFavicon(child_node, model,
+ &sync_child_node);
+ }
} else {
// Create a new bookmark node for the sync node.
- child_node = sync_service_->CreateBookmarkNode(&sync_child_node,
- parent_node,
- index);
+ child_node = ChangeProcessor::CreateBookmarkNode(&sync_child_node,
+ parent_node, model, index);
}
AssociateIds(child_node->id(), sync_child_id);
if (sync_child_node.GetIsFolder())
@@ -375,7 +377,8 @@ bool ModelAssociator::BuildAssocations() {
// 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 = sync_service_->CreateSyncNode(parent_node, i, &trans);
+ sync_child_id = ChangeProcessor::CreateSyncNode(parent_node, model, i,
+ &trans, this, sync_service_);
if (parent_node->GetChild(i)->is_folder())
dfs_stack.push(sync_child_id);
}
@@ -415,7 +418,7 @@ void ModelAssociator::PersistAssociations() {
int64 sync_id = *iter;
sync_api::WriteNode sync_node(&trans);
if (!sync_node.InitByIdLookup(sync_id)) {
- sync_service_->SetUnrecoverableError();
+ sync_service_->OnUnrecoverableError();
return;
}
int64 node_id;
diff --git a/chrome/browser/sync/glue/model_associator.h b/chrome/browser/sync/glue/model_associator.h
index 9d5f825..a3b6e766e 100644
--- a/chrome/browser/sync/glue/model_associator.h
+++ b/chrome/browser/sync/glue/model_associator.h
@@ -28,6 +28,8 @@ class ProfileSyncService;
namespace browser_sync {
+class ChangeProcessor;
+
// Contains all model assocation related logic:
// * Algorithm to associate bookmark model and sync model.
// * Methods to get a bookmark node for a given sync node and vice versa.
diff --git a/chrome/browser/sync/glue/sync_backend_host.cc b/chrome/browser/sync/glue/sync_backend_host.cc
index ee75a79..14c8588 100644
--- a/chrome/browser/sync/glue/sync_backend_host.cc
+++ b/chrome/browser/sync/glue/sync_backend_host.cc
@@ -7,6 +7,7 @@
#include "base/file_version_info.h"
#include "base/file_util.h"
#include "base/string_util.h"
+#include "chrome/browser/sync/glue/change_processor.h"
#include "chrome/browser/sync/glue/sync_backend_host.h"
#include "chrome/browser/sync/glue/http_bridge.h"
#include "chrome/browser/sync/glue/bookmark_model_worker.h"
@@ -23,11 +24,13 @@ static const FilePath::CharType kSyncDataFolderName[] =
namespace browser_sync {
SyncBackendHost::SyncBackendHost(SyncFrontend* frontend,
- const FilePath& profile_path)
+ const FilePath& profile_path,
+ ChangeProcessingInterface* processor)
: core_thread_("Chrome_SyncCoreThread"),
frontend_loop_(MessageLoop::current()),
bookmark_model_worker_(NULL),
frontend_(frontend),
+ processor_(processor),
sync_data_folder_path_(profile_path.Append(kSyncDataFolderName)),
last_auth_error_(AUTH_ERROR_NONE) {
core_ = new Core(this);
@@ -250,7 +253,7 @@ void SyncBackendHost::Core::OnChangesApplied(
DLOG(WARNING) << "Could not update bookmark model from non-UI thread";
return;
}
- host_->frontend_->ApplyModelChanges(trans, changes, change_count);
+ host_->processor_->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 eb89e43..a15e1a7 100644
--- a/chrome/browser/sync/glue/sync_backend_host.h
+++ b/chrome/browser/sync/glue/sync_backend_host.h
@@ -22,7 +22,48 @@
namespace browser_sync {
-class SyncFrontend;
+// SyncFrontend is the interface used by SyncBackendHost to communicate with
+// the entity that created it and, presumably, is interested in sync-related
+// activity.
+// NOTE: All methods will be invoked by a SyncBackendHost on the same thread
+// used to create that SyncBackendHost.
+class SyncFrontend {
+ public:
+ SyncFrontend() {}
+
+ // The backend has completed initialization and it is now ready to accept and
+ // process changes.
+ virtual void OnBackendInitialized() = 0;
+
+ // The backend queried the server recently and received some updates.
+ virtual void OnSyncCycleCompleted() = 0;
+
+ // The backend encountered an authentication problem and requests new
+ // credentials to be provided. See SyncBackendHost::Authenticate for details.
+ virtual void OnAuthError() = 0;
+
+ protected:
+ // Don't delete through SyncFrontend interface.
+ virtual ~SyncFrontend() {
+ }
+ private:
+ 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
@@ -36,8 +77,10 @@ class SyncBackendHost {
// Create a SyncBackendHost with a reference to the |frontend| that it serves
// and communicates to via the SyncFrontend interface (on the same thread
- // it used to call the constructor).
- SyncBackendHost(SyncFrontend* frontend, const FilePath& proifle_path);
+ // it used to call the constructor), and push changes from sync_api through
+ // |processor|.
+ SyncBackendHost(SyncFrontend* frontend, const FilePath& profile_path,
+ ChangeProcessingInterface* processor);
~SyncBackendHost();
// Called on |frontend_loop_| to kick off asynchronous initialization.
@@ -222,6 +265,8 @@ class SyncBackendHost {
// The frontend which we serve (and are owned by).
SyncFrontend* frontend_;
+ ChangeProcessingInterface* processor_; // Guaranteed to outlive us.
+
// Path of the folder that stores the sync data files.
FilePath sync_data_folder_path_;
@@ -231,43 +276,6 @@ class SyncBackendHost {
DISALLOW_COPY_AND_ASSIGN(SyncBackendHost);
};
-// SyncFrontend is the interface used by SyncBackendHost to communicate with
-// the entity that created it and, presumably, is interested in sync-related
-// activity.
-// NOTE: All methods will be invoked by a SyncBackendHost on the same thread
-// used to create that SyncBackendHost.
-class SyncFrontend {
- public:
- typedef sync_api::BaseTransaction BaseTransaction;
- typedef sync_api::SyncManager::ChangeRecord ChangeRecord;
- SyncFrontend() {
- }
-
- // The backend has completed initialization and it is now ready to accept and
- // process changes.
- virtual void OnBackendInitialized() = 0;
-
- // The backend queried the server recently and received some updates.
- virtual void OnSyncCycleCompleted() = 0;
-
- // The backend encountered an authentication problem and requests new
- // credentials to be provided. See SyncBackendHost::Authenticate for details.
- virtual void OnAuthError() = 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 ApplyModelChanges(const BaseTransaction* trans,
- const ChangeRecord* changes,
- int change_count) = 0;
- protected:
- // Don't delete through SyncFrontend interface.
- virtual ~SyncFrontend() {
- }
- private:
- DISALLOW_COPY_AND_ASSIGN(SyncFrontend);
-};
-
} // namespace browser_sync
#endif // CHROME_BROWSER_SYNC_GLUE_SYNC_BACKEND_HOST_H_
diff --git a/chrome/browser/sync/profile_sync_service.cc b/chrome/browser/sync/profile_sync_service.cc
index 4de8f38..a426e53 100644
--- a/chrome/browser/sync/profile_sync_service.cc
+++ b/chrome/browser/sync/profile_sync_service.cc
@@ -25,11 +25,14 @@
#include "chrome/browser/sync/personalization.h"
#include "chrome/browser/sync/personalization_strings.h"
#include "chrome/common/chrome_switches.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/notification_type.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/pref_service.h"
#include "chrome/common/time_format.h"
#include "views/window/window.h"
+using browser_sync::ChangeProcessor;
using browser_sync::ModelAssociator;
using browser_sync::SyncBackendHost;
@@ -43,15 +46,21 @@ ProfileSyncService::ProfileSyncService(Profile* profile)
backend_initialized_(false),
expecting_first_run_auth_needed_event_(false),
is_auth_in_progress_(false),
- ready_to_process_changes_(false),
unrecoverable_error_detected_(false),
ALLOW_THIS_IN_INITIALIZER_LIST(wizard_(this)) {
+ change_processor_.reset(new ChangeProcessor(this));
}
ProfileSyncService::~ProfileSyncService() {
Shutdown(false);
}
+void ProfileSyncService::set_model_associator(
+ browser_sync::ModelAssociator* associator) {
+ model_associator_ = associator;
+ change_processor_->set_model_associator(associator);
+}
+
void ProfileSyncService::Initialize() {
InitSettings();
RegisterPreferences();
@@ -108,15 +117,15 @@ void ProfileSyncService::StartUp() {
last_synced_time_ = base::Time::FromInternalValue(
profile_->GetPrefs()->GetInt64(prefs::kSyncLastSyncedTime));
- backend_.reset(new SyncBackendHost(this, profile_->GetPath()));
+ backend_.reset(new SyncBackendHost(this, profile_->GetPath(),
+ change_processor_.get()));
- // We add ourselves as an observer, and we remain one forever. Note we don't
- // keep any pointer to the model, we just receive notifications from it.
- BookmarkModel* model = profile_->GetBookmarkModel();
- model->AddObserver(this);
+ registrar_.Add(this, NotificationType::BOOKMARK_MODEL_LOADED,
+ Source<Profile>(profile_));
- // Create new model assocation manager.
+ // Create new model assocation manager and change processor.
model_associator_ = new ModelAssociator(this);
+ change_processor_->set_model_associator(model_associator_);
// TODO(timsteele): HttpBridgeFactory should take a const* to the profile's
// URLRequestContext, because it needs it to create HttpBridge objects, and
@@ -127,16 +136,15 @@ void ProfileSyncService::StartUp() {
}
void ProfileSyncService::Shutdown(bool sync_disabled) {
- if (backend_.get()) {
+ registrar_.RemoveAll();
+
+ if (backend_.get())
backend_->Shutdown(sync_disabled);
- backend_.reset();
- }
- BookmarkModel* model = profile_->GetBookmarkModel();
- if (model)
- model->RemoveObserver(this);
+ change_processor_->Stop();
+ backend_.reset();
- // Clear all assocations and throw away the assocation manager instance.
+ // Clear all associations and throw away the association manager instance.
if (model_associator_.get()) {
model_associator_->ClearAll();
model_associator_ = NULL;
@@ -146,7 +154,6 @@ void ProfileSyncService::Shutdown(bool sync_disabled) {
is_auth_in_progress_ = false;
backend_initialized_ = false;
expecting_first_run_auth_needed_event_ = false;
- ready_to_process_changes_ = false;
last_attempted_user_email_.clear();
}
@@ -172,90 +179,14 @@ void ProfileSyncService::DisableForUser() {
FOR_EACH_OBSERVER(Observer, observers_, OnStateChanged());
}
-void ProfileSyncService::Loaded(BookmarkModel* model) {
+void ProfileSyncService::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK_EQ(NotificationType::BOOKMARK_MODEL_LOADED, type.value);
+ registrar_.RemoveAll();
StartProcessingChangesIfReady();
}
-void ProfileSyncService::UpdateSyncNodeProperties(const BookmarkNode* src,
- sync_api::WriteNode* dst) {
- // Set the properties of the item.
- dst->SetIsFolder(src->is_folder());
- dst->SetTitle(WideToUTF16(src->GetTitle()).c_str());
- // URL is passed as a C string here because this interface avoids
- // string16. SetURL copies the data into its own memory.
- string16 url = UTF8ToUTF16(src->GetURL().spec());
- dst->SetURL(url.c_str());
- SetSyncNodeFavicon(src, dst);
-}
-
-void ProfileSyncService::EncodeFavicon(const BookmarkNode* src,
- std::vector<unsigned char>* dst) const {
- const SkBitmap& favicon = profile_->GetBookmarkModel()->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 (!PNGEncoder::EncodeBGRASkBitmap(favicon, false, dst))
- return;
-}
-
-void ProfileSyncService::RemoveOneSyncNode(sync_api::WriteTransaction* trans,
- const BookmarkNode* node) {
- sync_api::WriteNode sync_node(trans);
- if (!model_associator_->InitSyncNodeFromBookmarkId(node->id(), &sync_node)) {
- SetUnrecoverableError();
- return;
- }
- // This node should have no children.
- DCHECK(sync_node.GetFirstChildId() == sync_api::kInvalidId);
- // Remove association and delete the sync node.
- model_associator_->DisassociateIds(sync_node.GetId());
- sync_node.Remove();
-}
-
-void ProfileSyncService::RemoveSyncNodeHierarchy(const BookmarkNode* topmost) {
- sync_api::WriteTransaction trans(backend_->GetUserShareHandle());
-
- // 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.
-}
-
bool ProfileSyncService::MergeAndSyncAcceptanceNeeded() const {
// If we've shown the dialog before, don't show it again.
if (profile_->GetPrefs()->GetBoolean(prefs::kSyncHasSetupCompleted))
@@ -282,222 +213,14 @@ void ProfileSyncService::UpdateLastSyncedTime() {
profile_->GetPrefs()->ScheduleSavePersistentPrefs();
}
-void ProfileSyncService::BookmarkNodeAdded(BookmarkModel* model,
- const BookmarkNode* parent,
- int index) {
- if (!ShouldPushChanges())
- return;
-
- DCHECK(backend_->GetUserShareHandle());
-
- // Acquire a scoped write lock via a transaction.
- sync_api::WriteTransaction trans(backend_->GetUserShareHandle());
-
- CreateSyncNode(parent, index, &trans);
-}
-
-int64 ProfileSyncService::CreateSyncNode(const BookmarkNode* parent,
- int index,
- sync_api::WriteTransaction* trans) {
- 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)) {
- LOG(WARNING) << "Sync node creation failed; recovery unlikely";
- SetUnrecoverableError();
- return sync_api::kInvalidId;
- }
-
- UpdateSyncNodeProperties(child, &sync_child);
-
- // Associate the ID from the sync domain with the bookmark node, so that we
- // can refer back to this item later.
- model_associator_->AssociateIds(child->id(), sync_child.GetId());
-
- return sync_child.GetId();
-}
-
-void ProfileSyncService::BookmarkNodeRemoved(BookmarkModel* model,
- const BookmarkNode* parent,
- int index,
- const BookmarkNode* node) {
- if (!ShouldPushChanges())
- return;
-
- RemoveSyncNodeHierarchy(node);
-}
-
-void ProfileSyncService::BookmarkNodeChanged(BookmarkModel* model,
- const BookmarkNode* node) {
- if (!ShouldPushChanges())
- return;
-
- // We shouldn't see changes to the top-level nodes.
- DCHECK_NE(node, model->GetBookmarkBarNode());
- DCHECK_NE(node, model->other_node());
-
- // Acquire a scoped write lock via a transaction.
- sync_api::WriteTransaction trans(backend_->GetUserShareHandle());
-
- // Lookup the sync node that's associated with |node|.
- sync_api::WriteNode sync_node(&trans);
- if (!model_associator_->InitSyncNodeFromBookmarkId(node->id(), &sync_node)) {
- SetUnrecoverableError();
- return;
- }
-
- UpdateSyncNodeProperties(node, &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 ProfileSyncService::BookmarkNodeMoved(BookmarkModel* model,
- const BookmarkNode* old_parent,
- int old_index,
- const BookmarkNode* new_parent,
- int new_index) {
- if (!ShouldPushChanges())
- return;
-
- const BookmarkNode* child = new_parent->GetChild(new_index);
- // We shouldn't see changes to the top-level nodes.
- DCHECK_NE(child, model->GetBookmarkBarNode());
- DCHECK_NE(child, model->other_node());
-
- // Acquire a scoped write lock via a transaction.
- sync_api::WriteTransaction trans(backend_->GetUserShareHandle());
-
- // Lookup the sync node that's associated with |child|.
- sync_api::WriteNode sync_node(&trans);
- if (!model_associator_->InitSyncNodeFromBookmarkId(child->id(),
- &sync_node)) {
- SetUnrecoverableError();
- return;
- }
-
- if (!PlaceSyncNode(MOVE, new_parent, new_index, &trans, &sync_node)) {
- SetUnrecoverableError();
- return;
- }
-}
-
-void ProfileSyncService::BookmarkNodeFavIconLoaded(BookmarkModel* model,
- const BookmarkNode* node) {
- BookmarkNodeChanged(model, node);
-}
-
-void ProfileSyncService::BookmarkNodeChildrenReordered(
- BookmarkModel* model, const BookmarkNode* node) {
- if (!ShouldPushChanges())
- return;
-
- // Acquire a scoped write lock via a transaction.
- sync_api::WriteTransaction trans(backend_->GetUserShareHandle());
-
- // 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)) {
- SetUnrecoverableError();
- return;
- }
- DCHECK_EQ(sync_child.GetParentId(),
- model_associator_->GetSyncIdFromBookmarkId(node->id()));
-
- if (!PlaceSyncNode(MOVE, node, i, &trans, &sync_child)) {
- SetUnrecoverableError();
- return;
- }
- }
-}
-
-bool ProfileSyncService::PlaceSyncNode(MoveOrCreate operation,
- const BookmarkNode* parent,
- int index,
- sync_api::WriteTransaction* trans,
- sync_api::WriteNode* dst) {
- sync_api::ReadNode sync_parent(trans);
- if (!model_associator_->InitSyncNodeFromBookmarkId(parent->id(),
- &sync_parent)) {
- LOG(WARNING) << "Parent lookup failed";
- SetUnrecoverableError();
- 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 (!model_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;
-}
-
// An invariant has been violated. Transition to an error state where we try
// to do as little work as possible, to avoid further corruption or crashes.
-void ProfileSyncService::SetUnrecoverableError() {
+void ProfileSyncService::OnUnrecoverableError() {
unrecoverable_error_detected_ = true;
+ change_processor_->Stop();
LOG(ERROR) << "Unrecoverable error detected -- ProfileSyncService unusable.";
}
-// 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 ProfileSyncService::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;
-}
-
void ProfileSyncService::OnBackendInitialized() {
backend_initialized_ = true;
StartProcessingChangesIfReady();
@@ -553,206 +276,6 @@ void ProfileSyncService::ShowLoginDialog() {
wizard_.Step(SyncSetupWizard::GAIA_LOGIN);
}
-// 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 ProfileSyncService::ApplyModelChanges(
- const sync_api::BaseTransaction* trans,
- const sync_api::SyncManager::ChangeRecord* changes,
- int change_count) {
- if (!ShouldPushChanges())
- 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 = profile_->GetBookmarkModel();
-
- // 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->Remove(parent, parent->IndexOfChild(dst));
- dst = NULL;
- model_associator_->DisassociateIds(changes[i].id);
- } 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";
- SetUnrecoverableError();
- 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* ProfileSyncService::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, index);
- model_associator_->AssociateIds(dst->id(), 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, dst,
- UTF16ToWide(src->GetTitle()),
- src->GetIsFolder() ? GURL() : 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_->DisassociateIds(src->GetId());
- model_associator_->AssociateIds(dst->id(), src->GetId());
- }
- SetBookmarkFavicon(src, dst);
- }
-
- return dst;
-}
-
-// Creates a bookmark node under the given parent node from the given sync
-// node. Returns the newly created node.
-const BookmarkNode* ProfileSyncService::CreateBookmarkNode(
- sync_api::BaseNode* sync_node,
- const BookmarkNode* parent,
- int index) const {
- DCHECK(parent);
- DCHECK(index >= 0 && index <= parent->GetChildCount());
- BookmarkModel* model = profile_->GetBookmarkModel();
-
- const BookmarkNode* node;
- if (sync_node->GetIsFolder()) {
- node = model->AddGroup(parent, index, UTF16ToWide(sync_node->GetTitle()));
- } else {
- GURL url(sync_node->GetURL());
- node = model->AddURL(parent, index,
- UTF16ToWide(sync_node->GetTitle()), url);
- SetBookmarkFavicon(sync_node, node);
- }
- return node;
-}
-
-// Sets the favicon of the given bookmark node from the given sync node.
-bool ProfileSyncService::SetBookmarkFavicon(
- sync_api::BaseNode* sync_node,
- const BookmarkNode* bookmark_node) const {
- 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);
-
- history->AddPage(bookmark_node->GetURL());
- history->SetFavIcon(bookmark_node->GetURL(),
- fake_icon_url,
- icon_bytes_vector);
-
- return true;
-}
-
-void ProfileSyncService::SetSyncNodeFavicon(
- const BookmarkNode* bookmark_node,
- sync_api::WriteNode* sync_node) const {
- std::vector<unsigned char> favicon_bytes;
- EncodeFavicon(bookmark_node, &favicon_bytes);
- if (!favicon_bytes.empty())
- sync_node->SetFaviconBytes(&favicon_bytes[0], favicon_bytes.size());
-}
-
SyncBackendHost::StatusSummary ProfileSyncService::QuerySyncStatusSummary() {
return backend_->GetStatusSummary();
}
@@ -821,11 +344,12 @@ void ProfileSyncService::OnUserAcceptedMergeAndSync() {
wizard_.Step(SyncSetupWizard::DONE); // TODO(timsteele): error state?
if (!merge_success) {
LOG(ERROR) << "Model assocation failed.";
- SetUnrecoverableError();
+ OnUnrecoverableError();
return;
}
- ready_to_process_changes_ = true;
+ change_processor_->Start(profile_->GetBookmarkModel(),
+ backend_->GetUserShareHandle());
FOR_EACH_OBSERVER(Observer, observers_, OnStateChanged());
}
@@ -842,7 +366,7 @@ void ProfileSyncService::OnUserCancelledDialog() {
void ProfileSyncService::StartProcessingChangesIfReady() {
BookmarkModel* model = profile_->GetBookmarkModel();
- DCHECK(!ready_to_process_changes_);
+ DCHECK(!change_processor_->IsRunning());
// First check if the subsystems are ready. We can't proceed until they
// both have finished loading.
@@ -867,11 +391,12 @@ void ProfileSyncService::StartProcessingChangesIfReady() {
wizard_.Step(SyncSetupWizard::DONE); // TODO(timsteele): error state?
if (!merge_success) {
LOG(ERROR) << "Model assocation failed.";
- SetUnrecoverableError();
+ OnUnrecoverableError();
return;
}
- ready_to_process_changes_ = true;
+ change_processor_->Start(profile_->GetBookmarkModel(),
+ backend_->GetUserShareHandle());
FOR_EACH_OBSERVER(Observer, observers_, OnStateChanged());
}
@@ -892,8 +417,10 @@ void ProfileSyncService::SyncEvent(SyncEventCodes code) {
}
bool ProfileSyncService::ShouldPushChanges() {
- return ready_to_process_changes_ && // Wait for model load and merge.
- !unrecoverable_error_detected_; // Halt after any terrible events.
+ // 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();
}
#endif // CHROME_PERSONALIZATION
diff --git a/chrome/browser/sync/profile_sync_service.h b/chrome/browser/sync/profile_sync_service.h
index a2f956b..ae2047e 100644
--- a/chrome/browser/sync/profile_sync_service.h
+++ b/chrome/browser/sync/profile_sync_service.h
@@ -17,9 +17,11 @@
#include "base/scoped_ptr.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
#include "chrome/browser/profile.h"
+#include "chrome/browser/sync/glue/change_processor.h"
#include "chrome/browser/sync/glue/model_associator.h"
#include "chrome/browser/sync/glue/sync_backend_host.h"
#include "chrome/browser/views/sync/sync_setup_wizard.h"
+#include "chrome/common/notification_registrar.h"
#include "googleurl/src/gurl.h"
class CommandLine;
@@ -28,6 +30,18 @@ class Profile;
namespace browser_sync {
class ModelAssociator;
+
+class UnrecoverableErrorHandler {
+ public:
+ // Call this when normal operation detects that the bookmark model and the
+ // syncer model are inconsistent, or similar. The ProfileSyncService will
+ // try to avoid doing any work to avoid crashing or corrupting things
+ // further, and will report an error status if queried.
+ virtual void OnUnrecoverableError() = 0;
+ protected:
+ virtual ~UnrecoverableErrorHandler() { }
+};
+
}
// Various UI components such as the New Tab page can be driven by observing
@@ -47,8 +61,9 @@ class ProfileSyncServiceObserver {
// ProfileSyncService is the layer between browser subsystems like bookmarks,
// and the sync backend.
-class ProfileSyncService : public BookmarkModelObserver,
- public browser_sync::SyncFrontend {
+class ProfileSyncService : public NotificationObserver,
+ public browser_sync::SyncFrontend,
+ public browser_sync::UnrecoverableErrorHandler {
public:
typedef ProfileSyncServiceObserver Observer;
typedef browser_sync::SyncBackendHost::Status Status;
@@ -91,36 +106,15 @@ class ProfileSyncService : public BookmarkModelObserver,
bool HasSyncSetupCompleted() const;
void SetSyncSetupCompleted();
- // BookmarkModelObserver implementation.
- virtual void Loaded(BookmarkModel* model);
- 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);
+ // NotificationObserver implementation.
+ virtual void ProfileSyncService::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
// SyncFrontend implementation.
virtual void OnBackendInitialized();
virtual void OnSyncCycleCompleted();
virtual void OnAuthError();
- virtual void ApplyModelChanges(
- const sync_api::BaseTransaction* trans,
- const sync_api::SyncManager::ChangeRecord* changes,
- int change_count);
// Called when a user enters credentials through UI.
virtual void OnUserSubmittedAuth(const std::string& username,
@@ -196,6 +190,11 @@ class ProfileSyncService : public BookmarkModelObserver,
// Record stats on various events.
static void SyncEvent(SyncEventCodes code);
+ // UnrecoverableErrorHandler implementation.
+ virtual void OnUnrecoverableError();
+
+ browser_sync::SyncBackendHost* backend() { return backend_.get(); }
+
protected:
// Call this after any of the subsystems being synced (the bookmark
// model and the sync backend) finishes its initialization. When everything
@@ -203,15 +202,6 @@ class ProfileSyncService : public BookmarkModelObserver,
// initially in sync, and start forwarding changes between the two models.
void StartProcessingChangesIfReady();
- // Various member accessors needed by unit tests.
- browser_sync::SyncBackendHost* backend() { return backend_.get(); }
-
- // Call this when normal operation detects that the bookmark model and the
- // syncer model are inconsistent, or similar. The ProfileSyncService will
- // try to avoid doing any work to avoid crashing or corrupting things
- // further, and will report an error status if queried.
- void SetUnrecoverableError();
-
// Returns whether processing changes is allowed. Check this before doing
// any model-modifying operations.
bool ShouldPushChanges();
@@ -230,9 +220,7 @@ class ProfileSyncService : public BookmarkModelObserver,
virtual void InitializeBackend();
// Tests need this.
- void set_model_associator(browser_sync::ModelAssociator* manager) {
- model_associator_ = manager;
- }
+ void set_model_associator(browser_sync::ModelAssociator* associator);
// We keep track of the last auth error observed so we can cover up the first
// "expected" auth failure from observers.
@@ -244,90 +232,14 @@ class ProfileSyncService : public BookmarkModelObserver,
std::string last_attempted_user_email_;
private:
- friend class browser_sync::ModelAssociator;
friend class ProfileSyncServiceTest;
friend class ProfileSyncServiceTestHarness;
friend class TestModelAssociator;
FRIEND_TEST(ProfileSyncServiceTest, UnrecoverableErrorSuspendsService);
- enum MoveOrCreate {
- MOVE,
- CREATE,
- };
-
// Initializes the various settings from the command line.
void InitSettings();
- // 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.
- int64 CreateSyncNode(const BookmarkNode* parent,
- int index,
- sync_api::WriteTransaction* trans);
-
- // 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);
-
- // Creates a bookmark node under the given parent node from the given sync
- // node. Returns the newly created node.
- const BookmarkNode* CreateBookmarkNode(
- sync_api::BaseNode* sync_node,
- const BookmarkNode* parent,
- int index) const;
-
- // Sets the favicon of the given bookmark node from the given sync node.
- // Returns whether the favicon was set in the bookmark node.
- bool SetBookmarkFavicon(sync_api::BaseNode* sync_node,
- const BookmarkNode* bookmark_node) const;
-
- // Sets the favicon of the given sync node from the given bookmark node.
- void SetSyncNodeFavicon(const BookmarkNode* bookmark_node,
- sync_api::WriteNode* sync_node) const;
-
- // 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.
- bool PlaceSyncNode(MoveOrCreate operation,
- const BookmarkNode* parent,
- int index,
- sync_api::WriteTransaction* trans,
- sync_api::WriteNode* dst);
-
- // Copy properties (but not position) from |src| to |dst|.
- void UpdateSyncNodeProperties(const BookmarkNode* src,
- sync_api::WriteNode* dst);
-
- // Helper function to encode a bookmark's favicon into a PNG byte vector.
- void EncodeFavicon(const BookmarkNode* src,
- std::vector<unsigned char>* dst) const;
-
- // 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);
-
// Whether the sync merge warning should be shown.
bool MergeAndSyncAcceptanceNeeded() const;
@@ -355,6 +267,10 @@ class ProfileSyncService : public BookmarkModelObserver,
// other threads.
scoped_ptr<browser_sync::SyncBackendHost> backend_;
+ scoped_ptr<browser_sync::ChangeProcessor> change_processor_;
+
+ NotificationRegistrar registrar_;
+
// Whether the SyncBackendHost has been initialized.
bool backend_initialized_;
@@ -375,11 +291,6 @@ class ProfileSyncService : public BookmarkModelObserver,
// As its name suggests, this should NOT be used for anything other than UI.
bool is_auth_in_progress_;
- // True only after all bootstrapping has succeeded: the bookmark model is
- // loaded, the sync backend is initialized, and the two domains are
- // consistent with one another.
- bool ready_to_process_changes_;
-
// True if an unrecoverable error (e.g. violation of an assumed invariant)
// occurred during syncer operation. This value should be checked before
// doing any work that might corrupt things further.
diff --git a/chrome/browser/sync/profile_sync_service_unittest.cc b/chrome/browser/sync/profile_sync_service_unittest.cc
index ef12916..b2062b5 100644
--- a/chrome/browser/sync/profile_sync_service_unittest.cc
+++ b/chrome/browser/sync/profile_sync_service_unittest.cc
@@ -16,11 +16,13 @@
#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/sync_backend_host.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/testing_profile.h"
using std::vector;
+using browser_sync::ChangeProcessingInterface;
using browser_sync::ModelAssociator;
using browser_sync::SyncBackendHost;
@@ -222,9 +224,9 @@ class FakeServerChange {
}
// Pass the fake change list to |service|.
- void ApplyPendingChanges(ProfileSyncService* service) {
- service->ApplyModelChanges(trans_, changes_.size() ? &changes_[0] : NULL,
- changes_.size());
+ void ApplyPendingChanges(browser_sync::ChangeProcessingInterface* processor) {
+ processor->ApplyChangesFromSyncModel(trans_,
+ changes_.size() ? &changes_[0] : NULL, changes_.size());
}
const vector<sync_api::SyncManager::ChangeRecord>& changes() {
@@ -272,6 +274,11 @@ class ProfileSyncServiceTest : public testing::Test {
return service_->model_associator_;
}
+ ChangeProcessingInterface* change_processor() {
+ DCHECK(service_.get());
+ return service_->change_processor_.get();
+ }
+
void StartSyncService() {
if (!service_.get()) {
service_.reset(new TestProfileSyncService(profile_.get()));
@@ -539,7 +546,7 @@ TEST_F(ProfileSyncServiceTest, ServerChangeProcessing) {
for (it = adds.changes().begin(); it != adds.changes().end(); ++it)
ExpectBrowserNodeUnknown(it->id);
- adds.ApplyPendingChanges(service_.get());
+ adds.ApplyPendingChanges(change_processor());
// Make sure the bookmark model received all of the nodes in |adds|.
for (it = adds.changes().begin(); it != adds.changes().end(); ++it)
@@ -572,7 +579,7 @@ TEST_F(ProfileSyncServiceTest, ServerChangeProcessing) {
ExpectBrowserNodeParent(u3, u3_old_parent);
// Apply the changes.
- mods.ApplyPendingChanges(service_.get());
+ mods.ApplyPendingChanges(change_processor());
// Check for successful application.
for (it = mods.changes().begin(); it != mods.changes().end(); ++it)
@@ -587,7 +594,7 @@ TEST_F(ProfileSyncServiceTest, ServerChangeProcessing) {
ExpectBrowserNodeKnown(u2);
ExpectBrowserNodeKnown(u3);
- dels.ApplyPendingChanges(service_.get());
+ dels.ApplyPendingChanges(change_processor());
ExpectBrowserNodeUnknown(u2);
ExpectBrowserNodeUnknown(u3);
@@ -622,7 +629,7 @@ TEST_F(ProfileSyncServiceTest, ServerChangeRequiringFosterParent) {
for (it = adds.changes().begin(); it != adds.changes().end(); ++it)
ExpectBrowserNodeUnknown(it->id);
- adds.ApplyPendingChanges(service_.get());
+ adds.ApplyPendingChanges(change_processor());
// Make sure the bookmark model received all of the nodes in |adds|.
for (it = adds.changes().begin(); it != adds.changes().end(); ++it)
@@ -640,7 +647,7 @@ TEST_F(ProfileSyncServiceTest, ServerChangeRequiringFosterParent) {
ops.ModifyPosition(u5, f6, 0);
ops.Delete(f1);
- ops.ApplyPendingChanges(service_.get());
+ ops.ApplyPendingChanges(change_processor());
ExpectModelMatch(&trans);
}
@@ -658,7 +665,7 @@ TEST_F(ProfileSyncServiceTest, ServerChangeWithNonCanonicalURL) {
EXPECT_NE(GURL(url).spec(), url);
int64 u1 = adds.AddURL(L"u1", UTF8ToWide(url), other_bookmarks_id(), 0);
- adds.ApplyPendingChanges(service_.get());
+ adds.ApplyPendingChanges(change_processor());
EXPECT_TRUE(model_->other_node()->GetChildCount() == 1);
ExpectModelMatch(&trans);
@@ -688,7 +695,7 @@ TEST_F(ProfileSyncServiceTest, DISABLED_ServerChangeWithInvalidURL) {
EXPECT_FALSE(GURL("x").is_valid());
int64 u1 = adds.AddURL(L"u1", L"x", other_bookmarks_id(), 0);
- adds.ApplyPendingChanges(service_.get());
+ adds.ApplyPendingChanges(change_processor());
// We're lenient about what should happen -- the model could wind up with
// the node or without it; but things should be consistent, and we
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index be29b86..3a924ad 100644
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -1685,6 +1685,8 @@
'browser/sync/engine/syncapi.h',
'browser/sync/glue/bookmark_model_worker.cc',
'browser/sync/glue/bookmark_model_worker.h',
+ 'browser/sync/glue/change_processor.cc',
+ 'browser/sync/glue/change_processor.h',
'browser/sync/glue/http_bridge.cc',
'browser/sync/glue/http_bridge.h',
'browser/sync/glue/model_associator.cc',
diff --git a/chrome/test/live_sync/profile_sync_service_test_harness.cc b/chrome/test/live_sync/profile_sync_service_test_harness.cc
index debb782..61c8deb 100644
--- a/chrome/test/live_sync/profile_sync_service_test_harness.cc
+++ b/chrome/test/live_sync/profile_sync_service_test_harness.cc
@@ -145,7 +145,7 @@ bool ProfileSyncServiceTestHarness::RunStateChangeMachine() {
SignalStateCompleteWithNextState(WAITING_FOR_READY_TO_PROCESS_CHANGES);
break;
case WAITING_FOR_READY_TO_PROCESS_CHANGES:
- if (service_->ready_to_process_changes_) {
+ if (service_->ShouldPushChanges()) {
SignalStateCompleteWithNextState(WAITING_FOR_NOTHING);
}
break;