blob: db7c8b8bea824d292ef2de7b357c8e9d8a0139d2 [file] [log] [blame]
// Copyright (c) 2012 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_BOOKMARK_MODEL_ASSOCIATOR_H_
#define COMPONENTS_SYNC_BOOKMARKS_BOOKMARK_MODEL_ASSOCIATOR_H_
#include <stddef.h>
#include <stdint.h>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <unordered_set>
#include <vector>
#include "base/compiler_specific.h"
#include "base/containers/stack.h"
#include "base/hash/hash.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string16.h"
#include "base/threading/thread_checker.h"
#include "components/sync/base/unrecoverable_error_handler.h"
#include "components/sync/driver/model_associator.h"
#include "components/sync/model/data_type_error_handler.h"
class BookmarkUndoService;
class GURL;
namespace bookmarks {
class BookmarkModel;
class BookmarkNode;
} // namespace bookmarks
namespace favicon {
class FaviconService;
} // namespace favicon
namespace syncer {
class BaseNode;
class BaseTransaction;
class WriteTransaction;
struct UserShare;
} // namespace syncer
namespace sync_bookmarks {
// Contains all model association related logic:
// * Algorithm to associate bookmark model and sync model.
// * Methods to get a bookmark node for a given sync node and vice versa.
// * Persisting model associations and loading them back.
class BookmarkModelAssociator : public syncer::AssociatorInterface {
public:
static syncer::ModelType model_type() { return syncer::BOOKMARKS; }
// |expect_mobile_bookmarks_folder| controls whether or not we
// expect the mobile bookmarks permanent folder to be created.
// Should be set to true only by mobile clients.
BookmarkModelAssociator(
bookmarks::BookmarkModel* bookmark_model,
BookmarkUndoService* bookmark_undo_service,
favicon::FaviconService* favicon_service,
syncer::UserShare* user_share,
std::unique_ptr<syncer::DataTypeErrorHandler> unrecoverable_error_handler,
bool expect_mobile_bookmarks_folder);
~BookmarkModelAssociator() override;
bookmarks::BookmarkModel* GetBookmarkModel();
favicon::FaviconService* GetFaviconService();
BookmarkUndoService* GetBookmarkUndoService();
// AssociatorInterface implementation.
//
// AssociateModels iterates through both the sync and the browser
// bookmark model, looking for matched pairs of items. For any pairs it
// finds, it will call AssociateSyncID. For any unmatched items,
// MergeAndAssociateModels will try to repair the match, e.g. by adding a new
// node. After successful completion, the models should be identical and
// corresponding. Returns true on success. On failure of this step, we
// should abort the sync operation and report an error to the user.
syncer::SyncError AssociateModels(
syncer::SyncMergeResult* local_merge_result,
syncer::SyncMergeResult* syncer_merge_result) override;
syncer::SyncError DisassociateModels() override;
// The has_nodes out param is true if the sync model has nodes other
// than the permanent tagged nodes.
bool SyncModelHasUserCreatedNodes(bool* has_nodes) override;
// Returns sync id for the given bookmark node id.
// Returns syncer::kInvalidId if the sync node is not found for the given
// bookmark node id.
int64_t GetSyncIdFromChromeId(const int64_t& node_id);
// Returns the bookmark node for the given sync id.
// Returns null if no bookmark node is found for the given sync id.
const bookmarks::BookmarkNode* GetChromeNodeFromSyncId(int64_t sync_id);
// Initializes the given sync node from the given bookmark node id.
// Returns false if no sync node was found for the given bookmark node id or
// if the initialization of sync node fails.
bool InitSyncNodeFromChromeId(const int64_t& node_id,
syncer::BaseNode* sync_node);
// Associates the given bookmark node with the given sync node.
void Associate(const bookmarks::BookmarkNode* node,
const syncer::BaseNode& sync_node);
// Remove the association that corresponds to the given sync id.
void Disassociate(int64_t sync_id);
void AbortAssociation() override {
// No implementation needed, this associator runs on the main
// thread.
}
// See ModelAssociator interface.
bool CryptoReadyIfNecessary() override;
private:
using BookmarkIdToSyncIdMap = std::map<int64_t, int64_t>;
using SyncIdToBookmarkNodeMap =
std::map<int64_t, const bookmarks::BookmarkNode*>;
using DirtyAssociationsSyncIds = std::set<int64_t>;
using BookmarkList = std::vector<const bookmarks::BookmarkNode*>;
using BookmarkStack = base::stack<const bookmarks::BookmarkNode*>;
// Add association between native node and sync node to the maps.
void AddAssociation(const bookmarks::BookmarkNode* node, int64_t sync_id);
// Posts a task to persist dirty associations.
void PostPersistAssociationsTask();
// Persists all dirty associations.
void PersistAssociations();
// Result of the native model version check against the sync
// version performed by CheckModelSyncState.
enum NativeModelSyncState {
// The native version is syncer::syncable::kInvalidTransactionVersion,
// which is the case when the version has either not been set yet or reset
// as a result of a previous error during the association. Basically the
// state should return back to UNSET on an association following the one
// where the state was different than IN_SYNC.
UNSET,
// The native version was in sync with the Sync version.
IN_SYNC,
// The native version was behing the sync version which indicates a failure
// to persist the native bookmarks model.
BEHIND,
// The native version was ahead of the sync version which indicates a
// a failure to persist Sync DB.
AHEAD,
NATIVE_MODEL_SYNC_STATE_COUNT
};
// Helper class used within AssociateModels to simplify the logic and
// minimize the number of arguments passed between private functions.
class Context {
public:
Context(syncer::SyncMergeResult* local_merge_result,
syncer::SyncMergeResult* syncer_merge_result);
~Context();
// Push a sync node to the DFS stack.
void PushNode(int64_t sync_id);
// Pops a sync node from the DFS stack. Returns false if the stack
// is empty.
bool PopNode(int64_t* sync_id);
// The following methods are used to update |local_merge_result_| and
// |syncer_merge_result_|.
void SetPreAssociationVersions(int64_t native_version,
int64_t sync_version);
void SetNumItemsBeforeAssociation(int local_num, int sync_num);
void SetNumItemsAfterAssociation(int local_num, int sync_num);
void IncrementLocalItemsDeleted();
void IncrementLocalItemsAdded();
void IncrementLocalItemsModified();
void IncrementSyncItemsAdded();
void IncrementSyncItemsDeleted(int count);
void UpdateDuplicateCount(const base::string16& title, const GURL& url);
int duplicate_count() const { return duplicate_count_; }
NativeModelSyncState native_model_sync_state() const {
return native_model_sync_state_;
}
void set_native_model_sync_state(NativeModelSyncState state) {
native_model_sync_state_ = state;
}
// Bookmark roots participating in the sync.
void AddBookmarkRoot(const bookmarks::BookmarkNode* root);
const BookmarkList& bookmark_roots() const { return bookmark_roots_; }
// Gets pre-association sync version for Bookmarks datatype.
int64_t GetSyncPreAssociationVersion() const;
void MarkForVersionUpdate(const bookmarks::BookmarkNode* node);
const BookmarkList& bookmarks_for_version_update() const {
return bookmarks_for_version_update_;
}
private:
// DFS stack of sync nodes traversed during association.
base::stack<int64_t> dfs_stack_;
// Local and merge results are not owned.
syncer::SyncMergeResult* local_merge_result_;
syncer::SyncMergeResult* syncer_merge_result_;
// |hashes_| contains hash codes of all native bookmarks
// for the purpose of detecting duplicates. A small number of
// false positives due to hash collisions is OK because this
// data is used for reporting purposes only.
std::unordered_set<size_t> hashes_;
// Overall number of bookmark collisions from RecordDuplicates call.
int duplicate_count_;
// Result of the most recent BookmarkModelAssociator::CheckModelSyncState.
NativeModelSyncState native_model_sync_state_;
// List of bookmark model roots participating in the sync.
BookmarkList bookmark_roots_;
// List of bookmark nodes for which the transaction version needs to be
// updated.
BookmarkList bookmarks_for_version_update_;
DISALLOW_COPY_AND_ASSIGN(Context);
};
// Matches up the bookmark model and the sync model to build model
// associations.
syncer::SyncError BuildAssociations(Context* context);
// Two helper functions that populate SyncMergeResult with numbers of
// items before/after the association.
void SetNumItemsBeforeAssociation(syncer::BaseTransaction* trans,
Context* context);
void SetNumItemsAfterAssociation(syncer::BaseTransaction* trans,
Context* context);
// Used by SetNumItemsBeforeAssociation.
// Similar to BookmarkNode::GetTotalNodeCount but also scans the native
// model for duplicates and records them in |context|.
int GetTotalBookmarkCountAndRecordDuplicates(
const bookmarks::BookmarkNode* node,
Context* context) const;
// Helper function that associates all tagged permanent folders and primes
// the provided context with sync IDs of those folders.
syncer::SyncError AssociatePermanentFolders(syncer::BaseTransaction* trans,
Context* context);
// Associate a top-level node of the bookmark model with a permanent node in
// the sync domain. Such permanent nodes are identified by a tag that is
// well known to the server and the client, and is unique within a particular
// user's share. For example, "other_bookmarks" is the tag for the Other
// Bookmarks folder. The sync nodes are server-created.
// Returns true on success, false if association failed.
bool AssociateTaggedPermanentNode(
syncer::BaseTransaction* trans,
const bookmarks::BookmarkNode* permanent_node,
const std::string& tag) WARN_UNUSED_RESULT;
// Removes bookmark nodes whose corresponding sync nodes have been deleted
// according to sync delete journals.
void ApplyDeletesFromSyncJournal(syncer::BaseTransaction* trans,
Context* context);
// The main part of the association process that associatiates
// native nodes that are children of |parent_node| with sync nodes with IDs
// from |sync_ids|.
syncer::SyncError BuildAssociations(
syncer::WriteTransaction* trans,
const bookmarks::BookmarkNode* parent_node,
const std::vector<int64_t>& sync_ids,
Context* context);
// Helper method for creating a new native bookmark node.
const bookmarks::BookmarkNode* CreateBookmarkNode(
const bookmarks::BookmarkNode* parent_node,
size_t bookmark_index,
const syncer::BaseNode* sync_child_node,
const GURL& url,
Context* context,
syncer::SyncError* error);
// Check whether bookmark model and sync model are synced by comparing
// their transaction versions.
// Returns a PERSISTENCE_ERROR if a transaction mismatch was detected where
// the native model has a newer transaction verison.
syncer::SyncError CheckModelSyncState(Context* context) const;
base::ThreadChecker thread_checker_;
bookmarks::BookmarkModel* const bookmark_model_;
BookmarkUndoService* const bookmark_undo_service_;
favicon::FaviconService* const favicon_service_;
syncer::UserShare* const user_share_;
std::unique_ptr<syncer::DataTypeErrorHandler> unrecoverable_error_handler_;
const bool expect_mobile_bookmarks_folder_;
BookmarkIdToSyncIdMap id_map_;
SyncIdToBookmarkNodeMap id_map_inverse_;
// Stores sync ids for dirty associations.
DirtyAssociationsSyncIds dirty_associations_sync_ids_;
// Used to post PersistAssociation tasks to the current message loop and
// guarantees no invocations can occur if |this| has been deleted. (This
// allows this class to be non-refcounted).
base::WeakPtrFactory<BookmarkModelAssociator> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(BookmarkModelAssociator);
};
} // namespace sync_bookmarks
#endif // COMPONENTS_SYNC_BOOKMARKS_BOOKMARK_MODEL_ASSOCIATOR_H_