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