blob: 23f47b40b0b3aa5e92369cfcbb7393ee2d4d8334 [file] [log] [blame]
// 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_storage.h"
#include "base/compiler_specific.h"
#include "base/file_util.h"
#include "base/json_writer.h"
#include "base/message_loop.h"
#include "chrome/browser/bookmarks/bookmark_codec.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
#include "chrome/browser/profile.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/json_value_serializer.h"
namespace {
// Extension used for backup files (copy of main file created during startup).
const wchar_t* const kBackupExtension = L"bak";
// Extension for the temporary file. We write to the temp file than move to
// kBookmarksFileName.
const wchar_t* const kTmpExtension = L"tmp";
// How often we save.
const int kSaveDelayMS = 2500;
} // namespace
// BookmarkStorage -------------------------------------------------------------
BookmarkStorage::BookmarkStorage(Profile* profile, BookmarkModel* model)
: model_(model),
ALLOW_THIS_IN_INITIALIZER_LIST(save_factory_(this)),
backend_thread_(g_browser_process->file_thread()) {
std::wstring path = profile->GetPath();
file_util::AppendToPath(&path, chrome::kBookmarksFileName);
std::wstring tmp_history_path = profile->GetPath();
file_util::AppendToPath(&tmp_history_path, chrome::kHistoryBookmarksFileName);
backend_ = new BookmarkStorageBackend(path, tmp_history_path);
}
void BookmarkStorage::LoadBookmarks(bool load_from_history) {
if (!backend_thread()) {
backend_->Read(scoped_refptr<BookmarkStorage>(this), NULL,
load_from_history);
} else {
backend_thread()->message_loop()->PostTask(
FROM_HERE,
NewRunnableMethod(backend_.get(), &BookmarkStorageBackend::Read,
scoped_refptr<BookmarkStorage>(this),
MessageLoop::current(), load_from_history));
}
}
void BookmarkStorage::ScheduleSave() {
if (!backend_thread()) {
SaveNow();
} else if (save_factory_.empty()) {
MessageLoop::current()->PostDelayedTask(
FROM_HERE, save_factory_.NewRunnableMethod(&BookmarkStorage::SaveNow),
kSaveDelayMS);
}
}
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();
SaveNow();
}
model_ = NULL;
}
void BookmarkStorage::LoadedBookmarks(Value* root_value,
bool bookmark_file_exists,
bool loaded_from_history) {
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);
}
}
void BookmarkStorage::SaveNow() {
if (!model_ || !model_->IsLoaded()) {
// We should only get here if we have a valid model and it's finished
// loading.
NOTREACHED();
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));
}
}
// BookmarkStorageBackend ------------------------------------------------------
BookmarkStorageBackend::BookmarkStorageBackend(
const std::wstring& path,
const std::wstring& tmp_history_path)
: path_(path),
tmp_history_path_(tmp_history_path) {
// Make a backup of the current file.
std::wstring backup_path = path;
file_util::ReplaceExtension(&backup_path, kBackupExtension);
file_util::CopyFile(path, backup_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.
std::wstring tmp_file = path_;
file_util::ReplaceExtension(&tmp_file, 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_;
} else {
LOG(INFO) << "wrote bookmarks, file=" << path_;
}
// 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 BookmarkStorageBackend::Read(scoped_refptr<BookmarkStorage> service,
MessageLoop* message_loop,
bool load_from_history) {
const std::wstring& 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);
}
// 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));
} else {
service->LoadedBookmarks(root, bookmark_file_exists, load_from_history);
}
}