- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / activity_log / activity_database.cc
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