OLD | NEW |
| (Empty) |
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/bookmarks/core/browser/bookmark_model.h" | |
6 | |
7 #include <algorithm> | |
8 #include <functional> | |
9 | |
10 #include "base/bind.h" | |
11 #include "base/bind_helpers.h" | |
12 #include "base/i18n/string_compare.h" | |
13 #include "base/logging.h" | |
14 #include "base/macros.h" | |
15 #include "base/strings/string_util.h" | |
16 #include "components/bookmarks/core/browser/bookmark_expanded_state_tracker.h" | |
17 #include "components/bookmarks/core/browser/bookmark_index.h" | |
18 #include "components/bookmarks/core/browser/bookmark_match.h" | |
19 #include "components/bookmarks/core/browser/bookmark_model_observer.h" | |
20 #include "components/bookmarks/core/browser/bookmark_node_data.h" | |
21 #include "components/bookmarks/core/browser/bookmark_storage.h" | |
22 #include "components/bookmarks/core/browser/bookmark_utils.h" | |
23 #include "components/favicon_base/favicon_types.h" | |
24 #include "grit/component_strings.h" | |
25 #include "ui/base/l10n/l10n_util.h" | |
26 #include "ui/gfx/favicon_size.h" | |
27 | |
28 using base::Time; | |
29 | |
30 namespace { | |
31 | |
32 // Helper to get a mutable bookmark node. | |
33 BookmarkNode* AsMutable(const BookmarkNode* node) { | |
34 return const_cast<BookmarkNode*>(node); | |
35 } | |
36 | |
37 // Helper to get a mutable permanent bookmark node. | |
38 BookmarkPermanentNode* AsMutable(const BookmarkPermanentNode* node) { | |
39 return const_cast<BookmarkPermanentNode*>(node); | |
40 } | |
41 | |
42 // Comparator used when sorting permanent nodes. Nodes that are initially | |
43 // visible are sorted before nodes that are initially hidden. | |
44 class VisibilityComparator | |
45 : public std::binary_function<const BookmarkPermanentNode*, | |
46 const BookmarkPermanentNode*, | |
47 bool> { | |
48 public: | |
49 explicit VisibilityComparator(BookmarkClient* client) : client_(client) {} | |
50 | |
51 // Returns true if |n1| preceeds |n2|. | |
52 bool operator()(const BookmarkPermanentNode* n1, | |
53 const BookmarkPermanentNode* n2) { | |
54 bool n1_visible = client_->IsPermanentNodeVisible(n1->type()); | |
55 bool n2_visible = client_->IsPermanentNodeVisible(n2->type()); | |
56 return n1_visible != n2_visible && n1_visible; | |
57 } | |
58 | |
59 private: | |
60 BookmarkClient* client_; | |
61 }; | |
62 | |
63 // Comparator used when sorting bookmarks. Folders are sorted first, then | |
64 // bookmarks. | |
65 class SortComparator : public std::binary_function<const BookmarkNode*, | |
66 const BookmarkNode*, | |
67 bool> { | |
68 public: | |
69 explicit SortComparator(icu::Collator* collator) : collator_(collator) {} | |
70 | |
71 // Returns true if |n1| preceeds |n2|. | |
72 bool operator()(const BookmarkNode* n1, const BookmarkNode* n2) { | |
73 if (n1->type() == n2->type()) { | |
74 // Types are the same, compare the names. | |
75 if (!collator_) | |
76 return n1->GetTitle() < n2->GetTitle(); | |
77 return base::i18n::CompareString16WithCollator( | |
78 collator_, n1->GetTitle(), n2->GetTitle()) == UCOL_LESS; | |
79 } | |
80 // Types differ, sort such that folders come first. | |
81 return n1->is_folder(); | |
82 } | |
83 | |
84 private: | |
85 icu::Collator* collator_; | |
86 }; | |
87 | |
88 } // namespace | |
89 | |
90 // BookmarkModel -------------------------------------------------------------- | |
91 | |
92 BookmarkModel::BookmarkModel(BookmarkClient* client, bool index_urls) | |
93 : client_(client), | |
94 loaded_(false), | |
95 root_(GURL()), | |
96 bookmark_bar_node_(NULL), | |
97 other_node_(NULL), | |
98 mobile_node_(NULL), | |
99 next_node_id_(1), | |
100 observers_(ObserverList<BookmarkModelObserver>::NOTIFY_EXISTING_ONLY), | |
101 index_urls_(index_urls), | |
102 loaded_signal_(true, false), | |
103 extensive_changes_(0) { | |
104 DCHECK(client_); | |
105 } | |
106 | |
107 BookmarkModel::~BookmarkModel() { | |
108 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
109 BookmarkModelBeingDeleted(this)); | |
110 | |
111 if (store_.get()) { | |
112 // The store maintains a reference back to us. We need to tell it we're gone | |
113 // so that it doesn't try and invoke a method back on us again. | |
114 store_->BookmarkModelDeleted(); | |
115 } | |
116 } | |
117 | |
118 void BookmarkModel::Shutdown() { | |
119 if (loaded_) | |
120 return; | |
121 | |
122 // See comment in HistoryService::ShutdownOnUIThread where this is invoked for | |
123 // details. It is also called when the BookmarkModel is deleted. | |
124 loaded_signal_.Signal(); | |
125 } | |
126 | |
127 void BookmarkModel::Load( | |
128 PrefService* pref_service, | |
129 const std::string& accept_languages, | |
130 const base::FilePath& profile_path, | |
131 const scoped_refptr<base::SequencedTaskRunner>& io_task_runner, | |
132 const scoped_refptr<base::SequencedTaskRunner>& ui_task_runner) { | |
133 if (store_.get()) { | |
134 // If the store is non-null, it means Load was already invoked. Load should | |
135 // only be invoked once. | |
136 NOTREACHED(); | |
137 return; | |
138 } | |
139 | |
140 expanded_state_tracker_.reset( | |
141 new BookmarkExpandedStateTracker(this, pref_service)); | |
142 | |
143 // Load the bookmarks. BookmarkStorage notifies us when done. | |
144 store_ = new BookmarkStorage(this, profile_path, io_task_runner.get()); | |
145 store_->LoadBookmarks(CreateLoadDetails(accept_languages), ui_task_runner); | |
146 } | |
147 | |
148 const BookmarkNode* BookmarkModel::GetParentForNewNodes() { | |
149 std::vector<const BookmarkNode*> nodes = | |
150 bookmark_utils::GetMostRecentlyModifiedFolders(this, 1); | |
151 DCHECK(!nodes.empty()); // This list is always padded with default folders. | |
152 return nodes[0]; | |
153 } | |
154 | |
155 void BookmarkModel::AddObserver(BookmarkModelObserver* observer) { | |
156 observers_.AddObserver(observer); | |
157 } | |
158 | |
159 void BookmarkModel::RemoveObserver(BookmarkModelObserver* observer) { | |
160 observers_.RemoveObserver(observer); | |
161 } | |
162 | |
163 void BookmarkModel::BeginExtensiveChanges() { | |
164 if (++extensive_changes_ == 1) { | |
165 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
166 ExtensiveBookmarkChangesBeginning(this)); | |
167 } | |
168 } | |
169 | |
170 void BookmarkModel::EndExtensiveChanges() { | |
171 --extensive_changes_; | |
172 DCHECK_GE(extensive_changes_, 0); | |
173 if (extensive_changes_ == 0) { | |
174 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
175 ExtensiveBookmarkChangesEnded(this)); | |
176 } | |
177 } | |
178 | |
179 void BookmarkModel::BeginGroupedChanges() { | |
180 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
181 GroupedBookmarkChangesBeginning(this)); | |
182 } | |
183 | |
184 void BookmarkModel::EndGroupedChanges() { | |
185 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
186 GroupedBookmarkChangesEnded(this)); | |
187 } | |
188 | |
189 void BookmarkModel::Remove(const BookmarkNode* parent, int index) { | |
190 if (!loaded_ || !IsValidIndex(parent, index, false) || is_root_node(parent)) { | |
191 NOTREACHED(); | |
192 return; | |
193 } | |
194 RemoveAndDeleteNode(AsMutable(parent->GetChild(index))); | |
195 } | |
196 | |
197 void BookmarkModel::RemoveAll() { | |
198 std::set<GURL> removed_urls; | |
199 ScopedVector<BookmarkNode> removed_nodes; | |
200 | |
201 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
202 OnWillRemoveAllBookmarks(this)); | |
203 | |
204 BeginExtensiveChanges(); | |
205 // Skip deleting permanent nodes. Permanent bookmark nodes are the root and | |
206 // its immediate children. For removing all non permanent nodes just remove | |
207 // all children of non-root permanent nodes. | |
208 { | |
209 base::AutoLock url_lock(url_lock_); | |
210 for (int i = 0; i < root_.child_count(); ++i) { | |
211 BookmarkNode* permanent_node = root_.GetChild(i); | |
212 for (int j = permanent_node->child_count() - 1; j >= 0; --j) { | |
213 BookmarkNode* child_node = permanent_node->GetChild(j); | |
214 removed_nodes.push_back(child_node); | |
215 RemoveNodeAndGetRemovedUrls(child_node, &removed_urls); | |
216 } | |
217 } | |
218 } | |
219 EndExtensiveChanges(); | |
220 if (store_.get()) | |
221 store_->ScheduleSave(); | |
222 | |
223 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
224 BookmarkAllNodesRemoved(this, removed_urls)); | |
225 } | |
226 | |
227 void BookmarkModel::Move(const BookmarkNode* node, | |
228 const BookmarkNode* new_parent, | |
229 int index) { | |
230 if (!loaded_ || !node || !IsValidIndex(new_parent, index, true) || | |
231 is_root_node(new_parent) || is_permanent_node(node)) { | |
232 NOTREACHED(); | |
233 return; | |
234 } | |
235 | |
236 if (new_parent->HasAncestor(node)) { | |
237 // Can't make an ancestor of the node be a child of the node. | |
238 NOTREACHED(); | |
239 return; | |
240 } | |
241 | |
242 const BookmarkNode* old_parent = node->parent(); | |
243 int old_index = old_parent->GetIndexOf(node); | |
244 | |
245 if (old_parent == new_parent && | |
246 (index == old_index || index == old_index + 1)) { | |
247 // Node is already in this position, nothing to do. | |
248 return; | |
249 } | |
250 | |
251 SetDateFolderModified(new_parent, Time::Now()); | |
252 | |
253 if (old_parent == new_parent && index > old_index) | |
254 index--; | |
255 BookmarkNode* mutable_new_parent = AsMutable(new_parent); | |
256 mutable_new_parent->Add(AsMutable(node), index); | |
257 | |
258 if (store_.get()) | |
259 store_->ScheduleSave(); | |
260 | |
261 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
262 BookmarkNodeMoved(this, old_parent, old_index, | |
263 new_parent, index)); | |
264 } | |
265 | |
266 void BookmarkModel::Copy(const BookmarkNode* node, | |
267 const BookmarkNode* new_parent, | |
268 int index) { | |
269 if (!loaded_ || !node || !IsValidIndex(new_parent, index, true) || | |
270 is_root_node(new_parent) || is_permanent_node(node)) { | |
271 NOTREACHED(); | |
272 return; | |
273 } | |
274 | |
275 if (new_parent->HasAncestor(node)) { | |
276 // Can't make an ancestor of the node be a child of the node. | |
277 NOTREACHED(); | |
278 return; | |
279 } | |
280 | |
281 SetDateFolderModified(new_parent, Time::Now()); | |
282 BookmarkNodeData drag_data(node); | |
283 std::vector<BookmarkNodeData::Element> elements(drag_data.elements); | |
284 // CloneBookmarkNode will use BookmarkModel methods to do the job, so we | |
285 // don't need to send notifications here. | |
286 bookmark_utils::CloneBookmarkNode(this, elements, new_parent, index, true); | |
287 | |
288 if (store_.get()) | |
289 store_->ScheduleSave(); | |
290 } | |
291 | |
292 const gfx::Image& BookmarkModel::GetFavicon(const BookmarkNode* node) { | |
293 DCHECK(node); | |
294 if (node->favicon_state() == BookmarkNode::INVALID_FAVICON) { | |
295 BookmarkNode* mutable_node = AsMutable(node); | |
296 LoadFavicon( | |
297 mutable_node, | |
298 client_->PreferTouchIcon() ? | |
299 favicon_base::TOUCH_ICON : | |
300 favicon_base::FAVICON); | |
301 } | |
302 return node->favicon(); | |
303 } | |
304 | |
305 favicon_base::IconType BookmarkModel::GetFaviconType(const BookmarkNode* node) { | |
306 DCHECK(node); | |
307 return node->favicon_type(); | |
308 } | |
309 | |
310 void BookmarkModel::SetTitle(const BookmarkNode* node, | |
311 const base::string16& title) { | |
312 if (!node) { | |
313 NOTREACHED(); | |
314 return; | |
315 } | |
316 if (node->GetTitle() == title) | |
317 return; | |
318 | |
319 if (is_permanent_node(node)) { | |
320 NOTREACHED(); | |
321 return; | |
322 } | |
323 | |
324 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
325 OnWillChangeBookmarkNode(this, node)); | |
326 | |
327 // The title index doesn't support changing the title, instead we remove then | |
328 // add it back. | |
329 index_->Remove(node); | |
330 AsMutable(node)->SetTitle(title); | |
331 index_->Add(node); | |
332 | |
333 if (store_.get()) | |
334 store_->ScheduleSave(); | |
335 | |
336 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
337 BookmarkNodeChanged(this, node)); | |
338 } | |
339 | |
340 void BookmarkModel::SetURL(const BookmarkNode* node, const GURL& url) { | |
341 if (!node) { | |
342 NOTREACHED(); | |
343 return; | |
344 } | |
345 | |
346 // We cannot change the URL of a folder. | |
347 if (node->is_folder()) { | |
348 NOTREACHED(); | |
349 return; | |
350 } | |
351 | |
352 if (node->url() == url) | |
353 return; | |
354 | |
355 BookmarkNode* mutable_node = AsMutable(node); | |
356 mutable_node->InvalidateFavicon(); | |
357 CancelPendingFaviconLoadRequests(mutable_node); | |
358 | |
359 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
360 OnWillChangeBookmarkNode(this, node)); | |
361 | |
362 { | |
363 base::AutoLock url_lock(url_lock_); | |
364 RemoveNodeFromURLSet(mutable_node); | |
365 mutable_node->set_url(url); | |
366 nodes_ordered_by_url_set_.insert(mutable_node); | |
367 } | |
368 | |
369 if (store_.get()) | |
370 store_->ScheduleSave(); | |
371 | |
372 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
373 BookmarkNodeChanged(this, node)); | |
374 } | |
375 | |
376 void BookmarkModel::SetNodeMetaInfo(const BookmarkNode* node, | |
377 const std::string& key, | |
378 const std::string& value) { | |
379 std::string old_value; | |
380 if (node->GetMetaInfo(key, &old_value) && old_value == value) | |
381 return; | |
382 | |
383 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
384 OnWillChangeBookmarkMetaInfo(this, node)); | |
385 | |
386 if (AsMutable(node)->SetMetaInfo(key, value) && store_.get()) | |
387 store_->ScheduleSave(); | |
388 | |
389 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
390 BookmarkMetaInfoChanged(this, node)); | |
391 } | |
392 | |
393 void BookmarkModel::SetNodeMetaInfoMap( | |
394 const BookmarkNode* node, | |
395 const BookmarkNode::MetaInfoMap& meta_info_map) { | |
396 const BookmarkNode::MetaInfoMap* old_meta_info_map = node->GetMetaInfoMap(); | |
397 if ((!old_meta_info_map && meta_info_map.empty()) || | |
398 (old_meta_info_map && meta_info_map == *old_meta_info_map)) | |
399 return; | |
400 | |
401 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
402 OnWillChangeBookmarkMetaInfo(this, node)); | |
403 | |
404 AsMutable(node)->SetMetaInfoMap(meta_info_map); | |
405 if (store_.get()) | |
406 store_->ScheduleSave(); | |
407 | |
408 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
409 BookmarkMetaInfoChanged(this, node)); | |
410 } | |
411 | |
412 void BookmarkModel::DeleteNodeMetaInfo(const BookmarkNode* node, | |
413 const std::string& key) { | |
414 const BookmarkNode::MetaInfoMap* meta_info_map = node->GetMetaInfoMap(); | |
415 if (!meta_info_map || meta_info_map->find(key) == meta_info_map->end()) | |
416 return; | |
417 | |
418 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
419 OnWillChangeBookmarkMetaInfo(this, node)); | |
420 | |
421 if (AsMutable(node)->DeleteMetaInfo(key) && store_.get()) | |
422 store_->ScheduleSave(); | |
423 | |
424 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
425 BookmarkMetaInfoChanged(this, node)); | |
426 } | |
427 | |
428 void BookmarkModel::SetNodeSyncTransactionVersion( | |
429 const BookmarkNode* node, | |
430 int64 sync_transaction_version) { | |
431 if (sync_transaction_version == node->sync_transaction_version()) | |
432 return; | |
433 | |
434 AsMutable(node)->set_sync_transaction_version(sync_transaction_version); | |
435 if (store_.get()) | |
436 store_->ScheduleSave(); | |
437 } | |
438 | |
439 void BookmarkModel::OnFaviconChanged(const std::set<GURL>& urls) { | |
440 // Ignore events if |Load| has not been called yet. | |
441 if (!store_) | |
442 return; | |
443 | |
444 // Prevent the observers from getting confused for multiple favicon loads. | |
445 for (std::set<GURL>::const_iterator i = urls.begin(); i != urls.end(); ++i) { | |
446 std::vector<const BookmarkNode*> nodes; | |
447 GetNodesByURL(*i, &nodes); | |
448 for (size_t i = 0; i < nodes.size(); ++i) { | |
449 // Got an updated favicon, for a URL, do a new request. | |
450 BookmarkNode* node = AsMutable(nodes[i]); | |
451 node->InvalidateFavicon(); | |
452 CancelPendingFaviconLoadRequests(node); | |
453 FOR_EACH_OBSERVER(BookmarkModelObserver, | |
454 observers_, | |
455 BookmarkNodeFaviconChanged(this, node)); | |
456 } | |
457 } | |
458 } | |
459 | |
460 void BookmarkModel::SetDateAdded(const BookmarkNode* node, | |
461 Time date_added) { | |
462 if (!node) { | |
463 NOTREACHED(); | |
464 return; | |
465 } | |
466 | |
467 if (node->date_added() == date_added) | |
468 return; | |
469 | |
470 if (is_permanent_node(node)) { | |
471 NOTREACHED(); | |
472 return; | |
473 } | |
474 | |
475 AsMutable(node)->set_date_added(date_added); | |
476 | |
477 // Syncing might result in dates newer than the folder's last modified date. | |
478 if (date_added > node->parent()->date_folder_modified()) { | |
479 // Will trigger store_->ScheduleSave(). | |
480 SetDateFolderModified(node->parent(), date_added); | |
481 } else if (store_.get()) { | |
482 store_->ScheduleSave(); | |
483 } | |
484 } | |
485 | |
486 void BookmarkModel::GetNodesByURL(const GURL& url, | |
487 std::vector<const BookmarkNode*>* nodes) { | |
488 base::AutoLock url_lock(url_lock_); | |
489 BookmarkNode tmp_node(url); | |
490 NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(&tmp_node); | |
491 while (i != nodes_ordered_by_url_set_.end() && (*i)->url() == url) { | |
492 nodes->push_back(*i); | |
493 ++i; | |
494 } | |
495 } | |
496 | |
497 const BookmarkNode* BookmarkModel::GetMostRecentlyAddedNodeForURL( | |
498 const GURL& url) { | |
499 std::vector<const BookmarkNode*> nodes; | |
500 GetNodesByURL(url, &nodes); | |
501 if (nodes.empty()) | |
502 return NULL; | |
503 | |
504 std::sort(nodes.begin(), nodes.end(), &bookmark_utils::MoreRecentlyAdded); | |
505 return nodes.front(); | |
506 } | |
507 | |
508 bool BookmarkModel::HasBookmarks() { | |
509 base::AutoLock url_lock(url_lock_); | |
510 return !nodes_ordered_by_url_set_.empty(); | |
511 } | |
512 | |
513 bool BookmarkModel::IsBookmarked(const GURL& url) { | |
514 base::AutoLock url_lock(url_lock_); | |
515 return IsBookmarkedNoLock(url); | |
516 } | |
517 | |
518 void BookmarkModel::GetBookmarks( | |
519 std::vector<BookmarkService::URLAndTitle>* bookmarks) { | |
520 base::AutoLock url_lock(url_lock_); | |
521 const GURL* last_url = NULL; | |
522 for (NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.begin(); | |
523 i != nodes_ordered_by_url_set_.end(); ++i) { | |
524 const GURL* url = &((*i)->url()); | |
525 // Only add unique URLs. | |
526 if (!last_url || *url != *last_url) { | |
527 BookmarkService::URLAndTitle bookmark; | |
528 bookmark.url = *url; | |
529 bookmark.title = (*i)->GetTitle(); | |
530 bookmarks->push_back(bookmark); | |
531 } | |
532 last_url = url; | |
533 } | |
534 } | |
535 | |
536 void BookmarkModel::BlockTillLoaded() { | |
537 loaded_signal_.Wait(); | |
538 } | |
539 | |
540 const BookmarkNode* BookmarkModel::AddFolder(const BookmarkNode* parent, | |
541 int index, | |
542 const base::string16& title) { | |
543 return AddFolderWithMetaInfo(parent, index, title, NULL); | |
544 } | |
545 const BookmarkNode* BookmarkModel::AddFolderWithMetaInfo( | |
546 const BookmarkNode* parent, | |
547 int index, | |
548 const base::string16& title, | |
549 const BookmarkNode::MetaInfoMap* meta_info) { | |
550 if (!loaded_ || is_root_node(parent) || !IsValidIndex(parent, index, true)) { | |
551 // Can't add to the root. | |
552 NOTREACHED(); | |
553 return NULL; | |
554 } | |
555 | |
556 BookmarkNode* new_node = new BookmarkNode(generate_next_node_id(), GURL()); | |
557 new_node->set_date_folder_modified(Time::Now()); | |
558 // Folders shouldn't have line breaks in their titles. | |
559 new_node->SetTitle(title); | |
560 new_node->set_type(BookmarkNode::FOLDER); | |
561 if (meta_info) | |
562 new_node->SetMetaInfoMap(*meta_info); | |
563 | |
564 return AddNode(AsMutable(parent), index, new_node); | |
565 } | |
566 | |
567 const BookmarkNode* BookmarkModel::AddURL(const BookmarkNode* parent, | |
568 int index, | |
569 const base::string16& title, | |
570 const GURL& url) { | |
571 return AddURLWithCreationTimeAndMetaInfo( | |
572 parent, | |
573 index, | |
574 base::CollapseWhitespace(title, false), | |
575 url, | |
576 Time::Now(), | |
577 NULL); | |
578 } | |
579 | |
580 const BookmarkNode* BookmarkModel::AddURLWithCreationTimeAndMetaInfo( | |
581 const BookmarkNode* parent, | |
582 int index, | |
583 const base::string16& title, | |
584 const GURL& url, | |
585 const Time& creation_time, | |
586 const BookmarkNode::MetaInfoMap* meta_info) { | |
587 if (!loaded_ || !url.is_valid() || is_root_node(parent) || | |
588 !IsValidIndex(parent, index, true)) { | |
589 NOTREACHED(); | |
590 return NULL; | |
591 } | |
592 | |
593 // Syncing may result in dates newer than the last modified date. | |
594 if (creation_time > parent->date_folder_modified()) | |
595 SetDateFolderModified(parent, creation_time); | |
596 | |
597 BookmarkNode* new_node = new BookmarkNode(generate_next_node_id(), url); | |
598 new_node->SetTitle(title); | |
599 new_node->set_date_added(creation_time); | |
600 new_node->set_type(BookmarkNode::URL); | |
601 if (meta_info) | |
602 new_node->SetMetaInfoMap(*meta_info); | |
603 | |
604 { | |
605 // Only hold the lock for the duration of the insert. | |
606 base::AutoLock url_lock(url_lock_); | |
607 nodes_ordered_by_url_set_.insert(new_node); | |
608 } | |
609 | |
610 return AddNode(AsMutable(parent), index, new_node); | |
611 } | |
612 | |
613 void BookmarkModel::SortChildren(const BookmarkNode* parent) { | |
614 if (!parent || !parent->is_folder() || is_root_node(parent) || | |
615 parent->child_count() <= 1) { | |
616 return; | |
617 } | |
618 | |
619 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
620 OnWillReorderBookmarkNode(this, parent)); | |
621 | |
622 UErrorCode error = U_ZERO_ERROR; | |
623 scoped_ptr<icu::Collator> collator(icu::Collator::createInstance(error)); | |
624 if (U_FAILURE(error)) | |
625 collator.reset(NULL); | |
626 BookmarkNode* mutable_parent = AsMutable(parent); | |
627 std::sort(mutable_parent->children().begin(), | |
628 mutable_parent->children().end(), | |
629 SortComparator(collator.get())); | |
630 | |
631 if (store_.get()) | |
632 store_->ScheduleSave(); | |
633 | |
634 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
635 BookmarkNodeChildrenReordered(this, parent)); | |
636 } | |
637 | |
638 void BookmarkModel::ReorderChildren( | |
639 const BookmarkNode* parent, | |
640 const std::vector<const BookmarkNode*>& ordered_nodes) { | |
641 // Ensure that all children in |parent| are in |ordered_nodes|. | |
642 DCHECK_EQ(static_cast<size_t>(parent->child_count()), ordered_nodes.size()); | |
643 for (size_t i = 0; i < ordered_nodes.size(); ++i) | |
644 DCHECK_EQ(parent, ordered_nodes[i]->parent()); | |
645 | |
646 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
647 OnWillReorderBookmarkNode(this, parent)); | |
648 | |
649 AsMutable(parent)->SetChildren( | |
650 *(reinterpret_cast<const std::vector<BookmarkNode*>*>(&ordered_nodes))); | |
651 | |
652 if (store_.get()) | |
653 store_->ScheduleSave(); | |
654 | |
655 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
656 BookmarkNodeChildrenReordered(this, parent)); | |
657 } | |
658 | |
659 void BookmarkModel::SetDateFolderModified(const BookmarkNode* parent, | |
660 const Time time) { | |
661 DCHECK(parent); | |
662 AsMutable(parent)->set_date_folder_modified(time); | |
663 | |
664 if (store_.get()) | |
665 store_->ScheduleSave(); | |
666 } | |
667 | |
668 void BookmarkModel::ResetDateFolderModified(const BookmarkNode* node) { | |
669 SetDateFolderModified(node, Time()); | |
670 } | |
671 | |
672 void BookmarkModel::GetBookmarksMatching( | |
673 const base::string16& text, | |
674 size_t max_count, | |
675 std::vector<BookmarkMatch>* matches) { | |
676 if (!loaded_) | |
677 return; | |
678 | |
679 index_->GetBookmarksMatching(text, max_count, matches); | |
680 } | |
681 | |
682 void BookmarkModel::ClearStore() { | |
683 store_ = NULL; | |
684 } | |
685 | |
686 void BookmarkModel::SetPermanentNodeVisible(BookmarkNode::Type type, | |
687 bool value) { | |
688 AsMutable(PermanentNode(type))->set_visible( | |
689 value || client_->IsPermanentNodeVisible(type)); | |
690 } | |
691 | |
692 const BookmarkPermanentNode* BookmarkModel::PermanentNode( | |
693 BookmarkNode::Type type) { | |
694 DCHECK(loaded_); | |
695 switch (type) { | |
696 case BookmarkNode::BOOKMARK_BAR: | |
697 return bookmark_bar_node_; | |
698 case BookmarkNode::OTHER_NODE: | |
699 return other_node_; | |
700 case BookmarkNode::MOBILE: | |
701 return mobile_node_; | |
702 default: | |
703 NOTREACHED(); | |
704 return NULL; | |
705 } | |
706 } | |
707 | |
708 bool BookmarkModel::IsBookmarkedNoLock(const GURL& url) { | |
709 BookmarkNode tmp_node(url); | |
710 return (nodes_ordered_by_url_set_.find(&tmp_node) != | |
711 nodes_ordered_by_url_set_.end()); | |
712 } | |
713 | |
714 void BookmarkModel::RemoveNode(BookmarkNode* node, | |
715 std::set<GURL>* removed_urls) { | |
716 if (!loaded_ || !node || is_permanent_node(node)) { | |
717 NOTREACHED(); | |
718 return; | |
719 } | |
720 | |
721 url_lock_.AssertAcquired(); | |
722 if (node->is_url()) { | |
723 RemoveNodeFromURLSet(node); | |
724 removed_urls->insert(node->url()); | |
725 index_->Remove(node); | |
726 } | |
727 | |
728 CancelPendingFaviconLoadRequests(node); | |
729 | |
730 // Recurse through children. | |
731 for (int i = node->child_count() - 1; i >= 0; --i) | |
732 RemoveNode(node->GetChild(i), removed_urls); | |
733 } | |
734 | |
735 void BookmarkModel::DoneLoading(scoped_ptr<BookmarkLoadDetails> details) { | |
736 DCHECK(details); | |
737 if (loaded_) { | |
738 // We should only ever be loaded once. | |
739 NOTREACHED(); | |
740 return; | |
741 } | |
742 | |
743 next_node_id_ = details->max_id(); | |
744 if (details->computed_checksum() != details->stored_checksum() || | |
745 details->ids_reassigned()) { | |
746 // If bookmarks file changed externally, the IDs may have changed | |
747 // externally. In that case, the decoder may have reassigned IDs to make | |
748 // them unique. So when the file has changed externally, we should save the | |
749 // bookmarks file to persist new IDs. | |
750 if (store_.get()) | |
751 store_->ScheduleSave(); | |
752 } | |
753 bookmark_bar_node_ = details->release_bb_node(); | |
754 other_node_ = details->release_other_folder_node(); | |
755 mobile_node_ = details->release_mobile_folder_node(); | |
756 index_.reset(details->release_index()); | |
757 | |
758 // WARNING: order is important here, various places assume the order is | |
759 // constant (but can vary between embedders with the initial visibility | |
760 // of permanent nodes). | |
761 BookmarkPermanentNode* root_children[] = { | |
762 bookmark_bar_node_, other_node_, mobile_node_, | |
763 }; | |
764 std::stable_sort(root_children, | |
765 root_children + arraysize(root_children), | |
766 VisibilityComparator(client_)); | |
767 for (size_t i = 0; i < arraysize(root_children); ++i) { | |
768 root_.Add(root_children[i], static_cast<int>(i)); | |
769 } | |
770 | |
771 root_.SetMetaInfoMap(details->model_meta_info_map()); | |
772 root_.set_sync_transaction_version(details->model_sync_transaction_version()); | |
773 | |
774 { | |
775 base::AutoLock url_lock(url_lock_); | |
776 // Update nodes_ordered_by_url_set_ from the nodes. | |
777 PopulateNodesByURL(&root_); | |
778 } | |
779 | |
780 loaded_ = true; | |
781 | |
782 loaded_signal_.Signal(); | |
783 | |
784 // Notify our direct observers. | |
785 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
786 BookmarkModelLoaded(this, details->ids_reassigned())); | |
787 } | |
788 | |
789 void BookmarkModel::RemoveAndDeleteNode(BookmarkNode* delete_me) { | |
790 scoped_ptr<BookmarkNode> node(delete_me); | |
791 | |
792 const BookmarkNode* parent = node->parent(); | |
793 DCHECK(parent); | |
794 int index = parent->GetIndexOf(node.get()); | |
795 | |
796 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
797 OnWillRemoveBookmarks(this, parent, index, node.get())); | |
798 | |
799 std::set<GURL> removed_urls; | |
800 { | |
801 base::AutoLock url_lock(url_lock_); | |
802 RemoveNodeAndGetRemovedUrls(node.get(), &removed_urls); | |
803 } | |
804 | |
805 if (store_.get()) | |
806 store_->ScheduleSave(); | |
807 | |
808 FOR_EACH_OBSERVER( | |
809 BookmarkModelObserver, | |
810 observers_, | |
811 BookmarkNodeRemoved(this, parent, index, node.get(), removed_urls)); | |
812 } | |
813 | |
814 void BookmarkModel::RemoveNodeFromURLSet(BookmarkNode* node) { | |
815 // NOTE: this is called in such a way that url_lock_ is already held. As | |
816 // such, this doesn't explicitly grab the lock. | |
817 NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(node); | |
818 DCHECK(i != nodes_ordered_by_url_set_.end()); | |
819 // i points to the first node with the URL, advance until we find the | |
820 // node we're removing. | |
821 while (*i != node) | |
822 ++i; | |
823 nodes_ordered_by_url_set_.erase(i); | |
824 } | |
825 | |
826 void BookmarkModel::RemoveNodeAndGetRemovedUrls(BookmarkNode* node, | |
827 std::set<GURL>* removed_urls) { | |
828 // NOTE: this method should be always called with |url_lock_| held. | |
829 // This method does not explicitly acquires a lock. | |
830 url_lock_.AssertAcquired(); | |
831 DCHECK(removed_urls); | |
832 BookmarkNode* parent = AsMutable(node->parent()); | |
833 DCHECK(parent); | |
834 parent->Remove(node); | |
835 RemoveNode(node, removed_urls); | |
836 // RemoveNode adds an entry to removed_urls for each node of type URL. As we | |
837 // allow duplicates we need to remove any entries that are still bookmarked. | |
838 for (std::set<GURL>::iterator i = removed_urls->begin(); | |
839 i != removed_urls->end();) { | |
840 if (IsBookmarkedNoLock(*i)) { | |
841 // When we erase the iterator pointing at the erasee is | |
842 // invalidated, so using i++ here within the "erase" call is | |
843 // important as it advances the iterator before passing the | |
844 // old value through to erase. | |
845 removed_urls->erase(i++); | |
846 } else { | |
847 ++i; | |
848 } | |
849 } | |
850 } | |
851 | |
852 BookmarkNode* BookmarkModel::AddNode(BookmarkNode* parent, | |
853 int index, | |
854 BookmarkNode* node) { | |
855 parent->Add(node, index); | |
856 | |
857 if (store_.get()) | |
858 store_->ScheduleSave(); | |
859 | |
860 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
861 BookmarkNodeAdded(this, parent, index)); | |
862 | |
863 index_->Add(node); | |
864 | |
865 return node; | |
866 } | |
867 | |
868 bool BookmarkModel::IsValidIndex(const BookmarkNode* parent, | |
869 int index, | |
870 bool allow_end) { | |
871 return (parent && parent->is_folder() && | |
872 (index >= 0 && (index < parent->child_count() || | |
873 (allow_end && index == parent->child_count())))); | |
874 } | |
875 | |
876 BookmarkPermanentNode* BookmarkModel::CreatePermanentNode( | |
877 BookmarkNode::Type type) { | |
878 DCHECK(type == BookmarkNode::BOOKMARK_BAR || | |
879 type == BookmarkNode::OTHER_NODE || | |
880 type == BookmarkNode::MOBILE); | |
881 BookmarkPermanentNode* node = | |
882 new BookmarkPermanentNode(generate_next_node_id()); | |
883 node->set_visible(client_->IsPermanentNodeVisible(type)); | |
884 | |
885 int title_id; | |
886 switch (type) { | |
887 case BookmarkNode::BOOKMARK_BAR: | |
888 title_id = IDS_BOOKMARK_BAR_FOLDER_NAME; | |
889 break; | |
890 case BookmarkNode::OTHER_NODE: | |
891 title_id = IDS_BOOKMARK_BAR_OTHER_FOLDER_NAME; | |
892 break; | |
893 case BookmarkNode::MOBILE: | |
894 title_id = IDS_BOOKMARK_BAR_MOBILE_FOLDER_NAME; | |
895 break; | |
896 default: | |
897 NOTREACHED(); | |
898 title_id = IDS_BOOKMARK_BAR_FOLDER_NAME; | |
899 break; | |
900 } | |
901 node->SetTitle(l10n_util::GetStringUTF16(title_id)); | |
902 node->set_type(type); | |
903 return node; | |
904 } | |
905 | |
906 void BookmarkModel::OnFaviconDataAvailable( | |
907 BookmarkNode* node, | |
908 favicon_base::IconType icon_type, | |
909 const favicon_base::FaviconImageResult& image_result) { | |
910 DCHECK(node); | |
911 node->set_favicon_load_task_id(base::CancelableTaskTracker::kBadTaskId); | |
912 node->set_favicon_state(BookmarkNode::LOADED_FAVICON); | |
913 if (!image_result.image.IsEmpty()) { | |
914 node->set_favicon_type(icon_type); | |
915 node->set_favicon(image_result.image); | |
916 node->set_icon_url(image_result.icon_url); | |
917 FaviconLoaded(node); | |
918 } else if (icon_type == favicon_base::TOUCH_ICON) { | |
919 // Couldn't load the touch icon, fallback to the regular favicon. | |
920 DCHECK(client_->PreferTouchIcon()); | |
921 LoadFavicon(node, favicon_base::FAVICON); | |
922 } | |
923 } | |
924 | |
925 void BookmarkModel::LoadFavicon( | |
926 BookmarkNode* node, | |
927 favicon_base::IconType icon_type) { | |
928 if (node->is_folder()) | |
929 return; | |
930 | |
931 DCHECK(node->url().is_valid()); | |
932 node->set_favicon_state(BookmarkNode::LOADING_FAVICON); | |
933 base::CancelableTaskTracker::TaskId taskId = client_->GetFaviconImageForURL( | |
934 node->url(), | |
935 icon_type, | |
936 icon_type == favicon_base::FAVICON ? gfx::kFaviconSize : 0, | |
937 base::Bind( | |
938 &BookmarkModel::OnFaviconDataAvailable, | |
939 base::Unretained(this), | |
940 node, | |
941 icon_type), | |
942 &cancelable_task_tracker_); | |
943 if (taskId != base::CancelableTaskTracker::kBadTaskId) | |
944 node->set_favicon_load_task_id(taskId); | |
945 } | |
946 | |
947 void BookmarkModel::FaviconLoaded(const BookmarkNode* node) { | |
948 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, | |
949 BookmarkNodeFaviconChanged(this, node)); | |
950 } | |
951 | |
952 void BookmarkModel::CancelPendingFaviconLoadRequests(BookmarkNode* node) { | |
953 if (node->favicon_load_task_id() != base::CancelableTaskTracker::kBadTaskId) { | |
954 cancelable_task_tracker_.TryCancel(node->favicon_load_task_id()); | |
955 node->set_favicon_load_task_id(base::CancelableTaskTracker::kBadTaskId); | |
956 } | |
957 } | |
958 | |
959 void BookmarkModel::PopulateNodesByURL(BookmarkNode* node) { | |
960 // NOTE: this is called with url_lock_ already held. As such, this doesn't | |
961 // explicitly grab the lock. | |
962 if (node->is_url()) | |
963 nodes_ordered_by_url_set_.insert(node); | |
964 for (int i = 0; i < node->child_count(); ++i) | |
965 PopulateNodesByURL(node->GetChild(i)); | |
966 } | |
967 | |
968 int64 BookmarkModel::generate_next_node_id() { | |
969 return next_node_id_++; | |
970 } | |
971 | |
972 scoped_ptr<BookmarkLoadDetails> BookmarkModel::CreateLoadDetails( | |
973 const std::string& accept_languages) { | |
974 BookmarkPermanentNode* bb_node = | |
975 CreatePermanentNode(BookmarkNode::BOOKMARK_BAR); | |
976 BookmarkPermanentNode* other_node = | |
977 CreatePermanentNode(BookmarkNode::OTHER_NODE); | |
978 BookmarkPermanentNode* mobile_node = | |
979 CreatePermanentNode(BookmarkNode::MOBILE); | |
980 return scoped_ptr<BookmarkLoadDetails>(new BookmarkLoadDetails( | |
981 bb_node, | |
982 other_node, | |
983 mobile_node, | |
984 new BookmarkIndex(client_, index_urls_, accept_languages), | |
985 next_node_id_)); | |
986 } | |
OLD | NEW |