1// Copyright 2014 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 "components/bookmarks/browser/bookmark_storage.h"
6
7#include "base/bind.h"
8#include "base/compiler_specific.h"
9#include "base/files/file_util.h"
10#include "base/json/json_file_value_serializer.h"
11#include "base/json/json_string_value_serializer.h"
12#include "base/metrics/histogram.h"
13#include "base/sequenced_task_runner.h"
14#include "base/time/time.h"
15#include "components/bookmarks/browser/bookmark_codec.h"
16#include "components/bookmarks/browser/bookmark_index.h"
17#include "components/bookmarks/browser/bookmark_model.h"
18#include "components/bookmarks/common/bookmark_constants.h"
19#include "components/startup_metric_utils/startup_metric_utils.h"
20
21using base::TimeTicks;
22
23namespace bookmarks {
24
25namespace {
26
27// Extension used for backup files (copy of main file created during startup).
28const base::FilePath::CharType kBackupExtension[] = FILE_PATH_LITERAL("bak");
29
30// How often we save.
31const int kSaveDelayMS = 2500;
32
33void BackupCallback(const base::FilePath& path) {
34  base::FilePath backup_path = path.ReplaceExtension(kBackupExtension);
35  base::CopyFile(path, backup_path);
36}
37
38// Adds node to the model's index, recursing through all children as well.
39void AddBookmarksToIndex(BookmarkLoadDetails* details,
40                         BookmarkNode* node) {
41  if (node->is_url()) {
42    if (node->url().is_valid())
43      details->index()->Add(node);
44  } else {
45    for (int i = 0; i < node->child_count(); ++i)
46      AddBookmarksToIndex(details, node->GetChild(i));
47  }
48}
49
50void LoadCallback(const base::FilePath& path,
51                  const base::WeakPtr<BookmarkStorage>& storage,
52                  scoped_ptr<BookmarkLoadDetails> details,
53                  base::SequencedTaskRunner* task_runner) {
54  startup_metric_utils::ScopedSlowStartupUMA
55      scoped_timer("Startup.SlowStartupBookmarksLoad");
56  bool load_index = false;
57  bool bookmark_file_exists = base::PathExists(path);
58  if (bookmark_file_exists) {
59    JSONFileValueSerializer serializer(path);
60    scoped_ptr<base::Value> root(serializer.Deserialize(NULL, NULL));
61
62    if (root.get()) {
63      // Building the index can take a while, so we do it on the background
64      // thread.
65      int64 max_node_id = 0;
66      BookmarkCodec codec;
67      TimeTicks start_time = TimeTicks::Now();
68      codec.Decode(details->bb_node(), details->other_folder_node(),
69                   details->mobile_folder_node(), &max_node_id, *root.get());
70      details->set_max_id(std::max(max_node_id, details->max_id()));
71      details->set_computed_checksum(codec.computed_checksum());
72      details->set_stored_checksum(codec.stored_checksum());
73      details->set_ids_reassigned(codec.ids_reassigned());
74      details->set_model_meta_info_map(codec.model_meta_info_map());
75      details->set_model_sync_transaction_version(
76          codec.model_sync_transaction_version());
77      UMA_HISTOGRAM_TIMES("Bookmarks.DecodeTime",
78                          TimeTicks::Now() - start_time);
79
80      load_index = true;
81    }
82  }
83
84  // Load any extra root nodes now, after the IDs have been potentially
85  // reassigned.
86  details->LoadExtraNodes();
87
88  // Load the index if there are any bookmarks in the extra nodes.
89  const BookmarkPermanentNodeList& extra_nodes = details->extra_nodes();
90  for (size_t i = 0; i < extra_nodes.size(); ++i) {
91    if (!extra_nodes[i]->empty()) {
92      load_index = true;
93      break;
94    }
95  }
96
97  if (load_index) {
98    TimeTicks start_time = TimeTicks::Now();
99    AddBookmarksToIndex(details.get(), details->bb_node());
100    AddBookmarksToIndex(details.get(), details->other_folder_node());
101    AddBookmarksToIndex(details.get(), details->mobile_folder_node());
102    for (size_t i = 0; i < extra_nodes.size(); ++i)
103      AddBookmarksToIndex(details.get(), extra_nodes[i]);
104    UMA_HISTOGRAM_TIMES("Bookmarks.CreateBookmarkIndexTime",
105                        TimeTicks::Now() - start_time);
106  }
107
108  task_runner->PostTask(FROM_HERE,
109                        base::Bind(&BookmarkStorage::OnLoadFinished, storage,
110                                   base::Passed(&details)));
111}
112
113}  // namespace
114
115// BookmarkLoadDetails ---------------------------------------------------------
116
117BookmarkLoadDetails::BookmarkLoadDetails(
118    BookmarkPermanentNode* bb_node,
119    BookmarkPermanentNode* other_folder_node,
120    BookmarkPermanentNode* mobile_folder_node,
121    const LoadExtraCallback& load_extra_callback,
122    BookmarkIndex* index,
123    int64 max_id)
124    : bb_node_(bb_node),
125      other_folder_node_(other_folder_node),
126      mobile_folder_node_(mobile_folder_node),
127      load_extra_callback_(load_extra_callback),
128      index_(index),
129      model_sync_transaction_version_(
130          BookmarkNode::kInvalidSyncTransactionVersion),
131      max_id_(max_id),
132      ids_reassigned_(false) {
133}
134
135BookmarkLoadDetails::~BookmarkLoadDetails() {
136}
137
138void BookmarkLoadDetails::LoadExtraNodes() {
139  extra_nodes_ = load_extra_callback_.Run(&max_id_);
140}
141
142// BookmarkStorage -------------------------------------------------------------
143
144BookmarkStorage::BookmarkStorage(
145    BookmarkModel* model,
146    const base::FilePath& profile_path,
147    base::SequencedTaskRunner* sequenced_task_runner)
148    : model_(model),
149      writer_(profile_path.Append(kBookmarksFileName), sequenced_task_runner),
150      weak_factory_(this) {
151  sequenced_task_runner_ = sequenced_task_runner;
152  writer_.set_commit_interval(base::TimeDelta::FromMilliseconds(kSaveDelayMS));
153  sequenced_task_runner_->PostTask(FROM_HERE,
154                                   base::Bind(&BackupCallback, writer_.path()));
155}
156
157BookmarkStorage::~BookmarkStorage() {
158  if (writer_.HasPendingWrite())
159    writer_.DoScheduledWrite();
160}
161
162void BookmarkStorage::LoadBookmarks(
163    scoped_ptr<BookmarkLoadDetails> details,
164    const scoped_refptr<base::SequencedTaskRunner>& task_runner) {
165  sequenced_task_runner_->PostTask(FROM_HERE,
166                                   base::Bind(&LoadCallback,
167                                              writer_.path(),
168                                              weak_factory_.GetWeakPtr(),
169                                              base::Passed(&details),
170                                              task_runner));
171}
172
173void BookmarkStorage::ScheduleSave() {
174  writer_.ScheduleWrite(this);
175}
176
177void BookmarkStorage::BookmarkModelDeleted() {
178  // We need to save now as otherwise by the time SaveNow is invoked
179  // the model is gone.
180  if (writer_.HasPendingWrite())
181    SaveNow();
182  model_ = NULL;
183}
184
185bool BookmarkStorage::SerializeData(std::string* output) {
186  BookmarkCodec codec;
187  scoped_ptr<base::Value> value(codec.Encode(model_));
188  JSONStringValueSerializer serializer(output);
189  serializer.set_pretty_print(true);
190  return serializer.Serialize(*(value.get()));
191}
192
193void BookmarkStorage::OnLoadFinished(scoped_ptr<BookmarkLoadDetails> details) {
194  if (!model_)
195    return;
196
197  model_->DoneLoading(details.Pass());
198}
199
200bool BookmarkStorage::SaveNow() {
201  if (!model_ || !model_->loaded()) {
202    // We should only get here if we have a valid model and it's finished
203    // loading.
204    NOTREACHED();
205    return false;
206  }
207
208  std::string data;
209  if (!SerializeData(&data))
210    return false;
211  writer_.WriteNow(data);
212  return true;
213}
214
215}  // namespace bookmarks
216