Commit c80bdd06 authored by Mohamed Amir Yosef's avatar Mohamed Amir Yosef Committed by Commit Bot

[Sync::USS] Support metadata import/export in BookmarkModelTypeProcessor

This CL adds support for importing and exporting sync metadata to
the BookmarkModelTypeProcessor.

Sync metadata are compiled into BookmarkModelMetadata proto. This
proto is used to communicate the sync metadata out and into the
processor.

This CL prepares for another CL that would actually persist/load
the sync metadata to/from disk.

Bug: 516866
Change-Id: I0a4af54383ac336a759c0504e4feee1ff1bda7c1
Reviewed-on: https://chromium-review.googlesource.com/1098918
Commit-Queue: Mohamed Amir Yosef <mamir@chromium.org>
Reviewed-by: default avatarMikel Astiz <mastiz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#567982}
parent 2d53370e
...@@ -269,8 +269,9 @@ void ProfileSyncService::Initialize() { ...@@ -269,8 +269,9 @@ void ProfileSyncService::Initialize() {
if (base::FeatureList::IsEnabled(switches::kSyncUSSBookmarks)) { if (base::FeatureList::IsEnabled(switches::kSyncUSSBookmarks)) {
bookmark_model_type_processor_ = bookmark_model_type_processor_ =
std::make_unique<sync_bookmarks::BookmarkModelTypeProcessor>( std::make_unique<sync_bookmarks::BookmarkModelTypeProcessor>(
sync_client_->GetBookmarkModel(),
sync_client_->GetBookmarkUndoServiceIfExists()); sync_client_->GetBookmarkUndoServiceIfExists());
bookmark_model_type_processor_->DecodeSyncMetadata(
std::string(), base::DoNothing(), sync_client_->GetBookmarkModel());
} }
device_info_sync_bridge_ = std::make_unique<DeviceInfoSyncBridge>( device_info_sync_bridge_ = std::make_unique<DeviceInfoSyncBridge>(
......
// Copyright 2018 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.
syntax = "proto2";
option optimize_for = LITE_RUNTIME;
package sync_pb;
import "model_type_state.proto";
import "entity_metadata.proto";
// Corresponds to a single bookmark id/metadata pair.
message BookmarkMetadata {
// Bookmark local id.
optional int64 id = 1;
// Bookmarks sync metadata.
optional EntityMetadata metadata = 2;
}
// Sync proto to carry the sync metadata for the bookmarks model. It is used for
// persisting and loading sync metadata from disk.
message BookmarkModelMetadata {
// Bookmark global metadata.
optional ModelTypeState model_type_state = 1;
// A set of all bookmarks metadata.
repeated BookmarkMetadata bookmarks_metadata = 2;
}
...@@ -10,6 +10,7 @@ sync_protocol_sources = [ ...@@ -10,6 +10,7 @@ sync_protocol_sources = [
"//components/sync/protocol/arc_package_specifics.proto", "//components/sync/protocol/arc_package_specifics.proto",
"//components/sync/protocol/article_specifics.proto", "//components/sync/protocol/article_specifics.proto",
"//components/sync/protocol/autofill_specifics.proto", "//components/sync/protocol/autofill_specifics.proto",
"//components/sync/protocol/bookmark_model_metadata.proto",
"//components/sync/protocol/bookmark_specifics.proto", "//components/sync/protocol/bookmark_specifics.proto",
"//components/sync/protocol/client_commands.proto", "//components/sync/protocol/client_commands.proto",
"//components/sync/protocol/client_debug_info.proto", "//components/sync/protocol/client_debug_info.proto",
......
...@@ -41,6 +41,7 @@ source_set("unit_tests") { ...@@ -41,6 +41,7 @@ source_set("unit_tests") {
deps = [ deps = [
":sync_bookmarks", ":sync_bookmarks",
"//base", "//base",
"//base/test:test_support",
"//components/bookmarks/browser", "//components/bookmarks/browser",
"//components/bookmarks/test", "//components/bookmarks/test",
"//components/history/core/browser", "//components/history/core/browser",
......
...@@ -10,9 +10,11 @@ ...@@ -10,9 +10,11 @@
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "components/bookmarks/browser/bookmark_model.h" #include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_utils.h"
#include "components/sync/base/model_type.h" #include "components/sync/base/model_type.h"
#include "components/sync/engine/commit_queue.h" #include "components/sync/engine/commit_queue.h"
#include "components/sync/engine/model_type_processor_proxy.h" #include "components/sync/engine/model_type_processor_proxy.h"
#include "components/sync/protocol/bookmark_model_metadata.pb.h"
#include "components/undo/bookmark_undo_utils.h" #include "components/undo/bookmark_undo_utils.h"
namespace sync_bookmarks { namespace sync_bookmarks {
...@@ -92,6 +94,25 @@ const bookmarks::BookmarkNode* CreateBookmarkNode( ...@@ -92,6 +94,25 @@ const bookmarks::BookmarkNode* CreateBookmarkNode(
// TODO(crbug.com/516866): Add the favicon related code. // TODO(crbug.com/516866): Add the favicon related code.
} }
// Check whether an incoming specifics represent a valid bookmark or not.
// |is_folder| is whether this specifics is for a folder or not.
// Folders and tomstones entail different validation conditions.
bool IsValidBookmark(const sync_pb::BookmarkSpecifics& specifics,
bool is_folder) {
if (specifics.ByteSize() == 0) {
DLOG(ERROR) << "Invalid bookmark: empty specifics.";
return false;
}
if (is_folder) {
return true;
}
if (!GURL(specifics.url()).is_valid()) {
DLOG(ERROR) << "Invalid bookmark: invalid url in the specifics.";
return false;
}
return true;
}
class ScopedRemoteUpdateBookmarks { class ScopedRemoteUpdateBookmarks {
public: public:
// |bookmark_model| and |bookmark_undo_service| must not be null and must // |bookmark_model| and |bookmark_undo_service| must not be null and must
...@@ -126,11 +147,8 @@ class ScopedRemoteUpdateBookmarks { ...@@ -126,11 +147,8 @@ class ScopedRemoteUpdateBookmarks {
} // namespace } // namespace
BookmarkModelTypeProcessor::BookmarkModelTypeProcessor( BookmarkModelTypeProcessor::BookmarkModelTypeProcessor(
bookmarks::BookmarkModel* bookmark_model,
BookmarkUndoService* bookmark_undo_service) BookmarkUndoService* bookmark_undo_service)
: bookmark_model_(bookmark_model), : bookmark_undo_service_(bookmark_undo_service), weak_ptr_factory_(this) {}
bookmark_undo_service_(bookmark_undo_service),
weak_ptr_factory_(this) {}
BookmarkModelTypeProcessor::~BookmarkModelTypeProcessor() = default; BookmarkModelTypeProcessor::~BookmarkModelTypeProcessor() = default;
...@@ -138,6 +156,9 @@ void BookmarkModelTypeProcessor::ConnectSync( ...@@ -138,6 +156,9 @@ void BookmarkModelTypeProcessor::ConnectSync(
std::unique_ptr<syncer::CommitQueue> worker) { std::unique_ptr<syncer::CommitQueue> worker) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!worker_); DCHECK(!worker_);
DCHECK(bookmark_model_);
DCHECK(bookmark_tracker_);
worker_ = std::move(worker); worker_ = std::move(worker);
} }
...@@ -171,18 +192,18 @@ void BookmarkModelTypeProcessor::OnUpdateReceived( ...@@ -171,18 +192,18 @@ void BookmarkModelTypeProcessor::OnUpdateReceived(
bookmark_undo_service_); bookmark_undo_service_);
for (const syncer::UpdateResponseData* update : ReorderUpdates(updates)) { for (const syncer::UpdateResponseData* update : ReorderUpdates(updates)) {
const syncer::EntityData& update_data = update->entity.value(); const syncer::EntityData& update_entity = update->entity.value();
// TODO(crbug.com/516866): Check |update_data| for sanity. // TODO(crbug.com/516866): Check |update_entity| for sanity.
// 1. Has bookmark specifics or no specifics in case of delete. // 1. Has bookmark specifics or no specifics in case of delete.
// 2. All meta info entries in the specifics have unique keys. // 2. All meta info entries in the specifics have unique keys.
const SyncedBookmarkTracker::Entity* tracked_entity = const SyncedBookmarkTracker::Entity* tracked_entity =
bookmark_tracker_.GetEntityForSyncId(update_data.id); bookmark_tracker_->GetEntityForSyncId(update_entity.id);
if (update_data.is_deleted()) { if (update_entity.is_deleted()) {
ProcessRemoteDelete(update_data, tracked_entity); ProcessRemoteDelete(update_entity, tracked_entity);
continue; continue;
} }
if (!tracked_entity) { if (!tracked_entity) {
ProcessRemoteCreate(update_data); ProcessRemoteCreate(*update);
continue; continue;
} }
// Ignore changes to the permanent nodes (e.g. bookmarks bar). We only care // Ignore changes to the permanent nodes (e.g. bookmarks bar). We only care
...@@ -190,7 +211,7 @@ void BookmarkModelTypeProcessor::OnUpdateReceived( ...@@ -190,7 +211,7 @@ void BookmarkModelTypeProcessor::OnUpdateReceived(
if (bookmark_model_->is_permanent_node(tracked_entity->bookmark_node())) { if (bookmark_model_->is_permanent_node(tracked_entity->bookmark_node())) {
continue; continue;
} }
ProcessRemoteUpdate(update_data, tracked_entity); ProcessRemoteUpdate(*update, tracked_entity);
} }
} }
...@@ -203,7 +224,7 @@ BookmarkModelTypeProcessor::ReorderUpdatesForTest( ...@@ -203,7 +224,7 @@ BookmarkModelTypeProcessor::ReorderUpdatesForTest(
const SyncedBookmarkTracker* BookmarkModelTypeProcessor::GetTrackerForTest() const SyncedBookmarkTracker* BookmarkModelTypeProcessor::GetTrackerForTest()
const { const {
return &bookmark_tracker_; return bookmark_tracker_.get();
} }
// static // static
...@@ -215,33 +236,33 @@ BookmarkModelTypeProcessor::ReorderUpdates( ...@@ -215,33 +236,33 @@ BookmarkModelTypeProcessor::ReorderUpdates(
// should be fixed before enabling USS for bookmarks. // should be fixed before enabling USS for bookmarks.
std::vector<const syncer::UpdateResponseData*> ordered_updates; std::vector<const syncer::UpdateResponseData*> ordered_updates;
for (const syncer::UpdateResponseData& update : updates) { for (const syncer::UpdateResponseData& update : updates) {
const syncer::EntityData& update_data = update.entity.value(); const syncer::EntityData& update_entity = update.entity.value();
if (update_data.parent_id == "0") { if (update_entity.parent_id == "0") {
continue; continue;
} }
if (update_data.parent_id == kBookmarksRootId) { if (update_entity.parent_id == kBookmarksRootId) {
ordered_updates.push_back(&update); ordered_updates.push_back(&update);
} }
} }
for (const syncer::UpdateResponseData& update : updates) { for (const syncer::UpdateResponseData& update : updates) {
const syncer::EntityData& update_data = update.entity.value(); const syncer::EntityData& update_entity = update.entity.value();
// Deletions should come last. // Deletions should come last.
if (update_data.is_deleted()) { if (update_entity.is_deleted()) {
continue; continue;
} }
if (update_data.parent_id != "0" && if (update_entity.parent_id != "0" &&
update_data.parent_id != kBookmarksRootId) { update_entity.parent_id != kBookmarksRootId) {
ordered_updates.push_back(&update); ordered_updates.push_back(&update);
} }
} }
// Now add deletions. // Now add deletions.
for (const syncer::UpdateResponseData& update : updates) { for (const syncer::UpdateResponseData& update : updates) {
const syncer::EntityData& update_data = update.entity.value(); const syncer::EntityData& update_entity = update.entity.value();
if (!update_data.is_deleted()) { if (!update_entity.is_deleted()) {
continue; continue;
} }
if (update_data.parent_id != "0" && if (update_entity.parent_id != "0" &&
update_data.parent_id != kBookmarksRootId) { update_entity.parent_id != kBookmarksRootId) {
ordered_updates.push_back(&update); ordered_updates.push_back(&update);
} }
} }
...@@ -249,25 +270,34 @@ BookmarkModelTypeProcessor::ReorderUpdates( ...@@ -249,25 +270,34 @@ BookmarkModelTypeProcessor::ReorderUpdates(
} }
void BookmarkModelTypeProcessor::ProcessRemoteCreate( void BookmarkModelTypeProcessor::ProcessRemoteCreate(
const syncer::EntityData& update_data) { const syncer::UpdateResponseData& update) {
// Because the Synced Bookmarks node can be created server side, it's possible // Because the Synced Bookmarks node can be created server side, it's possible
// it'll arrive at the client as an update. In that case it won't have been // it'll arrive at the client as an update. In that case it won't have been
// associated at startup, the GetChromeNodeFromSyncId call above will return // associated at startup, the GetChromeNodeFromSyncId call above will return
// null, and we won't detect it as a permanent node, resulting in us trying to // null, and we won't detect it as a permanent node, resulting in us trying to
// create it here (which will fail). Therefore, we add special logic here just // create it here (which will fail). Therefore, we add special logic here just
// to detect the Synced Bookmarks folder. // to detect the Synced Bookmarks folder.
if (update_data.parent_id == kBookmarksRootId) { const syncer::EntityData& update_entity = update.entity.value();
DCHECK(!update_entity.is_deleted());
if (update_entity.parent_id == kBookmarksRootId) {
// Associate permanent folders. // Associate permanent folders.
AssociatePermanentFolder(update_data); // TODO(crbug.com/516866): Method documentation says this method should be
// used in initial sync only. Make sure this is the case.
AssociatePermanentFolder(update);
return; return;
} }
if (!IsValidBookmark(update_entity.specifics.bookmark(),
const bookmarks::BookmarkNode* parent_node = GetParentNode(update_data); update_entity.is_folder)) {
// Ignore creations with invalid specifics.
DLOG(ERROR) << "Couldn't add bookmark with an invalid specifics.";
return;
}
const bookmarks::BookmarkNode* parent_node = GetParentNode(update_entity);
if (!parent_node) { if (!parent_node) {
// If we cannot find the parent, we can do nothing. // If we cannot find the parent, we can do nothing.
DLOG(ERROR) << "Could not find parent of node being added." DLOG(ERROR) << "Could not find parent of node being added."
<< " Node title: " << update_data.specifics.bookmark().title() << " Node title: " << update_entity.specifics.bookmark().title()
<< ", parent id = " << update_data.parent_id; << ", parent id = " << update_entity.parent_id;
return; return;
} }
// TODO(crbug.com/516866): This code appends the code to the very end of the // TODO(crbug.com/516866): This code appends the code to the very end of the
...@@ -275,68 +305,80 @@ void BookmarkModelTypeProcessor::ProcessRemoteCreate( ...@@ -275,68 +305,80 @@ void BookmarkModelTypeProcessor::ProcessRemoteCreate(
// parent_node->child_count(). It should instead compute the exact using the // parent_node->child_count(). It should instead compute the exact using the
// unique position information of the new node as well as the siblings. // unique position information of the new node as well as the siblings.
const bookmarks::BookmarkNode* bookmark_node = CreateBookmarkNode( const bookmarks::BookmarkNode* bookmark_node = CreateBookmarkNode(
update_data, parent_node, bookmark_model_, parent_node->child_count()); update_entity, parent_node, bookmark_model_, parent_node->child_count());
if (!bookmark_node) { if (!bookmark_node) {
// We ignore bookmarks we can't add. // We ignore bookmarks we can't add.
DLOG(ERROR) << "Failed to create bookmark node with title " DLOG(ERROR) << "Failed to create bookmark node with title "
<< update_data.specifics.bookmark().title() << " and url " << update_entity.specifics.bookmark().title() << " and url "
<< update_data.specifics.bookmark().url(); << update_entity.specifics.bookmark().url();
return; return;
} }
bookmark_tracker_.Add(update_data.id, bookmark_node); bookmark_tracker_->Add(update_entity.id, bookmark_node,
// TODO(crbug.com/516866): Update metadata (e.g. server version, update.response_version, update_entity.creation_time,
// specifics_hash). update_entity.specifics);
} }
void BookmarkModelTypeProcessor::ProcessRemoteUpdate( void BookmarkModelTypeProcessor::ProcessRemoteUpdate(
const syncer::EntityData& update_data, const syncer::UpdateResponseData& update,
const SyncedBookmarkTracker::Entity* tracked_entity) { const SyncedBookmarkTracker::Entity* tracked_entity) {
const syncer::EntityData& update_entity = update.entity.value();
// Can only update existing nodes. // Can only update existing nodes.
DCHECK(tracked_entity); DCHECK(tracked_entity);
DCHECK_EQ(tracked_entity, DCHECK_EQ(tracked_entity,
bookmark_tracker_.GetEntityForSyncId(update_data.id)); bookmark_tracker_->GetEntityForSyncId(update_entity.id));
// Must no be a deletion // Must not be a deletion.
DCHECK(!update_data.is_deleted()); DCHECK(!update_entity.is_deleted());
if (!IsValidBookmark(update_entity.specifics.bookmark(),
update_entity.is_folder)) {
// Ignore updates with invalid specifics.
DLOG(ERROR) << "Couldn't update bookmark with an invalid specifics.";
return;
}
if (tracked_entity->IsUnsynced()) { if (tracked_entity->IsUnsynced()) {
// TODO(crbug.com/516866): Handle conflict resolution. // TODO(crbug.com/516866): Handle conflict resolution.
return; return;
} }
if (tracked_entity->MatchesData(update_data)) { if (tracked_entity->MatchesData(update_entity)) {
// TODO(crbug.com/516866): Update metadata (e.g. server version, bookmark_tracker_->Update(update_entity.id, update.response_version,
// specifics_hash). update_entity.modification_time,
update_entity.specifics);
// Since there is no change in the model data, we should trigger data
// persistence here to save latest metadata.
schedule_save_closure_.Run();
return; return;
} }
const bookmarks::BookmarkNode* node = tracked_entity->bookmark_node(); const bookmarks::BookmarkNode* node = tracked_entity->bookmark_node();
if (update_data.is_folder != node->is_folder()) { if (update_entity.is_folder != node->is_folder()) {
DLOG(ERROR) << "Could not update node. Remote node is a " DLOG(ERROR) << "Could not update node. Remote node is a "
<< (update_data.is_folder ? "folder" : "bookmark") << (update_entity.is_folder ? "folder" : "bookmark")
<< " while local node is a " << " while local node is a "
<< (node->is_folder() ? "folder" : "bookmark"); << (node->is_folder() ? "folder" : "bookmark");
return; return;
} }
const sync_pb::BookmarkSpecifics& specifics = const sync_pb::BookmarkSpecifics& specifics =
update_data.specifics.bookmark(); update_entity.specifics.bookmark();
if (!update_data.is_folder) { if (!update_entity.is_folder) {
bookmark_model_->SetURL(node, GURL(specifics.url())); bookmark_model_->SetURL(node, GURL(specifics.url()));
} }
bookmark_model_->SetTitle(node, base::UTF8ToUTF16(specifics.title())); bookmark_model_->SetTitle(node, base::UTF8ToUTF16(specifics.title()));
// TODO(crbug.com/516866): Add the favicon related code. // TODO(crbug.com/516866): Add the favicon related code.
bookmark_model_->SetNodeMetaInfoMap(node, GetBookmarkMetaInfo(update_data)); bookmark_model_->SetNodeMetaInfoMap(node, GetBookmarkMetaInfo(update_entity));
// TODO(crbug.com/516866): Update metadata (e.g. server version, bookmark_tracker_->Update(update_entity.id, update.response_version,
// specifics_hash). update_entity.modification_time,
update_entity.specifics);
// TODO(crbug.com/516866): Handle reparenting. // TODO(crbug.com/516866): Handle reparenting.
// TODO(crbug.com/516866): Handle the case of moving the bookmark to a new // TODO(crbug.com/516866): Handle the case of moving the bookmark to a new
// position under the same parent (i.e. change in the unique position) // position under the same parent (i.e. change in the unique position)
} }
void BookmarkModelTypeProcessor::ProcessRemoteDelete( void BookmarkModelTypeProcessor::ProcessRemoteDelete(
const syncer::EntityData& update_data, const syncer::EntityData& update_entity,
const SyncedBookmarkTracker::Entity* tracked_entity) { const SyncedBookmarkTracker::Entity* tracked_entity) {
DCHECK(update_data.is_deleted()); DCHECK(update_entity.is_deleted());
DCHECK_EQ(tracked_entity, DCHECK_EQ(tracked_entity,
bookmark_tracker_.GetEntityForSyncId(update_data.id)); bookmark_tracker_->GetEntityForSyncId(update_entity.id));
// Handle corner cases first. // Handle corner cases first.
if (tracked_entity == nullptr) { if (tracked_entity == nullptr) {
...@@ -359,13 +401,13 @@ void BookmarkModelTypeProcessor::ProcessRemoteDelete( ...@@ -359,13 +401,13 @@ void BookmarkModelTypeProcessor::ProcessRemoteDelete(
} }
bookmark_model_->Remove(node); bookmark_model_->Remove(node);
bookmark_tracker_.Remove(update_data.id); bookmark_tracker_->Remove(update_entity.id);
} }
const bookmarks::BookmarkNode* BookmarkModelTypeProcessor::GetParentNode( const bookmarks::BookmarkNode* BookmarkModelTypeProcessor::GetParentNode(
const syncer::EntityData& update_data) const { const syncer::EntityData& update_entity) const {
const SyncedBookmarkTracker::Entity* parent_entity = const SyncedBookmarkTracker::Entity* parent_entity =
bookmark_tracker_.GetEntityForSyncId(update_data.parent_id); bookmark_tracker_->GetEntityForSyncId(update_entity.parent_id);
if (!parent_entity) { if (!parent_entity) {
return nullptr; return nullptr;
} }
...@@ -373,23 +415,91 @@ const bookmarks::BookmarkNode* BookmarkModelTypeProcessor::GetParentNode( ...@@ -373,23 +415,91 @@ const bookmarks::BookmarkNode* BookmarkModelTypeProcessor::GetParentNode(
} }
void BookmarkModelTypeProcessor::AssociatePermanentFolder( void BookmarkModelTypeProcessor::AssociatePermanentFolder(
const syncer::EntityData& update_data) { const syncer::UpdateResponseData& update) {
DCHECK_EQ(update_data.parent_id, kBookmarksRootId); const syncer::EntityData& update_entity = update.entity.value();
DCHECK_EQ(update_entity.parent_id, kBookmarksRootId);
const bookmarks::BookmarkNode* permanent_node = nullptr; const bookmarks::BookmarkNode* permanent_node = nullptr;
if (update_data.server_defined_unique_tag == kBookmarkBarTag) { if (update_entity.server_defined_unique_tag == kBookmarkBarTag) {
permanent_node = bookmark_model_->bookmark_bar_node(); permanent_node = bookmark_model_->bookmark_bar_node();
} else if (update_data.server_defined_unique_tag == kOtherBookmarksTag) { } else if (update_entity.server_defined_unique_tag == kOtherBookmarksTag) {
permanent_node = bookmark_model_->other_node(); permanent_node = bookmark_model_->other_node();
} else if (update_data.server_defined_unique_tag == kMobileBookmarksTag) { } else if (update_entity.server_defined_unique_tag == kMobileBookmarksTag) {
permanent_node = bookmark_model_->mobile_node(); permanent_node = bookmark_model_->mobile_node();
} }
if (permanent_node != nullptr) { if (permanent_node != nullptr) {
bookmark_tracker_.Add(update_data.id, permanent_node); bookmark_tracker_->Add(update_entity.id, permanent_node,
update.response_version, update_entity.creation_time,
update_entity.specifics);
} }
} }
std::string BookmarkModelTypeProcessor::EncodeSyncMetadata() const {
sync_pb::BookmarkModelMetadata model_metadata =
bookmark_tracker_->BuildBookmarkModelMetadata();
std::string metadata_str;
model_metadata.SerializeToString(&metadata_str);
return metadata_str;
}
void BookmarkModelTypeProcessor::DecodeSyncMetadata(
const std::string& metadata_str,
const base::RepeatingClosure& schedule_save_closure,
bookmarks::BookmarkModel* model) {
DCHECK(model);
DCHECK(!bookmark_model_);
bookmark_model_ = model;
schedule_save_closure_ = schedule_save_closure;
sync_pb::BookmarkModelMetadata model_metadata;
model_metadata.ParseFromString(metadata_str);
auto model_type_state = std::make_unique<sync_pb::ModelTypeState>();
model_type_state->Swap(model_metadata.mutable_model_type_state());
std::vector<NodeMetadataPair> nodes_metadata;
if (model_type_state->initial_sync_done()) {
for (sync_pb::BookmarkMetadata& bookmark_metadata :
*model_metadata.mutable_bookmarks_metadata()) {
// TODO(crbug.com/516866): Replace with a more efficient way to retrieve
// all nodes and store in a map keyed by id instead of doing a lookup for
// every id.
const bookmarks::BookmarkNode* node = nullptr;
if (bookmark_metadata.metadata().is_deleted()) {
if (bookmark_metadata.has_id()) {
DLOG(ERROR) << "Error when decoding sync metadata: Tombstones "
"shouldn't have a bookmark id.";
continue;
}
} else {
if (!bookmark_metadata.has_id()) {
DLOG(ERROR)
<< "Error when decoding sync metadata: Bookmark id is missing.";
continue;
}
node = GetBookmarkNodeByID(bookmark_model_, bookmark_metadata.id());
if (node == nullptr) {
DLOG(ERROR) << "Error when decoding sync metadata: Cannot find the "
"bookmark node.";
continue;
}
}
auto metadata = std::make_unique<sync_pb::EntityMetadata>();
metadata->Swap(bookmark_metadata.mutable_metadata());
nodes_metadata.emplace_back(node, std::move(metadata));
}
} else if (!model_metadata.bookmarks_metadata().empty()) {
DLOG(ERROR)
<< "Persisted Metadata not empty while initial sync is not done.";
}
// TODO(crbug.com/516866): Handle local nodes that don't have a corresponding
// metadata.
bookmark_tracker_ = std::make_unique<SyncedBookmarkTracker>(
std::move(nodes_metadata), std::move(model_type_state));
ConnectIfReady();
}
base::WeakPtr<syncer::ModelTypeControllerDelegate> base::WeakPtr<syncer::ModelTypeControllerDelegate>
BookmarkModelTypeProcessor::GetWeakPtr() { BookmarkModelTypeProcessor::GetWeakPtr() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
...@@ -403,6 +513,20 @@ void BookmarkModelTypeProcessor::OnSyncStarting( ...@@ -403,6 +513,20 @@ void BookmarkModelTypeProcessor::OnSyncStarting(
DCHECK(start_callback); DCHECK(start_callback);
DVLOG(1) << "Sync is starting for Bookmarks"; DVLOG(1) << "Sync is starting for Bookmarks";
start_callback_ = std::move(start_callback);
ConnectIfReady();
}
void BookmarkModelTypeProcessor::ConnectIfReady() {
// Return if the model isn't ready.
if (!bookmark_model_) {
return;
}
// Return if Sync didn't start yet.
if (!start_callback_) {
return;
}
auto activation_context = auto activation_context =
std::make_unique<syncer::DataTypeActivationResponse>(); std::make_unique<syncer::DataTypeActivationResponse>();
// TODO(crbug.com/516866): Read the model type state from persisted sync // TODO(crbug.com/516866): Read the model type state from persisted sync
...@@ -414,7 +538,7 @@ void BookmarkModelTypeProcessor::OnSyncStarting( ...@@ -414,7 +538,7 @@ void BookmarkModelTypeProcessor::OnSyncStarting(
activation_context->type_processor = activation_context->type_processor =
std::make_unique<syncer::ModelTypeProcessorProxy>( std::make_unique<syncer::ModelTypeProcessorProxy>(
weak_ptr_factory_.GetWeakPtr(), base::ThreadTaskRunnerHandle::Get()); weak_ptr_factory_.GetWeakPtr(), base::ThreadTaskRunnerHandle::Get());
std::move(start_callback).Run(std::move(activation_context)); std::move(start_callback_).Run(std::move(activation_context));
} }
void BookmarkModelTypeProcessor::DisableSync() { void BookmarkModelTypeProcessor::DisableSync() {
......
...@@ -6,8 +6,10 @@ ...@@ -6,8 +6,10 @@
#define COMPONENTS_SYNC_BOOKMARKS_BOOKMARK_MODEL_TYPE_PROCESSOR_H_ #define COMPONENTS_SYNC_BOOKMARKS_BOOKMARK_MODEL_TYPE_PROCESSOR_H_
#include <memory> #include <memory>
#include <string>
#include <vector> #include <vector>
#include "base/callback_forward.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h" #include "base/sequence_checker.h"
...@@ -19,6 +21,7 @@ class BookmarkUndoService; ...@@ -19,6 +21,7 @@ class BookmarkUndoService;
namespace bookmarks { namespace bookmarks {
class BookmarkModel; class BookmarkModel;
class BookmarkNode;
} }
namespace sync_bookmarks { namespace sync_bookmarks {
...@@ -26,9 +29,8 @@ namespace sync_bookmarks { ...@@ -26,9 +29,8 @@ namespace sync_bookmarks {
class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor, class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor,
public syncer::ModelTypeControllerDelegate { public syncer::ModelTypeControllerDelegate {
public: public:
// |bookmark_undo_service| and |bookmark_undo_service| must not be nullptr and // |bookmark_undo_service| must not be nullptr and must outlive this object.
// must outlive this object. explicit BookmarkModelTypeProcessor(
BookmarkModelTypeProcessor(bookmarks::BookmarkModel* bookmark_model,
BookmarkUndoService* bookmark_undo_service); BookmarkUndoService* bookmark_undo_service);
~BookmarkModelTypeProcessor() override; ~BookmarkModelTypeProcessor() override;
...@@ -51,6 +53,21 @@ class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor, ...@@ -51,6 +53,21 @@ class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor,
void GetStatusCountersForDebugging(StatusCountersCallback callback) override; void GetStatusCountersForDebugging(StatusCountersCallback callback) override;
void RecordMemoryUsageHistogram() override; void RecordMemoryUsageHistogram() override;
// Encodes all sync metadata into a string, representing a state that can be
// restored via DecodeSyncMetadata() below.
std::string EncodeSyncMetadata() const;
// It mainly decodes a BookmarkModelMetadata proto seralized in
// |metadata_str|, and uses it to fill in the tracker and the model type state
// objects. |model| must not be null and must outlive this object. It is used
// to the retrieve the local node ids, and is stored in the processor to be
// used for further model operations. |schedule_save_closure| is a repeating
// closure used to schedule a save of the bookmark model together with the
// metadata.
void DecodeSyncMetadata(const std::string& metadata_str,
const base::RepeatingClosure& schedule_save_closure,
bookmarks::BookmarkModel* model);
// Public for testing. // Public for testing.
static std::vector<const syncer::UpdateResponseData*> ReorderUpdatesForTest( static std::vector<const syncer::UpdateResponseData*> ReorderUpdatesForTest(
const syncer::UpdateResponseDataList& updates); const syncer::UpdateResponseDataList& updates);
...@@ -72,7 +89,7 @@ class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor, ...@@ -72,7 +89,7 @@ class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor,
// Given a remote update entity, it returns the parent bookmark node of the // Given a remote update entity, it returns the parent bookmark node of the
// corresponding node. It returns null if the parent node cannot be found. // corresponding node. It returns null if the parent node cannot be found.
const bookmarks::BookmarkNode* GetParentNode( const bookmarks::BookmarkNode* GetParentNode(
const syncer::EntityData& update_data) const; const syncer::EntityData& update_entity) const;
// Processes a remote creation of a bookmark node. // Processes a remote creation of a bookmark node.
// 1. For permanent folders, they are only registered in |bookmark_tracker_|. // 1. For permanent folders, they are only registered in |bookmark_tracker_|.
...@@ -80,40 +97,55 @@ class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor, ...@@ -80,40 +97,55 @@ class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor,
// ignored. // ignored.
// 3. Otherwise, a new node is created in the local bookmark model and // 3. Otherwise, a new node is created in the local bookmark model and
// registered in |bookmark_tracker_|. // registered in |bookmark_tracker_|.
void ProcessRemoteCreate(const syncer::EntityData& update_data); void ProcessRemoteCreate(const syncer::UpdateResponseData& update);
// Processes a remote update of a bookmark node. |update_data| must not be a // Processes a remote update of a bookmark node. |update| must not be a
// deletion, and the server_id must be already tracked, otherwise, it is a // deletion, and the server_id must be already tracked, otherwise, it is a
// creation that gets handeled in ProcessRemoteCreate(). |tracked_entity| is // creation that gets handeled in ProcessRemoteCreate(). |tracked_entity| is
// the tracked entity for that server_id. It is passed as a dependency instead // the tracked entity for that server_id. It is passed as a dependency instead
// of performing a lookup inside ProcessRemoteUpdate() to avoid wasting CPU // of performing a lookup inside ProcessRemoteUpdate() to avoid wasting CPU
// cycles for doing another lookup (this code runs on the UI thread). // cycles for doing another lookup (this code runs on the UI thread).
void ProcessRemoteUpdate(const syncer::EntityData& update_data, void ProcessRemoteUpdate(const syncer::UpdateResponseData& update,
const SyncedBookmarkTracker::Entity* tracked_entity); const SyncedBookmarkTracker::Entity* tracked_entity);
// Process a remote delete of a bookmark node. |update_data| must not be a // Process a remote delete of a bookmark node. |update_entity| must not be a
// deletion. |tracked_entity| is the tracked entity for that server_id. It is // deletion. |tracked_entity| is the tracked entity for that server_id. It is
// passed as a dependency instead of performing a lookup inside // passed as a dependency instead of performing a lookup inside
// ProcessRemoteDelete() to avoid wasting CPU cycles for doing another lookup // ProcessRemoteDelete() to avoid wasting CPU cycles for doing another lookup
// (this code runs on the UI thread). // (this code runs on the UI thread).
void ProcessRemoteDelete(const syncer::EntityData& update_data, void ProcessRemoteDelete(const syncer::EntityData& update_entity,
const SyncedBookmarkTracker::Entity* tracked_entity); const SyncedBookmarkTracker::Entity* tracked_entity);
// Associates the permanent bookmark folders with the corresponding server // Associates the permanent bookmark folders with the corresponding server
// side ids and registers the association in |bookmark_tracker_|. // side ids and registers the association in |bookmark_tracker_|.
// |update_data| must contain server_defined_unique_tag that is used to // |update_entity| must contain server_defined_unique_tag that is used to
// determine the corresponding permanent node. All permanent nodes are assumed // determine the corresponding permanent node. All permanent nodes are assumed
// to be directly children nodes of |kBookmarksRootId|. This method is used in // to be directly children nodes of |kBookmarksRootId|. This method is used in
// the initial sync cycle only. // the initial sync cycle only.
void AssociatePermanentFolder(const syncer::EntityData& update_data); void AssociatePermanentFolder(const syncer::UpdateResponseData& update);
// If preconditions are met, inform sync that we are ready to connect.
void ConnectIfReady();
// Stores the start callback in between OnSyncStarting() and
// DecodeSyncMetadata().
StartCallback start_callback_;
// The bookmark model we are processing local changes from and forwarding // The bookmark model we are processing local changes from and forwarding
// remote changes to. // remote changes to. It is set during DecodeSyncMetadata(), which is called
bookmarks::BookmarkModel* const bookmark_model_; // during startup, as part of the bookmark-loading process.
bookmarks::BookmarkModel* bookmark_model_ = nullptr;
// Used to suspend bookmark undo when processing remote changes. // Used to suspend bookmark undo when processing remote changes.
BookmarkUndoService* const bookmark_undo_service_; BookmarkUndoService* const bookmark_undo_service_;
// The callback used to schedule the persistence of bookmark model as well as
// the metadata to a file during which latest metadata should also be pulled
// via EncodeSyncMetadata. Processor should invoke it upon changes in the
// metadata that don't imply changes in the model itself. Persisting updates
// that imply model changes is the model's responsibility.
base::RepeatingClosure schedule_save_closure_;
// Reference to the CommitQueue. // Reference to the CommitQueue.
// //
// The interface hides the posting of tasks across threads as well as the // The interface hides the posting of tasks across threads as well as the
...@@ -123,7 +155,9 @@ class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor, ...@@ -123,7 +155,9 @@ class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor,
// Keeps the mapping between server ids and bookmarks nodes. It also caches // Keeps the mapping between server ids and bookmarks nodes. It also caches
// the metadata upon a local change until the commit configration is received. // the metadata upon a local change until the commit configration is received.
SyncedBookmarkTracker bookmark_tracker_; // It is constructed and set during DecodeSyncMetadata(), which is called
// during startup, as part of the bookmark-loading process.
std::unique_ptr<SyncedBookmarkTracker> bookmark_tracker_;
base::WeakPtrFactory<BookmarkModelTypeProcessor> weak_ptr_factory_; base::WeakPtrFactory<BookmarkModelTypeProcessor> weak_ptr_factory_;
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <string> #include <string>
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/test/mock_callback.h"
#include "components/bookmarks/browser/bookmark_model.h" #include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/test/test_bookmark_client.h" #include "components/bookmarks/test/test_bookmark_client.h"
#include "components/sync/driver/fake_sync_client.h" #include "components/sync/driver/fake_sync_client.h"
...@@ -15,6 +16,7 @@ ...@@ -15,6 +16,7 @@
using base::ASCIIToUTF16; using base::ASCIIToUTF16;
using testing::Eq; using testing::Eq;
using testing::NiceMock;
using testing::NotNull; using testing::NotNull;
namespace sync_bookmarks { namespace sync_bookmarks {
...@@ -25,6 +27,7 @@ namespace { ...@@ -25,6 +27,7 @@ namespace {
// referred to as top level enities. // referred to as top level enities.
const char kRootParentTag[] = "0"; const char kRootParentTag[] = "0";
const char kBookmarkBarTag[] = "bookmark_bar"; const char kBookmarkBarTag[] = "bookmark_bar";
const char kBookmarkBarId[] = "bookmark_bar_id";
const char kBookmarksRootId[] = "32904_google_chrome_bookmarks"; const char kBookmarksRootId[] = "32904_google_chrome_bookmarks";
struct BookmarkInfo { struct BookmarkInfo {
...@@ -74,7 +77,7 @@ void AssertState(const BookmarkModelTypeProcessor* processor, ...@@ -74,7 +77,7 @@ void AssertState(const BookmarkModelTypeProcessor* processor,
ASSERT_THAT(node->url(), Eq(GURL(bookmark.url))); ASSERT_THAT(node->url(), Eq(GURL(bookmark.url)));
const SyncedBookmarkTracker::Entity* parent_entity = const SyncedBookmarkTracker::Entity* parent_entity =
tracker->GetEntityForSyncId(bookmark.parent_id); tracker->GetEntityForSyncId(bookmark.parent_id);
ASSERT_THAT(node->parent(), parent_entity->bookmark_node()); ASSERT_THAT(node->parent(), Eq(parent_entity->bookmark_node()));
} }
} }
...@@ -86,7 +89,7 @@ void InitWithSyncedBookmarks(const std::vector<BookmarkInfo>& bookmarks, ...@@ -86,7 +89,7 @@ void InitWithSyncedBookmarks(const std::vector<BookmarkInfo>& bookmarks,
syncer::UpdateResponseDataList updates; syncer::UpdateResponseDataList updates;
// Add update for the permanent folder "Bookmarks bar". // Add update for the permanent folder "Bookmarks bar".
updates.push_back( updates.push_back(
CreateUpdateData({"bookmark_bar", std::string(), std::string(), CreateUpdateData({kBookmarkBarId, std::string(), std::string(),
kBookmarksRootId, kBookmarkBarTag})); kBookmarksRootId, kBookmarkBarTag}));
for (BookmarkInfo bookmark : bookmarks) { for (BookmarkInfo bookmark : bookmarks) {
updates.push_back(CreateUpdateData(bookmark)); updates.push_back(CreateUpdateData(bookmark));
...@@ -129,17 +132,27 @@ class BookmarkModelTypeProcessorTest : public testing::Test { ...@@ -129,17 +132,27 @@ class BookmarkModelTypeProcessorTest : public testing::Test {
public: public:
BookmarkModelTypeProcessorTest() BookmarkModelTypeProcessorTest()
: bookmark_model_(bookmarks::TestBookmarkClient::CreateModel()), : bookmark_model_(bookmarks::TestBookmarkClient::CreateModel()),
sync_client_(bookmark_model_.get()) {} sync_client_(bookmark_model_.get()),
processor_(sync_client()->GetBookmarkUndoServiceIfExists()) {
processor_.DecodeSyncMetadata(std::string(), schedule_save_closure_.Get(),
bookmark_model_.get());
}
TestSyncClient* sync_client() { return &sync_client_; } TestSyncClient* sync_client() { return &sync_client_; }
bookmarks::BookmarkModel* bookmark_model() { return bookmark_model_.get(); } bookmarks::BookmarkModel* bookmark_model() { return bookmark_model_.get(); }
BookmarkModelTypeProcessor* processor() { return &processor_; }
base::MockCallback<base::RepeatingClosure>* schedule_save_closure() {
return &schedule_save_closure_;
}
private: private:
std::unique_ptr<bookmarks::BookmarkModel> bookmark_model_; std::unique_ptr<bookmarks::BookmarkModel> bookmark_model_;
TestSyncClient sync_client_; TestSyncClient sync_client_;
NiceMock<base::MockCallback<base::RepeatingClosure>> schedule_save_closure_;
BookmarkModelTypeProcessor processor_;
}; };
TEST_F(BookmarkModelTypeProcessorTest, ReorderUpdatesShouldIgnoreRootNodes) { TEST(BookmarkModelTypeProcessorReorderUpdatesTest, ShouldIgnoreRootNodes) {
syncer::UpdateResponseDataList updates; syncer::UpdateResponseDataList updates;
updates.push_back(CreateBookmarkRootUpdateData()); updates.push_back(CreateBookmarkRootUpdateData());
std::vector<const syncer::UpdateResponseData*> ordered_updates = std::vector<const syncer::UpdateResponseData*> ordered_updates =
...@@ -151,8 +164,8 @@ TEST_F(BookmarkModelTypeProcessorTest, ReorderUpdatesShouldIgnoreRootNodes) { ...@@ -151,8 +164,8 @@ TEST_F(BookmarkModelTypeProcessorTest, ReorderUpdatesShouldIgnoreRootNodes) {
// TODO(crbug.com/516866): This should change to cover the general case of // TODO(crbug.com/516866): This should change to cover the general case of
// parents before children for non-deletions, and another test should be added // parents before children for non-deletions, and another test should be added
// for children before parents for deletions. // for children before parents for deletions.
TEST_F(BookmarkModelTypeProcessorTest, TEST(BookmarkModelTypeProcessorReorderUpdatesTest,
ReorderUpdatesShouldPlacePermanentNodesFirstForNonDeletions) { ShouldPlacePermanentNodesFirstForNonDeletions) {
const std::string kNode1Id = "node1"; const std::string kNode1Id = "node1";
const std::string kNode2Id = "node2"; const std::string kNode2Id = "node2";
syncer::UpdateResponseDataList updates; syncer::UpdateResponseDataList updates;
...@@ -172,28 +185,27 @@ TEST_F(BookmarkModelTypeProcessorTest, ...@@ -172,28 +185,27 @@ TEST_F(BookmarkModelTypeProcessorTest,
} }
TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteCreation) { TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteCreation) {
BookmarkModelTypeProcessor processor(
sync_client()->GetBookmarkModel(),
sync_client()->GetBookmarkUndoServiceIfExists());
syncer::UpdateResponseDataList updates; syncer::UpdateResponseDataList updates;
// Add update for the permanent folder "Bookmarks bar". // Add update for the permanent folder "Bookmarks bar".
updates.push_back( updates.push_back(
CreateUpdateData({"bookmark_bar", std::string(), std::string(), CreateUpdateData({kBookmarkBarId, std::string(), std::string(),
kBookmarksRootId, kBookmarkBarTag})); kBookmarksRootId, kBookmarkBarTag}));
// Add update for another node under the bookmarks bar. // Add update for another node under the bookmarks bar.
const std::string kNodeId = "node_id"; const std::string kNodeId = "node_id";
const std::string kTitle = "title"; const std::string kTitle = "title";
const std::string kUrl = "http://www.url.com"; const std::string kUrl = "http://www.url.com";
updates.push_back(CreateUpdateData({kNodeId, kTitle, kUrl, kBookmarkBarTag, updates.push_back(CreateUpdateData({kNodeId, kTitle, kUrl, kBookmarkBarId,
/*server_tag=*/std::string()})); /*server_tag=*/std::string()}));
const bookmarks::BookmarkNode* bookmarkbar = const bookmarks::BookmarkNode* bookmarkbar =
bookmark_model()->bookmark_bar_node(); bookmark_model()->bookmark_bar_node();
EXPECT_TRUE(bookmarkbar->empty()); EXPECT_TRUE(bookmarkbar->empty());
processor.OnUpdateReceived(sync_pb::ModelTypeState(), updates); // Save will be scheduled in the model upon model change. No save should be
// scheduled from the processor.
EXPECT_CALL(*schedule_save_closure(), Run()).Times(0);
processor()->OnUpdateReceived(sync_pb::ModelTypeState(), updates);
ASSERT_THAT(bookmarkbar->GetChild(0), NotNull()); ASSERT_THAT(bookmarkbar->GetChild(0), NotNull());
EXPECT_THAT(bookmarkbar->GetChild(0)->GetTitle(), Eq(ASCIIToUTF16(kTitle))); EXPECT_THAT(bookmarkbar->GetChild(0)->GetTitle(), Eq(ASCIIToUTF16(kTitle)));
...@@ -201,18 +213,14 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteCreation) { ...@@ -201,18 +213,14 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteCreation) {
} }
TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteUpdate) { TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteUpdate) {
BookmarkModelTypeProcessor processor(
sync_client()->GetBookmarkModel(),
sync_client()->GetBookmarkUndoServiceIfExists());
const std::string kNodeId = "node_id"; const std::string kNodeId = "node_id";
const std::string kTitle = "title"; const std::string kTitle = "title";
const std::string kUrl = "http://www.url.com"; const std::string kUrl = "http://www.url.com";
std::vector<BookmarkInfo> bookmarks = { std::vector<BookmarkInfo> bookmarks = {
{kNodeId, kTitle, kUrl, kBookmarkBarTag, /*server_tag=*/std::string()}}; {kNodeId, kTitle, kUrl, kBookmarkBarId, /*server_tag=*/std::string()}};
InitWithSyncedBookmarks(bookmarks, &processor); InitWithSyncedBookmarks(bookmarks, processor());
// Make sure original bookmark exists. // Make sure original bookmark exists.
const bookmarks::BookmarkNode* bookmark_bar = const bookmarks::BookmarkNode* bookmark_bar =
...@@ -227,9 +235,13 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteUpdate) { ...@@ -227,9 +235,13 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteUpdate) {
const std::string kNewUrl = "http://www.new-url.com"; const std::string kNewUrl = "http://www.new-url.com";
syncer::UpdateResponseDataList updates; syncer::UpdateResponseDataList updates;
updates.push_back( updates.push_back(
CreateUpdateData({kNodeId, kNewTitle, kNewUrl, kBookmarkBarTag, CreateUpdateData({kNodeId, kNewTitle, kNewUrl, kBookmarkBarId,
/*server_tag=*/std::string()})); /*server_tag=*/std::string()}));
processor.OnUpdateReceived(sync_pb::ModelTypeState(), updates);
// Save will be scheduled in the model upon model change. No save should be
// scheduled from the processor.
EXPECT_CALL(*schedule_save_closure(), Run()).Times(0);
processor()->OnUpdateReceived(sync_pb::ModelTypeState(), updates);
// Check if the bookmark has been updated properly. // Check if the bookmark has been updated properly.
EXPECT_THAT(bookmark_bar->GetChild(0), Eq(bookmark_node)); EXPECT_THAT(bookmark_bar->GetChild(0), Eq(bookmark_node));
...@@ -237,10 +249,34 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteUpdate) { ...@@ -237,10 +249,34 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteUpdate) {
EXPECT_THAT(bookmark_node->url(), Eq(GURL(kNewUrl))); EXPECT_THAT(bookmark_node->url(), Eq(GURL(kNewUrl)));
} }
TEST_F(BookmarkModelTypeProcessorTest,
ShouldScheduleSaveAfterRemoteUpdateWithOnlyMetadataChange) {
const std::string kNodeId = "node_id";
const std::string kTitle = "title";
const std::string kUrl = "http://www.url.com";
std::vector<BookmarkInfo> bookmarks = {
{kNodeId, kTitle, kUrl, kBookmarkBarId, /*server_tag=*/std::string()}};
InitWithSyncedBookmarks(bookmarks, processor());
// Make sure original bookmark exists.
const bookmarks::BookmarkNode* bookmark_bar =
bookmark_model()->bookmark_bar_node();
const bookmarks::BookmarkNode* bookmark_node = bookmark_bar->GetChild(0);
ASSERT_THAT(bookmark_node, NotNull());
// Process an update for the same bookmark with the same data.
syncer::UpdateResponseDataList updates;
updates.push_back(CreateUpdateData({kNodeId, kTitle, kUrl, kBookmarkBarId,
/*server_tag=*/std::string()}));
updates[0].response_version++;
EXPECT_CALL(*schedule_save_closure(), Run());
processor()->OnUpdateReceived(sync_pb::ModelTypeState(), updates);
}
TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteDelete) { TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteDelete) {
BookmarkModelTypeProcessor processor(
sync_client()->GetBookmarkModel(),
sync_client()->GetBookmarkUndoServiceIfExists());
// Build this structure // Build this structure
// bookmark_bar // bookmark_bar
// |- folder1 // |- folder1
...@@ -266,16 +302,16 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteDelete) { ...@@ -266,16 +302,16 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteDelete) {
const std::string kUrl = "http://www.url.com"; const std::string kUrl = "http://www.url.com";
std::vector<BookmarkInfo> bookmarks = { std::vector<BookmarkInfo> bookmarks = {
{kFolder1Id, kFolder1, /*url=*/std::string(), kBookmarkBarTag, {kFolder1Id, kFolder1, /*url=*/std::string(), kBookmarkBarId,
/*server_tag=*/std::string()}, /*server_tag=*/std::string()},
{kTitle1Id, kTitle1, kUrl, kFolder1Id, /*server_tag=*/std::string()}, {kTitle1Id, kTitle1, kUrl, kFolder1Id, /*server_tag=*/std::string()},
{kTitle2Id, kTitle2, kUrl, kFolder1Id, /*server_tag=*/std::string()}, {kTitle2Id, kTitle2, kUrl, kFolder1Id, /*server_tag=*/std::string()},
{kFolder2Id, kFolder2, /*url=*/std::string(), kBookmarkBarTag, {kFolder2Id, kFolder2, /*url=*/std::string(), kBookmarkBarId,
/*server_tag=*/std::string()}, /*server_tag=*/std::string()},
{kTitle3Id, kTitle3, kUrl, kFolder2Id, /*server_tag=*/std::string()}, {kTitle3Id, kTitle3, kUrl, kFolder2Id, /*server_tag=*/std::string()},
}; };
InitWithSyncedBookmarks(bookmarks, &processor); InitWithSyncedBookmarks(bookmarks, processor());
const bookmarks::BookmarkNode* bookmarkbar = const bookmarks::BookmarkNode* bookmarkbar =
bookmark_model()->bookmark_bar_node(); bookmark_model()->bookmark_bar_node();
...@@ -295,7 +331,10 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteDelete) { ...@@ -295,7 +331,10 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteDelete) {
updates.push_back(CreateTombstone(kFolder1Id)); updates.push_back(CreateTombstone(kFolder1Id));
const sync_pb::ModelTypeState model_type_state; const sync_pb::ModelTypeState model_type_state;
processor.OnUpdateReceived(model_type_state, updates); // Save will be scheduled in the model upon model change. No save should be
// scheduled from the processor.
EXPECT_CALL(*schedule_save_closure(), Run()).Times(0);
processor()->OnUpdateReceived(model_type_state, updates);
// The structure should be // The structure should be
// bookmark_bar // bookmark_bar
...@@ -308,6 +347,155 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteDelete) { ...@@ -308,6 +347,155 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteDelete) {
Eq(ASCIIToUTF16(kTitle3))); Eq(ASCIIToUTF16(kTitle3)));
} }
TEST_F(BookmarkModelTypeProcessorTest, ShouldEncodeSyncMetadata) {
const std::string kNodeId1 = "node_id1";
const std::string kTitle1 = "title1";
const std::string kUrl1 = "http://www.url1.com";
const std::string kNodeId2 = "node_id2";
const std::string kTitle2 = "title2";
const std::string kUrl2 = "http://www.url2.com";
std::vector<BookmarkInfo> bookmarks = {
{kNodeId1, kTitle1, kUrl1, kBookmarkBarId, /*server_tag=*/std::string()},
{kNodeId2, kTitle2, kUrl2, kBookmarkBarId,
/*server_tag=*/std::string()}};
InitWithSyncedBookmarks(bookmarks, processor());
// Make sure original bookmark exists.
const bookmarks::BookmarkNode* bookmark_bar =
bookmark_model()->bookmark_bar_node();
const bookmarks::BookmarkNode* bookmark_node1 = bookmark_bar->GetChild(0);
const bookmarks::BookmarkNode* bookmark_node2 = bookmark_bar->GetChild(1);
ASSERT_THAT(bookmark_node1, NotNull());
ASSERT_THAT(bookmark_node2, NotNull());
std::string metadata_str = processor()->EncodeSyncMetadata();
sync_pb::BookmarkModelMetadata model_metadata;
EXPECT_TRUE(model_metadata.ParseFromString(metadata_str));
// There should be 3 entries now, one for the bookmark bar, and the other 2
// nodes.
ASSERT_THAT(model_metadata.bookmarks_metadata().size(), Eq(3));
EXPECT_THAT(model_metadata.bookmarks_metadata().Get(0).id(),
Eq(bookmark_bar->id()));
EXPECT_THAT(model_metadata.bookmarks_metadata().Get(0).metadata().server_id(),
Eq(kBookmarkBarId));
EXPECT_THAT(
model_metadata.bookmarks_metadata().Get(0).metadata().is_deleted(),
Eq(false));
EXPECT_THAT(model_metadata.bookmarks_metadata().Get(1).id(),
Eq(bookmark_node1->id()));
EXPECT_THAT(model_metadata.bookmarks_metadata().Get(1).metadata().server_id(),
Eq(kNodeId1));
EXPECT_THAT(
model_metadata.bookmarks_metadata().Get(1).metadata().is_deleted(),
Eq(false));
EXPECT_THAT(model_metadata.bookmarks_metadata().Get(2).id(),
Eq(bookmark_node2->id()));
EXPECT_THAT(model_metadata.bookmarks_metadata().Get(2).metadata().server_id(),
Eq(kNodeId2));
EXPECT_THAT(
model_metadata.bookmarks_metadata().Get(2).metadata().is_deleted(),
Eq(false));
// Process a remote delete for the first node.
syncer::UpdateResponseDataList updates;
updates.push_back(CreateTombstone(kNodeId1));
const sync_pb::ModelTypeState model_type_state;
processor()->OnUpdateReceived(model_type_state, updates);
metadata_str = processor()->EncodeSyncMetadata();
model_metadata.ParseFromString(metadata_str);
// There should be 3 entries now, one for the bookmark bar, and the remaning
// bookmark node.
ASSERT_THAT(model_metadata.bookmarks_metadata().size(), Eq(2));
EXPECT_THAT(model_metadata.bookmarks_metadata().Get(1).id(),
Eq(bookmark_node2->id()));
EXPECT_THAT(model_metadata.bookmarks_metadata().Get(1).metadata().server_id(),
Eq(kNodeId2));
EXPECT_THAT(
model_metadata.bookmarks_metadata().Get(1).metadata().is_deleted(),
Eq(false));
}
TEST_F(BookmarkModelTypeProcessorTest, ShouldDecodeSyncMetadata) {
const std::string kNodeId = "node_id1";
const std::string kTitle = "title1";
const std::string kUrl = "http://www.url1.com";
std::vector<BookmarkInfo> bookmarks = {
{kNodeId, kTitle, kUrl, kBookmarkBarId, /*server_tag=*/std::string()}};
const bookmarks::BookmarkNode* bookmark_bar_node =
bookmark_model()->bookmark_bar_node();
const bookmarks::BookmarkNode* bookmarknode = bookmark_model()->AddURL(
/*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle),
GURL(kUrl));
// TODO(crbug.com/516866): Remove this after initial sync done is properly set
// within the processor.
sync_pb::BookmarkModelMetadata model_metadata;
model_metadata.mutable_model_type_state()->set_initial_sync_done(true);
// Add an entry for bookmark bar.
sync_pb::BookmarkMetadata* bookmark_metadata =
model_metadata.add_bookmarks_metadata();
bookmark_metadata->set_id(bookmark_bar_node->id());
bookmark_metadata->mutable_metadata()->set_server_id(kBookmarkBarId);
// Add an entry for the bookmark node.
bookmark_metadata = model_metadata.add_bookmarks_metadata();
bookmark_metadata->set_id(bookmarknode->id());
bookmark_metadata->mutable_metadata()->set_server_id(kNodeId);
// Create a new processor and init it with the metadata str.
BookmarkModelTypeProcessor new_processor(
sync_client()->GetBookmarkUndoServiceIfExists());
std::string metadata_str;
model_metadata.SerializeToString(&metadata_str);
new_processor.DecodeSyncMetadata(metadata_str, base::DoNothing(),
bookmark_model());
AssertState(&new_processor, bookmarks);
}
TEST_F(BookmarkModelTypeProcessorTest, ShouldDecodeEncodedSyncMetadata) {
const std::string kNodeId1 = "node_id1";
const std::string kTitle1 = "title1";
const std::string kUrl1 = "http://www.url1.com";
const std::string kNodeId2 = "node_id2";
const std::string kTitle2 = "title2";
const std::string kUrl2 = "http://www.url2.com";
std::vector<BookmarkInfo> bookmarks = {
{kNodeId1, kTitle1, kUrl1, kBookmarkBarId, /*server_tag=*/std::string()},
{kNodeId2, kTitle2, kUrl2, kBookmarkBarId,
/*server_tag=*/std::string()}};
InitWithSyncedBookmarks(bookmarks, processor());
std::string metadata_str = processor()->EncodeSyncMetadata();
// TODO(crbug.com/516866): Remove this after initial sync done is properly set
// within the processor.
sync_pb::BookmarkModelMetadata model_metadata;
model_metadata.ParseFromString(metadata_str);
model_metadata.mutable_model_type_state()->set_initial_sync_done(true);
// Create a new processor and init it with the same metadata str.
BookmarkModelTypeProcessor new_processor(
sync_client()->GetBookmarkUndoServiceIfExists());
model_metadata.SerializeToString(&metadata_str);
new_processor.DecodeSyncMetadata(metadata_str, base::DoNothing(),
bookmark_model());
AssertState(&new_processor, bookmarks);
}
} // namespace } // namespace
} // namespace sync_bookmarks } // namespace sync_bookmarks
...@@ -6,30 +6,70 @@ ...@@ -6,30 +6,70 @@
#include <utility> #include <utility>
#include "base/base64.h"
#include "base/sha1.h"
#include "components/bookmarks/browser/bookmark_node.h"
#include "components/sync/base/time.h"
#include "components/sync/model/entity_data.h" #include "components/sync/model/entity_data.h"
namespace sync_bookmarks { namespace sync_bookmarks {
namespace {
void HashSpecifics(const sync_pb::EntitySpecifics& specifics,
std::string* hash) {
DCHECK_GT(specifics.ByteSize(), 0);
base::Base64Encode(base::SHA1HashString(specifics.SerializeAsString()), hash);
}
} // namespace
SyncedBookmarkTracker::Entity::Entity( SyncedBookmarkTracker::Entity::Entity(
const bookmarks::BookmarkNode* bookmark_node) const bookmarks::BookmarkNode* bookmark_node,
: bookmark_node_(bookmark_node) { std::unique_ptr<sync_pb::EntityMetadata> metadata)
DCHECK(bookmark_node); : bookmark_node_(bookmark_node), metadata_(std::move(metadata)) {
if (bookmark_node) {
DCHECK(!metadata_->is_deleted());
} else {
DCHECK(metadata_->is_deleted());
}
} }
SyncedBookmarkTracker::Entity::~Entity() = default; SyncedBookmarkTracker::Entity::~Entity() = default;
bool SyncedBookmarkTracker::Entity::MatchesData( bool SyncedBookmarkTracker::Entity::MatchesData(
const syncer::EntityData& data) const { const syncer::EntityData& data) const {
// TODO(crbug.com/516866): Implement properly. if (metadata_->is_deleted() || data.is_deleted()) {
return false; // In case of deletion, no need to check the specifics.
return metadata_->is_deleted() == data.is_deleted();
}
return MatchesSpecificsHash(data.specifics);
}
bool SyncedBookmarkTracker::Entity::MatchesSpecificsHash(
const sync_pb::EntitySpecifics& specifics) const {
DCHECK(!metadata_->is_deleted());
DCHECK_GT(specifics.ByteSize(), 0);
std::string hash;
HashSpecifics(specifics, &hash);
return hash == metadata_->specifics_hash();
} }
bool SyncedBookmarkTracker::Entity::IsUnsynced() const { bool SyncedBookmarkTracker::Entity::IsUnsynced() const {
// TODO(crbug.com/516866): Implement properly. return metadata_->sequence_number() > metadata_->acked_sequence_number();
return false; }
SyncedBookmarkTracker::SyncedBookmarkTracker(
std::vector<NodeMetadataPair> nodes_metadata,
std::unique_ptr<sync_pb::ModelTypeState> model_type_state)
: model_type_state_(std::move(model_type_state)) {
for (NodeMetadataPair& node_metadata : nodes_metadata) {
const std::string& sync_id = node_metadata.second->server_id();
sync_id_to_entities_map_[sync_id] = std::make_unique<Entity>(
node_metadata.first, std::move(node_metadata.second));
}
} }
SyncedBookmarkTracker::SyncedBookmarkTracker() = default;
SyncedBookmarkTracker::~SyncedBookmarkTracker() = default; SyncedBookmarkTracker::~SyncedBookmarkTracker() = default;
const SyncedBookmarkTracker::Entity* SyncedBookmarkTracker::GetEntityForSyncId( const SyncedBookmarkTracker::Entity* SyncedBookmarkTracker::GetEntityForSyncId(
...@@ -39,14 +79,56 @@ const SyncedBookmarkTracker::Entity* SyncedBookmarkTracker::GetEntityForSyncId( ...@@ -39,14 +79,56 @@ const SyncedBookmarkTracker::Entity* SyncedBookmarkTracker::GetEntityForSyncId(
} }
void SyncedBookmarkTracker::Add(const std::string& sync_id, void SyncedBookmarkTracker::Add(const std::string& sync_id,
const bookmarks::BookmarkNode* bookmark_node) { const bookmarks::BookmarkNode* bookmark_node,
sync_id_to_entities_map_[sync_id] = std::make_unique<Entity>(bookmark_node); int64_t server_version,
base::Time creation_time,
const sync_pb::EntitySpecifics& specifics) {
DCHECK_GT(specifics.ByteSize(), 0);
auto metadata = std::make_unique<sync_pb::EntityMetadata>();
metadata->set_is_deleted(false);
metadata->set_server_id(sync_id);
metadata->set_server_version(server_version);
metadata->set_creation_time(syncer::TimeToProtoTime(creation_time));
HashSpecifics(specifics, metadata->mutable_specifics_hash());
sync_id_to_entities_map_[sync_id] =
std::make_unique<Entity>(bookmark_node, std::move(metadata));
}
void SyncedBookmarkTracker::Update(const std::string& sync_id,
int64_t server_version,
base::Time modification_time,
const sync_pb::EntitySpecifics& specifics) {
DCHECK_GT(specifics.ByteSize(), 0);
auto it = sync_id_to_entities_map_.find(sync_id);
Entity* entity = it->second.get();
DCHECK(entity);
entity->metadata()->set_server_id(sync_id);
entity->metadata()->set_server_version(server_version);
entity->metadata()->set_modification_time(
syncer::TimeToProtoTime(modification_time));
HashSpecifics(specifics, entity->metadata()->mutable_specifics_hash());
} }
void SyncedBookmarkTracker::Remove(const std::string& sync_id) { void SyncedBookmarkTracker::Remove(const std::string& sync_id) {
sync_id_to_entities_map_.erase(sync_id); sync_id_to_entities_map_.erase(sync_id);
} }
sync_pb::BookmarkModelMetadata
SyncedBookmarkTracker::BuildBookmarkModelMetadata() const {
sync_pb::BookmarkModelMetadata model_metadata;
for (const std::pair<const std::string, std::unique_ptr<Entity>>& pair :
sync_id_to_entities_map_) {
sync_pb::BookmarkMetadata* bookmark_metadata =
model_metadata.add_bookmarks_metadata();
if (pair.second->bookmark_node() != nullptr) {
bookmark_metadata->set_id(pair.second->bookmark_node()->id());
}
*bookmark_metadata->mutable_metadata() = *pair.second->metadata();
}
*model_metadata.mutable_model_type_state() = *model_type_state_;
return model_metadata;
}
std::size_t SyncedBookmarkTracker::TrackedEntitiesCountForTest() const { std::size_t SyncedBookmarkTracker::TrackedEntitiesCountForTest() const {
return sync_id_to_entities_map_.size(); return sync_id_to_entities_map_.size();
} }
......
...@@ -8,8 +8,12 @@ ...@@ -8,8 +8,12 @@
#include <map> #include <map>
#include <memory> #include <memory>
#include <string> #include <string>
#include <utility>
#include <vector>
#include "base/macros.h" #include "base/macros.h"
#include "base/time/time.h"
#include "components/sync/protocol/bookmark_model_metadata.pb.h"
#include "components/sync/protocol/entity_metadata.pb.h" #include "components/sync/protocol/entity_metadata.pb.h"
namespace bookmarks { namespace bookmarks {
...@@ -22,6 +26,9 @@ struct EntityData; ...@@ -22,6 +26,9 @@ struct EntityData;
namespace sync_bookmarks { namespace sync_bookmarks {
using NodeMetadataPair = std::pair<const bookmarks::BookmarkNode*,
std::unique_ptr<sync_pb::EntityMetadata>>;
// This class is responsible for keeping the mapping between bookmarks node in // This class is responsible for keeping the mapping between bookmarks node in
// the local model and the server-side corresponding sync entities. It manages // the local model and the server-side corresponding sync entities. It manages
// the metadata for its entity and caches entity data upon a local change until // the metadata for its entity and caches entity data upon a local change until
...@@ -30,8 +37,9 @@ class SyncedBookmarkTracker { ...@@ -30,8 +37,9 @@ class SyncedBookmarkTracker {
public: public:
class Entity { class Entity {
public: public:
// |bookmark_node| must not be null and must outlive this object. // |bookmark_node| can be null for tombstones. |metadata| must not be null.
explicit Entity(const bookmarks::BookmarkNode* bookmark_node); Entity(const bookmarks::BookmarkNode* bookmark_node,
std::unique_ptr<sync_pb::EntityMetadata> metadata);
~Entity(); ~Entity();
// Returns true if this data is out of sync with the server. // Returns true if this data is out of sync with the server.
...@@ -41,32 +49,55 @@ class SyncedBookmarkTracker { ...@@ -41,32 +49,55 @@ class SyncedBookmarkTracker {
// Check whether |data| matches the stored specifics hash. // Check whether |data| matches the stored specifics hash.
bool MatchesData(const syncer::EntityData& data) const; bool MatchesData(const syncer::EntityData& data) const;
// It never returns null. // Returns null for tomstones.
const bookmarks::BookmarkNode* bookmark_node() const { const bookmarks::BookmarkNode* bookmark_node() const {
return bookmark_node_; return bookmark_node_;
} }
const sync_pb::EntityMetadata* metadata() const { return metadata_.get(); }
sync_pb::EntityMetadata* metadata() { return metadata_.get(); }
private: private:
// Check whether |specifics| matches the stored specifics_hash.
bool MatchesSpecificsHash(const sync_pb::EntitySpecifics& specifics) const;
const bookmarks::BookmarkNode* const bookmark_node_; const bookmarks::BookmarkNode* const bookmark_node_;
// Serializable Sync metadata.
std::unique_ptr<sync_pb::EntityMetadata> metadata_;
DISALLOW_COPY_AND_ASSIGN(Entity); DISALLOW_COPY_AND_ASSIGN(Entity);
}; };
SyncedBookmarkTracker(); SyncedBookmarkTracker(
std::vector<NodeMetadataPair> nodes_metadata,
std::unique_ptr<sync_pb::ModelTypeState> model_type_state);
~SyncedBookmarkTracker(); ~SyncedBookmarkTracker();
// Returns null if not entity is found. // Returns null if not entity is found.
const Entity* GetEntityForSyncId(const std::string& sync_id) const; const Entity* GetEntityForSyncId(const std::string& sync_id) const;
// Adds an entry for the |sync_id| and the corresponding local bookmark node // Adds an entry for the |sync_id| and the corresponding local bookmark node
// in |sync_id_to_entities_map_|. // and metadata in |sync_id_to_entities_map_|.
void Add(const std::string& sync_id, void Add(const std::string& sync_id,
const bookmarks::BookmarkNode* bookmark_node); const bookmarks::BookmarkNode* bookmark_node,
int64_t server_version,
base::Time modification_time,
const sync_pb::EntitySpecifics& specifics);
// Adds an existing entry for the |sync_id| and the corresponding metadata in
// |sync_id_to_entities_map_|.
void Update(const std::string& sync_id,
int64_t server_version,
base::Time modification_time,
const sync_pb::EntitySpecifics& specifics);
// Removes the entry coressponding to the |sync_id| from // Removes the entry coressponding to the |sync_id| from
// |sync_id_to_entities_map_|. // |sync_id_to_entities_map_|.
void Remove(const std::string& sync_id); void Remove(const std::string& sync_id);
sync_pb::BookmarkModelMetadata BuildBookmarkModelMetadata() const;
// Returns number of tracked entities. Used only in test. // Returns number of tracked entities. Used only in test.
std::size_t TrackedEntitiesCountForTest() const; std::size_t TrackedEntitiesCountForTest() const;
...@@ -77,6 +108,9 @@ class SyncedBookmarkTracker { ...@@ -77,6 +108,9 @@ class SyncedBookmarkTracker {
// contain model type data/specifics. // contain model type data/specifics.
std::map<std::string, std::unique_ptr<Entity>> sync_id_to_entities_map_; std::map<std::string, std::unique_ptr<Entity>> sync_id_to_entities_map_;
// The model metadata (progress marker, initial sync done, etc).
std::unique_ptr<sync_pb::ModelTypeState> model_type_state_;
DISALLOW_COPY_AND_ASSIGN(SyncedBookmarkTracker); DISALLOW_COPY_AND_ASSIGN(SyncedBookmarkTracker);
}; };
......
...@@ -4,7 +4,11 @@ ...@@ -4,7 +4,11 @@
#include "components/sync_bookmarks/synced_bookmark_tracker.h" #include "components/sync_bookmarks/synced_bookmark_tracker.h"
#include "base/base64.h"
#include "base/sha1.h"
#include "components/bookmarks/browser/bookmark_node.h" #include "components/bookmarks/browser/bookmark_node.h"
#include "components/sync/base/time.h"
#include "components/sync/model/entity_data.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -16,25 +20,55 @@ namespace sync_bookmarks { ...@@ -16,25 +20,55 @@ namespace sync_bookmarks {
namespace { namespace {
sync_pb::EntitySpecifics GenerateSpecifics(const std::string& title,
const std::string& url) {
sync_pb::EntitySpecifics specifics;
specifics.mutable_bookmark()->set_title(title);
specifics.mutable_bookmark()->set_url(url);
return specifics;
}
TEST(SyncedBookmarkTrackerTest, ShouldGetAssociatedNodes) { TEST(SyncedBookmarkTrackerTest, ShouldGetAssociatedNodes) {
SyncedBookmarkTracker tracker; SyncedBookmarkTracker tracker(std::vector<NodeMetadataPair>(),
std::make_unique<sync_pb::ModelTypeState>());
const std::string kSyncId = "SYNC_ID"; const std::string kSyncId = "SYNC_ID";
const std::string kTitle = "Title";
const GURL kUrl("http://www.foo.com");
const int64_t kId = 1; const int64_t kId = 1;
bookmarks::BookmarkNode node(kId, GURL()); const int64_t kServerVersion = 1000;
tracker.Add(kSyncId, &node); const base::Time kCreationTime(base::Time::Now() -
base::TimeDelta::FromSeconds(1));
const sync_pb::EntitySpecifics specifics =
GenerateSpecifics(std::string(), std::string());
bookmarks::BookmarkNode node(kId, kUrl);
tracker.Add(kSyncId, &node, kServerVersion, kCreationTime, specifics);
const SyncedBookmarkTracker::Entity* entity = const SyncedBookmarkTracker::Entity* entity =
tracker.GetEntityForSyncId(kSyncId); tracker.GetEntityForSyncId(kSyncId);
ASSERT_THAT(entity, NotNull()); ASSERT_THAT(entity, NotNull());
EXPECT_THAT(entity->bookmark_node(), Eq(&node)); EXPECT_THAT(entity->bookmark_node(), Eq(&node));
EXPECT_THAT(entity->metadata()->server_id(), Eq(kSyncId));
EXPECT_THAT(entity->metadata()->server_version(), Eq(kServerVersion));
EXPECT_THAT(entity->metadata()->creation_time(),
Eq(syncer::TimeToProtoTime(kCreationTime)));
syncer::EntityData data;
*data.specifics.mutable_bookmark() = specifics.bookmark();
EXPECT_TRUE(entity->MatchesData(data));
EXPECT_THAT(tracker.GetEntityForSyncId("unknown id"), IsNull()); EXPECT_THAT(tracker.GetEntityForSyncId("unknown id"), IsNull());
} }
TEST(SyncedBookmarkTrackerTest, ShouldReturnNullForDisassociatedNodes) { TEST(SyncedBookmarkTrackerTest, ShouldReturnNullForDisassociatedNodes) {
SyncedBookmarkTracker tracker; SyncedBookmarkTracker tracker(std::vector<NodeMetadataPair>(),
std::make_unique<sync_pb::ModelTypeState>());
const std::string kSyncId = "SYNC_ID"; const std::string kSyncId = "SYNC_ID";
const int64_t kId = 1; const int64_t kId = 1;
const int64_t kServerVersion = 1000;
const base::Time kModificationTime(base::Time::Now() -
base::TimeDelta::FromSeconds(1));
const sync_pb::EntitySpecifics specifics =
GenerateSpecifics(std::string(), std::string());
bookmarks::BookmarkNode node(kId, GURL()); bookmarks::BookmarkNode node(kId, GURL());
tracker.Add(kSyncId, &node); tracker.Add(kSyncId, &node, kServerVersion, kModificationTime, specifics);
ASSERT_THAT(tracker.GetEntityForSyncId(kSyncId), NotNull()); ASSERT_THAT(tracker.GetEntityForSyncId(kSyncId), NotNull());
tracker.Remove(kSyncId); tracker.Remove(kSyncId);
EXPECT_THAT(tracker.GetEntityForSyncId(kSyncId), IsNull()); EXPECT_THAT(tracker.GetEntityForSyncId(kSyncId), IsNull());
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment