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:
BackupTask(const FilePath & path)33 explicit BackupTask(const FilePath& path) : path_(path) {
34 }
35
Run()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:
LoadTask(const FilePath & path,BookmarkStorage * storage,BookmarkLoadDetails * details)51 LoadTask(const FilePath& path,
52 BookmarkStorage* storage,
53 BookmarkLoadDetails* details)
54 : path_(path),
55 storage_(storage),
56 details_(details) {
57 }
58
Run()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.
AddBookmarksToIndex(BookmarkNode * node)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
BookmarkLoadDetails(BookmarkNode * bb_node,BookmarkNode * other_folder_node,BookmarkIndex * index,int64 max_id)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
~BookmarkLoadDetails()127 BookmarkLoadDetails::~BookmarkLoadDetails() {
128 }
129
130 // BookmarkStorage -------------------------------------------------------------
131
BookmarkStorage(Profile * profile,BookmarkModel * model)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
~BookmarkStorage()144 BookmarkStorage::~BookmarkStorage() {
145 if (writer_.HasPendingWrite())
146 writer_.DoScheduledWrite();
147 }
148
LoadBookmarks(BookmarkLoadDetails * details)149 void BookmarkStorage::LoadBookmarks(BookmarkLoadDetails* details) {
150 DCHECK(!details_.get());
151 DCHECK(details);
152 details_.reset(details);
153 DoLoadBookmarks(writer_.path());
154 }
155
DoLoadBookmarks(const FilePath & path)156 void BookmarkStorage::DoLoadBookmarks(const FilePath& path) {
157 BrowserThread::PostTask(
158 BrowserThread::FILE, FROM_HERE, new LoadTask(path, this, details_.get()));
159 }
160
MigrateFromHistory()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
OnHistoryFinishedWriting()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
ScheduleSave()190 void BookmarkStorage::ScheduleSave() {
191 writer_.ScheduleWrite(this);
192 }
193
BookmarkModelDeleted()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
SerializeData(std::string * output)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
OnLoadFinished(bool file_exists,const FilePath & path)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
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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
SaveNow()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