1 // Copyright (c) 2012 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/chromeos/contacts/contact_database.h" 6 7 #include <set> 8 9 #include "base/file_util.h" 10 #include "base/metrics/histogram.h" 11 #include "base/sequenced_task_runner.h" 12 #include "base/threading/sequenced_worker_pool.h" 13 #include "chrome/browser/chromeos/contacts/contact.pb.h" 14 #include "content/public/browser/browser_thread.h" 15 #include "third_party/leveldatabase/src/include/leveldb/db.h" 16 #include "third_party/leveldatabase/src/include/leveldb/iterator.h" 17 #include "third_party/leveldatabase/src/include/leveldb/options.h" 18 #include "third_party/leveldatabase/src/include/leveldb/slice.h" 19 #include "third_party/leveldatabase/src/include/leveldb/status.h" 20 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" 21 22 using content::BrowserThread; 23 24 namespace contacts { 25 26 namespace { 27 28 // Initialization results reported via the "Contacts.DatabaseInitResult" 29 // histogram. 30 enum HistogramInitResult { 31 HISTOGRAM_INIT_RESULT_SUCCESS = 0, 32 HISTOGRAM_INIT_RESULT_FAILURE = 1, 33 HISTOGRAM_INIT_RESULT_DELETED_CORRUPTED = 2, 34 HISTOGRAM_INIT_RESULT_MAX_VALUE = 3, 35 }; 36 37 // Save results reported via the "Contacts.DatabaseSaveResult" histogram. 38 enum HistogramSaveResult { 39 HISTOGRAM_SAVE_RESULT_SUCCESS = 0, 40 HISTOGRAM_SAVE_RESULT_FAILURE = 1, 41 HISTOGRAM_SAVE_RESULT_MAX_VALUE = 2, 42 }; 43 44 // Load results reported via the "Contacts.DatabaseLoadResult" histogram. 45 enum HistogramLoadResult { 46 HISTOGRAM_LOAD_RESULT_SUCCESS = 0, 47 HISTOGRAM_LOAD_RESULT_METADATA_PARSE_FAILURE = 1, 48 HISTOGRAM_LOAD_RESULT_CONTACT_PARSE_FAILURE = 2, 49 HISTOGRAM_LOAD_RESULT_MAX_VALUE = 3, 50 }; 51 52 // LevelDB key used for storing UpdateMetadata messages. 53 const char kUpdateMetadataKey[] = "__chrome_update_metadata__"; 54 55 } // namespace 56 57 ContactDatabase::ContactDatabase() 58 : weak_ptr_factory_(this) { 59 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 60 base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool(); 61 task_runner_ = pool->GetSequencedTaskRunner(pool->GetSequenceToken()); 62 } 63 64 void ContactDatabase::DestroyOnUIThread() { 65 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 66 weak_ptr_factory_.InvalidateWeakPtrs(); 67 task_runner_->PostNonNestableTask( 68 FROM_HERE, 69 base::Bind(&ContactDatabase::DestroyFromTaskRunner, 70 base::Unretained(this))); 71 } 72 73 void ContactDatabase::Init(const base::FilePath& database_dir, 74 InitCallback callback) { 75 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 76 bool* success = new bool(false); 77 task_runner_->PostTaskAndReply( 78 FROM_HERE, 79 base::Bind(&ContactDatabase::InitFromTaskRunner, 80 base::Unretained(this), 81 database_dir, 82 success), 83 base::Bind(&ContactDatabase::RunInitCallback, 84 weak_ptr_factory_.GetWeakPtr(), 85 callback, 86 base::Owned(success))); 87 } 88 89 void ContactDatabase::SaveContacts(scoped_ptr<ContactPointers> contacts_to_save, 90 scoped_ptr<ContactIds> contact_ids_to_delete, 91 scoped_ptr<UpdateMetadata> metadata, 92 bool is_full_update, 93 SaveCallback callback) { 94 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 95 bool* success = new bool(false); 96 task_runner_->PostTaskAndReply( 97 FROM_HERE, 98 base::Bind(&ContactDatabase::SaveContactsFromTaskRunner, 99 base::Unretained(this), 100 base::Passed(&contacts_to_save), 101 base::Passed(&contact_ids_to_delete), 102 base::Passed(&metadata), 103 is_full_update, 104 success), 105 base::Bind(&ContactDatabase::RunSaveCallback, 106 weak_ptr_factory_.GetWeakPtr(), 107 callback, 108 base::Owned(success))); 109 } 110 111 void ContactDatabase::LoadContacts(LoadCallback callback) { 112 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 113 114 bool* success = new bool(false); 115 scoped_ptr<ScopedVector<Contact> > contacts(new ScopedVector<Contact>); 116 scoped_ptr<UpdateMetadata> metadata(new UpdateMetadata); 117 118 // Extract pointers before we calling Pass() so we can use them below. 119 ScopedVector<Contact>* contacts_ptr = contacts.get(); 120 UpdateMetadata* metadata_ptr = metadata.get(); 121 122 task_runner_->PostTaskAndReply( 123 FROM_HERE, 124 base::Bind(&ContactDatabase::LoadContactsFromTaskRunner, 125 base::Unretained(this), 126 success, 127 contacts_ptr, 128 metadata_ptr), 129 base::Bind(&ContactDatabase::RunLoadCallback, 130 weak_ptr_factory_.GetWeakPtr(), 131 callback, 132 base::Owned(success), 133 base::Passed(&contacts), 134 base::Passed(&metadata))); 135 } 136 137 ContactDatabase::~ContactDatabase() { 138 DCHECK(IsRunByTaskRunner()); 139 } 140 141 bool ContactDatabase::IsRunByTaskRunner() const { 142 return BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread(); 143 } 144 145 void ContactDatabase::DestroyFromTaskRunner() { 146 DCHECK(IsRunByTaskRunner()); 147 delete this; 148 } 149 150 void ContactDatabase::RunInitCallback(InitCallback callback, 151 const bool* success) { 152 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 153 callback.Run(*success); 154 } 155 156 void ContactDatabase::RunSaveCallback(SaveCallback callback, 157 const bool* success) { 158 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 159 callback.Run(*success); 160 } 161 162 void ContactDatabase::RunLoadCallback( 163 LoadCallback callback, 164 const bool* success, 165 scoped_ptr<ScopedVector<Contact> > contacts, 166 scoped_ptr<UpdateMetadata> metadata) { 167 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 168 callback.Run(*success, contacts.Pass(), metadata.Pass()); 169 } 170 171 void ContactDatabase::InitFromTaskRunner(const base::FilePath& database_dir, 172 bool* success) { 173 DCHECK(IsRunByTaskRunner()); 174 DCHECK(success); 175 176 VLOG(1) << "Opening " << database_dir.value(); 177 UMA_HISTOGRAM_MEMORY_KB("Contacts.DatabaseSizeBytes", 178 base::ComputeDirectorySize(database_dir)); 179 *success = false; 180 HistogramInitResult histogram_result = HISTOGRAM_INIT_RESULT_SUCCESS; 181 182 leveldb::Options options; 183 options.create_if_missing = true; 184 options.max_open_files = 64; // Use minimum. 185 bool delete_and_retry_on_corruption = true; 186 187 while (true) { 188 leveldb::DB* db = NULL; 189 leveldb::Status status = 190 leveldb::DB::Open(options, database_dir.value(), &db); 191 if (status.ok()) { 192 CHECK(db); 193 db_.reset(db); 194 *success = true; 195 return; 196 } 197 198 LOG(WARNING) << "Unable to open " << database_dir.value() << ": " 199 << status.ToString(); 200 201 // Delete the existing database and try again (just once, though). 202 if (status.IsCorruption() && delete_and_retry_on_corruption) { 203 LOG(WARNING) << "Deleting possibly-corrupt database"; 204 base::DeleteFile(database_dir, true); 205 delete_and_retry_on_corruption = false; 206 histogram_result = HISTOGRAM_INIT_RESULT_DELETED_CORRUPTED; 207 } else { 208 histogram_result = HISTOGRAM_INIT_RESULT_FAILURE; 209 break; 210 } 211 } 212 213 UMA_HISTOGRAM_ENUMERATION("Contacts.DatabaseInitResult", 214 histogram_result, 215 HISTOGRAM_INIT_RESULT_MAX_VALUE); 216 } 217 218 void ContactDatabase::SaveContactsFromTaskRunner( 219 scoped_ptr<ContactPointers> contacts_to_save, 220 scoped_ptr<ContactIds> contact_ids_to_delete, 221 scoped_ptr<UpdateMetadata> metadata, 222 bool is_full_update, 223 bool* success) { 224 DCHECK(IsRunByTaskRunner()); 225 DCHECK(success); 226 VLOG(1) << "Saving " << contacts_to_save->size() << " contact(s) to database " 227 << "and deleting " << contact_ids_to_delete->size() << " as " 228 << (is_full_update ? "full" : "incremental") << " update"; 229 230 *success = false; 231 232 // If we're doing a full update, find all of the existing keys first so we can 233 // delete ones that aren't present in the new set of contacts. 234 std::set<std::string> keys_to_delete; 235 if (is_full_update) { 236 leveldb::ReadOptions options; 237 scoped_ptr<leveldb::Iterator> db_iterator(db_->NewIterator(options)); 238 db_iterator->SeekToFirst(); 239 while (db_iterator->Valid()) { 240 std::string key = db_iterator->key().ToString(); 241 if (key != kUpdateMetadataKey) 242 keys_to_delete.insert(key); 243 db_iterator->Next(); 244 } 245 } else { 246 for (ContactIds::const_iterator it = contact_ids_to_delete->begin(); 247 it != contact_ids_to_delete->end(); ++it) { 248 keys_to_delete.insert(*it); 249 } 250 } 251 252 // TODO(derat): Serializing all of the contacts and so we can write them in a 253 // single batch may be expensive, memory-wise. Consider writing them in 254 // several batches instead. (To avoid using partial writes in the event of a 255 // crash, maybe add a dummy "write completed" contact that's removed in the 256 // first batch and added in the last.) 257 leveldb::WriteBatch updates; 258 for (ContactPointers::const_iterator it = contacts_to_save->begin(); 259 it != contacts_to_save->end(); ++it) { 260 const contacts::Contact& contact = **it; 261 if (contact.contact_id() == kUpdateMetadataKey) { 262 LOG(WARNING) << "Skipping contact with reserved ID " 263 << contact.contact_id(); 264 continue; 265 } 266 updates.Put(leveldb::Slice(contact.contact_id()), 267 leveldb::Slice(contact.SerializeAsString())); 268 if (is_full_update) 269 keys_to_delete.erase(contact.contact_id()); 270 } 271 272 for (std::set<std::string>::const_iterator it = keys_to_delete.begin(); 273 it != keys_to_delete.end(); ++it) { 274 updates.Delete(leveldb::Slice(*it)); 275 } 276 277 updates.Put(leveldb::Slice(kUpdateMetadataKey), 278 leveldb::Slice(metadata->SerializeAsString())); 279 280 leveldb::WriteOptions options; 281 options.sync = true; 282 leveldb::Status status = db_->Write(options, &updates); 283 if (status.ok()) 284 *success = true; 285 else 286 LOG(WARNING) << "Failed writing contacts: " << status.ToString(); 287 288 UMA_HISTOGRAM_ENUMERATION("Contacts.DatabaseSaveResult", 289 *success ? 290 HISTOGRAM_SAVE_RESULT_SUCCESS : 291 HISTOGRAM_SAVE_RESULT_FAILURE, 292 HISTOGRAM_SAVE_RESULT_MAX_VALUE); 293 } 294 295 void ContactDatabase::LoadContactsFromTaskRunner( 296 bool* success, 297 ScopedVector<Contact>* contacts, 298 UpdateMetadata* metadata) { 299 DCHECK(IsRunByTaskRunner()); 300 DCHECK(success); 301 DCHECK(contacts); 302 DCHECK(metadata); 303 304 *success = false; 305 contacts->clear(); 306 metadata->Clear(); 307 308 leveldb::ReadOptions options; 309 scoped_ptr<leveldb::Iterator> db_iterator(db_->NewIterator(options)); 310 db_iterator->SeekToFirst(); 311 while (db_iterator->Valid()) { 312 leveldb::Slice value_slice = db_iterator->value(); 313 314 if (db_iterator->key().ToString() == kUpdateMetadataKey) { 315 if (!metadata->ParseFromArray(value_slice.data(), value_slice.size())) { 316 LOG(WARNING) << "Unable to parse metadata"; 317 UMA_HISTOGRAM_ENUMERATION("Contacts.DatabaseLoadResult", 318 HISTOGRAM_LOAD_RESULT_METADATA_PARSE_FAILURE, 319 HISTOGRAM_LOAD_RESULT_MAX_VALUE); 320 return; 321 } 322 } else { 323 scoped_ptr<Contact> contact(new Contact); 324 if (!contact->ParseFromArray(value_slice.data(), value_slice.size())) { 325 LOG(WARNING) << "Unable to parse contact " 326 << db_iterator->key().ToString(); 327 UMA_HISTOGRAM_ENUMERATION("Contacts.DatabaseLoadResult", 328 HISTOGRAM_LOAD_RESULT_CONTACT_PARSE_FAILURE, 329 HISTOGRAM_LOAD_RESULT_MAX_VALUE); 330 return; 331 } 332 contacts->push_back(contact.release()); 333 } 334 db_iterator->Next(); 335 } 336 337 *success = true; 338 UMA_HISTOGRAM_ENUMERATION("Contacts.DatabaseLoadResult", 339 HISTOGRAM_LOAD_RESULT_SUCCESS, 340 HISTOGRAM_LOAD_RESULT_MAX_VALUE); 341 } 342 343 } // namespace contacts 344