bookmark_storage.cc revision dc0f95d653279beabeb9817299e2902918ba123e
1// Copyright (c) 2006-2008 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 "chrome/common/json_value_serializer.h"
17#include "chrome/common/notification_source.h"
18#include "chrome/common/notification_type.h"
19#include "content/browser/browser_thread.h"
20
21using base::TimeTicks;
22
23namespace {
24
25// Extension used for backup files (copy of main file created during startup).
26const FilePath::CharType kBackupExtension[] = FILE_PATH_LITERAL("bak");
27
28// How often we save.
29const int kSaveDelayMS = 2500;
30
31class 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
49class 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->GetChildCount(); ++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
116BookmarkLoadDetails::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
127BookmarkLoadDetails::~BookmarkLoadDetails() {
128}
129
130// BookmarkStorage -------------------------------------------------------------
131
132BookmarkStorage::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
144BookmarkStorage::~BookmarkStorage() {
145  if (writer_.HasPendingWrite())
146    writer_.DoScheduledWrite();
147}
148
149void BookmarkStorage::LoadBookmarks(BookmarkLoadDetails* details) {
150  DCHECK(!details_.get());
151  DCHECK(details);
152  details_.reset(details);
153  DoLoadBookmarks(writer_.path());
154}
155
156void BookmarkStorage::DoLoadBookmarks(const FilePath& path) {
157  BrowserThread::PostTask(
158      BrowserThread::FILE, FROM_HERE, new LoadTask(path, this, details_.get()));
159}
160
161void 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
181void 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
190void BookmarkStorage::ScheduleSave() {
191  writer_.ScheduleWrite(this);
192}
193
194void 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
202bool 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
210void 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
241void 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
255bool 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