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

[Sync::USS] Commit Local Bookmark Creations


Bug: 516866
Change-Id: Ic1b7c5c411a37a93371ea623aa3f696b2c365e76
Reviewed-on: https://chromium-review.googlesource.com/1112254
Commit-Queue: Mohamed Amir Yosef <mamir@chromium.org>
Reviewed-by: default avatarMikel Astiz <mastiz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#571948}
parent daa7d423
...@@ -244,6 +244,48 @@ IN_PROC_BROWSER_TEST_F(SingleClientBookmarksSyncTest, Sanity) { ...@@ -244,6 +244,48 @@ IN_PROC_BROWSER_TEST_F(SingleClientBookmarksSyncTest, Sanity) {
VerifyBookmarkModelMatchesFakeServer(kSingleProfileIndex); VerifyBookmarkModelMatchesFakeServer(kSingleProfileIndex);
} }
IN_PROC_BROWSER_TEST_P(SingleClientBookmarksSyncTestIncludingUssTests,
CommitLocalCreations) {
ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
// Starting state:
// other_node
// -> top
// -> tier1_a
// -> http://mail.google.com "tier1_a_url0"
// -> http://www.pandora.com "tier1_a_url1"
// -> http://www.facebook.com "tier1_a_url2"
// -> tier1_b
// -> http://www.nhl.com "tier1_b_url0"
const BookmarkNode* top = AddFolder(
kSingleProfileIndex, GetOtherNode(kSingleProfileIndex), 0, "top");
const BookmarkNode* tier1_a =
AddFolder(kSingleProfileIndex, top, 0, "tier1_a");
const BookmarkNode* tier1_b =
AddFolder(kSingleProfileIndex, top, 1, "tier1_b");
const BookmarkNode* tier1_a_url0 =
AddURL(kSingleProfileIndex, tier1_a, 0, "tier1_a_url0",
GURL("http://mail.google.com"));
const BookmarkNode* tier1_a_url1 =
AddURL(kSingleProfileIndex, tier1_a, 1, "tier1_a_url1",
GURL("http://www.pandora.com"));
const BookmarkNode* tier1_a_url2 =
AddURL(kSingleProfileIndex, tier1_a, 2, "tier1_a_url2",
GURL("http://www.facebook.com"));
const BookmarkNode* tier1_b_url0 =
AddURL(kSingleProfileIndex, tier1_b, 0, "tier1_b_url0",
GURL("http://www.nhl.com"));
EXPECT_TRUE(tier1_a_url0);
EXPECT_TRUE(tier1_a_url1);
EXPECT_TRUE(tier1_a_url2);
EXPECT_TRUE(tier1_b_url0);
// Setup sync, wait for its completion, and make sure changes were synced.
ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
ASSERT_TRUE(
UpdatedProgressMarkerChecker(GetSyncService(kSingleProfileIndex)).Wait());
EXPECT_TRUE(ModelMatchesVerifier(kSingleProfileIndex));
}
IN_PROC_BROWSER_TEST_P(SingleClientBookmarksSyncTestIncludingUssTests, IN_PROC_BROWSER_TEST_P(SingleClientBookmarksSyncTestIncludingUssTests,
InjectedBookmark) { InjectedBookmark) {
std::string title = "Montreal Canadiens"; std::string title = "Montreal Canadiens";
......
...@@ -23,13 +23,17 @@ struct CommitRequestData { ...@@ -23,13 +23,17 @@ struct CommitRequestData {
CommitRequestData(const CommitRequestData& other); CommitRequestData(const CommitRequestData& other);
~CommitRequestData(); ~CommitRequestData();
// Fields sent to the sync server.
EntityDataPtr entity; EntityDataPtr entity;
int64_t base_version = 0;
// Fields not sent to the sync server. However, they are kept to be sent back
// to the processor in the response.
// Strictly incrementing number for in-progress commits. More information // Strictly incrementing number for in-progress commits.
// about its meaning can be found in comments in the files that make use of // More information about its meaning can be found in comments in the files
// this struct. // that make use of this struct.
int64_t sequence_number = 0; int64_t sequence_number = 0;
int64_t base_version = 0;
std::string specifics_hash; std::string specifics_hash;
}; };
...@@ -39,6 +43,10 @@ struct CommitResponseData { ...@@ -39,6 +43,10 @@ struct CommitResponseData {
~CommitResponseData(); ~CommitResponseData();
std::string id; std::string id;
// The sync id that was sent in the request. Non-empty only if different from
// |id|. It could be different because the server can change the sync id
// (e.g. for newly created bookmarks),
std::string id_in_request;
std::string client_tag_hash; std::string client_tag_hash;
int64_t sequence_number = 0; int64_t sequence_number = 0;
int64_t response_version = 0; int64_t response_version = 0;
......
...@@ -97,6 +97,12 @@ SyncerError NonBlockingTypeCommitContribution::ProcessCommitResponse( ...@@ -97,6 +97,12 @@ SyncerError NonBlockingTypeCommitContribution::ProcessCommitResponse(
CommitResponseData response_data; CommitResponseData response_data;
const CommitRequestData& commit_request = commit_requests_[i]; const CommitRequestData& commit_request = commit_requests_[i];
response_data.id = entry_response.id_string(); response_data.id = entry_response.id_string();
if (response_data.id != commit_request.entity->id) {
// Server has changed the sync id in the request. Write back the
// original sync id. This is useful for data types without a notion of
// a client tag such as bookmarks.
response_data.id_in_request = commit_request.entity->id;
}
response_data.response_version = entry_response.version(); response_data.response_version = entry_response.version();
response_data.client_tag_hash = commit_request.entity->client_tag_hash; response_data.client_tag_hash = commit_request.entity->client_tag_hash;
response_data.sequence_number = commit_request.sequence_number; response_data.sequence_number = commit_request.sequence_number;
...@@ -190,20 +196,17 @@ void NonBlockingTypeCommitContribution::PopulateCommitProto( ...@@ -190,20 +196,17 @@ void NonBlockingTypeCommitContribution::PopulateCommitProto(
void NonBlockingTypeCommitContribution::AdjustCommitProto( void NonBlockingTypeCommitContribution::AdjustCommitProto(
sync_pb::SyncEntity* commit_proto) { sync_pb::SyncEntity* commit_proto) {
// Initial commits need our help to generate a client ID.
if (commit_proto->version() == kUncommittedVersion) { if (commit_proto->version() == kUncommittedVersion) {
DCHECK(commit_proto->id_string().empty()) << commit_proto->id_string();
// TODO(crbug.com/516866): This is incorrect for bookmarks for two reasons:
// 1) Won't be able to match previously committed bookmarks to the ones
// with server ID.
// 2) Recommitting an item in a case of failing to receive commit response
// would result in generating a different client ID, which in turn
// would result in a duplication.
// We should generate client ID on the frontend side instead.
commit_proto->set_id_string(base::GenerateGUID());
commit_proto->set_version(0); commit_proto->set_version(0);
} else { // Initial commits need our help to generate a client ID if they don't have
DCHECK(!commit_proto->id_string().empty()); // any. Bookmarks create their own IDs on the frontend side to be able to
// match them after commits. For other data types we generate one here. And
// since bookmarks don't have client tags, their server id should be stable
// across restarts in case of recommitting an item, it doesn't result in
// creating a duplicate.
if (commit_proto->id_string().empty()) {
commit_proto->set_id_string(base::GenerateGUID());
}
} }
// Encrypt the specifics and hide the title if necessary. // Encrypt the specifics and hide the title if necessary.
......
...@@ -43,9 +43,9 @@ message EntityMetadata { ...@@ -43,9 +43,9 @@ message EntityMetadata {
// those changes are based. // those changes are based.
optional int64 server_version = 6 [default = -1]; optional int64 server_version = 6 [default = -1];
// Entity creation and modification timestamps. // Entity creation and modification timestamps. Assigned by the client and
// Assigned by the client and synced by the server, though the server usually // synced by the server, though the server usually doesn't bother to inspect
// doesn't bother to inspect their values. // their values. They are encoded as milliseconds since the Unix epoch.
optional int64 creation_time = 7; optional int64 creation_time = 7;
optional int64 modification_time = 8; optional int64 modification_time = 8;
......
...@@ -39,6 +39,7 @@ source_set("unit_tests") { ...@@ -39,6 +39,7 @@ source_set("unit_tests") {
sources = [ sources = [
"bookmark_data_type_controller_unittest.cc", "bookmark_data_type_controller_unittest.cc",
"bookmark_model_observer_impl_unittest.cc",
"bookmark_model_type_processor_unittest.cc", "bookmark_model_type_processor_unittest.cc",
"synced_bookmark_tracker_unittest.cc", "synced_bookmark_tracker_unittest.cc",
] ]
......
...@@ -4,6 +4,17 @@ ...@@ -4,6 +4,17 @@
#include "components/sync_bookmarks/bookmark_model_observer_impl.h" #include "components/sync_bookmarks/bookmark_model_observer_impl.h"
#include <utility>
#include "base/guid.h"
#include "base/strings/utf_string_conversions.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_node.h"
#include "components/sync/base/hash_util.h"
#include "components/sync/base/unique_position.h"
#include "components/sync/engine/non_blocking_sync_common.h"
#include "components/sync_bookmarks/synced_bookmark_tracker.h"
namespace sync_bookmarks { namespace sync_bookmarks {
BookmarkModelObserverImpl::BookmarkModelObserverImpl( BookmarkModelObserverImpl::BookmarkModelObserverImpl(
...@@ -40,7 +51,104 @@ void BookmarkModelObserverImpl::BookmarkNodeAdded( ...@@ -40,7 +51,104 @@ void BookmarkModelObserverImpl::BookmarkNodeAdded(
bookmarks::BookmarkModel* model, bookmarks::BookmarkModel* model,
const bookmarks::BookmarkNode* parent, const bookmarks::BookmarkNode* parent,
int index) { int index) {
NOTIMPLEMENTED(); const bookmarks::BookmarkNode* node = parent->GetChild(index);
// TODO(crbug.com/516866): continue only if
// model->client()->CanSyncNode(node).
const SyncedBookmarkTracker::Entity* parent_entity =
bookmark_tracker_->GetEntityForBookmarkNode(parent);
if (!parent_entity) {
DLOG(WARNING) << "Bookmark parent lookup failed";
return;
}
// Similar to the diectory implementation here:
// https://cs.chromium.org/chromium/src/components/sync/syncable/mutable_entry.cc?l=237&gsn=CreateEntryKernel
// Assign a temp server id for the entity. Will be overriden by the actual
// server id upon receiving commit response.
const std::string sync_id = base::GenerateGUID();
const int64_t server_version = syncer::kUncommittedVersion;
const base::Time creation_time = base::Time::Now();
const sync_pb::UniquePosition unique_position =
ComputePosition(*parent, index, sync_id).ToProto();
sync_pb::EntitySpecifics specifics;
sync_pb::BookmarkSpecifics* bm_specifics = specifics.mutable_bookmark();
bm_specifics->set_url(node->url().spec());
// TODO(crbug.com/516866): Set the favicon.
bm_specifics->set_title(base::UTF16ToUTF8(node->GetTitle()));
bm_specifics->set_creation_time_us(
node->date_added().ToDeltaSinceWindowsEpoch().InMicroseconds());
bm_specifics->set_icon_url(node->icon_url() ? node->icon_url()->spec()
: std::string());
// TODO(crbug.com/516866): update the implementation to be similar to the
// directory implementation
// https://cs.chromium.org/chromium/src/components/sync_bookmarks/bookmark_change_processor.cc?l=882&rcl=f38001d936d8b2abb5743e85cbc88c72746ae3d2
if (node->GetMetaInfoMap()) {
for (const std::pair<std::string, std::string>& pair :
*node->GetMetaInfoMap()) {
sync_pb::MetaInfo* meta_info = bm_specifics->add_meta_info();
meta_info->set_key(pair.first);
meta_info->set_value(pair.second);
}
}
bookmark_tracker_->Add(sync_id, node, server_version, creation_time,
unique_position, specifics);
// Mark the entity that it needs to be committed.
bookmark_tracker_->IncrementSequenceNumber(sync_id);
nudge_for_commit_closure_.Run();
}
syncer::UniquePosition BookmarkModelObserverImpl::ComputePosition(
const bookmarks::BookmarkNode& parent,
int index,
const std::string& sync_id) {
const std::string& suffix = syncer::GenerateSyncableBookmarkHash(
bookmark_tracker_->model_type_state().cache_guid(), sync_id);
DCHECK_NE(0, parent.child_count());
if (parent.child_count() == 1) {
// No siblings, the parent has no other children.
return syncer::UniquePosition::InitialPosition(suffix);
}
if (index == 0) {
const bookmarks::BookmarkNode* successor_node = parent.GetChild(1);
const SyncedBookmarkTracker::Entity* successor_entity =
bookmark_tracker_->GetEntityForBookmarkNode(successor_node);
DCHECK(successor_entity);
// Insert at the beginning.
return syncer::UniquePosition::Before(
syncer::UniquePosition::FromProto(
successor_entity->metadata()->unique_position()),
suffix);
}
if (index == parent.child_count() - 1) {
// Insert at the end.
const bookmarks::BookmarkNode* predecessor_node =
parent.GetChild(index - 1);
const SyncedBookmarkTracker::Entity* predecessor_entity =
bookmark_tracker_->GetEntityForBookmarkNode(predecessor_node);
DCHECK(predecessor_entity);
return syncer::UniquePosition::After(
syncer::UniquePosition::FromProto(
predecessor_entity->metadata()->unique_position()),
suffix);
}
// Insert in the middle.
const bookmarks::BookmarkNode* successor_node = parent.GetChild(index + 1);
const SyncedBookmarkTracker::Entity* successor_entity =
bookmark_tracker_->GetEntityForBookmarkNode(successor_node);
DCHECK(successor_entity);
const bookmarks::BookmarkNode* predecessor_node = parent.GetChild(index - 1);
const SyncedBookmarkTracker::Entity* predecessor_entity =
bookmark_tracker_->GetEntityForBookmarkNode(predecessor_node);
DCHECK(predecessor_entity);
return syncer::UniquePosition::Between(
syncer::UniquePosition::FromProto(
predecessor_entity->metadata()->unique_position()),
syncer::UniquePosition::FromProto(
successor_entity->metadata()->unique_position()),
suffix);
} }
void BookmarkModelObserverImpl::BookmarkNodeRemoved( void BookmarkModelObserverImpl::BookmarkNodeRemoved(
......
...@@ -6,11 +6,16 @@ ...@@ -6,11 +6,16 @@
#define COMPONENTS_SYNC_BOOKMARKS_BOOKMARK_MODEL_OBSERVER_IMPL_H_ #define COMPONENTS_SYNC_BOOKMARKS_BOOKMARK_MODEL_OBSERVER_IMPL_H_
#include <set> #include <set>
#include <string>
#include "base/callback.h" #include "base/callback.h"
#include "components/bookmarks/browser/bookmark_model_observer.h" #include "components/bookmarks/browser/bookmark_model_observer.h"
#include "url/gurl.h" #include "url/gurl.h"
namespace syncer {
class UniquePosition;
}
namespace sync_bookmarks { namespace sync_bookmarks {
class SyncedBookmarkTracker; class SyncedBookmarkTracker;
...@@ -54,6 +59,10 @@ class BookmarkModelObserverImpl : public bookmarks::BookmarkModelObserver { ...@@ -54,6 +59,10 @@ class BookmarkModelObserverImpl : public bookmarks::BookmarkModelObserver {
const bookmarks::BookmarkNode* node) override; const bookmarks::BookmarkNode* node) override;
private: private:
syncer::UniquePosition ComputePosition(const bookmarks::BookmarkNode& parent,
int index,
const std::string& sync_id);
// Points to the tracker owned by the processor. It keeps the mapping between // Points to the tracker owned by the processor. It keeps the mapping between
// bookmark nodes and corresponding sync server entities. // bookmark nodes and corresponding sync server entities.
SyncedBookmarkTracker* const bookmark_tracker_; SyncedBookmarkTracker* const bookmark_tracker_;
......
// 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.
#include "components/sync_bookmarks/bookmark_model_observer_impl.h"
#include <memory>
#include <vector>
#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/base/time.h"
#include "components/sync/base/unique_position.h"
#include "components/sync_bookmarks/synced_bookmark_tracker.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace sync_bookmarks {
namespace {
using testing::Eq;
using testing::NiceMock;
using testing::NotNull;
const char kBookmarkBarId[] = "bookmark_bar_id";
const char kBookmarkBarTag[] = "bookmark_bar";
class BookmarkModelObserverImplTest : public testing::Test {
public:
BookmarkModelObserverImplTest()
: bookmark_model_(bookmarks::TestBookmarkClient::CreateModel()),
bookmark_tracker_(std::vector<NodeMetadataPair>(),
std::make_unique<sync_pb::ModelTypeState>()),
observer_(nudge_for_commit_closure_.Get(), &bookmark_tracker_) {
bookmark_model_->AddObserver(&observer_);
sync_pb::EntitySpecifics specifics;
specifics.mutable_bookmark()->set_title(kBookmarkBarTag);
bookmark_tracker_.Add(
/*sync_id=*/kBookmarkBarId,
/*bookmark_node=*/bookmark_model()->bookmark_bar_node(),
/*server_version=*/0, /*creation_time=*/base::Time::Now(),
syncer::UniquePosition::InitialPosition(
syncer::UniquePosition::RandomSuffix())
.ToProto(),
specifics);
}
bookmarks::BookmarkModel* bookmark_model() { return bookmark_model_.get(); }
SyncedBookmarkTracker* bookmark_tracker() { return &bookmark_tracker_; }
BookmarkModelObserverImpl* observer() { return &observer_; }
base::MockCallback<base::RepeatingClosure>* nudge_for_commit_closure() {
return &nudge_for_commit_closure_;
}
private:
std::unique_ptr<bookmarks::BookmarkModel> bookmark_model_;
NiceMock<base::MockCallback<base::RepeatingClosure>>
nudge_for_commit_closure_;
SyncedBookmarkTracker bookmark_tracker_;
BookmarkModelObserverImpl observer_;
};
TEST_F(BookmarkModelObserverImplTest,
BookmarkAddedShouldPutInTheTrackerAndNudgeForCommit) {
const std::string kTitle = "title";
const std::string kUrl = "http://www.url.com";
const size_t kMaxEntries = 10;
EXPECT_CALL(*nudge_for_commit_closure(), Run());
const bookmarks::BookmarkNode* bookmark_bar_node =
bookmark_model()->bookmark_bar_node();
const bookmarks::BookmarkNode* bookmark_node = bookmark_model()->AddURL(
/*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle),
GURL(kUrl));
EXPECT_THAT(bookmark_tracker()->TrackedEntitiesCountForTest(), 2U);
std::vector<const SyncedBookmarkTracker::Entity*> local_changes =
bookmark_tracker()->GetEntitiesWithLocalChanges(kMaxEntries);
ASSERT_THAT(local_changes.size(), 1U);
EXPECT_THAT(local_changes[0]->bookmark_node(), Eq(bookmark_node));
}
TEST_F(BookmarkModelObserverImplTest, ShouldPositionSiblings) {
const std::string kTitle = "title";
const std::string kUrl = "http://www.url.com";
// Build this structure:
// bookmark_bar
// |- node1
// |- node2
// Expectation:
// p1 < p2
const bookmarks::BookmarkNode* bookmark_bar_node =
bookmark_model()->bookmark_bar_node();
const bookmarks::BookmarkNode* bookmark_node1 = bookmark_model()->AddURL(
/*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle),
GURL(kUrl));
const bookmarks::BookmarkNode* bookmark_node2 = bookmark_model()->AddURL(
/*parent=*/bookmark_bar_node, /*index=*/1, base::UTF8ToUTF16(kTitle),
GURL(kUrl));
ASSERT_THAT(bookmark_tracker()->TrackedEntitiesCountForTest(), Eq(3U));
const SyncedBookmarkTracker::Entity* entity1 =
bookmark_tracker()->GetEntityForBookmarkNode(bookmark_node1);
ASSERT_THAT(entity1, NotNull());
syncer::UniquePosition p1 =
syncer::UniquePosition::FromProto(entity1->metadata()->unique_position());
const SyncedBookmarkTracker::Entity* entity2 =
bookmark_tracker()->GetEntityForBookmarkNode(bookmark_node2);
ASSERT_THAT(entity2, NotNull());
syncer::UniquePosition p2 =
syncer::UniquePosition::FromProto(entity2->metadata()->unique_position());
EXPECT_TRUE(p1.LessThan(p2));
// Now insert node3 at index 1 to build this structure:
// bookmark_bar
// |- node1
// |- node3
// |- node2
// Expectation:
// p1 < p2 (still holds)
// p1 < p3
// p3 < p2
const bookmarks::BookmarkNode* bookmark_node3 = bookmark_model()->AddURL(
/*parent=*/bookmark_bar_node, /*index=*/1, base::UTF8ToUTF16(kTitle),
GURL(kUrl));
EXPECT_THAT(bookmark_tracker()->TrackedEntitiesCountForTest(), Eq(4U));
const SyncedBookmarkTracker::Entity* entity3 =
bookmark_tracker()->GetEntityForBookmarkNode(bookmark_node3);
ASSERT_THAT(entity3, NotNull());
syncer::UniquePosition p3 =
syncer::UniquePosition::FromProto(entity3->metadata()->unique_position());
EXPECT_TRUE(p1.LessThan(p2));
EXPECT_TRUE(p1.LessThan(p3));
EXPECT_TRUE(p3.LessThan(p2));
}
} // namespace
} // namespace sync_bookmarks
...@@ -10,8 +10,10 @@ ...@@ -10,8 +10,10 @@
#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_node.h"
#include "components/bookmarks/browser/bookmark_utils.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/base/time.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/model/data_type_activation_request.h" #include "components/sync/model/data_type_activation_request.h"
...@@ -115,19 +117,46 @@ bool IsValidBookmark(const sync_pb::BookmarkSpecifics& specifics, ...@@ -115,19 +117,46 @@ bool IsValidBookmark(const sync_pb::BookmarkSpecifics& specifics,
return true; return true;
} }
sync_pb::EntitySpecifics SpecificsFromBookmarkNode(
const bookmarks::BookmarkNode* node) {
sync_pb::EntitySpecifics specifics;
sync_pb::BookmarkSpecifics* bm_specifics = specifics.mutable_bookmark();
bm_specifics->set_url(node->url().spec());
// TODO(crbug.com/516866): Set the favicon.
bm_specifics->set_title(base::UTF16ToUTF8(node->GetTitle()));
bm_specifics->set_creation_time_us(
node->date_added().ToDeltaSinceWindowsEpoch().InMicroseconds());
bm_specifics->set_icon_url(node->icon_url() ? node->icon_url()->spec()
: std::string());
for (const std::pair<std::string, std::string>& pair :
*node->GetMetaInfoMap()) {
sync_pb::MetaInfo* meta_info = bm_specifics->add_meta_info();
meta_info->set_key(pair.first);
meta_info->set_value(pair.second);
}
return specifics;
}
class ScopedRemoteUpdateBookmarks { class ScopedRemoteUpdateBookmarks {
public: public:
// |bookmark_model| and |bookmark_undo_service| must not be null and must // |bookmark_model|, |bookmark_undo_service| and |observer| must not be null
// outlive this object. // and must outlive this object.
ScopedRemoteUpdateBookmarks(bookmarks::BookmarkModel* bookmark_model, ScopedRemoteUpdateBookmarks(bookmarks::BookmarkModel* bookmark_model,
BookmarkUndoService* bookmark_undo_service) BookmarkUndoService* bookmark_undo_service,
: bookmark_model_(bookmark_model), suspend_undo_(bookmark_undo_service) { bookmarks::BookmarkModelObserver* observer)
: bookmark_model_(bookmark_model),
suspend_undo_(bookmark_undo_service),
observer_(observer) {
// Notify UI intensive observers of BookmarkModel that we are about to make // Notify UI intensive observers of BookmarkModel that we are about to make
// potentially significant changes to it, so the updates may be batched. For // potentially significant changes to it, so the updates may be batched. For
// example, on Mac, the bookmarks bar displays animations when bookmark // example, on Mac, the bookmarks bar displays animations when bookmark
// items are added or deleted. // items are added or deleted.
DCHECK(bookmark_model_); DCHECK(bookmark_model_);
bookmark_model_->BeginExtensiveChanges(); bookmark_model_->BeginExtensiveChanges();
// Shouldn't be notified upon changes due to sync.
bookmark_model_->RemoveObserver(observer_);
} }
~ScopedRemoteUpdateBookmarks() { ~ScopedRemoteUpdateBookmarks() {
...@@ -136,6 +165,7 @@ class ScopedRemoteUpdateBookmarks { ...@@ -136,6 +165,7 @@ class ScopedRemoteUpdateBookmarks {
// one described in https://crbug.com/281562, where old and new items on the // one described in https://crbug.com/281562, where old and new items on the
// bookmarks bar would overlap. // bookmarks bar would overlap.
bookmark_model_->EndExtensiveChanges(); bookmark_model_->EndExtensiveChanges();
bookmark_model_->AddObserver(observer_);
} }
private: private:
...@@ -144,6 +174,8 @@ class ScopedRemoteUpdateBookmarks { ...@@ -144,6 +174,8 @@ class ScopedRemoteUpdateBookmarks {
// Changes made to the bookmark model due to sync should not be undoable. // Changes made to the bookmark model due to sync should not be undoable.
ScopedSuspendBookmarkUndo suspend_undo_; ScopedSuspendBookmarkUndo suspend_undo_;
bookmarks::BookmarkModelObserver* const observer_;
DISALLOW_COPY_AND_ASSIGN(ScopedRemoteUpdateBookmarks); DISALLOW_COPY_AND_ASSIGN(ScopedRemoteUpdateBookmarks);
}; };
} // namespace } // namespace
...@@ -152,7 +184,11 @@ BookmarkModelTypeProcessor::BookmarkModelTypeProcessor( ...@@ -152,7 +184,11 @@ BookmarkModelTypeProcessor::BookmarkModelTypeProcessor(
BookmarkUndoService* bookmark_undo_service) BookmarkUndoService* bookmark_undo_service)
: 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() {
if (bookmark_model_ && bookmark_model_observer_) {
bookmark_model_->RemoveObserver(bookmark_model_observer_.get());
}
}
void BookmarkModelTypeProcessor::ConnectSync( void BookmarkModelTypeProcessor::ConnectSync(
std::unique_ptr<syncer::CommitQueue> worker) { std::unique_ptr<syncer::CommitQueue> worker) {
...@@ -177,35 +213,49 @@ void BookmarkModelTypeProcessor::GetLocalChanges( ...@@ -177,35 +213,49 @@ void BookmarkModelTypeProcessor::GetLocalChanges(
size_t max_entries, size_t max_entries,
const GetLocalChangesCallback& callback) { const GetLocalChangesCallback& callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
syncer::CommitRequestDataList local_changes; std::vector<syncer::CommitRequestData> local_changes =
BuildCommitRequestsForLocalChanges(max_entries);
callback.Run(std::move(local_changes)); callback.Run(std::move(local_changes));
NOTIMPLEMENTED();
} }
void BookmarkModelTypeProcessor::OnCommitCompleted( void BookmarkModelTypeProcessor::OnCommitCompleted(
const sync_pb::ModelTypeState& type_state, const sync_pb::ModelTypeState& type_state,
const syncer::CommitResponseDataList& response_list) { const syncer::CommitResponseDataList& response_list) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
NOTIMPLEMENTED();
for (const syncer::CommitResponseData& response : response_list) {
// In order to save space, |response.id_in_request| is written when it's
// different from |response.id|. If it's empty, then there was no id change
// during the commit, and |response.id| carries both the old and new ids.
const std::string& old_sync_id =
response.id_in_request.empty() ? response.id : response.id_in_request;
bookmark_tracker_->UpdateUponCommitResponse(old_sync_id, response.id,
response.sequence_number,
response.response_version);
}
bookmark_tracker_->set_model_type_state(
std::make_unique<sync_pb::ModelTypeState>(type_state));
schedule_save_closure_.Run();
} }
void BookmarkModelTypeProcessor::OnUpdateReceived( void BookmarkModelTypeProcessor::OnUpdateReceived(
const sync_pb::ModelTypeState& model_type_state, const sync_pb::ModelTypeState& model_type_state,
const syncer::UpdateResponseDataList& updates) { const syncer::UpdateResponseDataList& updates) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!model_type_state.cache_guid().empty());
DCHECK_EQ(model_type_state.cache_guid(), cache_guid_);
DCHECK(model_type_state.initial_sync_done());
if (!bookmark_tracker_) { if (!bookmark_tracker_) {
// TODO(crbug.com/516866): Implement the merge logic. // TODO(crbug.com/516866): Implement the merge logic.
auto state = std::make_unique<sync_pb::ModelTypeState>(model_type_state); StartTrackingMetadata(
state->set_cache_guid(cache_guid_); std::vector<NodeMetadataPair>(),
std::vector<NodeMetadataPair> nodes_metadata; std::make_unique<sync_pb::ModelTypeState>(model_type_state));
bookmark_tracker_ = std::make_unique<SyncedBookmarkTracker>(
std::move(nodes_metadata), std::move(state));
} }
// TODO(crbug.com/516866): Set the model type state. // TODO(crbug.com/516866): Set the model type state.
ScopedRemoteUpdateBookmarks update_bookmarks(bookmark_model_, ScopedRemoteUpdateBookmarks update_bookmarks(
bookmark_undo_service_); bookmark_model_, bookmark_undo_service_, bookmark_model_observer_.get());
for (const syncer::UpdateResponseData* update : ReorderUpdates(updates)) { for (const syncer::UpdateResponseData* update : ReorderUpdates(updates)) {
const syncer::EntityData& update_entity = update->entity.value(); const syncer::EntityData& update_entity = update->entity.value();
...@@ -454,10 +504,12 @@ void BookmarkModelTypeProcessor::AssociatePermanentFolder( ...@@ -454,10 +504,12 @@ void BookmarkModelTypeProcessor::AssociatePermanentFolder(
} }
std::string BookmarkModelTypeProcessor::EncodeSyncMetadata() const { std::string BookmarkModelTypeProcessor::EncodeSyncMetadata() const {
sync_pb::BookmarkModelMetadata model_metadata =
bookmark_tracker_->BuildBookmarkModelMetadata();
std::string metadata_str; std::string metadata_str;
model_metadata.SerializeToString(&metadata_str); if (bookmark_tracker_) {
sync_pb::BookmarkModelMetadata model_metadata =
bookmark_tracker_->BuildBookmarkModelMetadata();
model_metadata.SerializeToString(&metadata_str);
}
return metadata_str; return metadata_str;
} }
...@@ -513,14 +565,8 @@ void BookmarkModelTypeProcessor::DecodeSyncMetadata( ...@@ -513,14 +565,8 @@ void BookmarkModelTypeProcessor::DecodeSyncMetadata(
// TODO(crbug.com/516866): Handle local nodes that don't have a // TODO(crbug.com/516866): Handle local nodes that don't have a
// corresponding // corresponding
// metadata. // metadata.
bookmark_tracker_ = std::make_unique<SyncedBookmarkTracker>( StartTrackingMetadata(std::move(nodes_metadata),
std::move(nodes_metadata), std::move(model_type_state)); std::move(model_type_state));
bookmark_model_observer_ = std::make_unique<BookmarkModelObserverImpl>(
base::BindRepeating(&BookmarkModelTypeProcessor::NudgeForCommitIfNeeded,
base::Unretained(this)),
bookmark_tracker_.get());
// TODO(crbug.com/516866): Register the observer with the bookmark model.
} else if (!model_metadata.bookmarks_metadata().empty()) { } else if (!model_metadata.bookmarks_metadata().empty()) {
DLOG(ERROR) DLOG(ERROR)
<< "Persisted Metadata not empty while initial sync is not done."; << "Persisted Metadata not empty while initial sync is not done.";
...@@ -606,6 +652,76 @@ void BookmarkModelTypeProcessor::NudgeForCommitIfNeeded() { ...@@ -606,6 +652,76 @@ void BookmarkModelTypeProcessor::NudgeForCommitIfNeeded() {
} }
} }
std::vector<syncer::CommitRequestData>
BookmarkModelTypeProcessor::BuildCommitRequestsForLocalChanges(
size_t max_entries) {
DCHECK(bookmark_tracker_);
const std::vector<const SyncedBookmarkTracker::Entity*>
entities_with_local_changes =
bookmark_tracker_->GetEntitiesWithLocalChanges(max_entries);
DCHECK_LE(entities_with_local_changes.size(), max_entries);
std::vector<syncer::CommitRequestData> commit_requests;
for (const SyncedBookmarkTracker::Entity* entity :
entities_with_local_changes) {
DCHECK(entity->IsUnsynced());
const sync_pb::EntityMetadata* metadata = entity->metadata();
syncer::CommitRequestData request;
syncer::EntityData data;
data.id = metadata->server_id();
data.creation_time = syncer::ProtoTimeToTime(metadata->creation_time());
data.modification_time =
syncer::ProtoTimeToTime(metadata->modification_time());
if (!metadata->is_deleted()) {
const bookmarks::BookmarkNode* node = entity->bookmark_node();
DCHECK(node);
const bookmarks::BookmarkNode* parent = node->parent();
const SyncedBookmarkTracker::Entity* parent_entity =
bookmark_tracker_->GetEntityForBookmarkNode(parent);
DCHECK(parent_entity);
data.parent_id = parent_entity->metadata()->server_id();
// TODO(crbug.com/516866): Double check that custom passphrase works well
// with this implementation, because:
// 1. NonBlockingTypeCommitContribution::AdjustCommitProto() clears the
// title out.
// 2. Bookmarks (maybe ancient legacy bookmarks only?) use/used |name| to
// encode the title.
data.is_folder = node->is_folder();
// TODO(crbug.com/516866): Set the non_unique_name similar to directory
// implementation.
// https://cs.chromium.org/chromium/src/components/sync/syncable/write_node.cc?l=41&rcl=1675007db1e0eb03417e81442688bb11cd181f58
data.non_unique_name = base::UTF16ToUTF8(node->GetTitle());
data.unique_position = parent_entity->metadata()->unique_position();
// In case of deletion, make an EntityData with empty specifics to
// indicate deletion.
data.specifics = SpecificsFromBookmarkNode(node);
}
request.entity = data.PassToPtr();
request.sequence_number = metadata->sequence_number();
request.base_version = metadata->server_version();
// Specifics hash has been computed in the tracker when this entity has been
// added/updated.
request.specifics_hash = metadata->specifics_hash();
commit_requests.push_back(std::move(request));
}
return commit_requests;
}
void BookmarkModelTypeProcessor::StartTrackingMetadata(
std::vector<NodeMetadataPair> nodes_metadata,
std::unique_ptr<sync_pb::ModelTypeState> model_type_state) {
bookmark_tracker_ = std::make_unique<SyncedBookmarkTracker>(
std::move(nodes_metadata), std::move(model_type_state));
bookmark_model_observer_ = std::make_unique<BookmarkModelObserverImpl>(
base::BindRepeating(&BookmarkModelTypeProcessor::NudgeForCommitIfNeeded,
base::Unretained(this)),
bookmark_tracker_.get());
bookmark_model_->AddObserver(bookmark_model_observer_.get());
}
void BookmarkModelTypeProcessor::GetAllNodesForDebugging( void BookmarkModelTypeProcessor::GetAllNodesForDebugging(
AllNodesCallback callback) { AllNodesCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
......
...@@ -135,6 +135,16 @@ class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor, ...@@ -135,6 +135,16 @@ class BookmarkModelTypeProcessor : public syncer::ModelTypeProcessor,
// entities. // entities.
void NudgeForCommitIfNeeded(); void NudgeForCommitIfNeeded();
// Builds the commit requests list.
std::vector<syncer::CommitRequestData> BuildCommitRequestsForLocalChanges(
size_t max_entries);
// Instantiates the required objects to track metadata and starts observing
// changes from the bookmark model.
void StartTrackingMetadata(
std::vector<NodeMetadataPair> nodes_metadata,
std::unique_ptr<sync_pb::ModelTypeState> model_type_state);
// Stores the start callback in between OnSyncStarting() and // Stores the start callback in between OnSyncStarting() and
// DecodeSyncMetadata(). // DecodeSyncMetadata().
StartCallback start_callback_; StartCallback start_callback_;
......
...@@ -8,9 +8,11 @@ ...@@ -8,9 +8,11 @@
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/test/mock_callback.h" #include "base/test/mock_callback.h"
#include "base/test/scoped_task_environment.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"
#include "components/sync/model/data_type_activation_request.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"
...@@ -29,6 +31,7 @@ const char kRootParentTag[] = "0"; ...@@ -29,6 +31,7 @@ const char kRootParentTag[] = "0";
const char kBookmarkBarTag[] = "bookmark_bar"; const char kBookmarkBarTag[] = "bookmark_bar";
const char kBookmarkBarId[] = "bookmark_bar_id"; const char kBookmarkBarId[] = "bookmark_bar_id";
const char kBookmarksRootId[] = "32904_google_chrome_bookmarks"; const char kBookmarksRootId[] = "32904_google_chrome_bookmarks";
const char kCacheGuid[] = "generated_id";
struct BookmarkInfo { struct BookmarkInfo {
std::string server_id; std::string server_id;
...@@ -60,6 +63,13 @@ syncer::UpdateResponseData CreateUpdateData(const BookmarkInfo& bookmark_info) { ...@@ -60,6 +63,13 @@ syncer::UpdateResponseData CreateUpdateData(const BookmarkInfo& bookmark_info) {
return response_data; return response_data;
} }
sync_pb::ModelTypeState CreateDummyModelTypeState() {
sync_pb::ModelTypeState model_type_state;
model_type_state.set_cache_guid(kCacheGuid);
model_type_state.set_initial_sync_done(true);
return model_type_state;
}
void AssertState(const BookmarkModelTypeProcessor* processor, void AssertState(const BookmarkModelTypeProcessor* processor,
const std::vector<BookmarkInfo>& bookmarks) { const std::vector<BookmarkInfo>& bookmarks) {
const SyncedBookmarkTracker* tracker = processor->GetTrackerForTest(); const SyncedBookmarkTracker* tracker = processor->GetTrackerForTest();
...@@ -94,7 +104,7 @@ void InitWithSyncedBookmarks(const std::vector<BookmarkInfo>& bookmarks, ...@@ -94,7 +104,7 @@ void InitWithSyncedBookmarks(const std::vector<BookmarkInfo>& bookmarks,
for (BookmarkInfo bookmark : bookmarks) { for (BookmarkInfo bookmark : bookmarks) {
updates.push_back(CreateUpdateData(bookmark)); updates.push_back(CreateUpdateData(bookmark));
} }
processor->OnUpdateReceived(sync_pb::ModelTypeState(), updates); processor->OnUpdateReceived(CreateDummyModelTypeState(), updates);
AssertState(processor, bookmarks); AssertState(processor, bookmarks);
} }
...@@ -134,8 +144,14 @@ class BookmarkModelTypeProcessorTest : public testing::Test { ...@@ -134,8 +144,14 @@ class BookmarkModelTypeProcessorTest : public testing::Test {
: 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_(sync_client()->GetBookmarkUndoServiceIfExists()) {
// TODO(crbug.com/516866): This class assumes model is loaded and sync has
// started before running tests. We should test other variations (i.e. model
// isn't loaded yet and/or sync didn't start yet).
processor_.DecodeSyncMetadata(std::string(), schedule_save_closure_.Get(), processor_.DecodeSyncMetadata(std::string(), schedule_save_closure_.Get(),
bookmark_model_.get()); bookmark_model_.get());
syncer::DataTypeActivationRequest request;
request.cache_guid = kCacheGuid;
processor_.OnSyncStarting(request, base::DoNothing());
} }
TestSyncClient* sync_client() { return &sync_client_; } TestSyncClient* sync_client() { return &sync_client_; }
...@@ -146,9 +162,10 @@ class BookmarkModelTypeProcessorTest : public testing::Test { ...@@ -146,9 +162,10 @@ class BookmarkModelTypeProcessorTest : public testing::Test {
} }
private: private:
base::test::ScopedTaskEnvironment task_environment_;
NiceMock<base::MockCallback<base::RepeatingClosure>> schedule_save_closure_;
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_; BookmarkModelTypeProcessor processor_;
}; };
...@@ -205,7 +222,7 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteCreation) { ...@@ -205,7 +222,7 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteCreation) {
// Save will be scheduled in the model upon model change. No save should be // Save will be scheduled in the model upon model change. No save should be
// scheduled from the processor. // scheduled from the processor.
EXPECT_CALL(*schedule_save_closure(), Run()).Times(0); EXPECT_CALL(*schedule_save_closure(), Run()).Times(0);
processor()->OnUpdateReceived(sync_pb::ModelTypeState(), updates); processor()->OnUpdateReceived(CreateDummyModelTypeState(), 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)));
...@@ -241,7 +258,7 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteUpdate) { ...@@ -241,7 +258,7 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteUpdate) {
// Save will be scheduled in the model upon model change. No save should be // Save will be scheduled in the model upon model change. No save should be
// scheduled from the processor. // scheduled from the processor.
EXPECT_CALL(*schedule_save_closure(), Run()).Times(0); EXPECT_CALL(*schedule_save_closure(), Run()).Times(0);
processor()->OnUpdateReceived(sync_pb::ModelTypeState(), updates); processor()->OnUpdateReceived(CreateDummyModelTypeState(), 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));
...@@ -273,7 +290,7 @@ TEST_F(BookmarkModelTypeProcessorTest, ...@@ -273,7 +290,7 @@ TEST_F(BookmarkModelTypeProcessorTest,
updates[0].response_version++; updates[0].response_version++;
EXPECT_CALL(*schedule_save_closure(), Run()); EXPECT_CALL(*schedule_save_closure(), Run());
processor()->OnUpdateReceived(sync_pb::ModelTypeState(), updates); processor()->OnUpdateReceived(CreateDummyModelTypeState(), updates);
} }
TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteDelete) { TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteDelete) {
...@@ -330,11 +347,10 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteDelete) { ...@@ -330,11 +347,10 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldUpdateModelAfterRemoteDelete) {
updates.push_back(CreateTombstone(kTitle1Id)); updates.push_back(CreateTombstone(kTitle1Id));
updates.push_back(CreateTombstone(kFolder1Id)); updates.push_back(CreateTombstone(kFolder1Id));
const sync_pb::ModelTypeState model_type_state;
// Save will be scheduled in the model upon model change. No save should be // Save will be scheduled in the model upon model change. No save should be
// scheduled from the processor. // scheduled from the processor.
EXPECT_CALL(*schedule_save_closure(), Run()).Times(0); EXPECT_CALL(*schedule_save_closure(), Run()).Times(0);
processor()->OnUpdateReceived(model_type_state, updates); processor()->OnUpdateReceived(CreateDummyModelTypeState(), updates);
// The structure should be // The structure should be
// bookmark_bar // bookmark_bar
...@@ -406,8 +422,7 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldEncodeSyncMetadata) { ...@@ -406,8 +422,7 @@ TEST_F(BookmarkModelTypeProcessorTest, ShouldEncodeSyncMetadata) {
syncer::UpdateResponseDataList updates; syncer::UpdateResponseDataList updates;
updates.push_back(CreateTombstone(kNodeId1)); updates.push_back(CreateTombstone(kNodeId1));
const sync_pb::ModelTypeState model_type_state; processor()->OnUpdateReceived(CreateDummyModelTypeState(), updates);
processor()->OnUpdateReceived(model_type_state, updates);
metadata_str = processor()->EncodeSyncMetadata(); metadata_str = processor()->EncodeSyncMetadata();
model_metadata.ParseFromString(metadata_str); model_metadata.ParseFromString(metadata_str);
......
...@@ -22,7 +22,9 @@ BookmarkSyncService::BookmarkSyncService( ...@@ -22,7 +22,9 @@ BookmarkSyncService::BookmarkSyncService(
BookmarkSyncService::~BookmarkSyncService() {} BookmarkSyncService::~BookmarkSyncService() {}
void BookmarkSyncService::Shutdown() {} void BookmarkSyncService::Shutdown() {
bookmark_model_type_processor_.reset();
}
std::string BookmarkSyncService::EncodeBookmarkSyncMetadata() { std::string BookmarkSyncService::EncodeBookmarkSyncMetadata() {
if (!bookmark_model_type_processor_) { if (!bookmark_model_type_processor_) {
......
...@@ -43,6 +43,9 @@ bool SyncedBookmarkTracker::Entity::IsUnsynced() const { ...@@ -43,6 +43,9 @@ bool SyncedBookmarkTracker::Entity::IsUnsynced() const {
bool SyncedBookmarkTracker::Entity::MatchesData( bool SyncedBookmarkTracker::Entity::MatchesData(
const syncer::EntityData& data) const { const syncer::EntityData& data) const {
// TODO(crbug.com/516866): Check parent id and unique position.
// TODO(crbug.com/516866): Compare the actual specifics instead of the
// specifics hash.
if (metadata_->is_deleted() || data.is_deleted()) { if (metadata_->is_deleted() || data.is_deleted()) {
// In case of deletion, no need to check the specifics. // In case of deletion, no need to check the specifics.
return metadata_->is_deleted() == data.is_deleted(); return metadata_->is_deleted() == data.is_deleted();
...@@ -63,10 +66,13 @@ SyncedBookmarkTracker::SyncedBookmarkTracker( ...@@ -63,10 +66,13 @@ SyncedBookmarkTracker::SyncedBookmarkTracker(
std::vector<NodeMetadataPair> nodes_metadata, std::vector<NodeMetadataPair> nodes_metadata,
std::unique_ptr<sync_pb::ModelTypeState> model_type_state) std::unique_ptr<sync_pb::ModelTypeState> model_type_state)
: model_type_state_(std::move(model_type_state)) { : model_type_state_(std::move(model_type_state)) {
DCHECK(model_type_state_);
for (NodeMetadataPair& node_metadata : nodes_metadata) { for (NodeMetadataPair& node_metadata : nodes_metadata) {
const std::string& sync_id = node_metadata.second->server_id(); const std::string& sync_id = node_metadata.second->server_id();
sync_id_to_entities_map_[sync_id] = std::make_unique<Entity>( auto entity = std::make_unique<Entity>(node_metadata.first,
node_metadata.first, std::move(node_metadata.second)); std::move(node_metadata.second));
bookmark_node_to_entities_map_[node_metadata.first] = entity.get();
sync_id_to_entities_map_[sync_id] = std::move(entity);
} }
} }
...@@ -78,6 +84,13 @@ const SyncedBookmarkTracker::Entity* SyncedBookmarkTracker::GetEntityForSyncId( ...@@ -78,6 +84,13 @@ const SyncedBookmarkTracker::Entity* SyncedBookmarkTracker::GetEntityForSyncId(
return it != sync_id_to_entities_map_.end() ? it->second.get() : nullptr; return it != sync_id_to_entities_map_.end() ? it->second.get() : nullptr;
} }
const SyncedBookmarkTracker::Entity*
SyncedBookmarkTracker::GetEntityForBookmarkNode(
const bookmarks::BookmarkNode* node) const {
auto it = bookmark_node_to_entities_map_.find(node);
return it != bookmark_node_to_entities_map_.end() ? it->second : nullptr;
}
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,
int64_t server_version, int64_t server_version,
...@@ -90,12 +103,14 @@ void SyncedBookmarkTracker::Add(const std::string& sync_id, ...@@ -90,12 +103,14 @@ void SyncedBookmarkTracker::Add(const std::string& sync_id,
metadata->set_server_id(sync_id); metadata->set_server_id(sync_id);
metadata->set_server_version(server_version); metadata->set_server_version(server_version);
metadata->set_creation_time(syncer::TimeToProtoTime(creation_time)); metadata->set_creation_time(syncer::TimeToProtoTime(creation_time));
metadata->set_modification_time(syncer::TimeToProtoTime(creation_time));
metadata->set_sequence_number(0); metadata->set_sequence_number(0);
metadata->set_acked_sequence_number(0); metadata->set_acked_sequence_number(0);
metadata->mutable_unique_position()->CopyFrom(unique_position); metadata->mutable_unique_position()->CopyFrom(unique_position);
HashSpecifics(specifics, metadata->mutable_specifics_hash()); HashSpecifics(specifics, metadata->mutable_specifics_hash());
sync_id_to_entities_map_[sync_id] = auto entity = std::make_unique<Entity>(bookmark_node, std::move(metadata));
std::make_unique<Entity>(bookmark_node, std::move(metadata)); bookmark_node_to_entities_map_[bookmark_node] = entity.get();
sync_id_to_entities_map_[sync_id] = std::move(entity);
} }
void SyncedBookmarkTracker::Update(const std::string& sync_id, void SyncedBookmarkTracker::Update(const std::string& sync_id,
...@@ -114,6 +129,9 @@ void SyncedBookmarkTracker::Update(const std::string& sync_id, ...@@ -114,6 +129,9 @@ void SyncedBookmarkTracker::Update(const std::string& sync_id,
} }
void SyncedBookmarkTracker::Remove(const std::string& sync_id) { void SyncedBookmarkTracker::Remove(const std::string& sync_id) {
const Entity* entity = GetEntityForSyncId(sync_id);
DCHECK(entity);
bookmark_node_to_entities_map_.erase(entity->bookmark_node());
sync_id_to_entities_map_.erase(sync_id); sync_id_to_entities_map_.erase(sync_id);
} }
...@@ -155,6 +173,49 @@ bool SyncedBookmarkTracker::HasLocalChanges() const { ...@@ -155,6 +173,49 @@ bool SyncedBookmarkTracker::HasLocalChanges() const {
return false; return false;
} }
std::vector<const SyncedBookmarkTracker::Entity*>
SyncedBookmarkTracker::GetEntitiesWithLocalChanges(size_t max_entries) const {
// TODO(crbug.com/516866): Reorder local changes to e.g. parent creation
// before child creation and the otherway around for deletions.
std::vector<const SyncedBookmarkTracker::Entity*> entities_with_local_changes;
for (const std::pair<const std::string, std::unique_ptr<Entity>>& pair :
sync_id_to_entities_map_) {
Entity* entity = pair.second.get();
if (entity->IsUnsynced()) {
entities_with_local_changes.push_back(entity);
}
}
return entities_with_local_changes;
}
void SyncedBookmarkTracker::UpdateUponCommitResponse(
const std::string& old_id,
const std::string& new_id,
int64_t acked_sequence_number,
int64_t server_version) {
// TODO(crbug.com/516866): Update specifics if we decide to keep it.
auto it = sync_id_to_entities_map_.find(old_id);
Entity* entity =
it != sync_id_to_entities_map_.end() ? it->second.get() : nullptr;
if (it == sync_id_to_entities_map_.end()) {
DLOG(WARNING) << "Trying to update a non existing entity.";
return;
}
const bookmarks::BookmarkNode* node = entity->bookmark_node();
// TODO(crbug.com/516866): For tombstones, node would be null and the DCHECK
// below would be invalid. Handle deletions here may be or in the processor.
DCHECK(node);
if (old_id != new_id) {
auto it = sync_id_to_entities_map_.find(old_id);
entity->metadata()->set_server_id(new_id);
sync_id_to_entities_map_[new_id] = std::move(it->second);
sync_id_to_entities_map_.erase(old_id);
}
entity->metadata()->set_acked_sequence_number(acked_sequence_number);
entity->metadata()->set_server_version(server_version);
}
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();
} }
......
...@@ -76,20 +76,24 @@ class SyncedBookmarkTracker { ...@@ -76,20 +76,24 @@ class SyncedBookmarkTracker {
std::unique_ptr<sync_pb::ModelTypeState> model_type_state); std::unique_ptr<sync_pb::ModelTypeState> model_type_state);
~SyncedBookmarkTracker(); ~SyncedBookmarkTracker();
// Returns null if not entity is found. // Returns null if no entity is found.
const Entity* GetEntityForSyncId(const std::string& sync_id) const; const Entity* GetEntityForSyncId(const std::string& sync_id) const;
// Returns null if no entity is found.
const SyncedBookmarkTracker::Entity* GetEntityForBookmarkNode(
const bookmarks::BookmarkNode* node) 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
// and metadata 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, int64_t server_version,
base::Time modification_time, base::Time creation_time,
const sync_pb::UniquePosition& unique_position, const sync_pb::UniquePosition& unique_position,
const sync_pb::EntitySpecifics& specifics); const sync_pb::EntitySpecifics& specifics);
// Adds an existing entry for the |sync_id| and the corresponding metadata in // Updates an existing entry for the |sync_id| and the corresponding metadata
// |sync_id_to_entities_map_|. // in |sync_id_to_entities_map_|.
void Update(const std::string& sync_id, void Update(const std::string& sync_id,
int64_t server_version, int64_t server_version,
base::Time modification_time, base::Time modification_time,
...@@ -112,16 +116,38 @@ class SyncedBookmarkTracker { ...@@ -112,16 +116,38 @@ class SyncedBookmarkTracker {
return *model_type_state_; return *model_type_state_;
} }
void set_model_type_state(
std::unique_ptr<sync_pb::ModelTypeState> model_type_state) {
model_type_state_ = std::move(model_type_state);
}
std::vector<const Entity*> GetEntitiesWithLocalChanges(
size_t max_entries) const;
// Updates the tracker after receiving the commit response. |old_id| should be
// equal to |new_id| for all updates except the initial commit, where the
// temporary client-generated ID will be overriden by the server-provided
// final ID. In which case |sync_id_to_entities_map_| will be updated
// accordingly.
void UpdateUponCommitResponse(const std::string& old_id,
const std::string& new_id,
int64_t acked_sequence_number,
int64_t server_version);
// 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;
private: private:
// A map of sync server ids to sync entities. This should contain entries and // A map of sync server ids to sync entities. This should contain entries and
// metadata for almost everything. However, since local data are loaded only // metadata for almost everything.
// when needed (e.g. before a commit cycle), the entities may not always
// 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_;
// A map of bookmark nodes to sync entities. It's keyed by the bookmark node
// pointers which get assigned when loading the bookmark model. This map is
// first initialized in the constructor.
std::map<const bookmarks::BookmarkNode*, Entity*>
bookmark_node_to_entities_map_;
// The model metadata (progress marker, initial sync done, etc). // The model metadata (progress marker, initial sync done, etc).
std::unique_ptr<sync_pb::ModelTypeState> model_type_state_; std::unique_ptr<sync_pb::ModelTypeState> model_type_state_;
......
...@@ -108,6 +108,37 @@ TEST(SyncedBookmarkTrackerTest, ...@@ -108,6 +108,37 @@ TEST(SyncedBookmarkTrackerTest,
// request in a separate test probably. // request in a separate test probably.
} }
TEST(SyncedBookmarkTrackerTest, ShouldUpdateUponCommitResponseWithNewId) {
SyncedBookmarkTracker tracker(std::vector<NodeMetadataPair>(),
std::make_unique<sync_pb::ModelTypeState>());
const std::string kSyncId = "SYNC_ID";
const std::string kNewSyncId = "NEW_SYNC_ID";
const int64_t kId = 1;
const int64_t kServerVersion = 1000;
const int64_t kNewServerVersion = 1001;
const base::Time kModificationTime(base::Time::Now() -
base::TimeDelta::FromSeconds(1));
const sync_pb::UniquePosition unique_position;
const sync_pb::EntitySpecifics specifics =
GenerateSpecifics(/*title=*/std::string(), /*url=*/std::string());
bookmarks::BookmarkNode node(kId, GURL());
tracker.Add(kSyncId, &node, kServerVersion, kModificationTime,
unique_position, specifics);
ASSERT_THAT(tracker.GetEntityForSyncId(kSyncId), NotNull());
// Receive a commit response with a changed id.
tracker.UpdateUponCommitResponse(
kSyncId, kNewSyncId, /*acked_sequence_number=*/1, kNewServerVersion);
// Old id shouldn't be there.
EXPECT_THAT(tracker.GetEntityForSyncId(kSyncId), IsNull());
const SyncedBookmarkTracker::Entity* entity =
tracker.GetEntityForSyncId(kNewSyncId);
ASSERT_THAT(entity, NotNull());
EXPECT_THAT(entity->metadata()->server_id(), Eq(kNewSyncId));
EXPECT_THAT(entity->bookmark_node(), Eq(&node));
EXPECT_THAT(entity->metadata()->server_version(), Eq(kNewServerVersion));
}
} // namespace } // namespace
} // namespace sync_bookmarks } // namespace sync_bookmarks
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