// 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 #include #include #include #include #include #include #include "base/compiler_specific.h" #include "base/containers/hash_tables.h" #include "base/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_driver/data_type_error_handler.h" #include "components/sync_driver/model_associator.h" #include "sync/internal_api/public/util/unrecoverable_error_handler.h" class GURL; namespace bookmarks { class BookmarkModel; class BookmarkNode; } namespace syncer { class BaseNode; class BaseTransaction; struct UserShare; class WriteTransaction; } namespace sync_driver { class SyncClient; } namespace browser_sync { // 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 sync_driver:: PerDataTypeAssociatorInterface { 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, sync_driver::SyncClient* sync_client, syncer::UserShare* user_share, sync_driver::DataTypeErrorHandler* unrecoverable_error_handler, bool expect_mobile_bookmarks_folder); ~BookmarkModelAssociator() override; // 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) override; // 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) override; // 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) override; // Associates the given bookmark node with the given sync node. void Associate(const bookmarks::BookmarkNode* node, const syncer::BaseNode& sync_node) override; // Remove the association that corresponds to the given sync id. void Disassociate(int64_t sync_id) override; void AbortAssociation() override { // No implementation needed, this associator runs on the main // thread. } // See ModelAssociator interface. bool CryptoReadyIfNecessary() override; private: typedef std::map BookmarkIdToSyncIdMap; typedef std::map SyncIdToBookmarkNodeMap; typedef std::set DirtyAssociationsSyncIds; typedef std::vector BookmarkList; typedef std::stack BookmarkStack; // 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. std::stack 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. base::hash_set 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& sync_ids, Context* context); // Helper method for creating a new native bookmark node. const bookmarks::BookmarkNode* CreateBookmarkNode( const bookmarks::BookmarkNode* parent_node, int bookmark_index, const syncer::BaseNode* sync_child_node, const GURL& url, Context* context, syncer::SyncError* error); // Helper method for deleting a sync node and all its children. // Returns the number of sync nodes deleted. int RemoveSyncNodeHierarchy(syncer::WriteTransaction* trans, int64_t sync_id); // 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* bookmark_model_; sync_driver::SyncClient* sync_client_; syncer::UserShare* user_share_; sync_driver::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 weak_factory_; DISALLOW_COPY_AND_ASSIGN(BookmarkModelAssociator); }; } // namespace browser_sync #endif // COMPONENTS_SYNC_BOOKMARKS_BOOKMARK_MODEL_ASSOCIATOR_H_