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