| // Copyright (c) 2011 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_storage.h" |
| |
| #include "base/compiler_specific.h" |
| #include "base/file_util.h" |
| #include "base/file_util_proxy.h" |
| #include "base/metrics/histogram.h" |
| #include "base/time.h" |
| #include "chrome/browser/bookmarks/bookmark_codec.h" |
| #include "chrome/browser/bookmarks/bookmark_model.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/chrome_constants.h" |
| #include "content/browser/browser_thread.h" |
| #include "content/common/json_value_serializer.h" |
| #include "content/common/notification_source.h" |
| #include "content/common/notification_type.h" |
| |
| using base::TimeTicks; |
| |
| namespace { |
| |
| // Extension used for backup files (copy of main file created during startup). |
| const FilePath::CharType kBackupExtension[] = FILE_PATH_LITERAL("bak"); |
| |
| // How often we save. |
| const int kSaveDelayMS = 2500; |
| |
| class BackupTask : public Task { |
| public: |
| explicit BackupTask(const FilePath& path) : path_(path) { |
| } |
| |
| virtual void Run() { |
| FilePath backup_path = path_.ReplaceExtension(kBackupExtension); |
| file_util::CopyFile(path_, backup_path); |
| } |
| |
| private: |
| const FilePath path_; |
| |
| DISALLOW_COPY_AND_ASSIGN(BackupTask); |
| }; |
| |
| } // namespace |
| |
| class BookmarkStorage::LoadTask : public Task { |
| public: |
| LoadTask(const FilePath& path, |
| BookmarkStorage* storage, |
| BookmarkLoadDetails* details) |
| : path_(path), |
| storage_(storage), |
| details_(details) { |
| } |
| |
| virtual void Run() { |
| bool bookmark_file_exists = file_util::PathExists(path_); |
| if (bookmark_file_exists) { |
| JSONFileValueSerializer serializer(path_); |
| scoped_ptr<Value> root(serializer.Deserialize(NULL, NULL)); |
| |
| if (root.get()) { |
| // Building the index can take a while, so we do it on the background |
| // thread. |
| int64 max_node_id = 0; |
| BookmarkCodec codec; |
| TimeTicks start_time = TimeTicks::Now(); |
| codec.Decode(details_->bb_node(), details_->other_folder_node(), |
| &max_node_id, *root.get()); |
| details_->set_max_id(std::max(max_node_id, details_->max_id())); |
| details_->set_computed_checksum(codec.computed_checksum()); |
| details_->set_stored_checksum(codec.stored_checksum()); |
| details_->set_ids_reassigned(codec.ids_reassigned()); |
| UMA_HISTOGRAM_TIMES("Bookmarks.DecodeTime", |
| TimeTicks::Now() - start_time); |
| |
| start_time = TimeTicks::Now(); |
| AddBookmarksToIndex(details_->bb_node()); |
| AddBookmarksToIndex(details_->other_folder_node()); |
| UMA_HISTOGRAM_TIMES("Bookmarks.CreateBookmarkIndexTime", |
| TimeTicks::Now() - start_time); |
| } |
| } |
| |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| NewRunnableMethod( |
| storage_.get(), &BookmarkStorage::OnLoadFinished, |
| bookmark_file_exists, path_)); |
| } |
| |
| private: |
| // Adds node to the model's index, recursing through all children as well. |
| void AddBookmarksToIndex(BookmarkNode* node) { |
| if (node->is_url()) { |
| if (node->GetURL().is_valid()) |
| details_->index()->Add(node); |
| } else { |
| for (int i = 0; i < node->child_count(); ++i) |
| AddBookmarksToIndex(node->GetChild(i)); |
| } |
| } |
| |
| const FilePath path_; |
| scoped_refptr<BookmarkStorage> storage_; |
| BookmarkLoadDetails* details_; |
| |
| DISALLOW_COPY_AND_ASSIGN(LoadTask); |
| }; |
| |
| // BookmarkLoadDetails --------------------------------------------------------- |
| |
| BookmarkLoadDetails::BookmarkLoadDetails(BookmarkNode* bb_node, |
| BookmarkNode* other_folder_node, |
| BookmarkIndex* index, |
| int64 max_id) |
| : bb_node_(bb_node), |
| other_folder_node_(other_folder_node), |
| index_(index), |
| max_id_(max_id), |
| ids_reassigned_(false) { |
| } |
| |
| BookmarkLoadDetails::~BookmarkLoadDetails() { |
| } |
| |
| // BookmarkStorage ------------------------------------------------------------- |
| |
| BookmarkStorage::BookmarkStorage(Profile* profile, BookmarkModel* model) |
| : profile_(profile), |
| model_(model), |
| writer_(profile->GetPath().Append(chrome::kBookmarksFileName), |
| BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE)), |
| tmp_history_path_( |
| profile->GetPath().Append(chrome::kHistoryBookmarksFileName)) { |
| writer_.set_commit_interval(base::TimeDelta::FromMilliseconds(kSaveDelayMS)); |
| BrowserThread::PostTask( |
| BrowserThread::FILE, FROM_HERE, new BackupTask(writer_.path())); |
| } |
| |
| BookmarkStorage::~BookmarkStorage() { |
| if (writer_.HasPendingWrite()) |
| writer_.DoScheduledWrite(); |
| } |
| |
| void BookmarkStorage::LoadBookmarks(BookmarkLoadDetails* details) { |
| DCHECK(!details_.get()); |
| DCHECK(details); |
| details_.reset(details); |
| DoLoadBookmarks(writer_.path()); |
| } |
| |
| void BookmarkStorage::DoLoadBookmarks(const FilePath& path) { |
| BrowserThread::PostTask( |
| BrowserThread::FILE, FROM_HERE, new LoadTask(path, this, details_.get())); |
| } |
| |
| void BookmarkStorage::MigrateFromHistory() { |
| // We need to wait until history has finished loading before reading |
| // from generated bookmarks file. |
| HistoryService* history = |
| profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); |
| if (!history) { |
| // This happens in unit tests. |
| if (model_) |
| model_->DoneLoading(details_.release()); |
| return; |
| } |
| if (!history->BackendLoaded()) { |
| // The backend isn't finished loading. Wait for it. |
| notification_registrar_.Add(this, NotificationType::HISTORY_LOADED, |
| Source<Profile>(profile_)); |
| } else { |
| DoLoadBookmarks(tmp_history_path_); |
| } |
| } |
| |
| void BookmarkStorage::OnHistoryFinishedWriting() { |
| notification_registrar_.Remove(this, NotificationType::HISTORY_LOADED, |
| Source<Profile>(profile_)); |
| |
| // This is used when migrating bookmarks data from database to file. |
| // History wrote the file for us, and now we want to load data from it. |
| DoLoadBookmarks(tmp_history_path_); |
| } |
| |
| void BookmarkStorage::ScheduleSave() { |
| writer_.ScheduleWrite(this); |
| } |
| |
| void BookmarkStorage::BookmarkModelDeleted() { |
| // We need to save now as otherwise by the time SaveNow is invoked |
| // the model is gone. |
| if (writer_.HasPendingWrite()) |
| SaveNow(); |
| model_ = NULL; |
| } |
| |
| bool BookmarkStorage::SerializeData(std::string* output) { |
| BookmarkCodec codec; |
| scoped_ptr<Value> value(codec.Encode(model_)); |
| JSONStringValueSerializer serializer(output); |
| serializer.set_pretty_print(true); |
| return serializer.Serialize(*(value.get())); |
| } |
| |
| void BookmarkStorage::OnLoadFinished(bool file_exists, const FilePath& path) { |
| if (path == writer_.path() && !file_exists) { |
| // 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, it will write the |
| // bookmarks to a file for us. |
| MigrateFromHistory(); |
| return; |
| } |
| |
| if (!model_) |
| return; |
| |
| model_->DoneLoading(details_.release()); |
| |
| if (path == tmp_history_path_) { |
| // We just finished migration from history. Save now to new file, |
| // after the model is created and done loading. |
| SaveNow(); |
| |
| // Clean up after migration from history. |
| base::FileUtilProxy::Delete( |
| BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE), |
| tmp_history_path_, |
| false, |
| NULL); |
| } |
| } |
| |
| void BookmarkStorage::Observe(NotificationType type, |
| const NotificationSource& source, |
| const NotificationDetails& details) { |
| switch (type.value) { |
| case NotificationType::HISTORY_LOADED: |
| OnHistoryFinishedWriting(); |
| break; |
| |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| bool BookmarkStorage::SaveNow() { |
| if (!model_ || !model_->IsLoaded()) { |
| // We should only get here if we have a valid model and it's finished |
| // loading. |
| NOTREACHED(); |
| return false; |
| } |
| |
| std::string data; |
| if (!SerializeData(&data)) |
| return false; |
| writer_.WriteNow(data); |
| return true; |
| } |