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