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 27 using content::BrowserThread; 28 29 namespace 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. 38 static const int kSizeThresholdForFlush = 200; 39 40 ActivityDatabase::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 54 ActivityDatabase::~ActivityDatabase() {} 55 56 void 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 102 void ActivityDatabase::LogInitFailure() { 103 LOG(ERROR) << "Couldn't initialize the activity log database."; 104 SoftFailureClose(); 105 } 106 107 void 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 117 void ActivityDatabase::RecordBatchedActions() { 118 if (valid_db_) { 119 if (!delegate_->FlushDatabase(&db_)) 120 SoftFailureClose(); 121 } 122 } 123 124 void 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 137 sql::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 148 void 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 162 void 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 172 void ActivityDatabase::SoftFailureClose() { 173 valid_db_ = false; 174 timer_.Stop(); 175 delegate_->OnDatabaseFailure(); 176 } 177 178 void 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 189 void ActivityDatabase::RecordBatchedActionsWhileTesting() { 190 RecordBatchedActions(); 191 timer_.Stop(); 192 } 193 194 void 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 203 bool 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