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