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/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
21 using base::TimeTicks;
22
23 namespace bookmarks {
24
25 namespace {
26
27 // Extension used for backup files (copy of main file created during startup).
28 const base::FilePath::CharType kBackupExtension[] = FILE_PATH_LITERAL("bak");
29
30 // How often we save.
31 const int kSaveDelayMS = 2500;
32
BackupCallback(const base::FilePath & path)33 void 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.
AddBookmarksToIndex(BookmarkLoadDetails * details,BookmarkNode * node)39 void 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
LoadCallback(const base::FilePath & path,BookmarkStorage * storage,BookmarkLoadDetails * details,base::SequencedTaskRunner * task_runner)50 void LoadCallback(const base::FilePath& path,
51 BookmarkStorage* storage,
52 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, details->bb_node());
100 AddBookmarksToIndex(details, details->other_folder_node());
101 AddBookmarksToIndex(details, details->mobile_folder_node());
102 for (size_t i = 0; i < extra_nodes.size(); ++i)
103 AddBookmarksToIndex(details, 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 }
111
112 } // namespace
113
114 // BookmarkLoadDetails ---------------------------------------------------------
115
BookmarkLoadDetails(BookmarkPermanentNode * bb_node,BookmarkPermanentNode * other_folder_node,BookmarkPermanentNode * mobile_folder_node,const LoadExtraCallback & load_extra_callback,BookmarkIndex * index,int64 max_id)116 BookmarkLoadDetails::BookmarkLoadDetails(
117 BookmarkPermanentNode* bb_node,
118 BookmarkPermanentNode* other_folder_node,
119 BookmarkPermanentNode* mobile_folder_node,
120 const LoadExtraCallback& load_extra_callback,
121 BookmarkIndex* index,
122 int64 max_id)
123 : bb_node_(bb_node),
124 other_folder_node_(other_folder_node),
125 mobile_folder_node_(mobile_folder_node),
126 load_extra_callback_(load_extra_callback),
127 index_(index),
128 model_sync_transaction_version_(
129 BookmarkNode::kInvalidSyncTransactionVersion),
130 max_id_(max_id),
131 ids_reassigned_(false) {
132 }
133
~BookmarkLoadDetails()134 BookmarkLoadDetails::~BookmarkLoadDetails() {
135 }
136
LoadExtraNodes()137 void BookmarkLoadDetails::LoadExtraNodes() {
138 extra_nodes_ = load_extra_callback_.Run(&max_id_);
139 }
140
141 // BookmarkStorage -------------------------------------------------------------
142
BookmarkStorage(BookmarkModel * model,const base::FilePath & profile_path,base::SequencedTaskRunner * sequenced_task_runner)143 BookmarkStorage::BookmarkStorage(
144 BookmarkModel* model,
145 const base::FilePath& profile_path,
146 base::SequencedTaskRunner* sequenced_task_runner)
147 : model_(model),
148 writer_(profile_path.Append(bookmarks::kBookmarksFileName),
149 sequenced_task_runner) {
150 sequenced_task_runner_ = sequenced_task_runner;
151 writer_.set_commit_interval(base::TimeDelta::FromMilliseconds(kSaveDelayMS));
152 sequenced_task_runner_->PostTask(FROM_HERE,
153 base::Bind(&BackupCallback, writer_.path()));
154 }
155
~BookmarkStorage()156 BookmarkStorage::~BookmarkStorage() {
157 if (writer_.HasPendingWrite())
158 writer_.DoScheduledWrite();
159 }
160
LoadBookmarks(scoped_ptr<BookmarkLoadDetails> details,const scoped_refptr<base::SequencedTaskRunner> & task_runner)161 void BookmarkStorage::LoadBookmarks(
162 scoped_ptr<BookmarkLoadDetails> details,
163 const scoped_refptr<base::SequencedTaskRunner>& task_runner) {
164 DCHECK(!details_.get());
165 DCHECK(details);
166 details_ = details.Pass();
167 sequenced_task_runner_->PostTask(FROM_HERE,
168 base::Bind(&LoadCallback,
169 writer_.path(),
170 make_scoped_refptr(this),
171 details_.get(),
172 task_runner));
173 }
174
ScheduleSave()175 void BookmarkStorage::ScheduleSave() {
176 writer_.ScheduleWrite(this);
177 }
178
BookmarkModelDeleted()179 void BookmarkStorage::BookmarkModelDeleted() {
180 // We need to save now as otherwise by the time SaveNow is invoked
181 // the model is gone.
182 if (writer_.HasPendingWrite())
183 SaveNow();
184 model_ = NULL;
185 }
186
SerializeData(std::string * output)187 bool BookmarkStorage::SerializeData(std::string* output) {
188 BookmarkCodec codec;
189 scoped_ptr<base::Value> value(codec.Encode(model_));
190 JSONStringValueSerializer serializer(output);
191 serializer.set_pretty_print(true);
192 return serializer.Serialize(*(value.get()));
193 }
194
OnLoadFinished()195 void BookmarkStorage::OnLoadFinished() {
196 if (!model_)
197 return;
198
199 model_->DoneLoading(details_.Pass());
200 }
201
SaveNow()202 bool BookmarkStorage::SaveNow() {
203 if (!model_ || !model_->loaded()) {
204 // We should only get here if we have a valid model and it's finished
205 // loading.
206 NOTREACHED();
207 return false;
208 }
209
210 std::string data;
211 if (!SerializeData(&data))
212 return false;
213 writer_.WriteNow(data);
214 return true;
215 }
216
217 } // namespace bookmarks
218