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.
5 #include "components/enhanced_bookmarks/enhanced_bookmark_model.h"
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 "base/strings/string_number_conversions.h"
15 #include "components/bookmarks/browser/bookmark_model.h"
16 #include "components/bookmarks/browser/bookmark_node.h"
17 #include "components/enhanced_bookmarks/enhanced_bookmark_model_observer.h"
18 #include "components/enhanced_bookmarks/proto/metadata.pb.h"
19 #include "ui/base/models/tree_node_iterator.h"
23 const char* kBookmarkBarId
= "f_bookmarks_bar";
25 const char* kFlagsKey
= "stars.flags";
26 const char* kIdKey
= "stars.id";
27 const char* kImageDataKey
= "stars.imageData";
28 const char* kNoteKey
= "stars.note";
29 const char* kOldIdKey
= "stars.oldId";
30 const char* kPageDataKey
= "stars.pageData";
31 const char* kVersionKey
= "stars.version";
33 const char* kBookmarkPrefix
= "ebc_";
36 // When set the server will attempt to fill in image and snippet information.
37 NEEDS_OFFLINE_PROCESSING
= 0x1,
40 // Helper method for working with bookmark metainfo.
41 std::string
DataForMetaInfoField(const BookmarkNode
* node
,
42 const std::string
& field
) {
44 if (!node
->GetMetaInfo(field
, &value
))
48 if (!base::Base64Decode(value
, &decoded
))
54 // Helper method for working with ImageData_ImageInfo.
55 bool PopulateImageData(const image::collections::ImageData_ImageInfo
& info
,
59 if (!info
.has_url() || !info
.has_width() || !info
.has_height())
67 *width
= info
.width();
68 *height
= info
.height();
72 // Generate a random remote id, with a prefix that depends on whether the node
73 // is a folder or a bookmark.
74 std::string
GenerateRemoteId() {
75 std::stringstream random_id
;
76 random_id
<< kBookmarkPrefix
;
78 // Generate 32 digit hex string random suffix.
79 random_id
<< std::hex
<< std::setfill('0') << std::setw(16);
80 random_id
<< base::RandUint64() << base::RandUint64();
81 return random_id
.str();
85 namespace enhanced_bookmarks
{
87 EnhancedBookmarkModel::EnhancedBookmarkModel(BookmarkModel
* bookmark_model
,
88 const std::string
& version
)
89 : bookmark_model_(bookmark_model
),
92 weak_ptr_factory_(this) {
93 bookmark_model_
->AddObserver(this);
94 bookmark_model_
->AddNonClonedKey(kIdKey
);
95 if (bookmark_model_
->loaded()) {
101 EnhancedBookmarkModel::~EnhancedBookmarkModel() {
105 void EnhancedBookmarkModel::Shutdown() {
106 if (bookmark_model_
) {
107 FOR_EACH_OBSERVER(EnhancedBookmarkModelObserver
,
109 EnhancedBookmarkModelShuttingDown());
110 weak_ptr_factory_
.InvalidateWeakPtrs();
111 bookmark_model_
->RemoveObserver(this);
112 bookmark_model_
= NULL
;
116 void EnhancedBookmarkModel::AddObserver(
117 EnhancedBookmarkModelObserver
* observer
) {
118 observers_
.AddObserver(observer
);
121 void EnhancedBookmarkModel::RemoveObserver(
122 EnhancedBookmarkModelObserver
* observer
) {
123 observers_
.RemoveObserver(observer
);
126 // Moves |node| to |new_parent| and inserts it at the given |index|.
127 void EnhancedBookmarkModel::Move(const BookmarkNode
* node
,
128 const BookmarkNode
* new_parent
,
130 bookmark_model_
->Move(node
, new_parent
, index
);
133 // Adds a new folder node at the specified position.
134 const BookmarkNode
* EnhancedBookmarkModel::AddFolder(
135 const BookmarkNode
* parent
,
137 const base::string16
& title
) {
138 BookmarkNode::MetaInfoMap meta_info
;
139 meta_info
[kVersionKey
] = GetVersionString();
140 return bookmark_model_
->AddFolderWithMetaInfo(parent
, index
, title
,
144 // Adds a url at the specified position.
145 const BookmarkNode
* EnhancedBookmarkModel::AddURL(
146 const BookmarkNode
* parent
,
148 const base::string16
& title
,
150 const base::Time
& creation_time
) {
151 BookmarkNode::MetaInfoMap meta_info
;
152 meta_info
[kIdKey
] = GenerateRemoteId();
153 meta_info
[kVersionKey
] = GetVersionString();
154 return bookmark_model_
->AddURLWithCreationTimeAndMetaInfo(
155 parent
, index
, title
, url
, creation_time
, &meta_info
);
158 std::string
EnhancedBookmarkModel::GetRemoteId(const BookmarkNode
* node
) {
159 if (node
== bookmark_model_
->bookmark_bar_node())
160 return kBookmarkBarId
;
163 if (!node
->GetMetaInfo(kIdKey
, &id
))
164 return std::string();
168 const BookmarkNode
* EnhancedBookmarkModel::BookmarkForRemoteId(
169 const std::string
& remote_id
) {
170 IdToNodeMap::iterator it
= id_map_
.find(remote_id
);
171 if (it
!= id_map_
.end())
176 void EnhancedBookmarkModel::SetDescription(const BookmarkNode
* node
,
177 const std::string
& description
) {
178 SetMetaInfo(node
, kNoteKey
, description
);
181 std::string
EnhancedBookmarkModel::GetDescription(const BookmarkNode
* node
) {
182 // First, look for a custom note set by the user.
183 std::string description
;
184 if (node
->GetMetaInfo(kNoteKey
, &description
) && !description
.empty())
187 // If none are present, return the snippet.
188 return GetSnippet(node
);
191 bool EnhancedBookmarkModel::SetOriginalImage(const BookmarkNode
* node
,
195 DCHECK(node
->is_url());
196 DCHECK(url
.is_valid());
198 std::string
decoded(DataForMetaInfoField(node
, kImageDataKey
));
199 image::collections::ImageData data
;
201 // Try to populate the imageData with the existing data.
203 // If the parsing fails, something is wrong. Immediately fail.
204 bool result
= data
.ParseFromString(decoded
);
209 scoped_ptr
<image::collections::ImageData_ImageInfo
> info(
210 new image::collections::ImageData_ImageInfo
);
211 info
->set_url(url
.spec());
212 info
->set_width(width
);
213 info
->set_height(height
);
214 data
.set_allocated_original_info(info
.release());
217 bool result
= data
.SerializePartialToString(&output
);
222 base::Base64Encode(output
, &encoded
);
223 SetMetaInfo(node
, kImageDataKey
, encoded
);
227 bool EnhancedBookmarkModel::GetOriginalImage(const BookmarkNode
* node
,
231 std::string
decoded(DataForMetaInfoField(node
, kImageDataKey
));
235 image::collections::ImageData data
;
236 bool result
= data
.ParseFromString(decoded
);
240 if (!data
.has_original_info())
243 return PopulateImageData(data
.original_info(), url
, width
, height
);
246 bool EnhancedBookmarkModel::GetThumbnailImage(const BookmarkNode
* node
,
250 std::string
decoded(DataForMetaInfoField(node
, kImageDataKey
));
254 image::collections::ImageData data
;
255 bool result
= data
.ParseFromString(decoded
);
259 if (!data
.has_thumbnail_info())
262 return PopulateImageData(data
.thumbnail_info(), url
, width
, height
);
265 std::string
EnhancedBookmarkModel::GetSnippet(const BookmarkNode
* node
) {
266 std::string
decoded(DataForMetaInfoField(node
, kPageDataKey
));
270 image::collections::PageData data
;
271 bool result
= data
.ParseFromString(decoded
);
273 return std::string();
275 return data
.snippet();
278 void EnhancedBookmarkModel::SetVersionSuffix(
279 const std::string
& version_suffix
) {
280 version_suffix_
= version_suffix
;
283 void EnhancedBookmarkModel::BookmarkModelChanged() {
286 void EnhancedBookmarkModel::BookmarkModelLoaded(BookmarkModel
* model
,
287 bool ids_reassigned
) {
291 EnhancedBookmarkModelObserver
, observers_
, EnhancedBookmarkModelLoaded());
294 void EnhancedBookmarkModel::BookmarkNodeAdded(BookmarkModel
* model
,
295 const BookmarkNode
* parent
,
297 const BookmarkNode
* node
= parent
->GetChild(index
);
298 std::string remote_id
;
299 if (node
->GetMetaInfo(kIdKey
, &remote_id
)) {
301 ScheduleResetDuplicateRemoteIds();
302 } else if (node
->is_url()) {
303 set_needs_offline_processing_tasks_
[node
] =
304 make_linked_ptr(new base::CancelableClosure(
305 base::Bind(&EnhancedBookmarkModel::SetNeedsOfflineProcessing
,
306 weak_ptr_factory_
.GetWeakPtr(),
307 base::Unretained(node
))));
308 base::MessageLoopProxy::current()->PostTask(
309 FROM_HERE
, set_needs_offline_processing_tasks_
[node
]->callback());
312 EnhancedBookmarkModelObserver
, observers_
, EnhancedBookmarkAdded(node
));
315 void EnhancedBookmarkModel::BookmarkNodeRemoved(
316 BookmarkModel
* model
,
317 const BookmarkNode
* parent
,
319 const BookmarkNode
* node
,
320 const std::set
<GURL
>& removed_urls
) {
321 RemoveNodeFromMaps(node
);
323 EnhancedBookmarkModelObserver
, observers_
, EnhancedBookmarkRemoved(node
));
326 void EnhancedBookmarkModel::BookmarkNodeChanged(BookmarkModel
* model
,
327 const BookmarkNode
* node
) {
329 EnhancedBookmarkModelObserver
, observers_
,
330 EnhancedBookmarkNodeChanged(node
));
333 void EnhancedBookmarkModel::OnWillChangeBookmarkMetaInfo(
334 BookmarkModel
* model
,
335 const BookmarkNode
* node
) {
336 prev_remote_id_
= GetRemoteId(node
);
339 void EnhancedBookmarkModel::BookmarkMetaInfoChanged(BookmarkModel
* model
,
340 const BookmarkNode
* node
) {
341 std::string remote_id
= GetRemoteId(node
);
342 if (remote_id
!= prev_remote_id_
) {
343 id_map_
.erase(prev_remote_id_
);
344 if (!remote_id
.empty()) {
346 ScheduleResetDuplicateRemoteIds();
349 EnhancedBookmarkModelObserver
,
351 EnhancedBookmarkRemoteIdChanged(node
, prev_remote_id_
, remote_id
));
355 void EnhancedBookmarkModel::BookmarkAllUserNodesRemoved(
356 BookmarkModel
* model
,
357 const std::set
<GURL
>& removed_urls
) {
359 // Re-initialize so non-user nodes with remote ids are present in the map.
361 FOR_EACH_OBSERVER(EnhancedBookmarkModelObserver
,
363 EnhancedBookmarkAllUserNodesRemoved());
366 void EnhancedBookmarkModel::InitializeIdMap() {
367 ui::TreeNodeIterator
<const BookmarkNode
> iterator(
368 bookmark_model_
->root_node());
369 while (iterator
.has_next()) {
370 AddToIdMap(iterator
.Next());
372 ScheduleResetDuplicateRemoteIds();
375 void EnhancedBookmarkModel::AddToIdMap(const BookmarkNode
* node
) {
376 std::string remote_id
= GetRemoteId(node
);
377 if (remote_id
.empty())
380 // Try to insert the node.
381 std::pair
<IdToNodeMap::iterator
, bool> result
=
382 id_map_
.insert(make_pair(remote_id
, node
));
383 if (!result
.second
) {
384 // Some node already had the same remote id, so add both nodes to the
386 nodes_to_reset_
[result
.first
->second
] = remote_id
;
387 nodes_to_reset_
[node
] = remote_id
;
391 void EnhancedBookmarkModel::RemoveNodeFromMaps(const BookmarkNode
* node
) {
392 for (int i
= 0; i
< node
->child_count(); i
++) {
393 RemoveNodeFromMaps(node
->GetChild(i
));
395 std::string remote_id
= GetRemoteId(node
);
396 id_map_
.erase(remote_id
);
397 nodes_to_reset_
.erase(node
);
398 set_needs_offline_processing_tasks_
.erase(node
);
401 void EnhancedBookmarkModel::ScheduleResetDuplicateRemoteIds() {
402 if (!nodes_to_reset_
.empty()) {
403 base::MessageLoopProxy::current()->PostTask(
405 base::Bind(&EnhancedBookmarkModel::ResetDuplicateRemoteIds
,
406 weak_ptr_factory_
.GetWeakPtr()));
410 void EnhancedBookmarkModel::ResetDuplicateRemoteIds() {
411 for (NodeToIdMap::iterator it
= nodes_to_reset_
.begin();
412 it
!= nodes_to_reset_
.end();
414 BookmarkNode::MetaInfoMap meta_info
;
415 meta_info
[kIdKey
] = "";
416 meta_info
[kOldIdKey
] = it
->second
;
417 SetMultipleMetaInfo(it
->first
, meta_info
);
419 nodes_to_reset_
.clear();
422 void EnhancedBookmarkModel::SetNeedsOfflineProcessing(
423 const BookmarkNode
* node
) {
424 set_needs_offline_processing_tasks_
.erase(node
);
426 std::string flags_str
;
427 if (node
->GetMetaInfo(kFlagsKey
, &flags_str
)) {
428 if (!base::StringToInt(flags_str
, &flags
))
431 flags
|= NEEDS_OFFLINE_PROCESSING
;
432 SetMetaInfo(node
, kFlagsKey
, base::IntToString(flags
));
435 void EnhancedBookmarkModel::SetMetaInfo(const BookmarkNode
* node
,
436 const std::string
& field
,
437 const std::string
& value
) {
438 DCHECK(!bookmark_model_
->is_permanent_node(node
));
440 BookmarkNode::MetaInfoMap meta_info
;
441 const BookmarkNode::MetaInfoMap
* old_meta_info
= node
->GetMetaInfoMap();
443 meta_info
.insert(old_meta_info
->begin(), old_meta_info
->end());
445 // Don't update anything if the value to set is already there.
446 BookmarkNode::MetaInfoMap::iterator it
= meta_info
.find(field
);
447 if (it
!= meta_info
.end() && it
->second
== value
)
450 meta_info
[field
] = value
;
451 meta_info
[kVersionKey
] = GetVersionString();
452 bookmark_model_
->SetNodeMetaInfoMap(node
, meta_info
);
455 std::string
EnhancedBookmarkModel::GetVersionString() {
456 if (version_suffix_
.empty())
458 return version_
+ '/' + version_suffix_
;
461 void EnhancedBookmarkModel::SetMultipleMetaInfo(
462 const BookmarkNode
* node
,
463 BookmarkNode::MetaInfoMap meta_info
) {
464 DCHECK(!bookmark_model_
->is_permanent_node(node
));
466 // Don't update anything if every value is already set correctly.
467 if (node
->GetMetaInfoMap()) {
468 bool changed
= false;
469 const BookmarkNode::MetaInfoMap
* old_meta_info
= node
->GetMetaInfoMap();
470 for (BookmarkNode::MetaInfoMap::iterator it
= meta_info
.begin();
471 it
!= meta_info
.end();
473 BookmarkNode::MetaInfoMap::const_iterator old_field
=
474 old_meta_info
->find(it
->first
);
475 if (old_field
== old_meta_info
->end() ||
476 old_field
->second
!= it
->second
) {
484 // Fill in the values that aren't changing
485 meta_info
.insert(old_meta_info
->begin(), old_meta_info
->end());
488 meta_info
[kVersionKey
] = GetVersionString();
489 bookmark_model_
->SetNodeMetaInfoMap(node
, meta_info
);
492 bool EnhancedBookmarkModel::SetAllImages(const BookmarkNode
* node
,
493 const GURL
& image_url
,
496 const GURL
& thumbnail_url
,
498 int thumbnail_height
) {
499 DCHECK(node
->is_url());
500 DCHECK(image_url
.is_valid() || image_url
.is_empty());
501 DCHECK(thumbnail_url
.is_valid() || thumbnail_url
.is_empty());
502 std::string
decoded(DataForMetaInfoField(node
, kImageDataKey
));
503 image::collections::ImageData data
;
505 // Try to populate the imageData with the existing data.
507 // If the parsing fails, something is wrong. Immediately fail.
508 bool result
= data
.ParseFromString(decoded
);
513 if (image_url
.is_empty()) {
514 data
.release_original_info();
516 // Regardless of whether an image info exists, we make a new one.
517 // Intentially make a raw pointer.
518 image::collections::ImageData_ImageInfo
* info
=
519 new image::collections::ImageData_ImageInfo
;
520 info
->set_url(image_url
.spec());
521 info
->set_width(image_width
);
522 info
->set_height(image_height
);
523 // This method consumes the raw pointer.
524 data
.set_allocated_original_info(info
);
527 if (thumbnail_url
.is_empty()) {
528 data
.release_thumbnail_info();
530 // Regardless of whether an image info exists, we make a new one.
531 // Intentially make a raw pointer.
532 image::collections::ImageData_ImageInfo
* info
=
533 new image::collections::ImageData_ImageInfo
;
534 info
->set_url(thumbnail_url
.spec());
535 info
->set_width(thumbnail_width
);
536 info
->set_height(thumbnail_height
);
537 // This method consumes the raw pointer.
538 data
.set_allocated_thumbnail_info(info
);
541 bool result
= data
.SerializePartialToString(&output
);
546 base::Base64Encode(output
, &encoded
);
547 bookmark_model_
->SetNodeMetaInfo(node
, kImageDataKey
, encoded
);
551 } // namespace enhanced_bookmarks