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() {
if (base::FeatureList::IsEnabled(switches::kSyncUSSBookmarks)) {
bookmark_model_type_processor_ =
std::make_unique<sync_bookmarks::BookmarkModelTypeProcessor>(
sync_client_->GetBookmarkModel(),
sync_client_->GetBookmarkUndoServiceIfExists());
bookmark_model_type_processor_->DecodeSyncMetadata(
std::string(), base::DoNothing(), sync_client_->GetBookmarkModel());
}
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 = [
"//components/sync/protocol/arc_package_specifics.proto",
"//components/sync/protocol/article_specifics.proto",
"//components/sync/protocol/autofill_specifics.proto",
"//components/sync/protocol/bookmark_model_metadata.proto",
"//components/sync/protocol/bookmark_specifics.proto",
"//components/sync/protocol/client_commands.proto",
"//components/sync/protocol/client_debug_info.proto",
......
......@@ -41,6 +41,7 @@ source_set("unit_tests") {
deps = [
":sync_bookmarks",
"//base",
"//base/test:test_support",
"//components/bookmarks/browser",
"//components/bookmarks/test",
"//components/history/core/browser",
......
......@@ -10,9 +10,11 @@
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.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/engine/commit_queue.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"
namespace sync_bookmarks {
......@@ -92,6 +94,25 @@ const bookmarks::BookmarkNode* CreateBookmarkNode(
// 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 {
public:
// |bookmark_model| and |bookmark_undo_service| must not be null and must
......@@ -126,11 +147,8 @@ class ScopedRemoteUpdateBookmarks {
} // namespace
BookmarkModelTypeProcessor::BookmarkModelTypeProcessor(
bookmarks::BookmarkModel* bookmark_model,
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;
......@@ -138,6 +156,9 @@ void BookmarkModelTypeProcessor::ConnectSync(
std::unique_ptr<syncer::CommitQueue> worker) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!worker_);
DCHECK(bookmark_model_);
DCHECK(bookmark_tracker_);
worker_ = std::move(worker);
}
......@@ -171,18 +192,18 @@ void BookmarkModelTypeProcessor::OnUpdateReceived(
bookmark_undo_service_);
for (const syncer::UpdateResponseData* update : ReorderUpdates(updates)) {
const syncer::EntityData& update_data = update->entity.value();
// TODO(crbug.com/516866): Check |update_data| for sanity.
const syncer::EntityData& update_entity = update->entity.value();
// TODO(crbug.com/516866): Check |update_entity| for sanity.
// 1. Has bookmark specifics or no specifics in case of delete.
// 2. All meta info entries in the specifics have unique keys.
const SyncedBookmarkTracker::Entity* tracked_entity =
bookmark_tracker_.GetEntityForSyncId(update_data.id);
if (update_data.is_deleted()) {
ProcessRemoteDelete(update_data, tracked_entity);
bookmark_tracker_->GetEntityForSyncId(update_entity.id);
if (update_entity.is_deleted()) {
ProcessRemoteDelete(update_entity, tracked_entity);
continue;
}
if (!tracked_entity) {
ProcessRemoteCreate(update_data);
ProcessRemoteCreate(*update);
continue;
}
// Ignore changes to the permanent nodes (e.g. bookmarks bar). We only care
......@@ -190,7 +211,7 @@ void BookmarkModelTypeProcessor::OnUpdateReceived(
if (bookmark_model_->is_permanent_node(tracked_entity->bookmark_node())) {
continue;
}
ProcessRemoteUpdate(update_data, tracked_entity);
ProcessRemoteUpdate(*update, tracked_entity);
}
}
......@@ -203,7 +224,7 @@ BookmarkModelTypeProcessor::ReorderUpdatesForTest(
const SyncedBookmarkTracker* BookmarkModelTypeProcessor::GetTrackerForTest()
const {
return &bookmark_tracker_;
return bookmark_tracker_.get();
}
// static
......@@ -215,33 +236,33 @@ BookmarkModelTypeProcessor::ReorderUpdates(
// should be fixed before enabling USS for bookmarks.
std::vector<const syncer::UpdateResponseData*> ordered_updates;
for (const syncer::UpdateResponseData& update : updates) {
const syncer::EntityData& update_data = update.entity.value();
if (update_data.parent_id == "0") {
const syncer::EntityData& update_entity = update.entity.value();
if (update_entity.parent_id == "0") {
continue;
}
if (update_data.parent_id == kBookmarksRootId) {
if (update_entity.parent_id == kBookmarksRootId) {
ordered_updates.push_back(&update);
}
}
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.
if (update_data.is_deleted()) {
if (update_entity.is_deleted()) {
continue;
}
if (update_data.parent_id != "0" &&
update_data.parent_id != kBookmarksRootId) {
if (update_entity.parent_id != "0" &&
update_entity.parent_id != kBookmarksRootId) {
ordered_updates.push_back(&update);
}
}
// Now add deletions.
for (const syncer::UpdateResponseData& update : updates) {
const syncer::EntityData& update_data = update.entity.value();
if (!update_data.is_deleted()) {
const syncer::EntityData& update_entity = update.entity.value();
if (!update_entity.is_deleted()) {
continue;
}
if (update_data.parent_id != "0" &&
update_data.parent_id != kBookmarksRootId) {
if (update_entity.parent_id != "0" &&
update_entity.parent_id != kBookmarksRootId) {
ordered_updates.push_back(&update);
}
}
......@@ -249,25 +270,34 @@ BookmarkModelTypeProcessor::ReorderUpdates(
}
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
// 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
// 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
// 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.
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;
}
const bookmarks::BookmarkNode* parent_node = GetParentNode(update_data);
if (!IsValidBookmark(update_entity.specifics.bookmark(),
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 we cannot find the parent, we can do nothing.
DLOG(ERROR) << "Could not find parent of node being added."
<< " Node title: " << update_data.specifics.bookmark().title()
<< ", parent id = " << update_data.parent_id;
<< " Node title: " << update_entity.specifics.bookmark().title()
<< ", parent id = " << update_entity.parent_id;
return;
}
// TODO(crbug.com/516866): This code appends the code to the very end of the
......@@ -275,68 +305,80 @@ void BookmarkModelTypeProcessor::ProcessRemoteCreate(
// parent_node->child_count(). It should instead compute the exact using the
// unique position information of the new node as well as the siblings.
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) {
// We ignore bookmarks we can't add.
DLOG(ERROR) << "Failed to create bookmark node with title "
<< update_data.specifics.bookmark().title() << " and url "
<< update_data.specifics.bookmark().url();
<< update_entity.specifics.bookmark().title() << " and url "
<< update_entity.specifics.bookmark().url();
return;
}
bookmark_tracker_.Add(update_data.id, bookmark_node);
// TODO(crbug.com/516866): Update metadata (e.g. server version,
// specifics_hash).
bookmark_tracker_->Add(update_entity.id, bookmark_node,
update.response_version, update_entity.creation_time,
update_entity.specifics);
}
void BookmarkModelTypeProcessor::ProcessRemoteUpdate(
const syncer::EntityData& update_data,
const syncer::UpdateResponseData& update,
const SyncedBookmarkTracker::Entity* tracked_entity) {
const syncer::EntityData& update_entity = update.entity.value();
// Can only update existing nodes.
DCHECK(tracked_entity);
DCHECK_EQ(tracked_entity,
bookmark_tracker_.GetEntityForSyncId(update_data.id));
// Must no be a deletion
DCHECK(!update_data.is_deleted());
bookmark_tracker_->GetEntityForSyncId(update_entity.id));
// Must not be a deletion.
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()) {
// TODO(crbug.com/516866): Handle conflict resolution.
return;
}
if (tracked_entity->MatchesData(update_data)) {
// TODO(crbug.com/516866): Update metadata (e.g. server version,
// specifics_hash).
if (tracked_entity->MatchesData(update_entity)) {
bookmark_tracker_->Update(update_entity.id, update.response_version,
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;
}
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 "
<< (update_data.is_folder ? "folder" : "bookmark")
<< (update_entity.is_folder ? "folder" : "bookmark")
<< " while local node is a "
<< (node->is_folder() ? "folder" : "bookmark");
return;
}
const sync_pb::BookmarkSpecifics& specifics =
update_data.specifics.bookmark();
if (!update_data.is_folder) {
update_entity.specifics.bookmark();
if (!update_entity.is_folder) {
bookmark_model_->SetURL(node, GURL(specifics.url()));
}
bookmark_model_->SetTitle(node, base::UTF8ToUTF16(specifics.title()));
// TODO(crbug.com/516866): Add the favicon related code.
bookmark_model_->SetNodeMetaInfoMap(node, GetBookmarkMetaInfo(update_data));
// TODO(crbug.com/516866): Update metadata (e.g. server version,
// specifics_hash).
bookmark_model_->SetNodeMetaInfoMap(node, GetBookmarkMetaInfo(update_entity));
bookmark_tracker_->Update(update_entity.id, update.response_version,
update_entity.modification_time,
update_entity.specifics);
// TODO(crbug.com/516866): Handle reparenting.
// 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)
}
void BookmarkModelTypeProcessor::ProcessRemoteDelete(
const syncer::EntityData& update_data,
const syncer::EntityData& update_entity,
const SyncedBookmarkTracker::Entity* tracked_entity) {
DCHECK(update_data.is_deleted());
DCHECK(update_entity.is_deleted());
DCHECK_EQ(tracked_entity,
bookmark_tracker_.GetEntityForSyncId(update_data.id));
bookmark_tracker_->GetEntityForSyncId(update_entity.id));
// Handle corner cases first.
if (tracked_entity == nullptr) {
......@@ -359,13 +401,13 @@ void BookmarkModelTypeProcessor::ProcessRemoteDelete(
}
bookmark_model_->Remove(node);
bookmark_tracker_.Remove(update_data.id);
bookmark_tracker_->Remove(update_entity.id);
}
const bookmarks::BookmarkNode* BookmarkModelTypeProcessor::GetParentNode(
const syncer::EntityData& update_data) const {
const syncer::EntityData& update_entity) const {
const SyncedBookmarkTracker::Entity* parent_entity =
bookmark_tracker_.GetEntityForSyncId(update_data.parent_id);
bookmark_tracker_->GetEntityForSyncId(update_entity.parent_id);
if (!parent_entity) {
return nullptr;
}
......@@ -373,23 +415,91 @@ const bookmarks::BookmarkNode* BookmarkModelTypeProcessor::GetParentNode(
}
void BookmarkModelTypeProcessor::AssociatePermanentFolder(
const syncer::EntityData& update_data) {
DCHECK_EQ(update_data.parent_id, kBookmarksRootId);
const syncer::UpdateResponseData& update) {
const syncer::EntityData& update_entity = update.entity.value();
DCHECK_EQ(update_entity.parent_id, kBookmarksRootId);
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();
} else if (update_data.server_defined_unique_tag == kOtherBookmarksTag) {
} else if (update_entity.server_defined_unique_tag == kOtherBookmarksTag) {
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();
}
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>
BookmarkModelTypeProcessor::GetWeakPtr() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
......@@ -403,6 +513,20 @@ void BookmarkModelTypeProcessor::OnSyncStarting(
DCHECK(start_callback);
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 =
std::make_unique<syncer::DataTypeActivationResponse>();
// TODO(crbug.com/516866): Read the model type state from persisted sync
......@@ -414,7 +538,7 @@ void BookmarkModelTypeProcessor::OnSyncStarting(
activation_context->type_processor =
std::make_unique<syncer::ModelTypeProcessorProxy>(
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() {
......
......@@ -6,8 +6,10 @@
#define COMPONENTS_SYNC_BOOKMARKS_BOOKMARK_MODEL_TYPE_PROCESSOR_H_
#include <memory>
#include <string>
#include <vector>
#include "base/callback_forward.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
......@@ -19,6 +21,7 @@ class BookmarkUndoService;
namespace bookmarks {
class BookmarkModel;
class BookmarkNode;
}
namespace sync_bookmarks {
......@@ -26,10 +29,9 @@ namespace sync_bookmarks {
class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor,
public syncer::ModelTypeControllerDelegate {
public:
// |bookmark_undo_service| and |bookmark_undo_service| must not be nullptr and
// must outlive this object.
BookmarkModelTypeProcessor(bookmarks::BookmarkModel* bookmark_model,
BookmarkUndoService* bookmark_undo_service);
// |bookmark_undo_service| must not be nullptr and must outlive this object.
explicit BookmarkModelTypeProcessor(
BookmarkUndoService* bookmark_undo_service);
~BookmarkModelTypeProcessor() override;
// ModelTypeProcessor implementation.
......@@ -51,6 +53,21 @@ class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor,
void GetStatusCountersForDebugging(StatusCountersCallback callback) 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.
static std::vector<const syncer::UpdateResponseData*> ReorderUpdatesForTest(
const syncer::UpdateResponseDataList& updates);
......@@ -72,7 +89,7 @@ class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor,
// 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.
const bookmarks::BookmarkNode* GetParentNode(
const syncer::EntityData& update_data) const;
const syncer::EntityData& update_entity) const;
// Processes a remote creation of a bookmark node.
// 1. For permanent folders, they are only registered in |bookmark_tracker_|.
......@@ -80,40 +97,55 @@ class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor,
// ignored.
// 3. Otherwise, a new node is created in the local bookmark model and
// 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
// creation that gets handeled in ProcessRemoteCreate(). |tracked_entity| is
// the tracked entity for that server_id. It is passed as a dependency instead
// of performing a lookup inside ProcessRemoteUpdate() to avoid wasting CPU
// 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);
// 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
// passed as a dependency instead of performing a lookup inside
// ProcessRemoteDelete() to avoid wasting CPU cycles for doing another lookup
// (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);
// Associates the permanent bookmark folders with the corresponding server
// 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
// to be directly children nodes of |kBookmarksRootId|. This method is used in
// 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
// remote changes to.
bookmarks::BookmarkModel* const bookmark_model_;
// remote changes to. It is set during DecodeSyncMetadata(), which is called
// during startup, as part of the bookmark-loading process.
bookmarks::BookmarkModel* bookmark_model_ = nullptr;
// Used to suspend bookmark undo when processing remote changes.
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.
//
// The interface hides the posting of tasks across threads as well as the
......@@ -123,7 +155,9 @@ class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor,
// Keeps the mapping between server ids and bookmarks nodes. It also caches
// 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_;
......
......@@ -7,6 +7,7 @@
#include <string>
#include "base/strings/utf_string_conversions.h"
#include "base/test/mock_callback.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/test/test_bookmark_client.h"
#include "components/sync/driver/fake_sync_client.h"
......@@ -15,6 +16,7 @@
using base::ASCIIToUTF16;
using testing::Eq;
using testing::NiceMock;
using testing::NotNull;
namespace sync_bookmarks {
......@@ -25,6 +27,7 @@ namespace {
// referred to as top level enities.
const char kRootParentTag[] = "0";
const char kBookmarkBarTag[] = "bookmark_bar";
const char kBookmarkBarId[] = "bookmark_bar_id";
const char kBookmarksRootId[] = "32904_google_chrome_bookmarks";
struct BookmarkInfo {
......@@ -74,7 +77,7 @@ void AssertState(const BookmarkModelTypeProcessor* processor,
ASSERT_THAT(node->url(), Eq(GURL(bookmark.url)));
const SyncedBookmarkTracker::Entity* parent_entity =
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,
syncer::UpdateResponseDataList updates;
// Add update for the permanent folder "Bookmarks bar".
updates.push_back(
CreateUpdateData({"bookmark_bar", std::string(), std::string(),
CreateUpdateData({kBookmarkBarId, std::string(), std::string(),
kBookmarksRootId, kBookmarkBarTag}));
for (BookmarkInfo bookmark : bookmarks) {
updates.push_back(CreateUpdateData(bookmark));
......@@ -129,17 +132,27 @@ class BookmarkModelTypeProcessorTest : public testing::Test {
public:
BookmarkModelTypeProcessorTest()
: 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_; }
bookmarks::BookmarkModel* bookmark_model() { return bookmark_model_.get(); }
BookmarkModelTypeProcessor* processor() { return &processor_; }
base::MockCallback<base::RepeatingClosure>* schedule_save_closure() {
return &schedule_save_closure_;
}
private:
std::unique_ptr<bookmarks::BookmarkModel> bookmark_model_;
TestSyncClient sync_client_;
NiceMock<base::MockCallback<base::RepeatingClosure>> schedule_save_closure_;
BookmarkModelTypeProcessor processor_;
};
TEST_F(BookmarkModelTypeProcessorTest, ReorderUpdatesShouldIgnoreRootNodes) {
TEST(BookmarkModelTypeProcessorReorderUpdatesTest, ShouldIgnoreRootNodes) {
syncer::UpdateResponseDataList updates;
updates.push_back(CreateBookmarkRootUpdateData());
std::vector<const syncer::UpdateResponseData*> ordered_updates =
......@@ -151,8 +164,8 @@ TEST_F(BookmarkModelTypeProcessorTest, ReorderUpdatesShouldIgnoreRootNodes) {
// 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
// for children before parents for deletions.
TEST_F(BookmarkModelTypeProcessorTest,
ReorderUpdatesShouldPlacePermanentNodesFirstForNonDeletions) {
TEST(BookmarkModelTypeProcessorReorderUpdatesTest,
ShouldPlacePermanentNodesFirstForNonDeletions) {
const std::string kNode1Id = "node1";
const std::string kNode2Id = "node2";
syncer::UpdateResponseDataList updates;
......@@ -172,28 +185,27 @@ TEST_F(BookmarkModelTypeProcessorTest,
}
TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteCreation) {
BookmarkModelTypeProcessor processor(
sync_client()->GetBookmarkModel(),
sync_client()->GetBookmarkUndoServiceIfExists());
syncer::UpdateResponseDataList updates;
// Add update for the permanent folder "Bookmarks bar".
updates.push_back(
CreateUpdateData({"bookmark_bar", std::string(), std::string(),
CreateUpdateData({kBookmarkBarId, std::string(), std::string(),
kBookmarksRootId, kBookmarkBarTag}));
// Add update for another node under the bookmarks bar.
const std::string kNodeId = "node_id";
const std::string kTitle = "title";
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()}));
const bookmarks::BookmarkNode* bookmarkbar =
bookmark_model()->bookmark_bar_node();
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());
EXPECT_THAT(bookmarkbar->GetChild(0)->GetTitle(), Eq(ASCIIToUTF16(kTitle)));
......@@ -201,18 +213,14 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteCreation) {
}
TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteUpdate) {
BookmarkModelTypeProcessor processor(
sync_client()->GetBookmarkModel(),
sync_client()->GetBookmarkUndoServiceIfExists());
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, kBookmarkBarTag, /*server_tag=*/std::string()}};
{kNodeId, kTitle, kUrl, kBookmarkBarId, /*server_tag=*/std::string()}};
InitWithSyncedBookmarks(bookmarks, &processor);
InitWithSyncedBookmarks(bookmarks, processor());
// Make sure original bookmark exists.
const bookmarks::BookmarkNode* bookmark_bar =
......@@ -227,9 +235,13 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteUpdate) {
const std::string kNewUrl = "http://www.new-url.com";
syncer::UpdateResponseDataList updates;
updates.push_back(
CreateUpdateData({kNodeId, kNewTitle, kNewUrl, kBookmarkBarTag,
CreateUpdateData({kNodeId, kNewTitle, kNewUrl, kBookmarkBarId,
/*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.
EXPECT_THAT(bookmark_bar->GetChild(0), Eq(bookmark_node));
......@@ -237,10 +249,34 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteUpdate) {
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) {
BookmarkModelTypeProcessor processor(
sync_client()->GetBookmarkModel(),
sync_client()->GetBookmarkUndoServiceIfExists());
// Build this structure
// bookmark_bar
// |- folder1
......@@ -266,16 +302,16 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteDelete) {
const std::string kUrl = "http://www.url.com";
std::vector<BookmarkInfo> bookmarks = {
{kFolder1Id, kFolder1, /*url=*/std::string(), kBookmarkBarTag,
{kFolder1Id, kFolder1, /*url=*/std::string(), kBookmarkBarId,
/*server_tag=*/std::string()},
{kTitle1Id, kTitle1, 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()},
{kTitle3Id, kTitle3, kUrl, kFolder2Id, /*server_tag=*/std::string()},
};
InitWithSyncedBookmarks(bookmarks, &processor);
InitWithSyncedBookmarks(bookmarks, processor());
const bookmarks::BookmarkNode* bookmarkbar =
bookmark_model()->bookmark_bar_node();
......@@ -295,7 +331,10 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteDelete) {
updates.push_back(CreateTombstone(kFolder1Id));
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
// bookmark_bar
......@@ -308,6 +347,155 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteDelete) {
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 sync_bookmarks
......@@ -6,30 +6,70 @@
#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"
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(
const bookmarks::BookmarkNode* bookmark_node)
: bookmark_node_(bookmark_node) {
DCHECK(bookmark_node);
const bookmarks::BookmarkNode* bookmark_node,
std::unique_ptr<sync_pb::EntityMetadata> metadata)
: bookmark_node_(bookmark_node), metadata_(std::move(metadata)) {
if (bookmark_node) {
DCHECK(!metadata_->is_deleted());
} else {
DCHECK(metadata_->is_deleted());
}
}
SyncedBookmarkTracker::Entity::~Entity() = default;
bool SyncedBookmarkTracker::Entity::MatchesData(
const syncer::EntityData& data) const {
// TODO(crbug.com/516866): Implement properly.
return false;
if (metadata_->is_deleted() || data.is_deleted()) {
// 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 {
// TODO(crbug.com/516866): Implement properly.
return false;
return metadata_->sequence_number() > metadata_->acked_sequence_number();
}
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;
const SyncedBookmarkTracker::Entity* SyncedBookmarkTracker::GetEntityForSyncId(
......@@ -39,14 +79,56 @@ const SyncedBookmarkTracker::Entity* SyncedBookmarkTracker::GetEntityForSyncId(
}
void SyncedBookmarkTracker::Add(const std::string& sync_id,
const bookmarks::BookmarkNode* bookmark_node) {
sync_id_to_entities_map_[sync_id] = std::make_unique<Entity>(bookmark_node);
const bookmarks::BookmarkNode* 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) {
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 {
return sync_id_to_entities_map_.size();
}
......
......@@ -8,8 +8,12 @@
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#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"
namespace bookmarks {
......@@ -22,6 +26,9 @@ struct EntityData;
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
// 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
......@@ -30,8 +37,9 @@ class SyncedBookmarkTracker {
public:
class Entity {
public:
// |bookmark_node| must not be null and must outlive this object.
explicit Entity(const bookmarks::BookmarkNode* bookmark_node);
// |bookmark_node| can be null for tombstones. |metadata| must not be null.
Entity(const bookmarks::BookmarkNode* bookmark_node,
std::unique_ptr<sync_pb::EntityMetadata> metadata);
~Entity();
// Returns true if this data is out of sync with the server.
......@@ -41,32 +49,55 @@ class SyncedBookmarkTracker {
// Check whether |data| matches the stored specifics hash.
bool MatchesData(const syncer::EntityData& data) const;
// It never returns null.
// Returns null for tomstones.
const bookmarks::BookmarkNode* bookmark_node() const {
return bookmark_node_;
}
const sync_pb::EntityMetadata* metadata() const { return metadata_.get(); }
sync_pb::EntityMetadata* metadata() { return metadata_.get(); }
private:
// Check whether |specifics| matches the stored specifics_hash.
bool MatchesSpecificsHash(const sync_pb::EntitySpecifics& specifics) const;
const bookmarks::BookmarkNode* const bookmark_node_;
// Serializable Sync metadata.
std::unique_ptr<sync_pb::EntityMetadata> metadata_;
DISALLOW_COPY_AND_ASSIGN(Entity);
};
SyncedBookmarkTracker();
SyncedBookmarkTracker(
std::vector<NodeMetadataPair> nodes_metadata,
std::unique_ptr<sync_pb::ModelTypeState> model_type_state);
~SyncedBookmarkTracker();
// Returns null if not entity is found.
const Entity* GetEntityForSyncId(const std::string& sync_id) const;
// 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,
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
// |sync_id_to_entities_map_|.
void Remove(const std::string& sync_id);
sync_pb::BookmarkModelMetadata BuildBookmarkModelMetadata() const;
// Returns number of tracked entities. Used only in test.
std::size_t TrackedEntitiesCountForTest() const;
......@@ -77,6 +108,9 @@ class SyncedBookmarkTracker {
// contain model type data/specifics.
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);
};
......
......@@ -4,7 +4,11 @@
#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/sync/base/time.h"
#include "components/sync/model/entity_data.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -16,25 +20,55 @@ namespace sync_bookmarks {
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) {
SyncedBookmarkTracker tracker;
SyncedBookmarkTracker tracker(std::vector<NodeMetadataPair>(),
std::make_unique<sync_pb::ModelTypeState>());
const std::string kSyncId = "SYNC_ID";
const std::string kTitle = "Title";
const GURL kUrl("http://www.foo.com");
const int64_t kId = 1;
bookmarks::BookmarkNode node(kId, GURL());
tracker.Add(kSyncId, &node);
const int64_t kServerVersion = 1000;
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 =
tracker.GetEntityForSyncId(kSyncId);
ASSERT_THAT(entity, NotNull());
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());
}
TEST(SyncedBookmarkTrackerTest, ShouldReturnNullForDisassociatedNodes) {
SyncedBookmarkTracker tracker;
SyncedBookmarkTracker tracker(std::vector<NodeMetadataPair>(),
std::make_unique<sync_pb::ModelTypeState>());
const std::string kSyncId = "SYNC_ID";
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());
tracker.Add(kSyncId, &node);
tracker.Add(kSyncId, &node, kServerVersion, kModificationTime, specifics);
ASSERT_THAT(tracker.GetEntityForSyncId(kSyncId), NotNull());
tracker.Remove(kSyncId);
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