diff options
author | phajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-08 14:41:08 +0000 |
---|---|---|
committer | phajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-08 14:41:08 +0000 |
commit | 6c116404c0e4ebcbfea94ff91e0979bee67222e4 (patch) | |
tree | c749a0f0f2f538ed7651da2f0cda4a5667cd8fcc /chrome/browser | |
parent | 6f45d5f57decad87b06a63239d77657ed458e361 (diff) | |
download | chromium_src-6c116404c0e4ebcbfea94ff91e0979bee67222e4.zip chromium_src-6c116404c0e4ebcbfea94ff91e0979bee67222e4.tar.gz chromium_src-6c116404c0e4ebcbfea94ff91e0979bee67222e4.tar.bz2 |
Converted BookmarkStorage to use ImportantFileWriter
Also made BookmarkStorage completely responsible for
migration of bookmark data from history database.
Previously the logic crossed file and class boundaries a few
times. This made it a bit hard to follow. Now it should be
a bit more clear.
Made ImportantFileWriter also batch data serializations.
TEST=Make sure that bookmarks still work. Also test migrating bookmarks from history database.
http://crbug.com/10618
Review URL: http://codereview.chromium.org/99192
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15635 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r-- | chrome/browser/bookmarks/bookmark_model.cc | 80 | ||||
-rw-r--r-- | chrome/browser/bookmarks/bookmark_model.h | 23 | ||||
-rw-r--r-- | chrome/browser/bookmarks/bookmark_storage.cc | 300 | ||||
-rw-r--r-- | chrome/browser/bookmarks/bookmark_storage.h | 103 |
4 files changed, 242 insertions, 264 deletions
diff --git a/chrome/browser/bookmarks/bookmark_model.cc b/chrome/browser/bookmarks/bookmark_model.cc index e944419..646aa3f 100644 --- a/chrome/browser/bookmarks/bookmark_model.cc +++ b/chrome/browser/bookmarks/bookmark_model.cc @@ -109,7 +109,6 @@ BookmarkModel::BookmarkModel(Profile* profile) bookmark_bar_node_(NULL), other_node_(NULL), observers_(ObserverList<BookmarkModelObserver>::NOTIFY_EXISTING_ONLY), - waiting_for_history_load_(false), loaded_signal_(TRUE, FALSE) { // Create the bookmark bar and other bookmarks folders. These always exist. @@ -135,11 +134,6 @@ BookmarkModel::~BookmarkModel() { this, NotificationType::FAVICON_CHANGED, Source<Profile>(profile_)); } - if (waiting_for_history_load_) { - NotificationService::current()->RemoveObserver( - this, NotificationType::HISTORY_LOADED, Source<Profile>(profile_)); - } - FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, BookmarkModelBeingDeleted(this)); @@ -167,7 +161,7 @@ void BookmarkModel::Load() { // Load the bookmarks. BookmarkStorage notifies us when done. store_ = new BookmarkStorage(profile_, this); - store_->LoadBookmarks(false); + store_->LoadBookmarks(); } BookmarkNode* BookmarkModel::GetParentForNewNodes() { @@ -437,65 +431,9 @@ void BookmarkModel::RemoveNode(BookmarkNode* node, RemoveNode(node->GetChild(i), removed_urls); } -void BookmarkModel::OnBookmarkStorageLoadedBookmarks( - bool file_exists, - bool loaded_from_history) { - if (loaded_) { - NOTREACHED(); - return; - } - - LOG(INFO) << "Loaded bookmarks, file_exists=" << file_exists << - " from_history=" << loaded_from_history; - - 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. - LOG(INFO) << " waiting for history to finish"; - - waiting_for_history_load_ = true; - NotificationService::current()->AddObserver( - this, NotificationType::HISTORY_LOADED, Source<Profile>(profile_)); - } else { - OnHistoryDone(); - } -} - -void BookmarkModel::OnHistoryDone() { - if (loaded_) { - NOTREACHED(); - return; - } - - LOG(INFO) << " history done, reloading"; - - // 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 BookmarkModel::DoneLoading() { + DCHECK(!loaded_); + { AutoLock url_lock(url_lock_); // Update nodes_ordered_by_url_set_ from the nodes. @@ -718,18 +656,6 @@ void BookmarkModel::Observe(NotificationType type, break; } - case NotificationType::HISTORY_LOADED: { - if (waiting_for_history_load_) { - waiting_for_history_load_ = false; - NotificationService::current()->RemoveObserver( - this,NotificationType::HISTORY_LOADED, Source<Profile>(profile_)); - OnHistoryDone(); - } else { - NOTREACHED(); - } - break; - } - default: NOTREACHED(); break; diff --git a/chrome/browser/bookmarks/bookmark_model.h b/chrome/browser/bookmarks/bookmark_model.h index e406e27..4b987d9 100644 --- a/chrome/browser/bookmarks/bookmark_model.h +++ b/chrome/browser/bookmarks/bookmark_model.h @@ -339,24 +339,6 @@ class BookmarkModel : public NotificationObserver, public BookmarkService { // This does NOT delete the node. void RemoveNode(BookmarkNode* node, std::set<GURL>* removed_urls); - // Callback from BookmarkStorage that it has finished loading. This method - // may be hit twice. In particular, on construction BookmarkModel asks - // BookmarkStorage to load the bookmarks. BookmarkStorage invokes this method - // with loaded_from_history false and file_exists indicating whether the - // bookmarks file exists. If the file doesn't exist, we query history. When - // history calls us back (OnHistoryDone) we then ask BookmarkStorage to load - // from the migration file. BookmarkStorage again invokes this method, but - // with |loaded_from_history| true. - void OnBookmarkStorageLoadedBookmarks(bool file_exists, - bool loaded_from_history); - - // Used for migrating bookmarks from history to standalone file. - // - // Callback from history that it is done with an empty request. This is used - // if there is no bookmarks file. Once done, we attempt to load from the - // temporary file creating during migration. - void OnHistoryDone(); - // Invoked when loading is finished. Sets loaded_ and notifies observers. void DoneLoading(); @@ -444,11 +426,6 @@ class BookmarkModel : public NotificationObserver, public BookmarkService { // Reads/writes bookmarks to disk. scoped_refptr<BookmarkStorage> store_; - // Have we installed a listener on the NotificationService for - // NOTIFY_HISTORY_LOADED? A listener is installed if the bookmarks file - // doesn't exist and the history service hasn't finished loading. - bool waiting_for_history_load_; - base::WaitableEvent loaded_signal_; DISALLOW_COPY_AND_ASSIGN(BookmarkModel); diff --git a/chrome/browser/bookmarks/bookmark_storage.cc b/chrome/browser/bookmarks/bookmark_storage.cc index e529b43..7df18cd 100644 --- a/chrome/browser/bookmarks/bookmark_storage.cc +++ b/chrome/browser/bookmarks/bookmark_storage.cc @@ -9,172 +9,240 @@ #include "base/json_writer.h" #include "base/message_loop.h" #include "base/thread.h" +#include "base/time.h" #include "chrome/browser/bookmarks/bookmark_codec.h" #include "chrome/browser/bookmarks/bookmark_model.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/profile.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/json_value_serializer.h" +#include "chrome/common/notification_source.h" +#include "chrome/common/notification_type.h" namespace { // Extension used for backup files (copy of main file created during startup). const FilePath::CharType kBackupExtension[] = FILE_PATH_LITERAL("bak"); -// Extension for the temporary file. We write to the temp file than move to -// kBookmarksFileName. -const FilePath::CharType kTmpExtension[] = FILE_PATH_LITERAL("tmp"); - // 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); +}; + +class FileDeleteTask : public Task { + public: + explicit FileDeleteTask(const FilePath& path) : path_(path) { + } + + virtual void Run() { + file_util::Delete(path_, true); + } + + private: + const FilePath path_; + + DISALLOW_COPY_AND_ASSIGN(FileDeleteTask); +}; + } // namespace +class BookmarkStorage::LoadTask : public Task { + public: + LoadTask(const FilePath& path, MessageLoop* loop, + BookmarkStorage* storage) + : path_(path), + loop_(loop), + storage_(storage) { + } + + virtual void Run() { + bool bookmark_file_exists = file_util::PathExists(path_); + Value* root = NULL; + if (bookmark_file_exists) { + JSONFileValueSerializer serializer(path_); + root = serializer.Deserialize(NULL); + } + + // BookmarkStorage takes ownership of root. + if (loop_) { + loop_->PostTask(FROM_HERE, NewRunnableMethod( + storage_.get(), &BookmarkStorage::OnLoadFinished, + bookmark_file_exists, path_, root)); + } else { + storage_->OnLoadFinished(bookmark_file_exists, path_, root); + } + } + + private: + const FilePath path_; + MessageLoop* loop_; + scoped_refptr<BookmarkStorage> storage_; + + DISALLOW_COPY_AND_ASSIGN(LoadTask); +}; + // BookmarkStorage ------------------------------------------------------------- BookmarkStorage::BookmarkStorage(Profile* profile, BookmarkModel* model) - : model_(model), - ALLOW_THIS_IN_INITIALIZER_LIST(save_factory_(this)), - backend_thread_(g_browser_process->file_thread()) { - FilePath path = profile->GetPath().Append(chrome::kBookmarksFileName); - FilePath tmp_history_path = - profile->GetPath().Append(chrome::kHistoryBookmarksFileName); - backend_ = new BookmarkStorageBackend(path, tmp_history_path); + : profile_(profile), + model_(model), + backend_thread_(g_browser_process->file_thread()), + writer_(profile->GetPath().Append(chrome::kBookmarksFileName), + backend_thread_), + tmp_history_path_( + profile->GetPath().Append(chrome::kHistoryBookmarksFileName)) { + writer_.set_commit_interval(base::TimeDelta::FromMilliseconds(kSaveDelayMS)); + RunTaskOnBackendThread(new BackupTask(writer_.path())); +} + +BookmarkStorage::~BookmarkStorage() { + if (writer_.HasPendingWrite()) + writer_.DoScheduledWrite(); +} + +void BookmarkStorage::LoadBookmarks() { + DoLoadBookmarks(writer_.path()); +} + +void BookmarkStorage::DoLoadBookmarks(const FilePath& path) { + Task* task = new LoadTask(path, + backend_thread() ? MessageLoop::current() : NULL, + this); + RunTaskOnBackendThread(task); } -void BookmarkStorage::LoadBookmarks(bool load_from_history) { - if (!backend_thread()) { - backend_->Read(scoped_refptr<BookmarkStorage>(this), NULL, - load_from_history); +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(); + return; + } + if (!history->backend_loaded()) { + // The backend isn't finished loading. Wait for it. + notification_registrar_.Add(this, NotificationType::HISTORY_LOADED, + Source<Profile>(profile_)); } else { - backend_thread()->message_loop()->PostTask( - FROM_HERE, - NewRunnableMethod(backend_.get(), &BookmarkStorageBackend::Read, - scoped_refptr<BookmarkStorage>(this), - MessageLoop::current(), load_from_history)); + OnHistoryFinishedWriting(); } } +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() { - if (!backend_thread()) { - SaveNow(); - } else if (save_factory_.empty()) { - MessageLoop::current()->PostDelayedTask( - FROM_HERE, save_factory_.NewRunnableMethod(&BookmarkStorage::SaveNow), - kSaveDelayMS); - } + writer_.ScheduleWrite(this); } void BookmarkStorage::BookmarkModelDeleted() { - if (!save_factory_.empty()) { - // There's a pending save. We need to save now as otherwise by the time - // SaveNow is invoked the model is gone. - save_factory_.RevokeAll(); + // We need to save now as otherwise by the time SaveNow is invoked + // the model is gone. + if (writer_.HasPendingWrite()) SaveNow(); - } model_ = NULL; } -void BookmarkStorage::LoadedBookmarks(Value* root_value, - bool bookmark_file_exists, - bool loaded_from_history) { +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, + Value* root_value) { scoped_ptr<Value> value_ref(root_value); - if (model_) { - if (root_value) { - BookmarkCodec codec; - codec.Decode(model_, *root_value); - } - model_->OnBookmarkStorageLoadedBookmarks(bookmark_file_exists, - loaded_from_history); + 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; } -} -void BookmarkStorage::SaveNow() { - if (!model_ || !model_->IsLoaded()) { - // We should only get here if we have a valid model and it's finished - // loading. - NOTREACHED(); + if (!model_) return; - } - BookmarkCodec codec; - Value* value = codec.Encode(model_); - // The backend deletes value in write. - if (!backend_thread()) { - backend_->Write(value); - } else { - backend_thread()->message_loop()->PostTask( - FROM_HERE, - NewRunnableMethod(backend_.get(), &BookmarkStorageBackend::Write, - value)); + if (root_value) { + BookmarkCodec codec; + codec.Decode(model_, *root_value); } -} -// BookmarkStorageBackend ------------------------------------------------------ + model_->DoneLoading(); + + 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(); -BookmarkStorageBackend::BookmarkStorageBackend( - const FilePath& path, - const FilePath& tmp_history_path) - : path_(path), - tmp_history_path_(tmp_history_path) { - // Make a backup of the current file. - FilePath backup_path = path.ReplaceExtension(kBackupExtension); - file_util::CopyFile(path, backup_path); + // Clean up after migration from history. + RunTaskOnBackendThread(new FileDeleteTask(tmp_history_path_)); + } } -void BookmarkStorageBackend::Write(Value* value) { - DCHECK(value); - - // We own Value. - scoped_ptr<Value> value_ref(value); - - std::string content; - JSONWriter::Write(value, true, &content); - - // Write to a temp file, then rename. - FilePath tmp_file = path_.ReplaceExtension(kTmpExtension); - - int bytes_written = file_util::WriteFile(tmp_file, content.c_str(), - static_cast<int>(content.length())); - if (bytes_written != -1) { - if (!file_util::Move(tmp_file, path_)) { - // Rename failed. Try again on the off chance someone has locked either - // file and hope we're successful the second time through. - bool move_result = file_util::Move(tmp_file, path_); - DCHECK(move_result); - if (!move_result) - LOG(WARNING) << " writing bookmarks failed, result=" << move_result; - else - LOG(INFO) << "wrote bookmarks, file=" << path_.value(); - } else { - LOG(INFO) << "wrote bookmarks, file=" << path_.value(); - } - // Nuke the history file so that we don't attempt to load from it again. - file_util::Delete(tmp_history_path_, false); - } else { - LOG(WARNING) << " writing bookmarks failed, bytes written=" << - bytes_written; +void BookmarkStorage::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + switch (type.value) { + case NotificationType::HISTORY_LOADED: + OnHistoryFinishedWriting(); + break; + + default: + NOTREACHED(); + break; } } -void BookmarkStorageBackend::Read(scoped_refptr<BookmarkStorage> service, - MessageLoop* message_loop, - bool load_from_history) { - const FilePath& path = load_from_history ? tmp_history_path_ : path_; - bool bookmark_file_exists = file_util::PathExists(path); - Value* root = NULL; - if (bookmark_file_exists) { - JSONFileValueSerializer serializer(path); - root = serializer.Deserialize(NULL); +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; } - // BookmarkStorage takes ownership of root. - if (message_loop) { - message_loop->PostTask(FROM_HERE, NewRunnableMethod( - service.get(), &BookmarkStorage::LoadedBookmarks, root, - bookmark_file_exists, load_from_history)); + std::string data; + if (!SerializeData(&data)) + return false; + writer_.WriteNow(data); + return true; +} + +void BookmarkStorage::RunTaskOnBackendThread(Task* task) const { + if (backend_thread()) { + backend_thread()->message_loop()->PostTask(FROM_HERE, task); } else { - service->LoadedBookmarks(root, bookmark_file_exists, load_from_history); + task->Run(); + delete task; } } diff --git a/chrome/browser/bookmarks/bookmark_storage.h b/chrome/browser/bookmarks/bookmark_storage.h index 7048928..61162eb 100644 --- a/chrome/browser/bookmarks/bookmark_storage.h +++ b/chrome/browser/bookmarks/bookmark_storage.h @@ -7,12 +7,13 @@ #include "base/file_path.h" #include "base/ref_counted.h" -#include "base/task.h" +#include "chrome/common/important_file_writer.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" class BookmarkModel; -class BookmarkStorageBackend; class Profile; -class MessageLoop; +class Task; class Value; namespace base { @@ -24,18 +25,16 @@ class Thread; // as notifying the BookmarkStorage every time the model changes. // // Internally BookmarkStorage uses BookmarkCodec to do the actual read/write. - -class BookmarkStorage : public base::RefCountedThreadSafe<BookmarkStorage> { - friend class BookmarkStorageBackend; - +class BookmarkStorage : public NotificationObserver, + public ImportantFileWriter::DataSerializer, + public base::RefCountedThreadSafe<BookmarkStorage> { public: // Creates a BookmarkStorage for the specified model BookmarkStorage(Profile* profile, BookmarkModel* model); + ~BookmarkStorage(); - // Loads the bookmarks into the model, notifying the model when done. If - // load_from_history is true, the bookmarks are loaded from the file written - // by history (StarredURLDatabase). - void LoadBookmarks(bool load_from_history); + // Loads the bookmarks into the model, notifying the model when done. + void LoadBookmarks(); // Schedules saving the bookmark bar model to disk. void ScheduleSave(); @@ -44,59 +43,67 @@ class BookmarkStorage : public base::RefCountedThreadSafe<BookmarkStorage> { // a pending save, it is saved immediately. void BookmarkModelDeleted(); + // ImportantFileWriter::DataSerializer + virtual bool SerializeData(std::string* output); + private: + class LoadTask; + // Callback from backend with the results of the bookmark file. - void LoadedBookmarks(Value* root_value, - bool bookmark_file_exists, - bool loaded_from_history); + // This may be called multiple times, with different paths. This happens when + // we migrate bookmark data from database. + void OnLoadFinished(bool file_exists, const FilePath& path, + Value* root_value); - // Schedules a save on the backend thread. - void SaveNow(); + // Loads bookmark data from |file| and notifies the model when finished. + void DoLoadBookmarks(const FilePath& file); - // Returns the thread the backend is run on. - base::Thread* backend_thread() const { return backend_thread_; } + // Load bookmarks data from the file written by history (StarredURLDatabase). + void MigrateFromHistory(); - // The model. The model is NULL once BookmarkModelDeleted has been invoked. - BookmarkModel* model_; + // Called when history has written the file with bookmarks data. Loads data + // from that file. + void OnHistoryFinishedWriting(); - // Used to delay saves. - ScopedRunnableMethodFactory<BookmarkStorage> save_factory_; + // Called after we loaded file generated by history. Saves the data, deletes + // the temporary file, and notifies the model. + void FinishHistoryMigration(); - // The backend handles actual reading/writing to disk. - scoped_refptr<BookmarkStorageBackend> backend_; + // NotificationObserver + void Observe(NotificationType type, const NotificationSource& source, + const NotificationDetails& details); - // Thread read/writing is run on. This comes from the profile, and is null - // during testing. - base::Thread* backend_thread_; + // Serializes the data and schedules save using ImportantFileWriter. + // Returns true on successful serialization. + bool SaveNow(); - DISALLOW_COPY_AND_ASSIGN(BookmarkStorage); -}; + // Runs task on backend thread (or on current thread if backend thread + // is NULL). Takes ownership of |task|. + void RunTaskOnBackendThread(Task* task) const; -// Used to save the bookmarks on the file thread. -class BookmarkStorageBackend : - public base::RefCountedThreadSafe<BookmarkStorageBackend> { - public: - explicit BookmarkStorageBackend(const FilePath& path, - const FilePath& tmp_history_path); + // Returns the thread the backend is run on. + const base::Thread* backend_thread() const { return backend_thread_; } - // Writes the specified value to disk. This takes ownership of |value| and - // deletes it when done. - void Write(Value* value); + // Keep the pointer to profile, we may need it for migration from history. + Profile* profile_; - // Reads the bookmarks from kBookmarksFileName. Notifies |service| with - // the results on the specified MessageLoop. - void Read(scoped_refptr<BookmarkStorage> service, - MessageLoop* message_loop, - bool load_from_history); + // The model. The model is NULL once BookmarkModelDeleted has been invoked. + BookmarkModel* model_; - private: - // Path we read/write to. - const FilePath path_; + // Thread read/writing is run on. This comes from the profile, and is null + // during testing. + const base::Thread* backend_thread_; + + // Helper to write bookmark data safely. + ImportantFileWriter writer_; - // Path bookmarks are read from if asked to load from history file. + // Helper to ensure that we unregister from notifications on destruction. + NotificationRegistrar notification_registrar_; + + // Path to temporary file created during migrating bookmarks from history. const FilePath tmp_history_path_; - DISALLOW_COPY_AND_ASSIGN(BookmarkStorageBackend); + DISALLOW_COPY_AND_ASSIGN(BookmarkStorage); }; #endif // CHROME_BROWSER_BOOKMARKS_BOOKMARK_STORAGE_H_ |