blob: b3e772d59dd84b6d010cb2e6109debc8f13e719c [file] [log] [blame]
// 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.
#ifndef COMPONENTS_SYNC_BOOKMARKS_SYNCED_BOOKMARK_TRACKER_H_
#define COMPONENTS_SYNC_BOOKMARKS_SYNCED_BOOKMARK_TRACKER_H_
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "base/feature_list.h"
#include "base/macros.h"
#include "base/time/time.h"
#include "components/sync/protocol/bookmark_model_metadata.pb.h"
#include "components/sync/protocol/entity_metadata.pb.h"
#include "components/sync/protocol/unique_position.pb.h"
namespace bookmarks {
class BookmarkModel;
class BookmarkNode;
}
namespace syncer {
struct EntityData;
}
namespace sync_bookmarks {
// Exposed for testing.
extern const base::Feature kInvalidateBookmarkSyncMetadataIfMismatchingGuid;
extern const base::Feature kInvalidateBookmarkSyncMetadataIfClientTagMissing;
// This class is responsible for keeping the mapping between bookmark nodes in
// the local model and the server-side corresponding sync entities. It manages
// the metadata for its entities and caches entity data upon a local change
// until commit confirmation is received.
class SyncedBookmarkTracker {
public:
class Entity {
public:
// |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.
// A commit may or may not be in progress at this time.
bool IsUnsynced() const;
// Check whether |data| matches the stored specifics hash. It ignores parent
// information.
bool MatchesDataIgnoringParent(const syncer::EntityData& data) const;
// Check whether |specifics| matches the stored specifics_hash.
bool MatchesSpecificsHash(const sync_pb::EntitySpecifics& specifics) const;
// Check whether |favicon_png_bytes| matches the stored
// bookmark_favicon_hash.
bool MatchesFaviconHash(const std::string& favicon_png_bytes) const;
// Returns null for tombstones.
const bookmarks::BookmarkNode* bookmark_node() const {
return bookmark_node_;
}
// Used in local deletions to mark and entity as a tombstone.
void clear_bookmark_node() { bookmark_node_ = nullptr; }
// Used when replacing a node in order to update its otherwise immutable
// GUID.
void set_bookmark_node(const bookmarks::BookmarkNode* bookmark_node) {
bookmark_node_ = bookmark_node;
}
const sync_pb::EntityMetadata* metadata() const {
// TODO(crbug.com/516866): The below CHECK is added to debug some crashes.
// Should be removed after figuring out the reason for the crash.
CHECK(metadata_);
return metadata_.get();
}
sync_pb::EntityMetadata* metadata() {
// TODO(crbug.com/516866): The below CHECK is added to debug some crashes.
// Should be removed after figuring out the reason for the crash.
CHECK(metadata_);
return metadata_.get();
}
bool commit_may_have_started() const { return commit_may_have_started_; }
void set_commit_may_have_started(bool value) {
commit_may_have_started_ = value;
}
// Returns whether the bookmark's GUID is known to match the server-side
// originator client item ID (or for pre-2015 bookmarks, the equivalent
// inferred GUID). This function may return false negatives since the
// required local metadata got populated with M81.
// TODO(crbug.com/1032052): Remove this code once all local sync metadata
// is required to populate the client tag (and be considered invalid
// otherwise).
bool has_final_guid() const;
// Returns true if the final GUID is known and it matches |guid|.
bool final_guid_matches(const std::string& guid) const;
// TODO(crbug.com/1032052): Remove this code once all local sync metadata
// is required to populate the client tag (and be considered invalid
// otherwise).
void set_final_guid(const std::string& guid);
void PopulateFaviconHashIfUnset(const std::string& favicon_png_bytes);
// Returns the estimate of dynamically allocated memory in bytes.
size_t EstimateMemoryUsage() const;
private:
// Null for tombstones.
const bookmarks::BookmarkNode* bookmark_node_;
// Serializable Sync metadata.
const std::unique_ptr<sync_pb::EntityMetadata> metadata_;
// Whether there could be a commit sent to the server for this entity. It's
// used to protect against sending tombstones for entities that have never
// been sent to the server. It's only briefly false between the time was
// first added to the tracker until the first commit request is sent to the
// server. The tracker sets it to true in the constructor because this code
// path is only executed in production when loading from disk.
bool commit_may_have_started_ = false;
DISALLOW_COPY_AND_ASSIGN(Entity);
};
// Creates an empty instance with no entities. Never returns null.
static std::unique_ptr<SyncedBookmarkTracker> CreateEmpty(
sync_pb::ModelTypeState model_type_state);
// Loads a tracker from a proto (usually from disk) after enforcing the
// consistency of the metadata against the BookmarkModel. Returns null if the
// data is inconsistent with sync metadata (i.e. corrupt). |model| must not be
// null.
static std::unique_ptr<SyncedBookmarkTracker>
CreateFromBookmarkModelAndMetadata(
const bookmarks::BookmarkModel* model,
sync_pb::BookmarkModelMetadata model_metadata);
~SyncedBookmarkTracker();
// This method is used to denote that all bookmarks are reuploaded and there
// is no need to reupload them again after next browser startup.
void SetBookmarksFullTitleReuploaded();
// Returns null if no entity is found.
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;
// Returns null if no tombstone entity is found.
const Entity* GetTombstoneEntityForGuid(const std::string& guid) const;
// Starts tracking local bookmark |bookmark_node|, which must not be tracked
// beforehand. The rest of the arguments represent the initial metadata.
// Returns the tracked entity.
const Entity* Add(const bookmarks::BookmarkNode* bookmark_node,
const std::string& sync_id,
int64_t server_version,
base::Time creation_time,
const sync_pb::UniquePosition& unique_position,
const sync_pb::EntitySpecifics& specifics);
// Updates the sync metadata for a tracked entity. |entity| must be owned by
// this tracker.
void Update(const Entity* entity,
int64_t server_version,
base::Time modification_time,
const sync_pb::UniquePosition& unique_position,
const sync_pb::EntitySpecifics& specifics);
// Updates the server version of an existing entity. |entity| must be owned by
// this tracker.
void UpdateServerVersion(const Entity* entity, int64_t server_version);
// Populates a bookmark's final GUID. |entity| must be owned by this tracker.
void PopulateFinalGuid(const Entity* entity, const std::string& guid);
// Populates the metadata field representing the hashed favicon. This method
// is effectively used to backfill the proto field, which was introduced late.
void PopulateFaviconHashIfUnset(const Entity* entity,
const std::string& favicon_png_bytes);
// Marks an existing entry that a commit request might have been sent to the
// server. |entity| must be owned by this tracker.
void MarkCommitMayHaveStarted(const Entity* entity);
// This class maintains the order of calls to this method and the same order
// is guaranteed when returning local changes in
// GetEntitiesWithLocalChanges() as well as in BuildBookmarkModelMetadata().
// |entity| must be owned by this tracker.
void MarkDeleted(const Entity* entity);
// Untracks an entity, which also invalidates the pointer. |entity| must be
// owned by this tracker.
void Remove(const Entity* entity);
// Increment sequence number in the metadata for |entity|. |entity| must be
// owned by this tracker.
void IncrementSequenceNumber(const Entity* entity);
sync_pb::BookmarkModelMetadata BuildBookmarkModelMetadata() const;
// Returns true if there are any local entities to be committed.
bool HasLocalChanges() const;
const sync_pb::ModelTypeState& model_type_state() const {
return model_type_state_;
}
void set_model_type_state(sync_pb::ModelTypeState model_type_state) {
model_type_state_ = std::move(model_type_state);
}
std::vector<const Entity*> GetAllEntities() const;
std::vector<const Entity*> GetEntitiesWithLocalChanges(
size_t max_entries) const;
// Updates the tracker after receiving the commit response. |sync_id| should
// match the already tracked sync ID for |entity|, with the exception of the
// initial commit, where the temporary client-generated ID will be overridden
// by the server-provided final ID. |entity| must be owned by this tracker.
void UpdateUponCommitResponse(const Entity* entity,
const std::string& sync_id,
int64_t server_version,
int64_t acked_sequence_number);
// Informs the tracker that the sync ID for |entity| has changed. It updates
// the internal state of the tracker accordingly. |entity| must be owned by
// this tracker.
void UpdateSyncIdForLocalCreationIfNeeded(const Entity* entity,
const std::string& sync_id);
// Informs the tracker that a BookmarkNode has been replaced. It updates
// the internal state of the tracker accordingly.
void UpdateBookmarkNodePointer(const bookmarks::BookmarkNode* old_node,
const bookmarks::BookmarkNode* new_node);
// Used to start tracking an entity that overwrites a previous local tombstone
// (e.g. user-initiated bookmark deletion undo). |entity| must be owned by
// this tracker.
void UndeleteTombstoneForBookmarkNode(const Entity* entity,
const bookmarks::BookmarkNode* node);
// Set the value of |EntityMetadata.acked_sequence_number| for |entity| to be
// equal to |EntityMetadata.sequence_number| such that it is not returned in
// GetEntitiesWithLocalChanges(). |entity| must be owned by this tracker.
void AckSequenceNumber(const Entity* entity);
// Whether the tracker is empty or not.
bool IsEmpty() const;
// Returns the estimate of dynamically allocated memory in bytes.
size_t EstimateMemoryUsage() const;
// Returns number of tracked entities. Used only in test.
size_t TrackedEntitiesCountForTest() const;
// Returns number of tracked bookmarks that aren't deleted.
size_t TrackedBookmarksCountForDebugging() const;
// Returns number of bookmarks that have been deleted but the server hasn't
// confirmed the deletion yet.
size_t TrackedUncommittedTombstonesCountForDebugging() const;
// Clears the specifics hash for |entity|, useful for testing.
void ClearSpecificsHashForTest(const Entity* entity);
// Checks whther all nodes in |bookmark_model| that *should* be tracked as per
// CanSyncNode() are tracked.
void CheckAllNodesTracked(
const bookmarks::BookmarkModel* bookmark_model) const;
private:
// Enumeration of possible reasons why persisted metadata are considered
// corrupted and don't match the bookmark model. Used in UMA metrics. Do not
// re-order or delete these entries; they are used in a UMA histogram. Please
// edit SyncBookmarkModelMetadataCorruptionReason in enums.xml if a value is
// added.
enum class CorruptionReason {
NO_CORRUPTION = 0,
MISSING_SERVER_ID = 1,
BOOKMARK_ID_IN_TOMBSTONE = 2,
MISSING_BOOKMARK_ID = 3,
// COUNT_MISMATCH = 4, // Deprecated.
// IDS_MISMATCH = 5, // Deprecated.
DUPLICATED_SERVER_ID = 6,
UNKNOWN_BOOKMARK_ID = 7,
UNTRACKED_BOOKMARK = 8,
BOOKMARK_GUID_MISMATCH = 9,
DUPLICATED_CLIENT_TAG_HASH = 10,
TRACKED_MANAGED_NODE = 11,
MISSING_CLIENT_TAG_HASH = 12,
kMaxValue = MISSING_CLIENT_TAG_HASH
};
SyncedBookmarkTracker(sync_pb::ModelTypeState model_type_state,
bool bookmarks_full_title_reuploaded);
// Add entities to |this| tracker based on the content of |*model| and
// |model_metadata|. Validates the integrity of |*model| and |model_metadata|
// and returns an enum representing any inconsistency.
CorruptionReason InitEntitiesFromModelAndMetadata(
const bookmarks::BookmarkModel* model,
sync_pb::BookmarkModelMetadata model_metadata);
// Conceptually, find a tracked entity that matches |entity| and returns a
// non-const pointer of it. The actual implementation is a const_cast.
// |entity| must be owned by this tracker.
Entity* AsMutableEntity(const Entity* entity);
// Reorders |entities| that represents local non-deletions such that parent
// creation/update is before child creation/update. Returns the ordered list.
std::vector<const Entity*> ReorderUnsyncedEntitiesExceptDeletions(
const std::vector<const Entity*>& entities) const;
// This method is used to mark all entities except permanent nodes as
// unsynced. This will cause reuploading of all bookmarks. This reupload
// should be initiated only when the |bookmarks_full_title_reuploaded| field
// in BookmarksMetadata is false. This field is used to prevent reuploading
// after each browser restart. It is set to true in
// BuildBookmarkModelMetadata.
// TODO(crbug.com/1066962): remove this code when most of bookmarks are
// reuploaded.
void ReuploadBookmarksOnLoadIfNeeded();
// Recursive method that starting from |node| appends all corresponding
// entities with updates in top-down order to |ordered_entities|.
void TraverseAndAppend(const bookmarks::BookmarkNode* node,
std::vector<const SyncedBookmarkTracker::Entity*>*
ordered_entities) const;
// A map of sync server ids to sync entities. This should contain entries and
// metadata for almost everything.
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::unordered_map<const bookmarks::BookmarkNode*, Entity*>
bookmark_node_to_entities_map_;
// A list of pending local bookmark deletions. They should be sent to the
// server in the same order as stored in the list. The same order should also
// be maintained across browser restarts (i.e. across calls to the ctor() and
// BuildBookmarkModelMetadata().
std::vector<Entity*> ordered_local_tombstones_;
// The model metadata (progress marker, initial sync done, etc).
sync_pb::ModelTypeState model_type_state_;
// This field contains the value of
// BookmarksMetadata::bookmarks_full_title_reuploaded.
// TODO(crbug.com/1066962): remove this code when most of bookmarks are
// reuploaded.
bool bookmarks_full_title_reuploaded_ = false;
DISALLOW_COPY_AND_ASSIGN(SyncedBookmarkTracker);
};
} // namespace sync_bookmarks
#endif // COMPONENTS_SYNC_BOOKMARKS_SYNCED_BOOKMARK_TRACKER_H_