Home | History | Annotate | Download | only in bookmarks
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/bookmarks/bookmark_storage.h"
      6 
      7 #include "base/compiler_specific.h"
      8 #include "base/file_util.h"
      9 #include "base/file_util_proxy.h"
     10 #include "base/metrics/histogram.h"
     11 #include "base/time.h"
     12 #include "chrome/browser/bookmarks/bookmark_codec.h"
     13 #include "chrome/browser/bookmarks/bookmark_model.h"
     14 #include "chrome/browser/profiles/profile.h"
     15 #include "chrome/common/chrome_constants.h"
     16 #include "content/browser/browser_thread.h"
     17 #include "content/common/json_value_serializer.h"
     18 #include "content/common/notification_source.h"
     19 #include "content/common/notification_type.h"
     20 
     21 using base::TimeTicks;
     22 
     23 namespace {
     24 
     25 // Extension used for backup files (copy of main file created during startup).
     26 const FilePath::CharType kBackupExtension[] = FILE_PATH_LITERAL("bak");
     27 
     28 // How often we save.
     29 const int kSaveDelayMS = 2500;
     30 
     31 class BackupTask : public Task {
     32  public:
     33   explicit BackupTask(const FilePath& path) : path_(path) {
     34   }
     35 
     36   virtual void Run() {
     37     FilePath backup_path = path_.ReplaceExtension(kBackupExtension);
     38     file_util::CopyFile(path_, backup_path);
     39   }
     40 
     41  private:
     42   const FilePath path_;
     43 
     44   DISALLOW_COPY_AND_ASSIGN(BackupTask);
     45 };
     46 
     47 }  // namespace
     48 
     49 class BookmarkStorage::LoadTask : public Task {
     50  public:
     51   LoadTask(const FilePath& path,
     52            BookmarkStorage* storage,
     53            BookmarkLoadDetails* details)
     54       : path_(path),
     55         storage_(storage),
     56         details_(details) {
     57   }
     58 
     59   virtual void Run() {
     60     bool bookmark_file_exists = file_util::PathExists(path_);
     61     if (bookmark_file_exists) {
     62       JSONFileValueSerializer serializer(path_);
     63       scoped_ptr<Value> root(serializer.Deserialize(NULL, NULL));
     64 
     65       if (root.get()) {
     66         // Building the index can take a while, so we do it on the background
     67         // thread.
     68         int64 max_node_id = 0;
     69         BookmarkCodec codec;
     70         TimeTicks start_time = TimeTicks::Now();
     71         codec.Decode(details_->bb_node(), details_->other_folder_node(),
     72                      &max_node_id, *root.get());
     73         details_->set_max_id(std::max(max_node_id, details_->max_id()));
     74         details_->set_computed_checksum(codec.computed_checksum());
     75         details_->set_stored_checksum(codec.stored_checksum());
     76         details_->set_ids_reassigned(codec.ids_reassigned());
     77         UMA_HISTOGRAM_TIMES("Bookmarks.DecodeTime",
     78                             TimeTicks::Now() - start_time);
     79 
     80         start_time = TimeTicks::Now();
     81         AddBookmarksToIndex(details_->bb_node());
     82         AddBookmarksToIndex(details_->other_folder_node());
     83         UMA_HISTOGRAM_TIMES("Bookmarks.CreateBookmarkIndexTime",
     84                             TimeTicks::Now() - start_time);
     85       }
     86     }
     87 
     88     BrowserThread::PostTask(
     89         BrowserThread::UI, FROM_HERE,
     90         NewRunnableMethod(
     91             storage_.get(), &BookmarkStorage::OnLoadFinished,
     92             bookmark_file_exists, path_));
     93   }
     94 
     95  private:
     96   // Adds node to the model's index, recursing through all children as well.
     97   void AddBookmarksToIndex(BookmarkNode* node) {
     98     if (node->is_url()) {
     99       if (node->GetURL().is_valid())
    100         details_->index()->Add(node);
    101     } else {
    102       for (int i = 0; i < node->child_count(); ++i)
    103         AddBookmarksToIndex(node->GetChild(i));
    104     }
    105   }
    106 
    107   const FilePath path_;
    108   scoped_refptr<BookmarkStorage> storage_;
    109   BookmarkLoadDetails* details_;
    110 
    111   DISALLOW_COPY_AND_ASSIGN(LoadTask);
    112 };
    113 
    114 // BookmarkLoadDetails ---------------------------------------------------------
    115 
    116 BookmarkLoadDetails::BookmarkLoadDetails(BookmarkNode* bb_node,
    117                                          BookmarkNode* other_folder_node,
    118                                          BookmarkIndex* index,
    119                                          int64 max_id)
    120     : bb_node_(bb_node),
    121       other_folder_node_(other_folder_node),
    122       index_(index),
    123       max_id_(max_id),
    124       ids_reassigned_(false) {
    125 }
    126 
    127 BookmarkLoadDetails::~BookmarkLoadDetails() {
    128 }
    129 
    130 // BookmarkStorage -------------------------------------------------------------
    131 
    132 BookmarkStorage::BookmarkStorage(Profile* profile, BookmarkModel* model)
    133     : profile_(profile),
    134       model_(model),
    135       writer_(profile->GetPath().Append(chrome::kBookmarksFileName),
    136               BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE)),
    137       tmp_history_path_(
    138           profile->GetPath().Append(chrome::kHistoryBookmarksFileName)) {
    139   writer_.set_commit_interval(base::TimeDelta::FromMilliseconds(kSaveDelayMS));
    140   BrowserThread::PostTask(
    141       BrowserThread::FILE, FROM_HERE, new BackupTask(writer_.path()));
    142 }
    143 
    144 BookmarkStorage::~BookmarkStorage() {
    145   if (writer_.HasPendingWrite())
    146     writer_.DoScheduledWrite();
    147 }
    148 
    149 void BookmarkStorage::LoadBookmarks(BookmarkLoadDetails* details) {
    150   DCHECK(!details_.get());
    151   DCHECK(details);
    152   details_.reset(details);
    153   DoLoadBookmarks(writer_.path());
    154 }
    155 
    156 void BookmarkStorage::DoLoadBookmarks(const FilePath& path) {
    157   BrowserThread::PostTask(
    158       BrowserThread::FILE, FROM_HERE, new LoadTask(path, this, details_.get()));
    159 }
    160 
    161 void BookmarkStorage::MigrateFromHistory() {
    162   // We need to wait until history has finished loading before reading
    163   // from generated bookmarks file.
    164   HistoryService* history =
    165       profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
    166   if (!history) {
    167     // This happens in unit tests.
    168     if (model_)
    169       model_->DoneLoading(details_.release());
    170     return;
    171   }
    172   if (!history->BackendLoaded()) {
    173     // The backend isn't finished loading. Wait for it.
    174     notification_registrar_.Add(this, NotificationType::HISTORY_LOADED,
    175                                 Source<Profile>(profile_));
    176   } else {
    177     DoLoadBookmarks(tmp_history_path_);
    178   }
    179 }
    180 
    181 void BookmarkStorage::OnHistoryFinishedWriting() {
    182   notification_registrar_.Remove(this, NotificationType::HISTORY_LOADED,
    183                                  Source<Profile>(profile_));
    184 
    185   // This is used when migrating bookmarks data from database to file.
    186   // History wrote the file for us, and now we want to load data from it.
    187   DoLoadBookmarks(tmp_history_path_);
    188 }
    189 
    190 void BookmarkStorage::ScheduleSave() {
    191   writer_.ScheduleWrite(this);
    192 }
    193 
    194 void BookmarkStorage::BookmarkModelDeleted() {
    195   // We need to save now as otherwise by the time SaveNow is invoked
    196   // the model is gone.
    197   if (writer_.HasPendingWrite())
    198     SaveNow();
    199   model_ = NULL;
    200 }
    201 
    202 bool BookmarkStorage::SerializeData(std::string* output) {
    203   BookmarkCodec codec;
    204   scoped_ptr<Value> value(codec.Encode(model_));
    205   JSONStringValueSerializer serializer(output);
    206   serializer.set_pretty_print(true);
    207   return serializer.Serialize(*(value.get()));
    208 }
    209 
    210 void BookmarkStorage::OnLoadFinished(bool file_exists, const FilePath& path) {
    211   if (path == writer_.path() && !file_exists) {
    212     // The file doesn't exist. This means one of two things:
    213     // 1. A clean profile.
    214     // 2. The user is migrating from an older version where bookmarks were
    215     //    saved in history.
    216     // We assume step 2. If history had the bookmarks, it will write the
    217     // bookmarks to a file for us.
    218     MigrateFromHistory();
    219     return;
    220   }
    221 
    222   if (!model_)
    223     return;
    224 
    225   model_->DoneLoading(details_.release());
    226 
    227   if (path == tmp_history_path_) {
    228     // We just finished migration from history. Save now to new file,
    229     // after the model is created and done loading.
    230     SaveNow();
    231 
    232     // Clean up after migration from history.
    233     base::FileUtilProxy::Delete(
    234         BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE),
    235         tmp_history_path_,
    236         false,
    237         NULL);
    238   }
    239 }
    240 
    241 void BookmarkStorage::Observe(NotificationType type,
    242                               const NotificationSource& source,
    243                               const NotificationDetails& details) {
    244   switch (type.value) {
    245     case NotificationType::HISTORY_LOADED:
    246       OnHistoryFinishedWriting();
    247       break;
    248 
    249     default:
    250       NOTREACHED();
    251       break;
    252   }
    253 }
    254 
    255 bool BookmarkStorage::SaveNow() {
    256   if (!model_ || !model_->IsLoaded()) {
    257     // We should only get here if we have a valid model and it's finished
    258     // loading.
    259     NOTREACHED();
    260     return false;
    261   }
    262 
    263   std::string data;
    264   if (!SerializeData(&data))
    265     return false;
    266   writer_.WriteNow(data);
    267   return true;
    268 }
    269