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/extensions/activity_log/activity_database.h" 6 7#include <string> 8 9#include "base/command_line.h" 10#include "base/logging.h" 11#include "base/strings/string_util.h" 12#include "base/strings/stringprintf.h" 13#include "base/threading/thread.h" 14#include "base/threading/thread_checker.h" 15#include "base/time/clock.h" 16#include "base/time/time.h" 17#include "chrome/browser/extensions/activity_log/fullstream_ui_policy.h" 18#include "chrome/common/chrome_switches.h" 19#include "sql/error_delegate_util.h" 20#include "sql/transaction.h" 21#include "third_party/sqlite/sqlite3.h" 22 23#if defined(OS_MACOSX) 24#include "base/mac/mac_util.h" 25#endif 26 27using content::BrowserThread; 28 29namespace extensions { 30 31// A size threshold at which data should be flushed to the database. The 32// ActivityDatabase will signal the Delegate to write out data based on a 33// periodic timer, but will also initiate a flush if AdviseFlush indicates that 34// more than kSizeThresholdForFlush action records are queued in memory. This 35// should be set large enough that write costs can be amortized across many 36// records, but not so large that too much space can be tied up holding records 37// in memory. 38static const int kSizeThresholdForFlush = 200; 39 40ActivityDatabase::ActivityDatabase(ActivityDatabase::Delegate* delegate) 41 : delegate_(delegate), 42 valid_db_(false), 43 batch_mode_(true), 44 already_closed_(false), 45 did_init_(false) { 46 if (CommandLine::ForCurrentProcess()->HasSwitch( 47 switches::kEnableExtensionActivityLogTesting)) { 48 batching_period_ = base::TimeDelta::FromSeconds(10); 49 } else { 50 batching_period_ = base::TimeDelta::FromMinutes(2); 51 } 52} 53 54ActivityDatabase::~ActivityDatabase() {} 55 56void ActivityDatabase::Init(const base::FilePath& db_name) { 57 if (did_init_) return; 58 did_init_ = true; 59 if (BrowserThread::IsMessageLoopValid(BrowserThread::DB)) 60 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 61 db_.set_histogram_tag("Activity"); 62 db_.set_error_callback( 63 base::Bind(&ActivityDatabase::DatabaseErrorCallback, 64 base::Unretained(this))); 65 db_.set_page_size(4096); 66 db_.set_cache_size(32); 67 68 if (!db_.Open(db_name)) { 69 LOG(ERROR) << db_.GetErrorMessage(); 70 return LogInitFailure(); 71 } 72 73 // Wrap the initialization in a transaction so that the db doesn't 74 // get corrupted if init fails/crashes. 75 sql::Transaction committer(&db_); 76 if (!committer.Begin()) 77 return LogInitFailure(); 78 79#if defined(OS_MACOSX) 80 // Exclude the database from backups. 81 base::mac::SetFileBackupExclusion(db_name); 82#endif 83 84 if (!delegate_->InitDatabase(&db_)) 85 return LogInitFailure(); 86 87 sql::InitStatus stat = committer.Commit() ? sql::INIT_OK : sql::INIT_FAILURE; 88 if (stat != sql::INIT_OK) 89 return LogInitFailure(); 90 91 // Pre-loads the first <cache-size> pages into the cache. 92 // Doesn't do anything if the database is new. 93 db_.Preload(); 94 95 valid_db_ = true; 96 timer_.Start(FROM_HERE, 97 batching_period_, 98 this, 99 &ActivityDatabase::RecordBatchedActions); 100} 101 102void ActivityDatabase::LogInitFailure() { 103 LOG(ERROR) << "Couldn't initialize the activity log database."; 104 SoftFailureClose(); 105} 106 107void ActivityDatabase::AdviseFlush(int size) { 108 if (!valid_db_) 109 return; 110 if (!batch_mode_ || size == kFlushImmediately || 111 size >= kSizeThresholdForFlush) { 112 if (!delegate_->FlushDatabase(&db_)) 113 SoftFailureClose(); 114 } 115} 116 117void ActivityDatabase::RecordBatchedActions() { 118 if (valid_db_) { 119 if (!delegate_->FlushDatabase(&db_)) 120 SoftFailureClose(); 121 } 122} 123 124void ActivityDatabase::SetBatchModeForTesting(bool batch_mode) { 125 if (batch_mode && !batch_mode_) { 126 timer_.Start(FROM_HERE, 127 batching_period_, 128 this, 129 &ActivityDatabase::RecordBatchedActions); 130 } else if (!batch_mode && batch_mode_) { 131 timer_.Stop(); 132 RecordBatchedActions(); 133 } 134 batch_mode_ = batch_mode; 135} 136 137sql::Connection* ActivityDatabase::GetSqlConnection() { 138 if (BrowserThread::IsMessageLoopValid(BrowserThread::DB)) 139 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 140 if (valid_db_) { 141 return &db_; 142 } else { 143 LOG(WARNING) << "Activity log database is not valid"; 144 return NULL; 145 } 146} 147 148void ActivityDatabase::Close() { 149 timer_.Stop(); 150 if (!already_closed_) { 151 RecordBatchedActions(); 152 db_.reset_error_callback(); 153 } 154 valid_db_ = false; 155 already_closed_ = true; 156 // Call DatabaseCloseCallback() just before deleting the ActivityDatabase 157 // itself--these two objects should have the same lifetime. 158 delegate_->OnDatabaseClose(); 159 delete this; 160} 161 162void ActivityDatabase::HardFailureClose() { 163 if (already_closed_) return; 164 valid_db_ = false; 165 timer_.Stop(); 166 db_.reset_error_callback(); 167 db_.RazeAndClose(); 168 delegate_->OnDatabaseFailure(); 169 already_closed_ = true; 170} 171 172void ActivityDatabase::SoftFailureClose() { 173 valid_db_ = false; 174 timer_.Stop(); 175 delegate_->OnDatabaseFailure(); 176} 177 178void ActivityDatabase::DatabaseErrorCallback(int error, sql::Statement* stmt) { 179 if (sql::IsErrorCatastrophic(error)) { 180 LOG(ERROR) << "Killing the ActivityDatabase due to catastrophic error."; 181 HardFailureClose(); 182 } else if (error != SQLITE_BUSY) { 183 // We ignore SQLITE_BUSY errors because they are presumably transient. 184 LOG(ERROR) << "Closing the ActivityDatabase due to error."; 185 SoftFailureClose(); 186 } 187} 188 189void ActivityDatabase::RecordBatchedActionsWhileTesting() { 190 RecordBatchedActions(); 191 timer_.Stop(); 192} 193 194void ActivityDatabase::SetTimerForTesting(int ms) { 195 timer_.Stop(); 196 timer_.Start(FROM_HERE, 197 base::TimeDelta::FromMilliseconds(ms), 198 this, 199 &ActivityDatabase::RecordBatchedActionsWhileTesting); 200} 201 202// static 203bool ActivityDatabase::InitializeTable(sql::Connection* db, 204 const char* table_name, 205 const char* content_fields[], 206 const char* field_types[], 207 const int num_content_fields) { 208 if (!db->DoesTableExist(table_name)) { 209 std::string table_creator = 210 base::StringPrintf("CREATE TABLE %s (", table_name); 211 for (int i = 0; i < num_content_fields; i++) { 212 table_creator += base::StringPrintf("%s%s %s", 213 i == 0 ? "" : ", ", 214 content_fields[i], 215 field_types[i]); 216 } 217 table_creator += ")"; 218 if (!db->Execute(table_creator.c_str())) 219 return false; 220 } else { 221 // In case we ever want to add new fields, this initializes them to be 222 // empty strings. 223 for (int i = 0; i < num_content_fields; i++) { 224 if (!db->DoesColumnExist(table_name, content_fields[i])) { 225 std::string table_updater = base::StringPrintf( 226 "ALTER TABLE %s ADD COLUMN %s %s; ", 227 table_name, 228 content_fields[i], 229 field_types[i]); 230 if (!db->Execute(table_updater.c_str())) 231 return false; 232 } 233 } 234 } 235 return true; 236} 237 238} // namespace extensions 239