1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "components/enhanced_bookmarks/enhanced_bookmark_model.h" 6 7#include <iomanip> 8#include <sstream> 9 10#include "base/base64.h" 11#include "base/logging.h" 12#include "base/message_loop/message_loop_proxy.h" 13#include "base/rand_util.h" 14#include "components/bookmarks/browser/bookmark_model.h" 15#include "components/bookmarks/browser/bookmark_node.h" 16#include "components/enhanced_bookmarks/enhanced_bookmark_model_observer.h" 17#include "components/enhanced_bookmarks/proto/metadata.pb.h" 18#include "ui/base/models/tree_node_iterator.h" 19#include "url/gurl.h" 20 21namespace { 22const char* kBookmarkBarId = "f_bookmarks_bar"; 23 24const char* kIdKey = "stars.id"; 25const char* kImageDataKey = "stars.imageData"; 26const char* kNoteKey = "stars.note"; 27const char* kOldIdKey = "stars.oldId"; 28const char* kPageDataKey = "stars.pageData"; 29const char* kVersionKey = "stars.version"; 30 31const char* kBookmarkPrefix = "ebc_"; 32 33// Helper method for working with bookmark metainfo. 34std::string DataForMetaInfoField(const BookmarkNode* node, 35 const std::string& field) { 36 std::string value; 37 if (!node->GetMetaInfo(field, &value)) 38 return std::string(); 39 40 std::string decoded; 41 if (!base::Base64Decode(value, &decoded)) 42 return std::string(); 43 44 return decoded; 45} 46 47// Helper method for working with ImageData_ImageInfo. 48bool PopulateImageData(const image::collections::ImageData_ImageInfo& info, 49 GURL* out_url, 50 int* width, 51 int* height) { 52 if (!info.has_url() || !info.has_width() || !info.has_height()) 53 return false; 54 55 GURL url(info.url()); 56 if (!url.is_valid()) 57 return false; 58 59 *out_url = url; 60 *width = info.width(); 61 *height = info.height(); 62 return true; 63} 64 65// Generate a random remote id, with a prefix that depends on whether the node 66// is a folder or a bookmark. 67std::string GenerateRemoteId() { 68 std::stringstream random_id; 69 random_id << kBookmarkPrefix; 70 71 // Generate 32 digit hex string random suffix. 72 random_id << std::hex << std::setfill('0') << std::setw(16); 73 random_id << base::RandUint64() << base::RandUint64(); 74 return random_id.str(); 75} 76} // namespace 77 78namespace enhanced_bookmarks { 79 80EnhancedBookmarkModel::EnhancedBookmarkModel(BookmarkModel* bookmark_model, 81 const std::string& version) 82 : bookmark_model_(bookmark_model), 83 loaded_(false), 84 weak_ptr_factory_(this), 85 version_(version) { 86 bookmark_model_->AddObserver(this); 87 if (bookmark_model_->loaded()) { 88 InitializeIdMap(); 89 loaded_ = true; 90 } 91} 92 93EnhancedBookmarkModel::~EnhancedBookmarkModel() { 94} 95 96void EnhancedBookmarkModel::Shutdown() { 97 FOR_EACH_OBSERVER(EnhancedBookmarkModelObserver, 98 observers_, 99 EnhancedBookmarkModelShuttingDown()); 100 weak_ptr_factory_.InvalidateWeakPtrs(); 101 bookmark_model_->RemoveObserver(this); 102 bookmark_model_ = NULL; 103} 104 105void EnhancedBookmarkModel::AddObserver( 106 EnhancedBookmarkModelObserver* observer) { 107 observers_.AddObserver(observer); 108} 109 110void EnhancedBookmarkModel::RemoveObserver( 111 EnhancedBookmarkModelObserver* observer) { 112 observers_.RemoveObserver(observer); 113} 114 115// Moves |node| to |new_parent| and inserts it at the given |index|. 116void EnhancedBookmarkModel::Move(const BookmarkNode* node, 117 const BookmarkNode* new_parent, 118 int index) { 119 bookmark_model_->Move(node, new_parent, index); 120} 121 122// Adds a new folder node at the specified position. 123const BookmarkNode* EnhancedBookmarkModel::AddFolder( 124 const BookmarkNode* parent, 125 int index, 126 const base::string16& title) { 127 return bookmark_model_->AddFolder(parent, index, title); 128} 129 130// Adds a url at the specified position. 131const BookmarkNode* EnhancedBookmarkModel::AddURL( 132 const BookmarkNode* parent, 133 int index, 134 const base::string16& title, 135 const GURL& url, 136 const base::Time& creation_time) { 137 BookmarkNode::MetaInfoMap meta_info; 138 meta_info[kIdKey] = GenerateRemoteId(); 139 return bookmark_model_->AddURLWithCreationTimeAndMetaInfo( 140 parent, index, title, url, creation_time, &meta_info); 141} 142 143std::string EnhancedBookmarkModel::GetRemoteId(const BookmarkNode* node) { 144 if (node == bookmark_model_->bookmark_bar_node()) 145 return kBookmarkBarId; 146 147 std::string id; 148 if (!node->GetMetaInfo(kIdKey, &id)) 149 return std::string(); 150 return id; 151} 152 153const BookmarkNode* EnhancedBookmarkModel::BookmarkForRemoteId( 154 const std::string& remote_id) { 155 IdToNodeMap::iterator it = id_map_.find(remote_id); 156 if (it != id_map_.end()) 157 return it->second; 158 return NULL; 159} 160 161void EnhancedBookmarkModel::SetDescription(const BookmarkNode* node, 162 const std::string& description) { 163 SetMetaInfo(node, kNoteKey, description); 164} 165 166std::string EnhancedBookmarkModel::GetDescription(const BookmarkNode* node) { 167 // First, look for a custom note set by the user. 168 std::string description; 169 if (node->GetMetaInfo(kNoteKey, &description) && !description.empty()) 170 return description; 171 172 // If none are present, return the snippet. 173 return GetSnippet(node); 174} 175 176bool EnhancedBookmarkModel::SetOriginalImage(const BookmarkNode* node, 177 const GURL& url, 178 int width, 179 int height) { 180 DCHECK(node->is_url()); 181 DCHECK(url.is_valid()); 182 183 std::string decoded(DataForMetaInfoField(node, kImageDataKey)); 184 image::collections::ImageData data; 185 186 // Try to populate the imageData with the existing data. 187 if (decoded != "") { 188 // If the parsing fails, something is wrong. Immediately fail. 189 bool result = data.ParseFromString(decoded); 190 if (!result) 191 return false; 192 } 193 194 scoped_ptr<image::collections::ImageData_ImageInfo> info( 195 new image::collections::ImageData_ImageInfo); 196 info->set_url(url.spec()); 197 info->set_width(width); 198 info->set_height(height); 199 data.set_allocated_original_info(info.release()); 200 201 std::string output; 202 bool result = data.SerializePartialToString(&output); 203 if (!result) 204 return false; 205 206 std::string encoded; 207 base::Base64Encode(output, &encoded); 208 SetMetaInfo(node, kImageDataKey, encoded); 209 return true; 210} 211 212bool EnhancedBookmarkModel::GetOriginalImage(const BookmarkNode* node, 213 GURL* url, 214 int* width, 215 int* height) { 216 std::string decoded(DataForMetaInfoField(node, kImageDataKey)); 217 if (decoded == "") 218 return false; 219 220 image::collections::ImageData data; 221 bool result = data.ParseFromString(decoded); 222 if (!result) 223 return false; 224 225 if (!data.has_original_info()) 226 return false; 227 228 return PopulateImageData(data.original_info(), url, width, height); 229} 230 231bool EnhancedBookmarkModel::GetThumbnailImage(const BookmarkNode* node, 232 GURL* url, 233 int* width, 234 int* height) { 235 std::string decoded(DataForMetaInfoField(node, kImageDataKey)); 236 if (decoded == "") 237 return false; 238 239 image::collections::ImageData data; 240 bool result = data.ParseFromString(decoded); 241 if (!result) 242 return false; 243 244 if (!data.has_thumbnail_info()) 245 return false; 246 247 return PopulateImageData(data.thumbnail_info(), url, width, height); 248} 249 250std::string EnhancedBookmarkModel::GetSnippet(const BookmarkNode* node) { 251 std::string decoded(DataForMetaInfoField(node, kPageDataKey)); 252 if (decoded.empty()) 253 return decoded; 254 255 image::collections::PageData data; 256 bool result = data.ParseFromString(decoded); 257 if (!result) 258 return std::string(); 259 260 return data.snippet(); 261} 262 263void EnhancedBookmarkModel::SetVersionSuffix( 264 const std::string& version_suffix) { 265 version_suffix_ = version_suffix; 266} 267 268void EnhancedBookmarkModel::BookmarkModelChanged() { 269} 270 271void EnhancedBookmarkModel::BookmarkModelLoaded(BookmarkModel* model, 272 bool ids_reassigned) { 273 InitializeIdMap(); 274 FOR_EACH_OBSERVER( 275 EnhancedBookmarkModelObserver, observers_, EnhancedBookmarkModelLoaded()); 276} 277 278void EnhancedBookmarkModel::BookmarkNodeAdded(BookmarkModel* model, 279 const BookmarkNode* parent, 280 int index) { 281 const BookmarkNode* node = parent->GetChild(index); 282 AddToIdMap(node); 283 ScheduleResetDuplicateRemoteIds(); 284 FOR_EACH_OBSERVER( 285 EnhancedBookmarkModelObserver, observers_, EnhancedBookmarkAdded(node)); 286} 287 288void EnhancedBookmarkModel::BookmarkNodeRemoved( 289 BookmarkModel* model, 290 const BookmarkNode* parent, 291 int old_index, 292 const BookmarkNode* node, 293 const std::set<GURL>& removed_urls) { 294 std::string remote_id = GetRemoteId(node); 295 id_map_.erase(remote_id); 296 FOR_EACH_OBSERVER( 297 EnhancedBookmarkModelObserver, observers_, EnhancedBookmarkRemoved(node)); 298} 299 300void EnhancedBookmarkModel::OnWillChangeBookmarkMetaInfo( 301 BookmarkModel* model, 302 const BookmarkNode* node) { 303 prev_remote_id_ = GetRemoteId(node); 304} 305 306void EnhancedBookmarkModel::BookmarkMetaInfoChanged(BookmarkModel* model, 307 const BookmarkNode* node) { 308 std::string remote_id = GetRemoteId(node); 309 if (remote_id != prev_remote_id_) { 310 id_map_.erase(prev_remote_id_); 311 if (!remote_id.empty()) { 312 AddToIdMap(node); 313 ScheduleResetDuplicateRemoteIds(); 314 } 315 FOR_EACH_OBSERVER( 316 EnhancedBookmarkModelObserver, 317 observers_, 318 EnhancedBookmarkRemoteIdChanged(node, prev_remote_id_, remote_id)); 319 } 320} 321 322void EnhancedBookmarkModel::BookmarkAllUserNodesRemoved( 323 BookmarkModel* model, 324 const std::set<GURL>& removed_urls) { 325 id_map_.clear(); 326 // Re-initialize so non-user nodes with remote ids are present in the map. 327 InitializeIdMap(); 328 FOR_EACH_OBSERVER(EnhancedBookmarkModelObserver, 329 observers_, 330 EnhancedBookmarkAllUserNodesRemoved()); 331} 332 333void EnhancedBookmarkModel::InitializeIdMap() { 334 ui::TreeNodeIterator<const BookmarkNode> iterator( 335 bookmark_model_->root_node()); 336 while (iterator.has_next()) { 337 AddToIdMap(iterator.Next()); 338 } 339 ScheduleResetDuplicateRemoteIds(); 340} 341 342void EnhancedBookmarkModel::AddToIdMap(const BookmarkNode* node) { 343 std::string remote_id = GetRemoteId(node); 344 if (remote_id.empty()) 345 return; 346 347 // Try to insert the node. 348 std::pair<IdToNodeMap::iterator, bool> result = 349 id_map_.insert(make_pair(remote_id, node)); 350 if (!result.second) { 351 // Some node already had the same remote id, so add both nodes to the 352 // to-be-reset set. 353 nodes_to_reset_[result.first->second] = remote_id; 354 nodes_to_reset_[node] = remote_id; 355 } 356} 357 358void EnhancedBookmarkModel::ScheduleResetDuplicateRemoteIds() { 359 if (!nodes_to_reset_.empty()) { 360 base::MessageLoopProxy::current()->PostTask( 361 FROM_HERE, 362 base::Bind(&EnhancedBookmarkModel::ResetDuplicateRemoteIds, 363 weak_ptr_factory_.GetWeakPtr())); 364 } 365} 366 367void EnhancedBookmarkModel::ResetDuplicateRemoteIds() { 368 for (NodeToIdMap::iterator it = nodes_to_reset_.begin(); 369 it != nodes_to_reset_.end(); 370 ++it) { 371 BookmarkNode::MetaInfoMap meta_info; 372 meta_info[kIdKey] = ""; 373 meta_info[kOldIdKey] = it->second; 374 SetMultipleMetaInfo(it->first, meta_info); 375 } 376 nodes_to_reset_.clear(); 377} 378 379void EnhancedBookmarkModel::SetMetaInfo(const BookmarkNode* node, 380 const std::string& field, 381 const std::string& value) { 382 DCHECK(!bookmark_model_->is_permanent_node(node)); 383 384 BookmarkNode::MetaInfoMap meta_info; 385 const BookmarkNode::MetaInfoMap* old_meta_info = node->GetMetaInfoMap(); 386 if (old_meta_info) 387 meta_info.insert(old_meta_info->begin(), old_meta_info->end()); 388 389 // Don't update anything if the value to set is already there. 390 BookmarkNode::MetaInfoMap::iterator it = meta_info.find(field); 391 if (it != meta_info.end() && it->second == value) 392 return; 393 394 meta_info[field] = value; 395 meta_info[kVersionKey] = GetVersionString(); 396 bookmark_model_->SetNodeMetaInfoMap(node, meta_info); 397} 398 399std::string EnhancedBookmarkModel::GetVersionString() { 400 if (version_suffix_.empty()) 401 return version_; 402 return version_ + '/' + version_suffix_; 403} 404 405void EnhancedBookmarkModel::SetMultipleMetaInfo( 406 const BookmarkNode* node, 407 BookmarkNode::MetaInfoMap meta_info) { 408 DCHECK(!bookmark_model_->is_permanent_node(node)); 409 410 // Don't update anything if every value is already set correctly. 411 if (node->GetMetaInfoMap()) { 412 bool changed = false; 413 const BookmarkNode::MetaInfoMap* old_meta_info = node->GetMetaInfoMap(); 414 for (BookmarkNode::MetaInfoMap::iterator it = meta_info.begin(); 415 it != meta_info.end(); 416 ++it) { 417 BookmarkNode::MetaInfoMap::const_iterator old_field = 418 old_meta_info->find(it->first); 419 if (old_field == old_meta_info->end() || 420 old_field->second != it->second) { 421 changed = true; 422 break; 423 } 424 } 425 if (!changed) 426 return; 427 428 // Fill in the values that aren't changing 429 meta_info.insert(old_meta_info->begin(), old_meta_info->end()); 430 } 431 432 meta_info[kVersionKey] = GetVersionString(); 433 bookmark_model_->SetNodeMetaInfoMap(node, meta_info); 434} 435 436bool EnhancedBookmarkModel::SetAllImages(const BookmarkNode* node, 437 const GURL& image_url, 438 int image_width, 439 int image_height, 440 const GURL& thumbnail_url, 441 int thumbnail_width, 442 int thumbnail_height) { 443 DCHECK(node->is_url()); 444 DCHECK(image_url.is_valid() || image_url.is_empty()); 445 DCHECK(thumbnail_url.is_valid() || thumbnail_url.is_empty()); 446 std::string decoded(DataForMetaInfoField(node, kImageDataKey)); 447 image::collections::ImageData data; 448 449 // Try to populate the imageData with the existing data. 450 if (decoded != "") { 451 // If the parsing fails, something is wrong. Immediately fail. 452 bool result = data.ParseFromString(decoded); 453 if (!result) 454 return false; 455 } 456 457 if (image_url.is_empty()) { 458 data.release_original_info(); 459 } else { 460 // Regardless of whether an image info exists, we make a new one. 461 // Intentially make a raw pointer. 462 image::collections::ImageData_ImageInfo* info = 463 new image::collections::ImageData_ImageInfo; 464 info->set_url(image_url.spec()); 465 info->set_width(image_width); 466 info->set_height(image_height); 467 // This method consumes the raw pointer. 468 data.set_allocated_original_info(info); 469 } 470 471 if (thumbnail_url.is_empty()) { 472 data.release_thumbnail_info(); 473 } else { 474 // Regardless of whether an image info exists, we make a new one. 475 // Intentially make a raw pointer. 476 image::collections::ImageData_ImageInfo* info = 477 new image::collections::ImageData_ImageInfo; 478 info->set_url(thumbnail_url.spec()); 479 info->set_width(thumbnail_width); 480 info->set_height(thumbnail_height); 481 // This method consumes the raw pointer. 482 data.set_allocated_thumbnail_info(info); 483 } 484 std::string output; 485 bool result = data.SerializePartialToString(&output); 486 if (!result) 487 return false; 488 489 std::string encoded; 490 base::Base64Encode(output, &encoded); 491 bookmark_model_->SetNodeMetaInfo(node, kImageDataKey, encoded); 492 return true; 493} 494 495} // namespace enhanced_bookmarks 496