• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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