Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(137)

Side by Side Diff: chrome/browser/bookmarks/bookmark_tag_model.cc

Issue 26894002: Experimental bookmark model based on tags. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: More fixes. Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2013 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 "chrome/browser/bookmarks/bookmark_tag_model.h"
6
7 #include "base/auto_reset.h"
8 #include "base/json/json_string_value_serializer.h"
9 #include "base/observer_list.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
13 #include "chrome/browser/bookmarks/bookmark_tag_model_observer.h"
14 #include "ui/base/models/tree_node_iterator.h"
15
16 namespace {
17 // The key used to store the tag list in the metainfo of a bookmark.
18 const char* TAG_KEY = "TAG_KEY";
19
20 // Comparator to sort tags by usage.
21 struct TagComparator {
22 TagComparator(std::map<BookmarkTag, unsigned int>& tags) : tags_(tags) {
23 }
24 ~TagComparator() {}
25
26 bool operator()(const BookmarkTag& a, const BookmarkTag& b) {
27 return (tags_[a] < tags_[b]);
28 }
29
30 std::map<BookmarkTag, unsigned int>& tags_;
31 };
32
33 // The tags are currently stored in the BookmarkNode's metaInfo in JSON
34 // format. This function extracts the info from there and returns it in
35 // digestible format.
36 // If the Bookmark was never tagged before it is implicitely tagged with the
37 // title of all its ancestors in the BookmarkModel.
38 std::set<BookmarkTag> ExtractTagsFromBookmark(const BookmarkNode* bookmark) {
39 // This is awful BTW. Metainfo is itself an encoded JSON, and here we decode
40 // another layer.
41
42 // Retrieve the encodedData from the bookmark. If there is no encoded data
43 // at all returns the name of all the ancestors as separate tags.
44 std::string encoded;
45 if (!bookmark->GetMetaInfo(TAG_KEY, &encoded)) {
46 std::set<BookmarkTag> tags;
47 const BookmarkNode* folder = bookmark->parent();
48 while (folder && folder->type() == BookmarkNode::FOLDER) {
49 BookmarkTag trimmed_tag = CollapseWhitespace(folder->GetTitle(), true);
50 if (!trimmed_tag.empty())
51 tags.insert(trimmed_tag);
52 folder = folder->parent();
53 }
54 return tags;
55 }
56
57 // Decode into a base::Value. If the data is not encoded properly as a list
58 // return an empty result.
59 JSONStringValueSerializer serializer(&encoded);
60 int error_code = 0;
61 std::string error_message;
62 scoped_ptr<base::Value> result(serializer.Deserialize(&error_code,
63 &error_message));
64
65 if (error_code || !result->IsType(base::Value::TYPE_LIST))
66 return std::set<BookmarkTag>();
67
68 base::ListValue* list = NULL;
69 if (!result->GetAsList(&list) || list->empty())
70 return std::set<BookmarkTag>();
71
72 // Build the set.
73 std::set<BookmarkTag> return_value;
74
75 for (base::ListValue::iterator it = list->begin();
76 it != list->end(); ++it) {
77 base::Value* item = *it;
78 BookmarkTag tag;
79 if (!item->GetAsString(&tag))
80 continue;
81 return_value.insert(tag);
82 }
83 return return_value;
84 }
85 } // namespace
86
87 BookmarkTagModel::BookmarkTagModel(BookmarkModel* bookmark_model)
88 : bookmark_model_(bookmark_model),
89 loaded_(false),
90 observers_(ObserverList<BookmarkTagModelObserver>::NOTIFY_EXISTING_ONLY),
91 inhibit_change_notifications_(false) {
92 bookmark_model_->AddObserver(this);
93 if (bookmark_model_->loaded())
94 Load();
95 }
96
97 BookmarkTagModel::~BookmarkTagModel() {
98 if (bookmark_model_)
99 bookmark_model_->RemoveObserver(this);
100 }
101
102 // BookmarkModel forwarding.
103
104 void BookmarkTagModel::AddObserver(BookmarkTagModelObserver* observer) {
105 observers_.AddObserver(observer);
106 }
107
108 void BookmarkTagModel::RemoveObserver(BookmarkTagModelObserver* observer) {
109 observers_.RemoveObserver(observer);
110 }
111
112 void BookmarkTagModel::BeginExtensiveChanges() {
113 DCHECK(bookmark_model_);
114 bookmark_model_->BeginExtensiveChanges();
115 }
116
117 void BookmarkTagModel::EndExtensiveChanges() {
118 DCHECK(bookmark_model_);
119 bookmark_model_->EndExtensiveChanges();
120 }
121
122 bool BookmarkTagModel::IsDoingExtensiveChanges() const {
123 DCHECK(bookmark_model_);
124 return bookmark_model_->IsDoingExtensiveChanges();
125 }
126
127 void BookmarkTagModel::Remove(const BookmarkNode* bookmark) {
128 DCHECK(bookmark_model_);
129 DCHECK(loaded_);
130 const BookmarkNode* parent = bookmark->parent();
131 bookmark_model_->Remove(parent, parent->GetIndexOf(bookmark));
132 }
133
134 void BookmarkTagModel::RemoveAll() {
135 DCHECK(bookmark_model_);
136 DCHECK(loaded_);
137 bookmark_model_->RemoveAll();
138 }
139
140 const gfx::Image& BookmarkTagModel::GetFavicon(const BookmarkNode* bookmark) {
141 DCHECK(bookmark_model_);
142 DCHECK(loaded_);
143 return bookmark_model_->GetFavicon(bookmark);
144 }
145
146 void BookmarkTagModel::SetTitle(const BookmarkNode* bookmark,
147 const string16& title) {
148 DCHECK(bookmark_model_);
149 DCHECK(loaded_);
150 bookmark_model_->SetTitle(bookmark, title);
151 }
152
153 void BookmarkTagModel::SetURL(const BookmarkNode* bookmark, const GURL& url) {
154 DCHECK(bookmark_model_);
155 DCHECK(loaded_);
156 bookmark_model_->SetURL(bookmark, url);
157 }
158
159 void BookmarkTagModel::SetDateAdded(const BookmarkNode* bookmark,
160 base::Time date_added) {
161 DCHECK(bookmark_model_);
162 DCHECK(loaded_);
163 bookmark_model_->SetDateAdded(bookmark, date_added);
164 }
165
166 const BookmarkNode*
167 BookmarkTagModel::GetMostRecentlyAddedBookmarkForURL(const GURL& url) {
168 DCHECK(bookmark_model_);
169 DCHECK(loaded_);
170 return bookmark_model_->GetMostRecentlyAddedNodeForURL(url);
171 }
172
173 // Tags specific code.
174
175 const BookmarkNode* BookmarkTagModel::AddURL(
176 const string16& title,
177 const GURL& url,
178 const std::set<BookmarkTag>& tags) {
179 DCHECK(bookmark_model_);
180 DCHECK(loaded_);
181
182 const BookmarkNode* bookmark;
183 {
184 base::AutoReset<bool> inhibitor(&inhibit_change_notifications_, true);
185 const BookmarkNode* parent = bookmark_model_->GetParentForNewNodes();
186 bookmark = bookmark_model_->AddURL(parent, 0, title, url);
187 AddTagsToBookmark(tags, bookmark);
188 }
189 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
190 BookmarkNodeAdded(this, bookmark));
191
192 return bookmark;
193 }
194
195 std::set<BookmarkTag> BookmarkTagModel::GetTagsForBookmark(
196 const BookmarkNode* bookmark) {
197 DCHECK(loaded_);
198 return bookmark_to_tags_[bookmark];
199 }
200
201 void BookmarkTagModel::AddTagsToBookmark(
202 const std::set<BookmarkTag>& tags,
203 const BookmarkNode* bookmark) {
204 DCHECK(bookmark_model_);
205 std::set<BookmarkTag> all_tags(GetTagsForBookmark(bookmark));
206 for (std::set<BookmarkTag>::const_iterator it = tags.begin();
207 it != tags.end(); ++it) {
208 BookmarkTag trimmed_tag = CollapseWhitespace(*it, true);
209 if (trimmed_tag.empty())
210 continue;
211 all_tags.insert(trimmed_tag);
212 }
213 SetTagsOnBookmark(all_tags, bookmark);
214 }
215
216 void BookmarkTagModel::AddTagsToBookmarks(
217 const std::set<BookmarkTag>& tags,
218 const std::set<const BookmarkNode*>& bookmarks) {
219 for (std::set<const BookmarkNode*>::const_iterator it = bookmarks.begin();
220 it != bookmarks.end(); ++it) {
221 AddTagsToBookmark(tags, *it);
222 }
223 }
224
225 void BookmarkTagModel::RemoveTagsFromBookmark(
226 const std::set<BookmarkTag>& tags,
227 const BookmarkNode* bookmark) {
228 std::set<BookmarkTag> all_tags(GetTagsForBookmark(bookmark));
229 for (std::set<BookmarkTag>::const_iterator it = tags.begin();
230 it != tags.end(); ++it) {
231 all_tags.erase(*it);
232 }
233 SetTagsOnBookmark(all_tags, bookmark);
234 }
235
236 void BookmarkTagModel::RemoveTagsFromBookmarks(
237 const std::set<BookmarkTag>& tags,
238 const std::set<const BookmarkNode*>& bookmarks){
239 for (std::set<const BookmarkNode*>::const_iterator it = bookmarks.begin();
240 it != bookmarks.end(); ++it) {
241 RemoveTagsFromBookmark(tags, *it);
242 }
243 }
244
245 std::set<const BookmarkNode*> BookmarkTagModel::BookmarksForTags(
246 const std::set<BookmarkTag>& tags) {
247 DCHECK(loaded_);
248 // Count for each tags how many times a bookmark appeared.
249 std::map<const BookmarkNode*, size_t> bookmark_counts;
250 for (std::set<BookmarkTag>::const_iterator it = tags.begin();
251 it != tags.end(); ++it) {
252 const std::set<const BookmarkNode*>& subset(tag_to_bookmarks_[*it]);
253 for (std::set<const BookmarkNode*>::const_iterator tag_it = subset.begin();
254 tag_it != subset.end(); ++tag_it) {
255 bookmark_counts[*tag_it] += 1;
256 }
257 }
258 // Keep only the bookmarks that appeared in all the tags.
259 std::set<const BookmarkNode*> common_bookmarks;
260 for (std::map<const BookmarkNode*, size_t>::iterator it =
261 bookmark_counts.begin(); it != bookmark_counts.end(); ++it) {
262 if (it->second == tags.size())
263 common_bookmarks.insert(it->first);
264 }
265 return common_bookmarks;
266 }
267
268 std::set<const BookmarkNode*> BookmarkTagModel::BookmarksForTag(
269 const BookmarkTag& tag) {
270 DCHECK(!tag.empty());
271 return tag_to_bookmarks_[tag];
272 }
273
274 std::vector<BookmarkTag> BookmarkTagModel::TagsRelatedToTag(
275 const BookmarkTag& tag) {
276 DCHECK(loaded_);
277 std::map<BookmarkTag, unsigned int> tags;
278
279 if (tag.empty()) {
280 // Returns all the tags.
281 for (std::map<const BookmarkTag, std::set<const BookmarkNode*> >::iterator
282 it = tag_to_bookmarks_.begin(); it != tag_to_bookmarks_.end(); ++it) {
283 tags[it->first] = it->second.size();
284 }
285 } else {
286 std::set<const BookmarkNode*> bookmarks(BookmarksForTag(tag));
287
288 for (std::set<const BookmarkNode*>::iterator it = bookmarks.begin();
289 it != bookmarks.end(); ++it) {
290 const std::set<BookmarkTag>& subset(bookmark_to_tags_[*it]);
291 for (std::set<BookmarkTag>::const_iterator tag_it = subset.begin();
292 tag_it != subset.end(); ++tag_it) {
293 tags[*tag_it] += 1;
294 }
295 }
296 tags.erase(tag); // A tag is not related to itself.
297 }
298
299 std::vector<BookmarkTag> sorted_tags;
300 for (std::map<BookmarkTag, unsigned int>::iterator it = tags.begin();
301 it != tags.end(); ++it) {
302 sorted_tags.push_back(it->first);
303 }
304 std::sort(sorted_tags.begin(), sorted_tags.end(), TagComparator(tags));
305 return sorted_tags;
306 }
307
308 // BookmarkModelObserver methods.
309
310 void BookmarkTagModel::Loaded(BookmarkModel* model, bool ids_reassigned) {
311 Load();
312 }
313
314 void BookmarkTagModel::BookmarkModelBeingDeleted(BookmarkModel* model) {
315 DCHECK(bookmark_model_);
316 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
317 BookmarkTagModelBeingDeleted(this));
318 bookmark_model_ = NULL;
319 }
320
321 void BookmarkTagModel::BookmarkNodeMoved(BookmarkModel* model,
322 const BookmarkNode* old_parent,
323 int old_index,
324 const BookmarkNode* new_parent,
325 int new_index) {
326 DCHECK(loaded_);
327 const BookmarkNode* bookmark = new_parent->GetChild(new_index);
328 if (bookmark->is_folder()) {
329 ReloadDescendants(bookmark);
330 } else if (bookmark->is_url()) {
331 std::string encoded;
332 if (!bookmark->GetMetaInfo(TAG_KEY, &encoded)) {
333 // The bookmark moved and the system currently use its ancestors name as a
334 // poor approximation for tags.
335 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
336 OnWillChangeBookmarkTags(this, bookmark));
337 RemoveBookmark(bookmark);
338 LoadBookmark(bookmark);
339 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
340 BookmarkTagsChanged(this, bookmark));
341 }
342 }
343 }
344
345 void BookmarkTagModel::BookmarkNodeAdded(BookmarkModel* model,
346 const BookmarkNode* parent,
347 int index) {
348 DCHECK(loaded_);
349 const BookmarkNode* bookmark = parent->GetChild(index);
350 if (!bookmark->is_url())
351 return;
352 LoadBookmark(bookmark);
353
354 if (!inhibit_change_notifications_)
355 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
356 BookmarkNodeAdded(this, bookmark));
357 }
358
359 void BookmarkTagModel::OnWillRemoveBookmarks(BookmarkModel* model,
360 const BookmarkNode* parent,
361 int old_index,
362 const BookmarkNode* node) {
363 DCHECK(loaded_);
364 if (!node->is_url())
365 return;
366 RemoveBookmark(node);
367 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
368 OnWillRemoveBookmarks(this, node));
369 }
370
371 void BookmarkTagModel::BookmarkNodeRemoved(BookmarkModel* model,
372 const BookmarkNode* parent,
373 int old_index,
374 const BookmarkNode* node) {
375 DCHECK(loaded_);
376 if (!node->is_url())
377 return;
378 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
379 BookmarkNodeRemoved(this, node));
380 }
381
382 void BookmarkTagModel::OnWillChangeBookmarkNode(BookmarkModel* model,
383 const BookmarkNode* node) {
384 DCHECK(loaded_);
385 if (!node->is_url() || inhibit_change_notifications_)
386 return;
387 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
388 OnWillChangeBookmarkNode(this, node));
389 }
390
391 void BookmarkTagModel::BookmarkNodeChanged(BookmarkModel* model,
392 const BookmarkNode* node) {
393 DCHECK(loaded_);
394 if (node->is_folder()) {
395 // A folder title changed. This may change the tags on all the descendants
396 // still using the default tag list of all ancestors.
397 ReloadDescendants(node);
398 } else if (node->is_url()) {
399 if (!inhibit_change_notifications_)
400 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
401 BookmarkNodeChanged(this, node));
402 }
403 }
404
405 void BookmarkTagModel::OnWillChangeBookmarkMetaInfo(BookmarkModel* model,
406 const BookmarkNode* node) {
407 DCHECK(loaded_);
408 if (!node->is_url() || inhibit_change_notifications_)
409 return;
410 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
411 OnWillChangeBookmarkTags(this, node));
412 }
413
414 void BookmarkTagModel::BookmarkMetaInfoChanged(BookmarkModel* model,
415 const BookmarkNode* node) {
416 DCHECK(loaded_);
417 if (!node->is_url())
418 return;
419 RemoveBookmark(node);
420 LoadBookmark(node);
421 if (!inhibit_change_notifications_)
422 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
423 BookmarkTagsChanged(this, node));
424 }
425
426 void BookmarkTagModel::BookmarkNodeFaviconChanged(BookmarkModel* model,
427 const BookmarkNode* node) {
428 DCHECK(loaded_);
429 if (!node->is_url())
430 return;
431 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
432 BookmarkNodeFaviconChanged(this, node));
433 }
434
435 void BookmarkTagModel::OnWillReorderBookmarkNode(BookmarkModel* model,
436 const BookmarkNode* node) {
437 // This model doesn't care.
438 }
439
440 void BookmarkTagModel::BookmarkNodeChildrenReordered(BookmarkModel* model,
441 const BookmarkNode* node) {
442 // This model doesn't care.
443 }
444
445 void BookmarkTagModel::ExtensiveBookmarkChangesBeginning(BookmarkModel* model) {
446 DCHECK(loaded_);
447 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
448 ExtensiveBookmarkChangesBeginning(this));
449 }
450
451 void BookmarkTagModel::ExtensiveBookmarkChangesEnded(BookmarkModel* model) {
452 DCHECK(loaded_);
453 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
454 ExtensiveBookmarkChangesEnded(this));
455 }
456
457 void BookmarkTagModel::OnWillRemoveAllBookmarks(BookmarkModel* model) {
458 DCHECK(loaded_);
459 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
460 OnWillRemoveAllBookmarks(this));
461 }
462
463 void BookmarkTagModel::BookmarkAllNodesRemoved(BookmarkModel* model){
464 DCHECK(loaded_);
465 tag_to_bookmarks_.clear();
466 bookmark_to_tags_.clear();
467 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
468 BookmarkAllNodesRemoved(this));
469 }
470
471 // Private methods.
472
473 void BookmarkTagModel::SetTagsOnBookmark(const std::set<BookmarkTag>& tags,
474 const BookmarkNode* bookmark) {
475 DCHECK(bookmark_model_);
476 DCHECK(loaded_);
477
478 // Build a ListValue.
479 std::vector<BookmarkTag> tag_vector(tags.begin(), tags.end());
480 base::ListValue list;
481 list.AppendStrings(tag_vector);
482
483 // Encodes it.
484 std::string encoded;
485 JSONStringValueSerializer serializer(&encoded);
486
487 // Pushes it in the bookmark's metainfo. Even if the tag list is empty the
488 // empty list must be put on the node to avoid reverting to the tag list
489 // derived from the hierarchy.
490 // The internal caches of the BookmarkTagModel are updated when the
491 // notification from the BookmarkModel is received.
492 serializer.Serialize(list);
493 bookmark_model_->SetNodeMetaInfo(bookmark, TAG_KEY, encoded);
494 }
495
496 void BookmarkTagModel::Load() {
497 DCHECK(bookmark_model_);
498 DCHECK(!loaded_);
499 ui::TreeNodeIterator<const BookmarkNode> iterator(
500 bookmark_model_->root_node());
501 while (iterator.has_next()) {
502 const BookmarkNode* bookmark = iterator.Next();
503 if (bookmark->is_url())
504 LoadBookmark(bookmark);
505 }
506 loaded_ = true;
507 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
508 Loaded(this));
509 }
510
511 void BookmarkTagModel::ReloadDescendants(const BookmarkNode* folder) {
512 DCHECK(folder->is_folder());
513 ExtensiveChanges scoped(ExtensiveChanges(this));
514 ui::TreeNodeIterator<const BookmarkNode> iterator(folder);
515 while (iterator.has_next()) {
516 const BookmarkNode* bookmark = iterator.Next();
517 if (bookmark->is_url()) {
518 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
519 OnWillChangeBookmarkTags(this, bookmark));
520 RemoveBookmark(bookmark);
521 LoadBookmark(bookmark);
522 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
523 BookmarkTagsChanged(this, bookmark));
524 }
525 }
526 }
527
528 void BookmarkTagModel::LoadBookmark(const BookmarkNode* bookmark) {
529 DCHECK(bookmark_model_);
530 DCHECK(bookmark->is_url());
531 std::set<BookmarkTag> tags(ExtractTagsFromBookmark(bookmark));
532
533 bookmark_to_tags_[bookmark] = tags;
534 for (std::set<BookmarkTag>::iterator it = tags.begin();
535 it != tags.end(); ++it) {
536 tag_to_bookmarks_[*it].insert(bookmark);
537 }
538 }
539
540 void BookmarkTagModel::RemoveBookmark(const BookmarkNode* bookmark) {
541 DCHECK(bookmark_model_);
542 DCHECK(bookmark->is_url());
543 std::set<BookmarkTag> tags(bookmark_to_tags_[bookmark]);
544 bookmark_to_tags_.erase(bookmark);
545
546 for (std::set<BookmarkTag>::iterator it = tags.begin();
547 it != tags.end(); ++it) {
548 tag_to_bookmarks_[*it].erase(bookmark);
549 // Remove the tags no longer used.
550 if (!tag_to_bookmarks_[*it].size())
551 tag_to_bookmarks_.erase(*it);
552 }
553 }
OLDNEW
« no previous file with comments | « chrome/browser/bookmarks/bookmark_tag_model.h ('k') | chrome/browser/bookmarks/bookmark_tag_model_observer.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698