普通文本  |  269行  |  8.17 KB

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