Home | History | Annotate | Download | only in enhanced_bookmarks
      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 
     21 namespace {
     22 const char* kBookmarkBarId = "f_bookmarks_bar";
     23 
     24 const char* kIdKey = "stars.id";
     25 const char* kImageDataKey = "stars.imageData";
     26 const char* kNoteKey = "stars.note";
     27 const char* kOldIdKey = "stars.oldId";
     28 const char* kPageDataKey = "stars.pageData";
     29 const char* kVersionKey = "stars.version";
     30 
     31 const char* kBookmarkPrefix = "ebc_";
     32 
     33 // Helper method for working with bookmark metainfo.
     34 std::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.
     48 bool 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.
     67 std::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 
     78 namespace enhanced_bookmarks {
     79 
     80 EnhancedBookmarkModel::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 
     93 EnhancedBookmarkModel::~EnhancedBookmarkModel() {
     94 }
     95 
     96 void 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 
    105 void EnhancedBookmarkModel::AddObserver(
    106     EnhancedBookmarkModelObserver* observer) {
    107   observers_.AddObserver(observer);
    108 }
    109 
    110 void EnhancedBookmarkModel::RemoveObserver(
    111     EnhancedBookmarkModelObserver* observer) {
    112   observers_.RemoveObserver(observer);
    113 }
    114 
    115 // Moves |node| to |new_parent| and inserts it at the given |index|.
    116 void 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.
    123 const 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.
    131 const 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 
    143 std::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 
    153 const 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 
    161 void EnhancedBookmarkModel::SetDescription(const BookmarkNode* node,
    162                                            const std::string& description) {
    163   SetMetaInfo(node, kNoteKey, description);
    164 }
    165 
    166 std::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 
    176 bool 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 
    212 bool 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 
    231 bool 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 
    250 std::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 
    263 void EnhancedBookmarkModel::SetVersionSuffix(
    264     const std::string& version_suffix) {
    265   version_suffix_ = version_suffix;
    266 }
    267 
    268 void EnhancedBookmarkModel::BookmarkModelChanged() {
    269 }
    270 
    271 void EnhancedBookmarkModel::BookmarkModelLoaded(BookmarkModel* model,
    272                                                 bool ids_reassigned) {
    273   InitializeIdMap();
    274   FOR_EACH_OBSERVER(
    275       EnhancedBookmarkModelObserver, observers_, EnhancedBookmarkModelLoaded());
    276 }
    277 
    278 void 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 
    288 void 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 
    300 void EnhancedBookmarkModel::OnWillChangeBookmarkMetaInfo(
    301     BookmarkModel* model,
    302     const BookmarkNode* node) {
    303   prev_remote_id_ = GetRemoteId(node);
    304 }
    305 
    306 void 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 
    322 void 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 
    333 void 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 
    342 void 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 
    358 void 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 
    367 void 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 
    379 void 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 
    399 std::string EnhancedBookmarkModel::GetVersionString() {
    400   if (version_suffix_.empty())
    401     return version_;
    402   return version_ + '/' + version_suffix_;
    403 }
    404 
    405 void 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 
    436 bool 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