// 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;
}