1// Copyright (c) 2011 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/callback.h"
11#include "base/memory/scoped_vector.h"
12#include "build/build_config.h"
13#include "chrome/browser/bookmarks/bookmark_index.h"
14#include "chrome/browser/bookmarks/bookmark_storage.h"
15#include "chrome/browser/bookmarks/bookmark_utils.h"
16#include "chrome/browser/browser_process.h"
17#include "chrome/browser/history/history_notifications.h"
18#include "chrome/browser/profiles/profile.h"
19#include "content/common/notification_service.h"
20#include "grit/generated_resources.h"
21#include "ui/base/l10n/l10n_util.h"
22#include "ui/base/l10n/l10n_util_collator.h"
23#include "ui/gfx/codec/png_codec.h"
24
25using base::Time;
26
27namespace {
28
29// Helper to get a mutable bookmark node.
30static BookmarkNode* AsMutable(const BookmarkNode* node) {
31  return const_cast<BookmarkNode*>(node);
32}
33
34}  // anonymous namespace
35
36// BookmarkNode ---------------------------------------------------------------
37
38BookmarkNode::BookmarkNode(const GURL& url)
39    : url_(url) {
40  Initialize(0);
41}
42
43BookmarkNode::BookmarkNode(int64 id, const GURL& url)
44    : url_(url) {
45  Initialize(id);
46}
47
48BookmarkNode::~BookmarkNode() {
49}
50
51void BookmarkNode::Initialize(int64 id) {
52  id_ = id;
53  loaded_favicon_ = false;
54  favicon_load_handle_ = 0;
55  type_ = !url_.is_empty() ? URL : BOOKMARK_BAR;
56  date_added_ = Time::Now();
57}
58
59void BookmarkNode::InvalidateFavicon() {
60  loaded_favicon_ = false;
61  favicon_ = SkBitmap();
62}
63
64void BookmarkNode::Reset(const history::StarredEntry& entry) {
65  DCHECK(entry.type != history::StarredEntry::URL || entry.url == url_);
66
67  favicon_ = SkBitmap();
68  switch (entry.type) {
69    case history::StarredEntry::URL:
70      type_ = BookmarkNode::URL;
71      break;
72    case history::StarredEntry::USER_FOLDER:
73      type_ = BookmarkNode::FOLDER;
74      break;
75    case history::StarredEntry::BOOKMARK_BAR:
76      type_ = BookmarkNode::BOOKMARK_BAR;
77      break;
78    case history::StarredEntry::OTHER:
79      type_ = BookmarkNode::OTHER_NODE;
80      break;
81    default:
82      NOTREACHED();
83  }
84  date_added_ = entry.date_added;
85  date_folder_modified_ = entry.date_folder_modified;
86  set_title(entry.title);
87}
88
89// BookmarkModel --------------------------------------------------------------
90
91namespace {
92
93// Comparator used when sorting bookmarks. Folders are sorted first, then
94// bookmarks.
95class SortComparator : public std::binary_function<const BookmarkNode*,
96                                                   const BookmarkNode*,
97                                                   bool> {
98 public:
99  explicit SortComparator(icu::Collator* collator) : collator_(collator) { }
100
101  // Returns true if lhs preceeds rhs.
102  bool operator() (const BookmarkNode* n1, const BookmarkNode* n2) {
103    if (n1->type() == n2->type()) {
104      // Types are the same, compare the names.
105      if (!collator_)
106        return n1->GetTitle() < n2->GetTitle();
107      return l10n_util::CompareString16WithCollator(
108          collator_, n1->GetTitle(), n2->GetTitle()) == UCOL_LESS;
109    }
110    // Types differ, sort such that folders come first.
111    return n1->is_folder();
112  }
113
114 private:
115  icu::Collator* collator_;
116};
117
118}  // namespace
119
120BookmarkModel::BookmarkModel(Profile* profile)
121    : profile_(profile),
122      loaded_(false),
123      file_changed_(false),
124      root_(GURL()),
125      bookmark_bar_node_(NULL),
126      other_node_(NULL),
127      next_node_id_(1),
128      observers_(ObserverList<BookmarkModelObserver>::NOTIFY_EXISTING_ONLY),
129      loaded_signal_(TRUE, FALSE) {
130  if (!profile_) {
131    // Profile is null during testing.
132    DoneLoading(CreateLoadDetails());
133  }
134}
135
136BookmarkModel::~BookmarkModel() {
137  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
138                    BookmarkModelBeingDeleted(this));
139
140  if (store_) {
141    // The store maintains a reference back to us. We need to tell it we're gone
142    // so that it doesn't try and invoke a method back on us again.
143    store_->BookmarkModelDeleted();
144  }
145}
146
147void BookmarkModel::Load() {
148  if (store_.get()) {
149    // If the store is non-null, it means Load was already invoked. Load should
150    // only be invoked once.
151    NOTREACHED();
152    return;
153  }
154
155  // Listen for changes to favicons so that we can update the favicon of the
156  // node appropriately.
157  registrar_.Add(this, NotificationType::FAVICON_CHANGED,
158                 Source<Profile>(profile_));
159
160  // Load the bookmarks. BookmarkStorage notifies us when done.
161  store_ = new BookmarkStorage(profile_, this);
162  store_->LoadBookmarks(CreateLoadDetails());
163}
164
165const BookmarkNode* BookmarkModel::GetParentForNewNodes() {
166  std::vector<const BookmarkNode*> nodes =
167      bookmark_utils::GetMostRecentlyModifiedFolders(this, 1);
168  return nodes.empty() ? bookmark_bar_node_ : nodes[0];
169}
170
171void BookmarkModel::Remove(const BookmarkNode* parent, int index) {
172  if (!loaded_ || !IsValidIndex(parent, index, false) || is_root(parent)) {
173    NOTREACHED();
174    return;
175  }
176  RemoveAndDeleteNode(AsMutable(parent->GetChild(index)));
177}
178
179void BookmarkModel::Move(const BookmarkNode* node,
180                         const BookmarkNode* new_parent,
181                         int index) {
182  if (!loaded_ || !node || !IsValidIndex(new_parent, index, true) ||
183      is_root(new_parent) || is_permanent_node(node)) {
184    NOTREACHED();
185    return;
186  }
187
188  if (new_parent->HasAncestor(node)) {
189    // Can't make an ancestor of the node be a child of the node.
190    NOTREACHED();
191    return;
192  }
193
194  SetDateFolderModified(new_parent, Time::Now());
195
196  const BookmarkNode* old_parent = node->parent();
197  int old_index = old_parent->GetIndexOf(node);
198
199  if (old_parent == new_parent &&
200      (index == old_index || index == old_index + 1)) {
201    // Node is already in this position, nothing to do.
202    return;
203  }
204
205  if (old_parent == new_parent && index > old_index)
206    index--;
207  BookmarkNode* mutable_new_parent = AsMutable(new_parent);
208  mutable_new_parent->Add(AsMutable(node), index);
209
210  if (store_.get())
211    store_->ScheduleSave();
212
213  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
214                    BookmarkNodeMoved(this, old_parent, old_index,
215                                      new_parent, index));
216}
217
218void BookmarkModel::Copy(const BookmarkNode* node,
219                         const BookmarkNode* new_parent,
220                         int index) {
221  if (!loaded_ || !node || !IsValidIndex(new_parent, index, true) ||
222      is_root(new_parent) || is_permanent_node(node)) {
223    NOTREACHED();
224    return;
225  }
226
227  if (new_parent->HasAncestor(node)) {
228    // Can't make an ancestor of the node be a child of the node.
229    NOTREACHED();
230    return;
231  }
232
233  SetDateFolderModified(new_parent, Time::Now());
234  BookmarkNodeData drag_data_(node);
235  std::vector<BookmarkNodeData::Element> elements(drag_data_.elements);
236  // CloneBookmarkNode will use BookmarkModel methods to do the job, so we
237  // don't need to send notifications here.
238  bookmark_utils::CloneBookmarkNode(this, elements, new_parent, index);
239
240  if (store_.get())
241    store_->ScheduleSave();
242}
243
244const SkBitmap& BookmarkModel::GetFavicon(const BookmarkNode* node) {
245  DCHECK(node);
246  if (!node->is_favicon_loaded()) {
247    BookmarkNode* mutable_node = AsMutable(node);
248    mutable_node->set_favicon_loaded(true);
249    LoadFavicon(mutable_node);
250  }
251  return node->favicon();
252}
253
254void BookmarkModel::SetTitle(const BookmarkNode* node, const string16& title) {
255  if (!node) {
256    NOTREACHED();
257    return;
258  }
259  if (node->GetTitle() == title)
260    return;
261
262  if (node == bookmark_bar_node_ || node == other_node_) {
263    NOTREACHED();
264    return;
265  }
266
267  // The title index doesn't support changing the title, instead we remove then
268  // add it back.
269  index_->Remove(node);
270  AsMutable(node)->set_title(title);
271  index_->Add(node);
272
273  if (store_.get())
274    store_->ScheduleSave();
275
276  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
277                    BookmarkNodeChanged(this, node));
278}
279
280void BookmarkModel::SetURL(const BookmarkNode* node, const GURL& url) {
281  if (!node) {
282    NOTREACHED();
283    return;
284  }
285
286  // We cannot change the URL of a folder.
287  if (node->is_folder()) {
288    NOTREACHED();
289    return;
290  }
291
292  if (url == node->GetURL())
293    return;
294
295  AsMutable(node)->InvalidateFavicon();
296  CancelPendingFaviconLoadRequests(AsMutable(node));
297
298  {
299    base::AutoLock url_lock(url_lock_);
300    NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(
301        AsMutable(node));
302    DCHECK(i != nodes_ordered_by_url_set_.end());
303    // i points to the first node with the URL, advance until we find the
304    // node we're removing.
305    while (*i != node)
306      ++i;
307    nodes_ordered_by_url_set_.erase(i);
308
309    AsMutable(node)->SetURL(url);
310    nodes_ordered_by_url_set_.insert(AsMutable(node));
311  }
312
313  if (store_.get())
314    store_->ScheduleSave();
315
316  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
317                    BookmarkNodeChanged(this, node));
318}
319
320bool BookmarkModel::IsLoaded() {
321  return loaded_;
322}
323
324void BookmarkModel::GetNodesByURL(const GURL& url,
325                                  std::vector<const BookmarkNode*>* nodes) {
326  base::AutoLock url_lock(url_lock_);
327  BookmarkNode tmp_node(url);
328  NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(&tmp_node);
329  while (i != nodes_ordered_by_url_set_.end() && (*i)->GetURL() == url) {
330    nodes->push_back(*i);
331    ++i;
332  }
333}
334
335const BookmarkNode* BookmarkModel::GetMostRecentlyAddedNodeForURL(
336    const GURL& url) {
337  std::vector<const BookmarkNode*> nodes;
338  GetNodesByURL(url, &nodes);
339  if (nodes.empty())
340    return NULL;
341
342  std::sort(nodes.begin(), nodes.end(), &bookmark_utils::MoreRecentlyAdded);
343  return nodes.front();
344}
345
346void BookmarkModel::GetBookmarks(std::vector<GURL>* urls) {
347  base::AutoLock url_lock(url_lock_);
348  const GURL* last_url = NULL;
349  for (NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.begin();
350       i != nodes_ordered_by_url_set_.end(); ++i) {
351    const GURL* url = &((*i)->GetURL());
352    // Only add unique URLs.
353    if (!last_url || *url != *last_url)
354      urls->push_back(*url);
355    last_url = url;
356  }
357}
358
359bool BookmarkModel::HasBookmarks() {
360  base::AutoLock url_lock(url_lock_);
361  return !nodes_ordered_by_url_set_.empty();
362}
363
364bool BookmarkModel::IsBookmarked(const GURL& url) {
365  base::AutoLock url_lock(url_lock_);
366  return IsBookmarkedNoLock(url);
367}
368
369const BookmarkNode* BookmarkModel::GetNodeByID(int64 id) {
370  // TODO(sky): TreeNode needs a method that visits all nodes using a predicate.
371  return GetNodeByID(&root_, id);
372}
373
374const BookmarkNode* BookmarkModel::AddFolder(const BookmarkNode* parent,
375                                             int index,
376                                             const string16& title) {
377  if (!loaded_ || parent == &root_ || !IsValidIndex(parent, index, true)) {
378    // Can't add to the root.
379    NOTREACHED();
380    return NULL;
381  }
382
383  BookmarkNode* new_node = new BookmarkNode(generate_next_node_id(),
384                                            GURL());
385  new_node->set_date_folder_modified(Time::Now());
386  new_node->set_title(title);
387  new_node->set_type(BookmarkNode::FOLDER);
388
389  return AddNode(AsMutable(parent), index, new_node, false);
390}
391
392const BookmarkNode* BookmarkModel::AddURL(const BookmarkNode* parent,
393                                          int index,
394                                          const string16& title,
395                                          const GURL& url) {
396  return AddURLWithCreationTime(parent, index, title, url, Time::Now());
397}
398
399const BookmarkNode* BookmarkModel::AddURLWithCreationTime(
400    const BookmarkNode* parent,
401    int index,
402    const string16& title,
403    const GURL& url,
404    const Time& creation_time) {
405  if (!loaded_ || !url.is_valid() || is_root(parent) ||
406      !IsValidIndex(parent, index, true)) {
407    NOTREACHED();
408    return NULL;
409  }
410
411  bool was_bookmarked = IsBookmarked(url);
412
413  SetDateFolderModified(parent, creation_time);
414
415  BookmarkNode* new_node = new BookmarkNode(generate_next_node_id(), url);
416  new_node->set_title(title);
417  new_node->set_date_added(creation_time);
418  new_node->set_type(BookmarkNode::URL);
419
420  {
421    // Only hold the lock for the duration of the insert.
422    base::AutoLock url_lock(url_lock_);
423    nodes_ordered_by_url_set_.insert(new_node);
424  }
425
426  return AddNode(AsMutable(parent), index, new_node, was_bookmarked);
427}
428
429void BookmarkModel::SortChildren(const BookmarkNode* parent) {
430  if (!parent || !parent->is_folder() || is_root(parent) ||
431      parent->child_count() <= 1) {
432    return;
433  }
434
435  UErrorCode error = U_ZERO_ERROR;
436  scoped_ptr<icu::Collator> collator(
437      icu::Collator::createInstance(
438          icu::Locale(g_browser_process->GetApplicationLocale().c_str()),
439          error));
440  if (U_FAILURE(error))
441    collator.reset(NULL);
442  BookmarkNode* mutable_parent = AsMutable(parent);
443  std::sort(mutable_parent->children().begin(),
444            mutable_parent->children().end(),
445            SortComparator(collator.get()));
446
447  if (store_.get())
448    store_->ScheduleSave();
449
450  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
451                    BookmarkNodeChildrenReordered(this, parent));
452}
453
454void BookmarkModel::SetURLStarred(const GURL& url,
455                                  const string16& title,
456                                  bool is_starred) {
457  std::vector<const BookmarkNode*> bookmarks;
458  GetNodesByURL(url, &bookmarks);
459  bool bookmarks_exist = !bookmarks.empty();
460  if (is_starred == bookmarks_exist)
461    return;  // Nothing to do, state already matches.
462
463  if (is_starred) {
464    // Create a bookmark.
465    const BookmarkNode* parent = GetParentForNewNodes();
466    AddURL(parent, parent->child_count(), title, url);
467  } else {
468    // Remove all the bookmarks.
469    for (size_t i = 0; i < bookmarks.size(); ++i) {
470      const BookmarkNode* node = bookmarks[i];
471      int index = node->parent()->GetIndexOf(node);
472      if (index > -1)
473        Remove(node->parent(), index);
474    }
475  }
476}
477
478void BookmarkModel::SetDateFolderModified(const BookmarkNode* parent,
479                                          const Time time) {
480  DCHECK(parent);
481  AsMutable(parent)->set_date_folder_modified(time);
482
483  if (store_.get())
484    store_->ScheduleSave();
485}
486
487void BookmarkModel::ResetDateFolderModified(const BookmarkNode* node) {
488  SetDateFolderModified(node, Time());
489}
490
491void BookmarkModel::GetBookmarksWithTitlesMatching(
492    const string16& text,
493    size_t max_count,
494    std::vector<bookmark_utils::TitleMatch>* matches) {
495  if (!loaded_)
496    return;
497
498  index_->GetBookmarksWithTitlesMatching(text, max_count, matches);
499}
500
501void BookmarkModel::ClearStore() {
502  registrar_.RemoveAll();
503  store_ = NULL;
504}
505
506bool BookmarkModel::IsBookmarkedNoLock(const GURL& url) {
507  BookmarkNode tmp_node(url);
508  return (nodes_ordered_by_url_set_.find(&tmp_node) !=
509          nodes_ordered_by_url_set_.end());
510}
511
512void BookmarkModel::FaviconLoaded(const BookmarkNode* node) {
513  // Send out notification to the observer.
514  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
515                    BookmarkNodeFaviconLoaded(this, node));
516}
517
518void BookmarkModel::RemoveNode(BookmarkNode* node,
519                               std::set<GURL>* removed_urls) {
520  if (!loaded_ || !node || is_permanent_node(node)) {
521    NOTREACHED();
522    return;
523  }
524
525  if (node->type() == BookmarkNode::URL) {
526    // NOTE: this is called in such a way that url_lock_ is already held. As
527    // such, this doesn't explicitly grab the lock.
528    NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(node);
529    DCHECK(i != nodes_ordered_by_url_set_.end());
530    // i points to the first node with the URL, advance until we find the
531    // node we're removing.
532    while (*i != node)
533      ++i;
534    nodes_ordered_by_url_set_.erase(i);
535    removed_urls->insert(node->GetURL());
536
537    index_->Remove(node);
538  }
539
540  CancelPendingFaviconLoadRequests(node);
541
542  // Recurse through children.
543  for (int i = node->child_count() - 1; i >= 0; --i)
544    RemoveNode(node->GetChild(i), removed_urls);
545}
546
547void BookmarkModel::DoneLoading(
548    BookmarkLoadDetails* details_delete_me) {
549  DCHECK(details_delete_me);
550  scoped_ptr<BookmarkLoadDetails> details(details_delete_me);
551  if (loaded_) {
552    // We should only ever be loaded once.
553    NOTREACHED();
554    return;
555  }
556
557  next_node_id_ = details->max_id();
558  if (details->computed_checksum() != details->stored_checksum())
559    SetFileChanged();
560  if (details->computed_checksum() != details->stored_checksum() ||
561      details->ids_reassigned()) {
562    // If bookmarks file changed externally, the IDs may have changed
563    // externally. In that case, the decoder may have reassigned IDs to make
564    // them unique. So when the file has changed externally, we should save the
565    // bookmarks file to persist new IDs.
566    if (store_.get())
567      store_->ScheduleSave();
568  }
569  bookmark_bar_node_ = details->release_bb_node();
570  other_node_ = details->release_other_folder_node();
571  index_.reset(details->release_index());
572
573  // WARNING: order is important here, various places assume bookmark bar then
574  // other node.
575  root_.Add(bookmark_bar_node_, 0);
576  root_.Add(other_node_, 1);
577
578  {
579    base::AutoLock url_lock(url_lock_);
580    // Update nodes_ordered_by_url_set_ from the nodes.
581    PopulateNodesByURL(&root_);
582  }
583
584  loaded_ = true;
585
586  loaded_signal_.Signal();
587
588  // Notify our direct observers.
589  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, Loaded(this));
590
591  // And generic notification.
592  NotificationService::current()->Notify(
593      NotificationType::BOOKMARK_MODEL_LOADED,
594      Source<Profile>(profile_),
595      NotificationService::NoDetails());
596}
597
598void BookmarkModel::RemoveAndDeleteNode(BookmarkNode* delete_me) {
599  scoped_ptr<BookmarkNode> node(delete_me);
600
601  BookmarkNode* parent = AsMutable(node->parent());
602  DCHECK(parent);
603  int index = parent->GetIndexOf(node.get());
604  parent->Remove(node.get());
605  history::URLsStarredDetails details(false);
606  {
607    base::AutoLock url_lock(url_lock_);
608    RemoveNode(node.get(), &details.changed_urls);
609
610    // RemoveNode adds an entry to changed_urls for each node of type URL. As we
611    // allow duplicates we need to remove any entries that are still bookmarked.
612    for (std::set<GURL>::iterator i = details.changed_urls.begin();
613         i != details.changed_urls.end(); ) {
614      if (IsBookmarkedNoLock(*i)) {
615        // When we erase the iterator pointing at the erasee is
616        // invalidated, so using i++ here within the "erase" call is
617        // important as it advances the iterator before passing the
618        // old value through to erase.
619        details.changed_urls.erase(i++);
620      } else {
621        ++i;
622      }
623    }
624  }
625
626  if (store_.get())
627    store_->ScheduleSave();
628
629  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
630                    BookmarkNodeRemoved(this, parent, index, node.get()));
631
632  if (details.changed_urls.empty()) {
633    // No point in sending out notification if the starred state didn't change.
634    return;
635  }
636
637  if (profile_) {
638    HistoryService* history =
639        profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
640    if (history)
641      history->URLsNoLongerBookmarked(details.changed_urls);
642  }
643
644  NotificationService::current()->Notify(
645      NotificationType::URLS_STARRED,
646      Source<Profile>(profile_),
647      Details<history::URLsStarredDetails>(&details));
648}
649
650void BookmarkModel::BeginImportMode() {
651  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
652                    BookmarkImportBeginning(this));
653}
654
655void BookmarkModel::EndImportMode() {
656  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
657                    BookmarkImportEnding(this));
658}
659
660BookmarkNode* BookmarkModel::AddNode(BookmarkNode* parent,
661                                     int index,
662                                     BookmarkNode* node,
663                                     bool was_bookmarked) {
664  parent->Add(node, index);
665
666  if (store_.get())
667    store_->ScheduleSave();
668
669  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
670                    BookmarkNodeAdded(this, parent, index));
671
672  index_->Add(node);
673
674  if (node->type() == BookmarkNode::URL && !was_bookmarked) {
675    history::URLsStarredDetails details(true);
676    details.changed_urls.insert(node->GetURL());
677    NotificationService::current()->Notify(
678        NotificationType::URLS_STARRED,
679        Source<Profile>(profile_),
680        Details<history::URLsStarredDetails>(&details));
681  }
682  return node;
683}
684
685void BookmarkModel::BlockTillLoaded() {
686  loaded_signal_.Wait();
687}
688
689const BookmarkNode* BookmarkModel::GetNodeByID(const BookmarkNode* node,
690                                               int64 id) {
691  if (node->id() == id)
692    return node;
693
694  for (int i = 0, child_count = node->child_count(); i < child_count; ++i) {
695    const BookmarkNode* result = GetNodeByID(node->GetChild(i), id);
696    if (result)
697      return result;
698  }
699  return NULL;
700}
701
702bool BookmarkModel::IsValidIndex(const BookmarkNode* parent,
703                                 int index,
704                                 bool allow_end) {
705  return (parent && parent->is_folder() &&
706          (index >= 0 && (index < parent->child_count() ||
707                          (allow_end && index == parent->child_count()))));
708}
709
710BookmarkNode* BookmarkModel::CreateBookmarkNode() {
711  history::StarredEntry entry;
712  entry.type = history::StarredEntry::BOOKMARK_BAR;
713  return CreateRootNodeFromStarredEntry(entry);
714}
715
716BookmarkNode* BookmarkModel::CreateOtherBookmarksNode() {
717  history::StarredEntry entry;
718  entry.type = history::StarredEntry::OTHER;
719  return CreateRootNodeFromStarredEntry(entry);
720}
721
722BookmarkNode* BookmarkModel::CreateRootNodeFromStarredEntry(
723    const history::StarredEntry& entry) {
724  DCHECK(entry.type == history::StarredEntry::BOOKMARK_BAR ||
725         entry.type == history::StarredEntry::OTHER);
726  BookmarkNode* node = new BookmarkNode(generate_next_node_id(), GURL());
727  node->Reset(entry);
728  if (entry.type == history::StarredEntry::BOOKMARK_BAR) {
729    node->set_title(l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_FOLDER_NAME));
730  } else {
731    node->set_title(
732        l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OTHER_FOLDER_NAME));
733  }
734  return node;
735}
736
737void BookmarkModel::OnFaviconDataAvailable(
738    FaviconService::Handle handle,
739    history::FaviconData favicon) {
740  SkBitmap favicon_bitmap;
741  BookmarkNode* node =
742      load_consumer_.GetClientData(
743          profile_->GetFaviconService(Profile::EXPLICIT_ACCESS), handle);
744  DCHECK(node);
745  node->set_favicon_load_handle(0);
746  if (favicon.is_valid() && gfx::PNGCodec::Decode(favicon.image_data->front(),
747                                                  favicon.image_data->size(),
748                                                  &favicon_bitmap)) {
749    node->set_favicon(favicon_bitmap);
750    FaviconLoaded(node);
751  }
752}
753
754void BookmarkModel::LoadFavicon(BookmarkNode* node) {
755  if (node->type() != BookmarkNode::URL)
756    return;
757
758  DCHECK(node->GetURL().is_valid());
759  FaviconService* favicon_service =
760      profile_->GetFaviconService(Profile::EXPLICIT_ACCESS);
761  if (!favicon_service)
762    return;
763  FaviconService::Handle handle = favicon_service->GetFaviconForURL(
764      node->GetURL(), history::FAVICON, &load_consumer_,
765      NewCallback(this, &BookmarkModel::OnFaviconDataAvailable));
766  load_consumer_.SetClientData(favicon_service, handle, node);
767  node->set_favicon_load_handle(handle);
768}
769
770void BookmarkModel::CancelPendingFaviconLoadRequests(BookmarkNode* node) {
771  if (node->favicon_load_handle()) {
772    FaviconService* favicon_service =
773        profile_->GetFaviconService(Profile::EXPLICIT_ACCESS);
774    if (favicon_service)
775      favicon_service->CancelRequest(node->favicon_load_handle());
776    node->set_favicon_load_handle(0);
777  }
778}
779
780void BookmarkModel::Observe(NotificationType type,
781                            const NotificationSource& source,
782                            const NotificationDetails& details) {
783  switch (type.value) {
784    case NotificationType::FAVICON_CHANGED: {
785      // Prevent the observers from getting confused for multiple favicon loads.
786      Details<history::FaviconChangeDetails> favicon_details(details);
787      for (std::set<GURL>::const_iterator i = favicon_details->urls.begin();
788           i != favicon_details->urls.end(); ++i) {
789        std::vector<const BookmarkNode*> nodes;
790        GetNodesByURL(*i, &nodes);
791        for (size_t i = 0; i < nodes.size(); ++i) {
792          // Got an updated favicon, for a URL, do a new request.
793          BookmarkNode* node = AsMutable(nodes[i]);
794          node->InvalidateFavicon();
795          CancelPendingFaviconLoadRequests(node);
796          FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
797                            BookmarkNodeChanged(this, node));
798        }
799      }
800      break;
801    }
802
803    default:
804      NOTREACHED();
805      break;
806  }
807}
808
809void BookmarkModel::PopulateNodesByURL(BookmarkNode* node) {
810  // NOTE: this is called with url_lock_ already held. As such, this doesn't
811  // explicitly grab the lock.
812  if (node->is_url())
813    nodes_ordered_by_url_set_.insert(node);
814  for (int i = 0; i < node->child_count(); ++i)
815    PopulateNodesByURL(node->GetChild(i));
816}
817
818int64 BookmarkModel::generate_next_node_id() {
819  return next_node_id_++;
820}
821
822void BookmarkModel::SetFileChanged() {
823  file_changed_ = true;
824}
825
826BookmarkLoadDetails* BookmarkModel::CreateLoadDetails() {
827  BookmarkNode* bb_node = CreateBookmarkNode();
828  BookmarkNode* other_folder_node = CreateOtherBookmarksNode();
829  return new BookmarkLoadDetails(
830      bb_node, other_folder_node, new BookmarkIndex(profile()), next_node_id_);
831}
832