Home | History | Annotate | Download | only in activity_log
      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_CURRENTLY_ON(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_CURRENTLY_ON(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