diff options
Diffstat (limited to 'chrome/browser/bookmarks/bookmark_bar_model.cc')
-rw-r--r-- | chrome/browser/bookmarks/bookmark_bar_model.cc | 694 |
1 files changed, 694 insertions, 0 deletions
diff --git a/chrome/browser/bookmarks/bookmark_bar_model.cc b/chrome/browser/bookmarks/bookmark_bar_model.cc new file mode 100644 index 0000000..df35fe5 --- /dev/null +++ b/chrome/browser/bookmarks/bookmark_bar_model.cc @@ -0,0 +1,694 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/bookmarks/bookmark_bar_model.h" + +#include "base/gfx/png_decoder.h" +#include "chrome/browser/bookmarks/bookmark_storage.h" +#include "chrome/browser/history/query_parser.h" +#include "chrome/browser/profile.h" +#include "chrome/common/scoped_vector.h" + +#include "generated_resources.h" + +namespace { + +// Functions used for sorting. +bool MoreRecentlyModified(BookmarkBarNode* n1, BookmarkBarNode* n2) { + return n1->date_group_modified() > n2->date_group_modified(); +} + +bool MoreRecentlyAdded(BookmarkBarNode* n1, BookmarkBarNode* n2) { + return n1->date_added() > n2->date_added(); +} + +} // namespace + +// BookmarkBarNode ------------------------------------------------------------ + +namespace { + +// ID for BookmarkBarNodes. +// Various places assume an invalid id if == 0, for that reason we start with 1. +int next_id_ = 1; + +} + +const SkBitmap& BookmarkBarNode::GetFavIcon() { + if (!loaded_favicon_) { + loaded_favicon_ = true; + model_->LoadFavIcon(this); + } + return favicon_; +} + +BookmarkBarNode::BookmarkBarNode(BookmarkBarModel* model, const GURL& url) + : model_(model), + id_(next_id_++), + loaded_favicon_(false), + favicon_load_handle_(0), + url_(url), + type_(!url.is_empty() ? history::StarredEntry::URL : + history::StarredEntry::BOOKMARK_BAR), + date_added_(Time::Now()) { +} + +void BookmarkBarNode::Reset(const history::StarredEntry& entry) { + DCHECK(entry.type != history::StarredEntry::URL || + entry.url == url_); + + favicon_ = SkBitmap(); + type_ = entry.type; + date_added_ = entry.date_added; + date_group_modified_ = entry.date_group_modified; + SetTitle(entry.title); +} + +// BookmarkBarModel ----------------------------------------------------------- + +BookmarkBarModel::BookmarkBarModel(Profile* profile) + : profile_(profile), + loaded_(false), +#pragma warning(suppress: 4355) // Okay to pass "this" here. + root_(this, GURL()), + bookmark_bar_node_(NULL), + other_node_(NULL), + waiting_for_history_load_(false), + loaded_signal_(CreateEvent(NULL, TRUE, FALSE, NULL)) { + // Create the bookmark bar and other bookmarks folders. These always exist. + CreateBookmarkBarNode(); + CreateOtherBookmarksNode(); + + // And add them to the root. + // + // WARNING: order is important here, various places assume bookmark bar then + // other node. + root_.Add(0, bookmark_bar_node_); + root_.Add(1, other_node_); + + if (!profile_) { + // Profile is null during testing. + DoneLoading(); + } +} + +BookmarkBarModel::~BookmarkBarModel() { + if (profile_ && store_.get()) { + NotificationService::current()->RemoveObserver( + this, NOTIFY_FAVICON_CHANGED, Source<Profile>(profile_)); + } + + if (waiting_for_history_load_) { + NotificationService::current()->RemoveObserver( + this, NOTIFY_HISTORY_LOADED, Source<Profile>(profile_)); + } + + FOR_EACH_OBSERVER(BookmarkBarModelObserver, observers_, + BookmarkModelBeingDeleted(this)); + + if (store_) { + // The store maintains a reference back to us. We need to tell it we're gone + // so that it doesn't try and invoke a method back on us again. + store_->BookmarkModelDeleted(); + } +} + +void BookmarkBarModel::Load() { + if (store_.get()) { + // If the store is non-null, it means Load was already invoked. Load should + // only be invoked once. + NOTREACHED(); + return; + } + + // Listen for changes to favicons so that we can update the favicon of the + // node appropriately. + NotificationService::current()->AddObserver( + this, NOTIFY_FAVICON_CHANGED, Source<Profile>(profile_)); + + // Load the bookmarks. BookmarkStorage notifies us when done. + store_ = new BookmarkStorage(profile_, this); + store_->LoadBookmarks(false); +} + +BookmarkBarNode* BookmarkBarModel::GetParentForNewNodes() { + std::vector<BookmarkBarNode*> nodes; + + GetMostRecentlyModifiedGroupNodes(&root_, 1, &nodes); + return nodes.empty() ? bookmark_bar_node_ : nodes[0]; +} + +std::vector<BookmarkBarNode*> BookmarkBarModel::GetMostRecentlyModifiedGroups( + size_t max_count) { + std::vector<BookmarkBarNode*> nodes; + GetMostRecentlyModifiedGroupNodes(&root_, max_count, &nodes); + + if (nodes.size() < max_count) { + // Add the bookmark bar and other nodes if there is space. + if (find(nodes.begin(), nodes.end(), bookmark_bar_node_) == nodes.end()) + nodes.push_back(bookmark_bar_node_); + + if (nodes.size() < max_count && + find(nodes.begin(), nodes.end(), other_node_) == nodes.end()) { + nodes.push_back(other_node_); + } + } + return nodes; +} + +void BookmarkBarModel::GetMostRecentlyAddedEntries( + size_t count, + std::vector<BookmarkBarNode*>* nodes) { + AutoLock url_lock(url_lock_); + for (NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.begin(); + i != nodes_ordered_by_url_set_.end(); ++i) { + std::vector<BookmarkBarNode*>::iterator insert_position = + std::upper_bound(nodes->begin(), nodes->end(), *i, &MoreRecentlyAdded); + if (nodes->size() < count || insert_position != nodes->end()) { + nodes->insert(insert_position, *i); + while (nodes->size() > count) + nodes->pop_back(); + } + } +} + +void BookmarkBarModel::GetBookmarksMatchingText( + const std::wstring& text, + size_t max_count, + std::vector<TitleMatch>* matches) { + QueryParser parser; + ScopedVector<QueryNode> query_nodes; + parser.ParseQuery(text, &query_nodes.get()); + if (query_nodes.empty()) + return; + + AutoLock url_lock(url_lock_); + Snippet::MatchPositions match_position; + for (NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.begin(); + i != nodes_ordered_by_url_set_.end(); ++i) { + if (parser.DoesQueryMatch((*i)->GetTitle(), query_nodes.get(), + &match_position)) { + matches->push_back(TitleMatch()); + matches->back().node = *i; + matches->back().match_positions.swap(match_position); + if (matches->size() == max_count) + break; + } + } +} + +void BookmarkBarModel::Remove(BookmarkBarNode* parent, int index) { + if (!loaded_ || !IsValidIndex(parent, index, false) || parent == &root_) { + NOTREACHED(); + return; + } + RemoveAndDeleteNode(parent->GetChild(index)); +} + +void BookmarkBarModel::Move(BookmarkBarNode* node, + BookmarkBarNode* new_parent, + int index) { + if (!loaded_ || !node || !IsValidIndex(new_parent, index, true) || + new_parent == &root_ || node == &root_ || node == bookmark_bar_node_ || + node == other_node_) { + NOTREACHED(); + return; + } + + if (new_parent->HasAncestor(node)) { + // Can't make an ancestor of the node be a child of the node. + NOTREACHED(); + return; + } + + SetDateGroupModified(new_parent, Time::Now()); + + BookmarkBarNode* old_parent = node->GetParent(); + int old_index = old_parent->IndexOfChild(node); + + if (old_parent == new_parent && + (index == old_index || index == old_index + 1)) { + // Node is already in this position, nothing to do. + return; + } + + if (old_parent == new_parent && index > old_index) + index--; + new_parent->Add(index, node); + + if (store_.get()) + store_->ScheduleSave(); + + FOR_EACH_OBSERVER(BookmarkBarModelObserver, observers_, + BookmarkNodeMoved(this, old_parent, old_index, + new_parent, index)); +} + +void BookmarkBarModel::SetTitle(BookmarkBarNode* node, + const std::wstring& title) { + if (!node) { + NOTREACHED(); + return; + } + if (node->GetTitle() == title) + return; + + node->SetTitle(title); + + if (store_.get()) + store_->ScheduleSave(); + + FOR_EACH_OBSERVER(BookmarkBarModelObserver, observers_, + BookmarkNodeChanged(this, node)); +} + +BookmarkBarNode* BookmarkBarModel::GetNodeByURL(const GURL& url) { + AutoLock url_lock(url_lock_); + BookmarkBarNode tmp_node(this, url); + NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(&tmp_node); + return (i != nodes_ordered_by_url_set_.end()) ? *i : NULL; +} + +void BookmarkBarModel::GetBookmarks(std::vector<GURL>* urls) { + AutoLock url_lock(url_lock_); + for (NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.begin(); + i != nodes_ordered_by_url_set_.end(); ++i) { + urls->push_back((*i)->GetURL()); + } +} + +BookmarkBarNode* BookmarkBarModel::GetNodeByID(int id) { + // TODO(sky): TreeNode needs a method that visits all nodes using a predicate. + return GetNodeByID(&root_, id); +} + +BookmarkBarNode* BookmarkBarModel::AddGroup( + BookmarkBarNode* parent, + int index, + const std::wstring& title) { + if (!loaded_ || parent == &root_ || !IsValidIndex(parent, index, true)) { + // Can't add to the root. + NOTREACHED(); + return NULL; + } + + BookmarkBarNode* new_node = new BookmarkBarNode(this, GURL()); + new_node->SetTitle(title); + new_node->type_ = history::StarredEntry::USER_GROUP; + + return AddNode(parent, index, new_node); +} + +BookmarkBarNode* BookmarkBarModel::AddURL(BookmarkBarNode* parent, + int index, + const std::wstring& title, + const GURL& url) { + return AddURLWithCreationTime(parent, index, title, url, Time::Now()); +} + +BookmarkBarNode* BookmarkBarModel::AddURLWithCreationTime( + BookmarkBarNode* parent, + int index, + const std::wstring& title, + const GURL& url, + const Time& creation_time) { + if (!loaded_ || !url.is_valid() || parent == &root_ || + !IsValidIndex(parent, index, true)) { + NOTREACHED(); + return NULL; + } + + BookmarkBarNode* existing_node = GetNodeByURL(url); + if (existing_node) { + Move(existing_node, parent, index); + SetTitle(existing_node, title); + return existing_node; + } + + SetDateGroupModified(parent, creation_time); + + BookmarkBarNode* new_node = new BookmarkBarNode(this, url); + new_node->SetTitle(title); + new_node->date_added_ = creation_time; + new_node->type_ = history::StarredEntry::URL; + + AutoLock url_lock(url_lock_); + nodes_ordered_by_url_set_.insert(new_node); + + return AddNode(parent, index, new_node); +} + +void BookmarkBarModel::SetURLStarred(const GURL& url, + const std::wstring& title, + bool is_starred) { + BookmarkBarNode* node = GetNodeByURL(url); + if (is_starred && !node) { + // Add the url. + BookmarkBarNode* parent = GetParentForNewNodes(); + AddURL(parent, parent->GetChildCount(), title, url); + } else if (!is_starred && node) { + Remove(node->GetParent(), node->GetParent()->IndexOfChild(node)); + } +} + +void BookmarkBarModel::ResetDateGroupModified(BookmarkBarNode* node) { + SetDateGroupModified(node, Time()); +} + +void BookmarkBarModel::FavIconLoaded(BookmarkBarNode* node) { + // Send out notification to the observer. + FOR_EACH_OBSERVER(BookmarkBarModelObserver, observers_, + BookmarkNodeFavIconLoaded(this, node)); +} + +void BookmarkBarModel::RemoveNode(BookmarkBarNode* node, + std::set<GURL>* removed_urls) { + if (!loaded_ || !node || node == &root_ || node == bookmark_bar_node_ || + node == other_node_) { + NOTREACHED(); + return; + } + + if (node->GetType() == history::StarredEntry::URL) { + // NOTE: this is called in such a way that url_lock_ is already held. As + // such, this doesn't explicitly grab the lock. + NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(node); + DCHECK(i != nodes_ordered_by_url_set_.end()); + nodes_ordered_by_url_set_.erase(i); + removed_urls->insert(node->GetURL()); + } + + CancelPendingFavIconLoadRequests(node); + + // Recurse through children. + for (int i = node->GetChildCount() - 1; i >= 0; --i) + RemoveNode(node->GetChild(i), removed_urls); +} + +void BookmarkBarModel::OnBookmarkStorageLoadedBookmarks( + bool file_exists, + bool loaded_from_history) { + if (loaded_) { + NOTREACHED(); + return; + } + + if (file_exists || loaded_from_history || !profile_ || + !profile_->GetHistoryService(Profile::EXPLICIT_ACCESS)) { + // The file exists, we're loaded. + DoneLoading(); + + if (loaded_from_history) { + // We were just populated from the historical file. Schedule a save so + // that the main file is up to date. + store_->ScheduleSave(); + } + return; + } + + // The file doesn't exist. This means one of two things: + // 1. A clean profile. + // 2. The user is migrating from an older version where bookmarks were saved + // in history. + // We assume step 2. If history had the bookmarks, history will write the + // bookmarks to a file for us. We need to wait until history has finished + // loading before reading from that file. + HistoryService* history = + profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (!history->backend_loaded()) { + // The backend isn't finished loading. Wait for it. + waiting_for_history_load_ = true; + NotificationService::current()->AddObserver( + this, NOTIFY_HISTORY_LOADED, Source<Profile>(profile_)); + } else { + OnHistoryDone(); + } +} + +void BookmarkBarModel::OnHistoryDone() { + if (loaded_) { + NOTREACHED(); + return; + } + + // If the bookmarks were stored in the db the db will have migrated them to + // a file now. Try loading from the file. + store_->LoadBookmarks(true); +} + +void BookmarkBarModel::DoneLoading() { + { + AutoLock url_lock(url_lock_); + // Update nodes_ordered_by_url_set_ from the nodes. + PopulateNodesByURL(&root_); + } + + loaded_ = true; + + if (loaded_signal_.Get()) + SetEvent(loaded_signal_.Get()); + + + // Notify our direct observers. + FOR_EACH_OBSERVER(BookmarkBarModelObserver, observers_, Loaded(this)); + + // And generic notification. + NotificationService::current()->Notify( + NOTIFY_BOOKMARK_MODEL_LOADED, + Source<Profile>(profile_), + NotificationService::NoDetails()); +} + +void BookmarkBarModel::RemoveAndDeleteNode(BookmarkBarNode* delete_me) { + scoped_ptr<BookmarkBarNode> node(delete_me); + + BookmarkBarNode* parent = node->GetParent(); + DCHECK(parent); + int index = parent->IndexOfChild(node.get()); + parent->Remove(index); + history::URLsStarredDetails details(false); + { + AutoLock url_lock(url_lock_); + RemoveNode(node.get(), &details.changed_urls); + } + + if (store_.get()) + store_->ScheduleSave(); + + FOR_EACH_OBSERVER(BookmarkBarModelObserver, observers_, + BookmarkNodeRemoved(this, parent, index)); + + if (profile_) { + HistoryService* history = + profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (history) + history->URLsNoLongerBookmarked(details.changed_urls); + } + + NotificationService::current()->Notify(NOTIFY_URLS_STARRED, + Source<Profile>(profile_), + Details<history::URLsStarredDetails>(&details)); +} + +BookmarkBarNode* BookmarkBarModel::AddNode(BookmarkBarNode* parent, + int index, + BookmarkBarNode* node) { + parent->Add(index, node); + + if (store_.get()) + store_->ScheduleSave(); + + FOR_EACH_OBSERVER(BookmarkBarModelObserver, observers_, + BookmarkNodeAdded(this, parent, index)); + + if (node->GetType() == history::StarredEntry::URL) { + history::URLsStarredDetails details(true); + details.changed_urls.insert(node->GetURL()); + NotificationService::current()->Notify(NOTIFY_URLS_STARRED, + Source<Profile>(profile_), + Details<history::URLsStarredDetails>(&details)); + } + return node; +} + +void BookmarkBarModel::BlockTillLoaded() { + if (loaded_signal_.Get()) + WaitForSingleObject(loaded_signal_.Get(), INFINITE); +} + +BookmarkBarNode* BookmarkBarModel::GetNodeByID(BookmarkBarNode* node, + int id) { + if (node->id() == id) + return node; + + for (int i = 0; i < node->GetChildCount(); ++i) { + BookmarkBarNode* result = GetNodeByID(node->GetChild(i), id); + if (result) + return result; + } + return NULL; +} + +bool BookmarkBarModel::IsValidIndex(BookmarkBarNode* parent, + int index, + bool allow_end) { + return (parent && + (index >= 0 && (index < parent->GetChildCount() || + (allow_end && index == parent->GetChildCount())))); + } + +void BookmarkBarModel::SetDateGroupModified(BookmarkBarNode* parent, + const Time time) { + DCHECK(parent); + parent->date_group_modified_ = time; + + if (store_.get()) + store_->ScheduleSave(); +} + +void BookmarkBarModel::CreateBookmarkBarNode() { + history::StarredEntry entry; + entry.type = history::StarredEntry::BOOKMARK_BAR; + bookmark_bar_node_ = CreateRootNodeFromStarredEntry(entry); +} + +void BookmarkBarModel::CreateOtherBookmarksNode() { + history::StarredEntry entry; + entry.type = history::StarredEntry::OTHER; + other_node_ = CreateRootNodeFromStarredEntry(entry); +} + +BookmarkBarNode* BookmarkBarModel::CreateRootNodeFromStarredEntry( + const history::StarredEntry& entry) { + DCHECK(entry.type == history::StarredEntry::BOOKMARK_BAR || + entry.type == history::StarredEntry::OTHER); + BookmarkBarNode* node = new BookmarkBarNode(this, GURL()); + node->Reset(entry); + if (entry.type == history::StarredEntry::BOOKMARK_BAR) + node->SetTitle(l10n_util::GetString(IDS_BOOMARK_BAR_FOLDER_NAME)); + else + node->SetTitle(l10n_util::GetString(IDS_BOOMARK_BAR_OTHER_FOLDER_NAME)); + return node; +} + +void BookmarkBarModel::OnFavIconDataAvailable( + HistoryService::Handle handle, + bool know_favicon, + scoped_refptr<RefCountedBytes> data, + bool expired, + GURL icon_url) { + SkBitmap fav_icon; + BookmarkBarNode* node = + load_consumer_.GetClientData( + profile_->GetHistoryService(Profile::EXPLICIT_ACCESS), handle); + DCHECK(node); + node->favicon_load_handle_ = 0; + if (know_favicon && data.get() && + PNGDecoder::Decode(&data->data, &fav_icon)) { + node->favicon_ = fav_icon; + FavIconLoaded(node); + } +} + +void BookmarkBarModel::LoadFavIcon(BookmarkBarNode* node) { + if (node->GetType() != history::StarredEntry::URL) + return; + + DCHECK(node->GetURL().is_valid()); + HistoryService* history_service = + profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (!history_service) + return; + + HistoryService::Handle handle = history_service->GetFavIconForURL( + node->GetURL(), &load_consumer_, + NewCallback(this, &BookmarkBarModel::OnFavIconDataAvailable)); + load_consumer_.SetClientData(history_service, handle, node); + node->favicon_load_handle_ = handle; +} + +void BookmarkBarModel::CancelPendingFavIconLoadRequests(BookmarkBarNode* node) { + if (node->favicon_load_handle_) { + HistoryService* history = + profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (history) + history->CancelRequest(node->favicon_load_handle_); + node->favicon_load_handle_ = 0; + } +} + +void BookmarkBarModel::GetMostRecentlyModifiedGroupNodes( + BookmarkBarNode* parent, + size_t count, + std::vector<BookmarkBarNode*>* nodes) { + if (parent != &root_ && parent->is_folder() && + parent->date_group_modified() > Time()) { + if (count == 0) { + nodes->push_back(parent); + } else { + std::vector<BookmarkBarNode*>::iterator i = + std::upper_bound(nodes->begin(), nodes->end(), parent, + &MoreRecentlyModified); + if (nodes->size() < count || i != nodes->end()) { + nodes->insert(i, parent); + while (nodes->size() > count) + nodes->pop_back(); + } + } + } // else case, the root node, which we don't care about or imported nodes + // (which have a time of 0). + for (int i = 0; i < parent->GetChildCount(); ++i) { + BookmarkBarNode* child = parent->GetChild(i); + if (child->is_folder()) + GetMostRecentlyModifiedGroupNodes(child, count, nodes); + } +} + +void BookmarkBarModel::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + switch (type) { + case NOTIFY_FAVICON_CHANGED: { + // Prevent the observers from getting confused for multiple favicon loads. + Details<history::FavIconChangeDetails> favicon_details(details); + for (std::set<GURL>::const_iterator i = favicon_details->urls.begin(); + i != favicon_details->urls.end(); ++i) { + BookmarkBarNode* node = GetNodeByURL(*i); + if (node) { + // Got an updated favicon, for a URL, do a new request. + node->InvalidateFavicon(); + CancelPendingFavIconLoadRequests(node); + FOR_EACH_OBSERVER(BookmarkBarModelObserver, observers_, + BookmarkNodeChanged(this, node)); + } + } + break; + } + + case NOTIFY_HISTORY_LOADED: { + if (waiting_for_history_load_) { + waiting_for_history_load_ = false; + NotificationService::current()->RemoveObserver( + this, NOTIFY_HISTORY_LOADED, Source<Profile>(profile_)); + OnHistoryDone(); + } else { + NOTREACHED(); + } + break; + } + + default: + NOTREACHED(); + break; + } +} + +void BookmarkBarModel::PopulateNodesByURL(BookmarkBarNode* node) { + // NOTE: this is called with url_lock_ already held. As such, this doesn't + // explicitly grab the lock. + if (node->is_url()) + nodes_ordered_by_url_set_.insert(node); + for (int i = 0; i < node->GetChildCount(); ++i) + PopulateNodesByURL(node->GetChild(i)); +} |