// Copyright 2014 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/enhanced_bookmarks/enhanced_bookmark_model.h" #include #include #include "base/base64.h" #include "base/location.h" #include "base/logging.h" #include "base/rand_util.h" #include "base/single_thread_task_runner.h" #include "base/strings/string_number_conversions.h" #include "base/thread_task_runner_handle.h" #include "components/bookmarks/browser/bookmark_model.h" #include "components/bookmarks/browser/bookmark_node.h" #include "components/enhanced_bookmarks/enhanced_bookmark_model_observer.h" #include "components/enhanced_bookmarks/proto/metadata.pb.h" #include "ui/base/models/tree_node_iterator.h" #include "url/gurl.h" using bookmarks::BookmarkModel; using bookmarks::BookmarkNode; namespace { const char* kBookmarkBarId = "f_bookmarks_bar"; const char* kIdKey = "stars.id"; const char* kImageDataKey = "stars.imageData"; const char* kNoteKey = "stars.note"; const char* kOldIdKey = "stars.oldId"; const char* kPageDataKey = "stars.pageData"; const char* kVersionKey = "stars.version"; const char* kBookmarkPrefix = "ebc_"; // Helper method for working with bookmark metainfo. std::string DataForMetaInfoField(const BookmarkNode* node, const std::string& field) { std::string value; if (!node->GetMetaInfo(field, &value)) return std::string(); std::string decoded; if (!base::Base64Decode(value, &decoded)) return std::string(); return decoded; } // Helper method for working with ImageData_ImageInfo. bool PopulateImageData(const image::collections::ImageData_ImageInfo& info, GURL* out_url, int* width, int* height) { if (!info.has_url() || !info.has_width() || !info.has_height()) return false; GURL url(info.url()); if (!url.is_valid()) return false; *out_url = url; *width = info.width(); *height = info.height(); return true; } // Generate a random remote id, with a prefix that depends on whether the node // is a folder or a bookmark. std::string GenerateRemoteId() { std::stringstream random_id; random_id << kBookmarkPrefix; // Generate 32 digit hex string random suffix. random_id << std::hex << std::setfill('0') << std::setw(16); random_id << base::RandUint64() << base::RandUint64(); return random_id.str(); } } // namespace namespace enhanced_bookmarks { EnhancedBookmarkModel::EnhancedBookmarkModel(BookmarkModel* bookmark_model, const std::string& version) : bookmark_model_(bookmark_model), loaded_(false), version_(version), weak_ptr_factory_(this) { bookmark_model_->AddObserver(this); bookmark_model_->AddNonClonedKey(kIdKey); if (bookmark_model_->loaded()) { InitializeIdMap(); loaded_ = true; } } EnhancedBookmarkModel::~EnhancedBookmarkModel() { Shutdown(); } void EnhancedBookmarkModel::Shutdown() { if (bookmark_model_) { FOR_EACH_OBSERVER(EnhancedBookmarkModelObserver, observers_, EnhancedBookmarkModelShuttingDown()); weak_ptr_factory_.InvalidateWeakPtrs(); bookmark_model_->RemoveObserver(this); bookmark_model_ = NULL; } } void EnhancedBookmarkModel::AddObserver( EnhancedBookmarkModelObserver* observer) { observers_.AddObserver(observer); } void EnhancedBookmarkModel::RemoveObserver( EnhancedBookmarkModelObserver* observer) { observers_.RemoveObserver(observer); } // Moves |node| to |new_parent| and inserts it at the given |index|. void EnhancedBookmarkModel::Move(const BookmarkNode* node, const BookmarkNode* new_parent, int index) { bookmark_model_->Move(node, new_parent, index); } // Adds a new folder node at the specified position. const BookmarkNode* EnhancedBookmarkModel::AddFolder( const BookmarkNode* parent, int index, const base::string16& title) { BookmarkNode::MetaInfoMap meta_info; meta_info[kVersionKey] = GetVersionString(); return bookmark_model_->AddFolderWithMetaInfo(parent, index, title, &meta_info); } // Adds a url at the specified position. const BookmarkNode* EnhancedBookmarkModel::AddURL( const BookmarkNode* parent, int index, const base::string16& title, const GURL& url, const base::Time& creation_time) { BookmarkNode::MetaInfoMap meta_info; meta_info[kIdKey] = GenerateRemoteId(); meta_info[kVersionKey] = GetVersionString(); return bookmark_model_->AddURLWithCreationTimeAndMetaInfo( parent, index, title, url, creation_time, &meta_info); } std::string EnhancedBookmarkModel::GetRemoteId(const BookmarkNode* node) { if (node == bookmark_model_->bookmark_bar_node()) return kBookmarkBarId; std::string id; if (!node->GetMetaInfo(kIdKey, &id)) return std::string(); return id; } const BookmarkNode* EnhancedBookmarkModel::BookmarkForRemoteId( const std::string& remote_id) { IdToNodeMap::iterator it = id_map_.find(remote_id); if (it != id_map_.end()) return it->second; return NULL; } void EnhancedBookmarkModel::SetDescription(const BookmarkNode* node, const std::string& description) { SetMetaInfo(node, kNoteKey, description); } std::string EnhancedBookmarkModel::GetDescription(const BookmarkNode* node) { // First, look for a custom note set by the user. std::string description; if (node->GetMetaInfo(kNoteKey, &description) && !description.empty()) return description; // If none are present, return the snippet. return GetSnippet(node); } bool EnhancedBookmarkModel::SetOriginalImage(const BookmarkNode* node, const GURL& url, int width, int height) { DCHECK(node->is_url()); DCHECK(url.is_valid()); std::string decoded(DataForMetaInfoField(node, kImageDataKey)); image::collections::ImageData data; // Try to populate the imageData with the existing data. if (!decoded.empty()) { // If the parsing fails, something is wrong. Immediately fail. bool result = data.ParseFromString(decoded); if (!result) return false; } scoped_ptr info( new image::collections::ImageData_ImageInfo); info->set_url(url.spec()); info->set_width(width); info->set_height(height); data.set_allocated_original_info(info.release()); std::string output; bool result = data.SerializePartialToString(&output); if (!result) return false; std::string encoded; base::Base64Encode(output, &encoded); SetMetaInfo(node, kImageDataKey, encoded); return true; } void EnhancedBookmarkModel::RemoveImageData(const BookmarkNode* node) { DCHECK(node->is_url()); image::collections::ImageData data; data.set_user_removed_image(true); std::string encoded_data; base::Base64Encode(data.SerializeAsString(), &encoded_data); SetMetaInfo(node, kImageDataKey, encoded_data); } bool EnhancedBookmarkModel::GetOriginalImage(const BookmarkNode* node, GURL* url, int* width, int* height) { std::string decoded(DataForMetaInfoField(node, kImageDataKey)); if (decoded.empty()) return false; image::collections::ImageData data; bool result = data.ParseFromString(decoded); if (!result) return false; if (!data.has_original_info()) return false; return PopulateImageData(data.original_info(), url, width, height); } bool EnhancedBookmarkModel::GetThumbnailImage(const BookmarkNode* node, GURL* url, int* width, int* height) { std::string decoded(DataForMetaInfoField(node, kImageDataKey)); if (decoded.empty()) return false; image::collections::ImageData data; bool result = data.ParseFromString(decoded); if (!result) return false; if (!data.has_thumbnail_info()) return false; return PopulateImageData(data.thumbnail_info(), url, width, height); } std::string EnhancedBookmarkModel::GetSnippet(const BookmarkNode* node) { std::string decoded(DataForMetaInfoField(node, kPageDataKey)); if (decoded.empty()) return decoded; image::collections::PageData data; bool result = data.ParseFromString(decoded); if (!result) return std::string(); return data.snippet(); } void EnhancedBookmarkModel::SetVersionSuffix( const std::string& version_suffix) { version_suffix_ = version_suffix; } void EnhancedBookmarkModel::BookmarkModelChanged() { } void EnhancedBookmarkModel::BookmarkModelLoaded(BookmarkModel* model, bool ids_reassigned) { InitializeIdMap(); loaded_ = true; FOR_EACH_OBSERVER( EnhancedBookmarkModelObserver, observers_, EnhancedBookmarkModelLoaded()); } void EnhancedBookmarkModel::BookmarkNodeAdded(BookmarkModel* model, const BookmarkNode* parent, int index) { const BookmarkNode* node = parent->GetChild(index); std::string remote_id; if (node->GetMetaInfo(kIdKey, &remote_id)) { AddToIdMap(node); ScheduleResetDuplicateRemoteIds(); } FOR_EACH_OBSERVER( EnhancedBookmarkModelObserver, observers_, EnhancedBookmarkAdded(node)); } void EnhancedBookmarkModel::BookmarkNodeRemoved( BookmarkModel* model, const BookmarkNode* parent, int old_index, const BookmarkNode* node, const std::set& removed_urls) { RemoveNodeFromMaps(node); FOR_EACH_OBSERVER( EnhancedBookmarkModelObserver, observers_, EnhancedBookmarkRemoved(node)); } void EnhancedBookmarkModel::BookmarkNodeChanged(BookmarkModel* model, const BookmarkNode* node) { FOR_EACH_OBSERVER( EnhancedBookmarkModelObserver, observers_, EnhancedBookmarkNodeChanged(node)); } void EnhancedBookmarkModel::OnWillChangeBookmarkMetaInfo( BookmarkModel* model, const BookmarkNode* node) { prev_remote_id_ = GetRemoteId(node); } void EnhancedBookmarkModel::BookmarkMetaInfoChanged(BookmarkModel* model, const BookmarkNode* node) { std::string remote_id = GetRemoteId(node); if (remote_id != prev_remote_id_) { id_map_.erase(prev_remote_id_); if (!remote_id.empty()) { AddToIdMap(node); ScheduleResetDuplicateRemoteIds(); } FOR_EACH_OBSERVER( EnhancedBookmarkModelObserver, observers_, EnhancedBookmarkRemoteIdChanged(node, prev_remote_id_, remote_id)); } } void EnhancedBookmarkModel::BookmarkAllUserNodesRemoved( BookmarkModel* model, const std::set& removed_urls) { id_map_.clear(); // Re-initialize so non-user nodes with remote ids are present in the map. InitializeIdMap(); FOR_EACH_OBSERVER(EnhancedBookmarkModelObserver, observers_, EnhancedBookmarkAllUserNodesRemoved()); } void EnhancedBookmarkModel::InitializeIdMap() { ui::TreeNodeIterator iterator( bookmark_model_->root_node()); while (iterator.has_next()) { AddToIdMap(iterator.Next()); } ScheduleResetDuplicateRemoteIds(); } void EnhancedBookmarkModel::AddToIdMap(const BookmarkNode* node) { std::string remote_id = GetRemoteId(node); if (remote_id.empty()) return; // Try to insert the node. std::pair result = id_map_.insert(make_pair(remote_id, node)); if (!result.second) { // Some node already had the same remote id, so add both nodes to the // to-be-reset set. nodes_to_reset_[result.first->second] = remote_id; nodes_to_reset_[node] = remote_id; } } void EnhancedBookmarkModel::RemoveNodeFromMaps(const BookmarkNode* node) { for (int i = 0; i < node->child_count(); i++) { RemoveNodeFromMaps(node->GetChild(i)); } std::string remote_id = GetRemoteId(node); id_map_.erase(remote_id); nodes_to_reset_.erase(node); } void EnhancedBookmarkModel::ScheduleResetDuplicateRemoteIds() { if (!nodes_to_reset_.empty()) { base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::Bind(&EnhancedBookmarkModel::ResetDuplicateRemoteIds, weak_ptr_factory_.GetWeakPtr())); } } void EnhancedBookmarkModel::ResetDuplicateRemoteIds() { for (NodeToIdMap::iterator it = nodes_to_reset_.begin(); it != nodes_to_reset_.end(); ++it) { BookmarkNode::MetaInfoMap meta_info; meta_info[kIdKey] = ""; meta_info[kOldIdKey] = it->second; SetMultipleMetaInfo(it->first, meta_info); } nodes_to_reset_.clear(); } void EnhancedBookmarkModel::SetMetaInfo(const BookmarkNode* node, const std::string& field, const std::string& value) { DCHECK(!bookmark_model_->is_permanent_node(node)); BookmarkNode::MetaInfoMap meta_info; const BookmarkNode::MetaInfoMap* old_meta_info = node->GetMetaInfoMap(); if (old_meta_info) meta_info.insert(old_meta_info->begin(), old_meta_info->end()); // Don't update anything if the value to set is already there. BookmarkNode::MetaInfoMap::iterator it = meta_info.find(field); if (it != meta_info.end() && it->second == value) return; meta_info[field] = value; meta_info[kVersionKey] = GetVersionString(); bookmark_model_->SetNodeMetaInfoMap(node, meta_info); } std::string EnhancedBookmarkModel::GetVersionString() { if (version_suffix_.empty()) return version_; return version_ + '/' + version_suffix_; } void EnhancedBookmarkModel::SetMultipleMetaInfo( const BookmarkNode* node, BookmarkNode::MetaInfoMap meta_info) { DCHECK(!bookmark_model_->is_permanent_node(node)); // Don't update anything if every value is already set correctly. if (node->GetMetaInfoMap()) { bool changed = false; const BookmarkNode::MetaInfoMap* old_meta_info = node->GetMetaInfoMap(); for (BookmarkNode::MetaInfoMap::iterator it = meta_info.begin(); it != meta_info.end(); ++it) { BookmarkNode::MetaInfoMap::const_iterator old_field = old_meta_info->find(it->first); if (old_field == old_meta_info->end() || old_field->second != it->second) { changed = true; break; } } if (!changed) return; // Fill in the values that aren't changing meta_info.insert(old_meta_info->begin(), old_meta_info->end()); } meta_info[kVersionKey] = GetVersionString(); bookmark_model_->SetNodeMetaInfoMap(node, meta_info); } bool EnhancedBookmarkModel::SetAllImages(const BookmarkNode* node, const GURL& image_url, int image_width, int image_height, const GURL& thumbnail_url, int thumbnail_width, int thumbnail_height) { DCHECK(node->is_url()); DCHECK(image_url.is_valid() || image_url.is_empty()); DCHECK(thumbnail_url.is_valid() || thumbnail_url.is_empty()); std::string decoded(DataForMetaInfoField(node, kImageDataKey)); image::collections::ImageData data; // Try to populate the imageData with the existing data. if (!decoded.empty()) { // If the parsing fails, something is wrong. Immediately fail. bool result = data.ParseFromString(decoded); if (!result) return false; } if (image_url.is_empty()) { data.release_original_info(); } else { // Regardless of whether an image info exists, we make a new one. // Intentially make a raw pointer. image::collections::ImageData_ImageInfo* info = new image::collections::ImageData_ImageInfo; info->set_url(image_url.spec()); info->set_width(image_width); info->set_height(image_height); // This method consumes the raw pointer. data.set_allocated_original_info(info); } if (thumbnail_url.is_empty()) { data.release_thumbnail_info(); } else { // Regardless of whether an image info exists, we make a new one. // Intentially make a raw pointer. image::collections::ImageData_ImageInfo* info = new image::collections::ImageData_ImageInfo; info->set_url(thumbnail_url.spec()); info->set_width(thumbnail_width); info->set_height(thumbnail_height); // This method consumes the raw pointer. data.set_allocated_thumbnail_info(info); } std::string output; bool result = data.SerializePartialToString(&output); if (!result) return false; std::string encoded; base::Base64Encode(output, &encoded); bookmark_model_->SetNodeMetaInfo(node, kImageDataKey, encoded); return true; } } // namespace enhanced_bookmarks