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: 33 explicit BackupTask(const FilePath& path) : path_(path) { 34 } 35 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: 51 LoadTask(const FilePath& path, 52 BookmarkStorage* storage, 53 BookmarkLoadDetails* details) 54 : path_(path), 55 storage_(storage), 56 details_(details) { 57 } 58 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. 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 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 127 BookmarkLoadDetails::~BookmarkLoadDetails() { 128 } 129 130 // BookmarkStorage ------------------------------------------------------------- 131 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 144 BookmarkStorage::~BookmarkStorage() { 145 if (writer_.HasPendingWrite()) 146 writer_.DoScheduledWrite(); 147 } 148 149 void BookmarkStorage::LoadBookmarks(BookmarkLoadDetails* details) { 150 DCHECK(!details_.get()); 151 DCHECK(details); 152 details_.reset(details); 153 DoLoadBookmarks(writer_.path()); 154 } 155 156 void BookmarkStorage::DoLoadBookmarks(const FilePath& path) { 157 BrowserThread::PostTask( 158 BrowserThread::FILE, FROM_HERE, new LoadTask(path, this, details_.get())); 159 } 160 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 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 190 void BookmarkStorage::ScheduleSave() { 191 writer_.ScheduleWrite(this); 192 } 193 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 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 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 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 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