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.
DataForMetaInfoField(const BookmarkNode * node,const std::string & field)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.
PopulateImageData(const image::collections::ImageData_ImageInfo & info,GURL * out_url,int * width,int * height)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.
GenerateRemoteId()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
EnhancedBookmarkModel(BookmarkModel * bookmark_model,const std::string & version)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
~EnhancedBookmarkModel()93 EnhancedBookmarkModel::~EnhancedBookmarkModel() {
94 }
95
Shutdown()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
AddObserver(EnhancedBookmarkModelObserver * observer)105 void EnhancedBookmarkModel::AddObserver(
106 EnhancedBookmarkModelObserver* observer) {
107 observers_.AddObserver(observer);
108 }
109
RemoveObserver(EnhancedBookmarkModelObserver * observer)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|.
Move(const BookmarkNode * node,const BookmarkNode * new_parent,int 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.
AddFolder(const BookmarkNode * parent,int index,const base::string16 & title)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.
AddURL(const BookmarkNode * parent,int index,const base::string16 & title,const GURL & url,const base::Time & creation_time)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
GetRemoteId(const BookmarkNode * node)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
BookmarkForRemoteId(const std::string & remote_id)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
SetDescription(const BookmarkNode * node,const std::string & description)161 void EnhancedBookmarkModel::SetDescription(const BookmarkNode* node,
162 const std::string& description) {
163 SetMetaInfo(node, kNoteKey, description);
164 }
165
GetDescription(const BookmarkNode * node)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
SetOriginalImage(const BookmarkNode * node,const GURL & url,int width,int height)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
GetOriginalImage(const BookmarkNode * node,GURL * url,int * width,int * height)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
GetThumbnailImage(const BookmarkNode * node,GURL * url,int * width,int * height)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
GetSnippet(const BookmarkNode * node)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
SetVersionSuffix(const std::string & version_suffix)263 void EnhancedBookmarkModel::SetVersionSuffix(
264 const std::string& version_suffix) {
265 version_suffix_ = version_suffix;
266 }
267
BookmarkModelChanged()268 void EnhancedBookmarkModel::BookmarkModelChanged() {
269 }
270
BookmarkModelLoaded(BookmarkModel * model,bool ids_reassigned)271 void EnhancedBookmarkModel::BookmarkModelLoaded(BookmarkModel* model,
272 bool ids_reassigned) {
273 InitializeIdMap();
274 FOR_EACH_OBSERVER(
275 EnhancedBookmarkModelObserver, observers_, EnhancedBookmarkModelLoaded());
276 }
277
BookmarkNodeAdded(BookmarkModel * model,const BookmarkNode * parent,int index)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
BookmarkNodeRemoved(BookmarkModel * model,const BookmarkNode * parent,int old_index,const BookmarkNode * node,const std::set<GURL> & removed_urls)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
OnWillChangeBookmarkMetaInfo(BookmarkModel * model,const BookmarkNode * node)300 void EnhancedBookmarkModel::OnWillChangeBookmarkMetaInfo(
301 BookmarkModel* model,
302 const BookmarkNode* node) {
303 prev_remote_id_ = GetRemoteId(node);
304 }
305
BookmarkMetaInfoChanged(BookmarkModel * model,const BookmarkNode * node)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
BookmarkAllUserNodesRemoved(BookmarkModel * model,const std::set<GURL> & removed_urls)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
InitializeIdMap()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
AddToIdMap(const BookmarkNode * node)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
ScheduleResetDuplicateRemoteIds()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
ResetDuplicateRemoteIds()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
SetMetaInfo(const BookmarkNode * node,const std::string & field,const std::string & value)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
GetVersionString()399 std::string EnhancedBookmarkModel::GetVersionString() {
400 if (version_suffix_.empty())
401 return version_;
402 return version_ + '/' + version_suffix_;
403 }
404
SetMultipleMetaInfo(const BookmarkNode * node,BookmarkNode::MetaInfoMap meta_info)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
SetAllImages(const BookmarkNode * node,const GURL & image_url,int image_width,int image_height,const GURL & thumbnail_url,int thumbnail_width,int thumbnail_height)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