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