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"
22 using bookmarks::BookmarkModel
;
25 const char* kBookmarkBarId
= "f_bookmarks_bar";
27 const char* kFlagsKey
= "stars.flags";
28 const char* kIdKey
= "stars.id";
29 const char* kImageDataKey
= "stars.imageData";
30 const char* kNoteKey
= "stars.note";
31 const char* kOldIdKey
= "stars.oldId";
32 const char* kPageDataKey
= "stars.pageData";
33 const char* kVersionKey
= "stars.version";
35 const char* kBookmarkPrefix
= "ebc_";
38 // When set the server will attempt to fill in image and snippet information.
39 NEEDS_OFFLINE_PROCESSING
= 0x1,
42 // Helper method for working with bookmark metainfo.
43 std::string
DataForMetaInfoField(const BookmarkNode
* node
,
44 const std::string
& field
) {
46 if (!node
->GetMetaInfo(field
, &value
))
50 if (!base::Base64Decode(value
, &decoded
))
56 // Helper method for working with ImageData_ImageInfo.
57 bool PopulateImageData(const image::collections::ImageData_ImageInfo
& info
,
61 if (!info
.has_url() || !info
.has_width() || !info
.has_height())
69 *width
= info
.width();
70 *height
= info
.height();
74 // Generate a random remote id, with a prefix that depends on whether the node
75 // is a folder or a bookmark.
76 std::string
GenerateRemoteId() {
77 std::stringstream random_id
;
78 random_id
<< kBookmarkPrefix
;
80 // Generate 32 digit hex string random suffix.
81 random_id
<< std::hex
<< std::setfill('0') << std::setw(16);
82 random_id
<< base::RandUint64() << base::RandUint64();
83 return random_id
.str();
87 namespace enhanced_bookmarks
{
89 EnhancedBookmarkModel::EnhancedBookmarkModel(BookmarkModel
* bookmark_model
,
90 const std::string
& version
)
91 : bookmark_model_(bookmark_model
),
94 weak_ptr_factory_(this) {
95 bookmark_model_
->AddObserver(this);
96 bookmark_model_
->AddNonClonedKey(kIdKey
);
97 if (bookmark_model_
->loaded()) {
103 EnhancedBookmarkModel::~EnhancedBookmarkModel() {
107 void EnhancedBookmarkModel::Shutdown() {
108 if (bookmark_model_
) {
109 FOR_EACH_OBSERVER(EnhancedBookmarkModelObserver
,
111 EnhancedBookmarkModelShuttingDown());
112 weak_ptr_factory_
.InvalidateWeakPtrs();
113 bookmark_model_
->RemoveObserver(this);
114 bookmark_model_
= NULL
;
118 void EnhancedBookmarkModel::AddObserver(
119 EnhancedBookmarkModelObserver
* observer
) {
120 observers_
.AddObserver(observer
);
123 void EnhancedBookmarkModel::RemoveObserver(
124 EnhancedBookmarkModelObserver
* observer
) {
125 observers_
.RemoveObserver(observer
);
128 // Moves |node| to |new_parent| and inserts it at the given |index|.
129 void EnhancedBookmarkModel::Move(const BookmarkNode
* node
,
130 const BookmarkNode
* new_parent
,
132 bookmark_model_
->Move(node
, new_parent
, index
);
135 // Adds a new folder node at the specified position.
136 const BookmarkNode
* EnhancedBookmarkModel::AddFolder(
137 const BookmarkNode
* parent
,
139 const base::string16
& title
) {
140 BookmarkNode::MetaInfoMap meta_info
;
141 meta_info
[kVersionKey
] = GetVersionString();
142 return bookmark_model_
->AddFolderWithMetaInfo(parent
, index
, title
,
146 // Adds a url at the specified position.
147 const BookmarkNode
* EnhancedBookmarkModel::AddURL(
148 const BookmarkNode
* parent
,
150 const base::string16
& title
,
152 const base::Time
& creation_time
) {
153 BookmarkNode::MetaInfoMap meta_info
;
154 meta_info
[kIdKey
] = GenerateRemoteId();
155 meta_info
[kVersionKey
] = GetVersionString();
156 return bookmark_model_
->AddURLWithCreationTimeAndMetaInfo(
157 parent
, index
, title
, url
, creation_time
, &meta_info
);
160 std::string
EnhancedBookmarkModel::GetRemoteId(const BookmarkNode
* node
) {
161 if (node
== bookmark_model_
->bookmark_bar_node())
162 return kBookmarkBarId
;
165 if (!node
->GetMetaInfo(kIdKey
, &id
))
166 return std::string();
170 const BookmarkNode
* EnhancedBookmarkModel::BookmarkForRemoteId(
171 const std::string
& remote_id
) {
172 IdToNodeMap::iterator it
= id_map_
.find(remote_id
);
173 if (it
!= id_map_
.end())
178 void EnhancedBookmarkModel::SetDescription(const BookmarkNode
* node
,
179 const std::string
& description
) {
180 SetMetaInfo(node
, kNoteKey
, description
);
183 std::string
EnhancedBookmarkModel::GetDescription(const BookmarkNode
* node
) {
184 // First, look for a custom note set by the user.
185 std::string description
;
186 if (node
->GetMetaInfo(kNoteKey
, &description
) && !description
.empty())
189 // If none are present, return the snippet.
190 return GetSnippet(node
);
193 bool EnhancedBookmarkModel::SetOriginalImage(const BookmarkNode
* node
,
197 DCHECK(node
->is_url());
198 DCHECK(url
.is_valid());
200 std::string
decoded(DataForMetaInfoField(node
, kImageDataKey
));
201 image::collections::ImageData data
;
203 // Try to populate the imageData with the existing data.
204 if (!decoded
.empty()) {
205 // If the parsing fails, something is wrong. Immediately fail.
206 bool result
= data
.ParseFromString(decoded
);
211 scoped_ptr
<image::collections::ImageData_ImageInfo
> info(
212 new image::collections::ImageData_ImageInfo
);
213 info
->set_url(url
.spec());
214 info
->set_width(width
);
215 info
->set_height(height
);
216 data
.set_allocated_original_info(info
.release());
219 bool result
= data
.SerializePartialToString(&output
);
224 base::Base64Encode(output
, &encoded
);
225 SetMetaInfo(node
, kImageDataKey
, encoded
);
229 bool EnhancedBookmarkModel::GetOriginalImage(const BookmarkNode
* node
,
233 std::string
decoded(DataForMetaInfoField(node
, kImageDataKey
));
237 image::collections::ImageData data
;
238 bool result
= data
.ParseFromString(decoded
);
242 if (!data
.has_original_info())
245 return PopulateImageData(data
.original_info(), url
, width
, height
);
248 bool EnhancedBookmarkModel::GetThumbnailImage(const BookmarkNode
* node
,
252 std::string
decoded(DataForMetaInfoField(node
, kImageDataKey
));
256 image::collections::ImageData data
;
257 bool result
= data
.ParseFromString(decoded
);
261 if (!data
.has_thumbnail_info())
264 return PopulateImageData(data
.thumbnail_info(), url
, width
, height
);
267 std::string
EnhancedBookmarkModel::GetSnippet(const BookmarkNode
* node
) {
268 std::string
decoded(DataForMetaInfoField(node
, kPageDataKey
));
272 image::collections::PageData data
;
273 bool result
= data
.ParseFromString(decoded
);
275 return std::string();
277 return data
.snippet();
280 void EnhancedBookmarkModel::SetVersionSuffix(
281 const std::string
& version_suffix
) {
282 version_suffix_
= version_suffix
;
285 void EnhancedBookmarkModel::BookmarkModelChanged() {
288 void EnhancedBookmarkModel::BookmarkModelLoaded(BookmarkModel
* model
,
289 bool ids_reassigned
) {
293 EnhancedBookmarkModelObserver
, observers_
, EnhancedBookmarkModelLoaded());
296 void EnhancedBookmarkModel::BookmarkNodeAdded(BookmarkModel
* model
,
297 const BookmarkNode
* parent
,
299 const BookmarkNode
* node
= parent
->GetChild(index
);
300 std::string remote_id
;
301 if (node
->GetMetaInfo(kIdKey
, &remote_id
)) {
303 ScheduleResetDuplicateRemoteIds();
304 } else if (node
->is_url()) {
305 set_needs_offline_processing_tasks_
[node
] =
306 make_linked_ptr(new base::CancelableClosure(
307 base::Bind(&EnhancedBookmarkModel::SetNeedsOfflineProcessing
,
308 weak_ptr_factory_
.GetWeakPtr(),
309 base::Unretained(node
))));
310 base::MessageLoopProxy::current()->PostTask(
311 FROM_HERE
, set_needs_offline_processing_tasks_
[node
]->callback());
314 EnhancedBookmarkModelObserver
, observers_
, EnhancedBookmarkAdded(node
));
317 void EnhancedBookmarkModel::BookmarkNodeRemoved(
318 BookmarkModel
* model
,
319 const BookmarkNode
* parent
,
321 const BookmarkNode
* node
,
322 const std::set
<GURL
>& removed_urls
) {
323 RemoveNodeFromMaps(node
);
325 EnhancedBookmarkModelObserver
, observers_
, EnhancedBookmarkRemoved(node
));
328 void EnhancedBookmarkModel::BookmarkNodeChanged(BookmarkModel
* model
,
329 const BookmarkNode
* node
) {
331 EnhancedBookmarkModelObserver
, observers_
,
332 EnhancedBookmarkNodeChanged(node
));
335 void EnhancedBookmarkModel::OnWillChangeBookmarkMetaInfo(
336 BookmarkModel
* model
,
337 const BookmarkNode
* node
) {
338 prev_remote_id_
= GetRemoteId(node
);
341 void EnhancedBookmarkModel::BookmarkMetaInfoChanged(BookmarkModel
* model
,
342 const BookmarkNode
* node
) {
343 std::string remote_id
= GetRemoteId(node
);
344 if (remote_id
!= prev_remote_id_
) {
345 id_map_
.erase(prev_remote_id_
);
346 if (!remote_id
.empty()) {
348 ScheduleResetDuplicateRemoteIds();
351 EnhancedBookmarkModelObserver
,
353 EnhancedBookmarkRemoteIdChanged(node
, prev_remote_id_
, remote_id
));
357 void EnhancedBookmarkModel::BookmarkAllUserNodesRemoved(
358 BookmarkModel
* model
,
359 const std::set
<GURL
>& removed_urls
) {
361 // Re-initialize so non-user nodes with remote ids are present in the map.
363 FOR_EACH_OBSERVER(EnhancedBookmarkModelObserver
,
365 EnhancedBookmarkAllUserNodesRemoved());
368 void EnhancedBookmarkModel::InitializeIdMap() {
369 ui::TreeNodeIterator
<const BookmarkNode
> iterator(
370 bookmark_model_
->root_node());
371 while (iterator
.has_next()) {
372 AddToIdMap(iterator
.Next());
374 ScheduleResetDuplicateRemoteIds();
377 void EnhancedBookmarkModel::AddToIdMap(const BookmarkNode
* node
) {
378 std::string remote_id
= GetRemoteId(node
);
379 if (remote_id
.empty())
382 // Try to insert the node.
383 std::pair
<IdToNodeMap::iterator
, bool> result
=
384 id_map_
.insert(make_pair(remote_id
, node
));
385 if (!result
.second
) {
386 // Some node already had the same remote id, so add both nodes to the
388 nodes_to_reset_
[result
.first
->second
] = remote_id
;
389 nodes_to_reset_
[node
] = remote_id
;
393 void EnhancedBookmarkModel::RemoveNodeFromMaps(const BookmarkNode
* node
) {
394 for (int i
= 0; i
< node
->child_count(); i
++) {
395 RemoveNodeFromMaps(node
->GetChild(i
));
397 std::string remote_id
= GetRemoteId(node
);
398 id_map_
.erase(remote_id
);
399 nodes_to_reset_
.erase(node
);
400 set_needs_offline_processing_tasks_
.erase(node
);
403 void EnhancedBookmarkModel::ScheduleResetDuplicateRemoteIds() {
404 if (!nodes_to_reset_
.empty()) {
405 base::MessageLoopProxy::current()->PostTask(
407 base::Bind(&EnhancedBookmarkModel::ResetDuplicateRemoteIds
,
408 weak_ptr_factory_
.GetWeakPtr()));
412 void EnhancedBookmarkModel::ResetDuplicateRemoteIds() {
413 for (NodeToIdMap::iterator it
= nodes_to_reset_
.begin();
414 it
!= nodes_to_reset_
.end();
416 BookmarkNode::MetaInfoMap meta_info
;
417 meta_info
[kIdKey
] = "";
418 meta_info
[kOldIdKey
] = it
->second
;
419 SetMultipleMetaInfo(it
->first
, meta_info
);
421 nodes_to_reset_
.clear();
424 void EnhancedBookmarkModel::SetNeedsOfflineProcessing(
425 const BookmarkNode
* node
) {
426 set_needs_offline_processing_tasks_
.erase(node
);
428 std::string flags_str
;
429 if (node
->GetMetaInfo(kFlagsKey
, &flags_str
)) {
430 if (!base::StringToInt(flags_str
, &flags
))
433 flags
|= NEEDS_OFFLINE_PROCESSING
;
434 SetMetaInfo(node
, kFlagsKey
, base::IntToString(flags
));
437 void EnhancedBookmarkModel::SetMetaInfo(const BookmarkNode
* node
,
438 const std::string
& field
,
439 const std::string
& value
) {
440 DCHECK(!bookmark_model_
->is_permanent_node(node
));
442 BookmarkNode::MetaInfoMap meta_info
;
443 const BookmarkNode::MetaInfoMap
* old_meta_info
= node
->GetMetaInfoMap();
445 meta_info
.insert(old_meta_info
->begin(), old_meta_info
->end());
447 // Don't update anything if the value to set is already there.
448 BookmarkNode::MetaInfoMap::iterator it
= meta_info
.find(field
);
449 if (it
!= meta_info
.end() && it
->second
== value
)
452 meta_info
[field
] = value
;
453 meta_info
[kVersionKey
] = GetVersionString();
454 bookmark_model_
->SetNodeMetaInfoMap(node
, meta_info
);
457 std::string
EnhancedBookmarkModel::GetVersionString() {
458 if (version_suffix_
.empty())
460 return version_
+ '/' + version_suffix_
;
463 void EnhancedBookmarkModel::SetMultipleMetaInfo(
464 const BookmarkNode
* node
,
465 BookmarkNode::MetaInfoMap meta_info
) {
466 DCHECK(!bookmark_model_
->is_permanent_node(node
));
468 // Don't update anything if every value is already set correctly.
469 if (node
->GetMetaInfoMap()) {
470 bool changed
= false;
471 const BookmarkNode::MetaInfoMap
* old_meta_info
= node
->GetMetaInfoMap();
472 for (BookmarkNode::MetaInfoMap::iterator it
= meta_info
.begin();
473 it
!= meta_info
.end();
475 BookmarkNode::MetaInfoMap::const_iterator old_field
=
476 old_meta_info
->find(it
->first
);
477 if (old_field
== old_meta_info
->end() ||
478 old_field
->second
!= it
->second
) {
486 // Fill in the values that aren't changing
487 meta_info
.insert(old_meta_info
->begin(), old_meta_info
->end());
490 meta_info
[kVersionKey
] = GetVersionString();
491 bookmark_model_
->SetNodeMetaInfoMap(node
, meta_info
);
494 bool EnhancedBookmarkModel::SetAllImages(const BookmarkNode
* node
,
495 const GURL
& image_url
,
498 const GURL
& thumbnail_url
,
500 int thumbnail_height
) {
501 DCHECK(node
->is_url());
502 DCHECK(image_url
.is_valid() || image_url
.is_empty());
503 DCHECK(thumbnail_url
.is_valid() || thumbnail_url
.is_empty());
504 std::string
decoded(DataForMetaInfoField(node
, kImageDataKey
));
505 image::collections::ImageData data
;
507 // Try to populate the imageData with the existing data.
508 if (!decoded
.empty()) {
509 // If the parsing fails, something is wrong. Immediately fail.
510 bool result
= data
.ParseFromString(decoded
);
515 if (image_url
.is_empty()) {
516 data
.release_original_info();
518 // Regardless of whether an image info exists, we make a new one.
519 // Intentially make a raw pointer.
520 image::collections::ImageData_ImageInfo
* info
=
521 new image::collections::ImageData_ImageInfo
;
522 info
->set_url(image_url
.spec());
523 info
->set_width(image_width
);
524 info
->set_height(image_height
);
525 // This method consumes the raw pointer.
526 data
.set_allocated_original_info(info
);
529 if (thumbnail_url
.is_empty()) {
530 data
.release_thumbnail_info();
532 // Regardless of whether an image info exists, we make a new one.
533 // Intentially make a raw pointer.
534 image::collections::ImageData_ImageInfo
* info
=
535 new image::collections::ImageData_ImageInfo
;
536 info
->set_url(thumbnail_url
.spec());
537 info
->set_width(thumbnail_width
);
538 info
->set_height(thumbnail_height
);
539 // This method consumes the raw pointer.
540 data
.set_allocated_thumbnail_info(info
);
543 bool result
= data
.SerializePartialToString(&output
);
548 base::Base64Encode(output
, &encoded
);
549 bookmark_model_
->SetNodeMetaInfo(node
, kImageDataKey
, encoded
);
553 } // namespace enhanced_bookmarks