Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / extensions / activity_log / counting_policy.cc
blobe5b022590b616d17ed485ab51dbd7d8480b54c12
1 // Copyright 2013 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 // A policy for storing activity log data to a database that performs
6 // aggregation to reduce the size of the database. The database layout is
7 // nearly the same as FullStreamUIPolicy, which stores a complete log, with a
8 // few changes:
9 // - a "count" column is added to track how many log records were merged
10 // together into this row
11 // - the "time" column measures the most recent time that the current row was
12 // updated
13 // When writing a record, if a row already exists where all other columns
14 // (extension_id, action_type, api_name, args, urls, etc.) all match, and the
15 // previous time falls within today (the current time), then the count field on
16 // the old row is incremented. Otherwise, a new row is written.
18 // For many text columns, repeated strings are compressed by moving string
19 // storage to a separate table ("string_ids") and storing only an identifier in
20 // the logging table. For example, if the api_name_x column contained the
21 // value 4 and the string_ids table contained a row with primary key 4 and
22 // value 'tabs.query', then the api_name field should be taken to have the
23 // value 'tabs.query'. Each column ending with "_x" is compressed in this way.
24 // All lookups are to the string_ids table, except for the page_url_x and
25 // arg_url_x columns, which are converted via the url_ids table (this
26 // separation of URL values is to help simplify history clearing).
28 // The activitylog_uncompressed view allows for simpler reading of the activity
29 // log contents with identifiers already translated to string values.
31 #include "chrome/browser/extensions/activity_log/counting_policy.h"
33 #include <map>
34 #include <string>
35 #include <vector>
37 #include "base/callback.h"
38 #include "base/files/file_path.h"
39 #include "base/json/json_reader.h"
40 #include "base/json/json_string_value_serializer.h"
41 #include "base/strings/string_util.h"
42 #include "base/strings/stringprintf.h"
43 #include "chrome/common/chrome_constants.h"
44 #include "sql/statement.h"
45 #include "sql/transaction.h"
47 using content::BrowserThread;
49 namespace {
51 using extensions::Action;
53 // Delay between cleaning passes (to delete old action records) through the
54 // database.
55 const int kCleaningDelayInHours = 12;
57 // We should log the arguments to these API calls. Be careful when
58 // constructing this whitelist to not keep arguments that might compromise
59 // privacy by logging too much data to the activity log.
61 // TODO(mvrable): The contents of this whitelist should be reviewed and
62 // expanded as needed.
63 struct ApiList {
64 Action::ActionType type;
65 const char* name;
68 const ApiList kAlwaysLog[] = {
69 {Action::ACTION_API_CALL, "bookmarks.create"},
70 {Action::ACTION_API_CALL, "bookmarks.update"},
71 {Action::ACTION_API_CALL, "cookies.get"},
72 {Action::ACTION_API_CALL, "cookies.getAll"},
73 {Action::ACTION_API_CALL, "extension.connect"},
74 {Action::ACTION_API_CALL, "extension.sendMessage"},
75 {Action::ACTION_API_CALL, "fileSystem.chooseEntry"},
76 {Action::ACTION_API_CALL, "socket.bind"},
77 {Action::ACTION_API_CALL, "socket.connect"},
78 {Action::ACTION_API_CALL, "socket.create"},
79 {Action::ACTION_API_CALL, "socket.listen"},
80 {Action::ACTION_API_CALL, "tabs.executeScript"},
81 {Action::ACTION_API_CALL, "tabs.insertCSS"},
82 {Action::ACTION_API_CALL, "types.ChromeSetting.clear"},
83 {Action::ACTION_API_CALL, "types.ChromeSetting.get"},
84 {Action::ACTION_API_CALL, "types.ChromeSetting.set"},
85 {Action::ACTION_CONTENT_SCRIPT, ""},
86 {Action::ACTION_DOM_ACCESS, "Document.createElement"},
87 {Action::ACTION_DOM_ACCESS, "Document.createElementNS"},
90 // Columns in the main database table. See the file-level comment for a
91 // discussion of how data is stored and the meanings of the _x columns.
92 const char* const kTableContentFields[] = {
93 "count", "extension_id_x", "time", "action_type", "api_name_x", "args_x",
94 "page_url_x", "page_title_x", "arg_url_x", "other_x"};
95 const char* const kTableFieldTypes[] = {
96 "INTEGER NOT NULL DEFAULT 1", "INTEGER NOT NULL", "INTEGER", "INTEGER",
97 "INTEGER", "INTEGER", "INTEGER", "INTEGER", "INTEGER",
98 "INTEGER"};
100 // Miscellaneous SQL commands for initializing the database; these should be
101 // idempotent.
102 static const char kPolicyMiscSetup[] =
103 // The activitylog_uncompressed view performs string lookups for simpler
104 // access to the log data.
105 "DROP VIEW IF EXISTS activitylog_uncompressed;\n"
106 "CREATE VIEW activitylog_uncompressed AS\n"
107 "SELECT count,\n"
108 " x1.value AS extension_id,\n"
109 " time,\n"
110 " action_type,\n"
111 " x2.value AS api_name,\n"
112 " x3.value AS args,\n"
113 " x4.value AS page_url,\n"
114 " x5.value AS page_title,\n"
115 " x6.value AS arg_url,\n"
116 " x7.value AS other,\n"
117 " activitylog_compressed.rowid AS activity_id\n"
118 "FROM activitylog_compressed\n"
119 " LEFT JOIN string_ids AS x1 ON (x1.id = extension_id_x)\n"
120 " LEFT JOIN string_ids AS x2 ON (x2.id = api_name_x)\n"
121 " LEFT JOIN string_ids AS x3 ON (x3.id = args_x)\n"
122 " LEFT JOIN url_ids AS x4 ON (x4.id = page_url_x)\n"
123 " LEFT JOIN string_ids AS x5 ON (x5.id = page_title_x)\n"
124 " LEFT JOIN url_ids AS x6 ON (x6.id = arg_url_x)\n"
125 " LEFT JOIN string_ids AS x7 ON (x7.id = other_x);\n"
126 // An index on all fields except count and time: all the fields that aren't
127 // changed when incrementing a count. This should accelerate finding the
128 // rows to update (at worst several rows will need to be checked to find
129 // the one in the right time range).
130 "CREATE INDEX IF NOT EXISTS activitylog_compressed_index\n"
131 "ON activitylog_compressed(extension_id_x, action_type, api_name_x,\n"
132 " args_x, page_url_x, page_title_x, arg_url_x, other_x)";
134 // SQL statements to clean old, unused entries out of the string and URL id
135 // tables.
136 static const char kStringTableCleanup[] =
137 "DELETE FROM string_ids WHERE id NOT IN\n"
138 "(SELECT extension_id_x FROM activitylog_compressed\n"
139 " WHERE extension_id_x IS NOT NULL\n"
140 " UNION SELECT api_name_x FROM activitylog_compressed\n"
141 " WHERE api_name_x IS NOT NULL\n"
142 " UNION SELECT args_x FROM activitylog_compressed\n"
143 " WHERE args_x IS NOT NULL\n"
144 " UNION SELECT page_title_x FROM activitylog_compressed\n"
145 " WHERE page_title_x IS NOT NULL\n"
146 " UNION SELECT other_x FROM activitylog_compressed\n"
147 " WHERE other_x IS NOT NULL)";
148 static const char kUrlTableCleanup[] =
149 "DELETE FROM url_ids WHERE id NOT IN\n"
150 "(SELECT page_url_x FROM activitylog_compressed\n"
151 " WHERE page_url_x IS NOT NULL\n"
152 " UNION SELECT arg_url_x FROM activitylog_compressed\n"
153 " WHERE arg_url_x IS NOT NULL)";
155 } // namespace
157 namespace extensions {
159 const char CountingPolicy::kTableName[] = "activitylog_compressed";
160 const char CountingPolicy::kReadViewName[] = "activitylog_uncompressed";
162 CountingPolicy::CountingPolicy(Profile* profile)
163 : ActivityLogDatabasePolicy(
164 profile,
165 base::FilePath(chrome::kExtensionActivityLogFilename)),
166 string_table_("string_ids"),
167 url_table_("url_ids"),
168 retention_time_(base::TimeDelta::FromHours(60)) {
169 for (size_t i = 0; i < arraysize(kAlwaysLog); i++) {
170 api_arg_whitelist_.insert(
171 std::make_pair(kAlwaysLog[i].type, kAlwaysLog[i].name));
175 CountingPolicy::~CountingPolicy() {}
177 bool CountingPolicy::InitDatabase(sql::Connection* db) {
178 if (!Util::DropObsoleteTables(db))
179 return false;
181 if (!string_table_.Initialize(db))
182 return false;
183 if (!url_table_.Initialize(db))
184 return false;
186 // Create the unified activity log entry table.
187 if (!ActivityDatabase::InitializeTable(db,
188 kTableName,
189 kTableContentFields,
190 kTableFieldTypes,
191 arraysize(kTableContentFields)))
192 return false;
194 // Create a view for easily accessing the uncompressed form of the data, and
195 // any necessary indexes if needed.
196 return db->Execute(kPolicyMiscSetup);
199 void CountingPolicy::ProcessAction(scoped_refptr<Action> action) {
200 ScheduleAndForget(this, &CountingPolicy::QueueAction, action);
203 void CountingPolicy::QueueAction(scoped_refptr<Action> action) {
204 if (activity_database()->is_db_valid()) {
205 action = action->Clone();
206 Util::StripPrivacySensitiveFields(action);
207 Util::StripArguments(api_arg_whitelist_, action);
209 // If the current action falls on a different date than the ones in the
210 // queue, flush the queue out now to prevent any false merging (actions
211 // from different days being merged).
212 base::Time new_date = action->time().LocalMidnight();
213 if (new_date != queued_actions_date_)
214 activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately);
215 queued_actions_date_ = new_date;
217 ActionQueue::iterator queued_entry = queued_actions_.find(action);
218 if (queued_entry == queued_actions_.end()) {
219 queued_actions_[action] = 1;
220 } else {
221 // Update the timestamp in the key to be the latest time seen. Modifying
222 // the time is safe since that field is not involved in key comparisons
223 // in the map.
224 using std::max;
225 queued_entry->first->set_time(
226 max(queued_entry->first->time(), action->time()));
227 queued_entry->second++;
229 activity_database()->AdviseFlush(queued_actions_.size());
233 bool CountingPolicy::FlushDatabase(sql::Connection* db) {
234 // Columns that must match exactly for database rows to be coalesced.
235 static const char* const matched_columns[] = {
236 "extension_id_x", "action_type", "api_name_x", "args_x", "page_url_x",
237 "page_title_x", "arg_url_x", "other_x"};
238 ActionQueue queue;
239 queue.swap(queued_actions_);
241 // Whether to clean old records out of the activity log database. Do this
242 // much less frequently than database flushes since it is expensive, but
243 // always check on the first database flush (since there might be a large
244 // amount of data to clear).
245 bool clean_database = (last_database_cleaning_time_.is_null() ||
246 Now() - last_database_cleaning_time_ >
247 base::TimeDelta::FromHours(kCleaningDelayInHours));
249 if (queue.empty() && !clean_database)
250 return true;
252 sql::Transaction transaction(db);
253 if (!transaction.Begin())
254 return false;
256 // Adding an Action to the database is a two step process that depends on
257 // whether the count on an existing row can be incremented or a new row needs
258 // to be inserted.
259 // 1. Run the query in locate_str to search for a row which matches and can
260 // have the count incremented.
261 // 2a. If found, increment the count using update_str and the rowid found in
262 // step 1, or
263 // 2b. If not found, insert a new row using insert_str.
264 std::string locate_str =
265 "SELECT rowid FROM " + std::string(kTableName) +
266 " WHERE time >= ? AND time < ?";
267 std::string insert_str =
268 "INSERT INTO " + std::string(kTableName) + "(count, time";
269 std::string update_str =
270 "UPDATE " + std::string(kTableName) +
271 " SET count = count + ?, time = max(?, time)"
272 " WHERE rowid = ?";
274 for (size_t i = 0; i < arraysize(matched_columns); i++) {
275 locate_str = base::StringPrintf(
276 "%s AND %s IS ?", locate_str.c_str(), matched_columns[i]);
277 insert_str =
278 base::StringPrintf("%s, %s", insert_str.c_str(), matched_columns[i]);
280 insert_str += ") VALUES (?, ?";
281 for (size_t i = 0; i < arraysize(matched_columns); i++) {
282 insert_str += ", ?";
284 locate_str += " ORDER BY time DESC LIMIT 1";
285 insert_str += ")";
287 for (ActionQueue::iterator i = queue.begin(); i != queue.end(); ++i) {
288 const Action& action = *i->first.get();
289 int count = i->second;
291 base::Time day_start = action.time().LocalMidnight();
292 base::Time next_day = Util::AddDays(day_start, 1);
294 // The contents in values must match up with fields in matched_columns. A
295 // value of -1 is used to encode a null database value.
296 int64 id;
297 std::vector<int64> matched_values;
299 if (!string_table_.StringToInt(db, action.extension_id(), &id))
300 return false;
301 matched_values.push_back(id);
303 matched_values.push_back(static_cast<int>(action.action_type()));
305 if (!string_table_.StringToInt(db, action.api_name(), &id))
306 return false;
307 matched_values.push_back(id);
309 if (action.args()) {
310 std::string args = Util::Serialize(action.args());
311 // TODO(mvrable): For now, truncate long argument lists. This is a
312 // workaround for excessively-long values coming from DOM logging. When
313 // the V8ValueConverter is fixed to return more reasonable values, we can
314 // drop the truncation.
315 if (args.length() > 10000) {
316 args = "[\"<too_large>\"]";
318 if (!string_table_.StringToInt(db, args, &id))
319 return false;
320 matched_values.push_back(id);
321 } else {
322 matched_values.push_back(-1);
325 std::string page_url_string = action.SerializePageUrl();
326 if (!page_url_string.empty()) {
327 if (!url_table_.StringToInt(db, page_url_string, &id))
328 return false;
329 matched_values.push_back(id);
330 } else {
331 matched_values.push_back(-1);
334 // TODO(mvrable): Create a title_table_?
335 if (!action.page_title().empty()) {
336 if (!string_table_.StringToInt(db, action.page_title(), &id))
337 return false;
338 matched_values.push_back(id);
339 } else {
340 matched_values.push_back(-1);
343 std::string arg_url_string = action.SerializeArgUrl();
344 if (!arg_url_string.empty()) {
345 if (!url_table_.StringToInt(db, arg_url_string, &id))
346 return false;
347 matched_values.push_back(id);
348 } else {
349 matched_values.push_back(-1);
352 if (action.other()) {
353 if (!string_table_.StringToInt(db, Util::Serialize(action.other()), &id))
354 return false;
355 matched_values.push_back(id);
356 } else {
357 matched_values.push_back(-1);
360 // Search for a matching row for this action whose count can be
361 // incremented.
362 sql::Statement locate_statement(db->GetCachedStatement(
363 sql::StatementID(SQL_FROM_HERE), locate_str.c_str()));
364 locate_statement.BindInt64(0, day_start.ToInternalValue());
365 locate_statement.BindInt64(1, next_day.ToInternalValue());
366 for (size_t j = 0; j < matched_values.size(); j++) {
367 // A call to BindNull when matched_values contains -1 is likely not
368 // necessary as parameters default to null before they are explicitly
369 // bound. But to be completely clear, and in case a cached statement
370 // ever comes with some values already bound, we bind all parameters
371 // (even null ones) explicitly.
372 if (matched_values[j] == -1)
373 locate_statement.BindNull(j + 2);
374 else
375 locate_statement.BindInt64(j + 2, matched_values[j]);
378 if (locate_statement.Step()) {
379 // A matching row was found. Update the count and time.
380 int64 rowid = locate_statement.ColumnInt64(0);
381 sql::Statement update_statement(db->GetCachedStatement(
382 sql::StatementID(SQL_FROM_HERE), update_str.c_str()));
383 update_statement.BindInt(0, count);
384 update_statement.BindInt64(1, action.time().ToInternalValue());
385 update_statement.BindInt64(2, rowid);
386 if (!update_statement.Run())
387 return false;
388 } else if (locate_statement.Succeeded()) {
389 // No matching row was found, so we need to insert one.
390 sql::Statement insert_statement(db->GetCachedStatement(
391 sql::StatementID(SQL_FROM_HERE), insert_str.c_str()));
392 insert_statement.BindInt(0, count);
393 insert_statement.BindInt64(1, action.time().ToInternalValue());
394 for (size_t j = 0; j < matched_values.size(); j++) {
395 if (matched_values[j] == -1)
396 insert_statement.BindNull(j + 2);
397 else
398 insert_statement.BindInt64(j + 2, matched_values[j]);
400 if (!insert_statement.Run())
401 return false;
402 } else {
403 // Database error.
404 return false;
408 if (clean_database) {
409 base::Time cutoff = (Now() - retention_time()).LocalMidnight();
410 if (!CleanOlderThan(db, cutoff))
411 return false;
412 last_database_cleaning_time_ = Now();
415 if (!transaction.Commit())
416 return false;
418 return true;
421 scoped_ptr<Action::ActionVector> CountingPolicy::DoReadFilteredData(
422 const std::string& extension_id,
423 const Action::ActionType type,
424 const std::string& api_name,
425 const std::string& page_url,
426 const std::string& arg_url,
427 const int days_ago) {
428 // Ensure data is flushed to the database first so that we query over all
429 // data.
430 activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately);
431 scoped_ptr<Action::ActionVector> actions(new Action::ActionVector());
433 sql::Connection* db = GetDatabaseConnection();
434 if (!db)
435 return actions.Pass();
437 // Build up the query based on which parameters were specified.
438 std::string where_str = "";
439 std::string where_next = "";
440 if (!extension_id.empty()) {
441 where_str += "extension_id=?";
442 where_next = " AND ";
444 if (!api_name.empty()) {
445 where_str += where_next + "api_name=?";
446 where_next = " AND ";
448 if (type != Action::ACTION_ANY) {
449 where_str += where_next + "action_type=?";
450 where_next = " AND ";
452 if (!page_url.empty()) {
453 where_str += where_next + "page_url LIKE ?";
454 where_next = " AND ";
456 if (!arg_url.empty()) {
457 where_str += where_next + "arg_url LIKE ?";
458 where_next = " AND ";
460 if (days_ago >= 0)
461 where_str += where_next + "time BETWEEN ? AND ?";
463 std::string query_str = base::StringPrintf(
464 "SELECT extension_id,time, action_type, api_name, args, page_url,"
465 "page_title, arg_url, other, count, activity_id FROM %s %s %s ORDER BY "
466 "count DESC, time DESC LIMIT 300",
467 kReadViewName,
468 where_str.empty() ? "" : "WHERE",
469 where_str.c_str());
470 sql::Statement query(db->GetUniqueStatement(query_str.c_str()));
471 int i = -1;
472 if (!extension_id.empty())
473 query.BindString(++i, extension_id);
474 if (!api_name.empty())
475 query.BindString(++i, api_name);
476 if (type != Action::ACTION_ANY)
477 query.BindInt(++i, static_cast<int>(type));
478 if (!page_url.empty())
479 query.BindString(++i, page_url + "%");
480 if (!arg_url.empty())
481 query.BindString(++i, arg_url + "%");
482 if (days_ago >= 0) {
483 int64 early_bound;
484 int64 late_bound;
485 Util::ComputeDatabaseTimeBounds(Now(), days_ago, &early_bound, &late_bound);
486 query.BindInt64(++i, early_bound);
487 query.BindInt64(++i, late_bound);
490 // Execute the query and get results.
491 while (query.is_valid() && query.Step()) {
492 scoped_refptr<Action> action =
493 new Action(query.ColumnString(0),
494 base::Time::FromInternalValue(query.ColumnInt64(1)),
495 static_cast<Action::ActionType>(query.ColumnInt(2)),
496 query.ColumnString(3), query.ColumnInt64(10));
498 if (query.ColumnType(4) != sql::COLUMN_TYPE_NULL) {
499 scoped_ptr<base::Value> parsed_value(
500 base::JSONReader::DeprecatedRead(query.ColumnString(4)));
501 if (parsed_value && parsed_value->IsType(base::Value::TYPE_LIST)) {
502 action->set_args(make_scoped_ptr(
503 static_cast<base::ListValue*>(parsed_value.release())));
507 action->ParsePageUrl(query.ColumnString(5));
508 action->set_page_title(query.ColumnString(6));
509 action->ParseArgUrl(query.ColumnString(7));
511 if (query.ColumnType(8) != sql::COLUMN_TYPE_NULL) {
512 scoped_ptr<base::Value> parsed_value(
513 base::JSONReader::DeprecatedRead(query.ColumnString(8)));
514 if (parsed_value && parsed_value->IsType(base::Value::TYPE_DICTIONARY)) {
515 action->set_other(make_scoped_ptr(
516 static_cast<base::DictionaryValue*>(parsed_value.release())));
519 action->set_count(query.ColumnInt(9));
520 actions->push_back(action);
523 return actions.Pass();
526 void CountingPolicy::DoRemoveActions(const std::vector<int64>& action_ids) {
527 if (action_ids.empty())
528 return;
530 sql::Connection* db = GetDatabaseConnection();
531 if (!db) {
532 LOG(ERROR) << "Unable to connect to database";
533 return;
536 // Flush data first so the activity removal affects queued-up data as well.
537 activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately);
539 sql::Transaction transaction(db);
540 if (!transaction.Begin())
541 return;
543 std::string statement_str =
544 base::StringPrintf("DELETE FROM %s WHERE rowid = ?", kTableName);
545 sql::Statement statement(db->GetCachedStatement(
546 sql::StatementID(SQL_FROM_HERE), statement_str.c_str()));
547 for (size_t i = 0; i < action_ids.size(); i++) {
548 statement.Reset(true);
549 statement.BindInt64(0, action_ids[i]);
550 if (!statement.Run()) {
551 LOG(ERROR) << "Removing activities from database failed: "
552 << statement.GetSQLStatement();
553 break;
557 CleanStringTables(db);
559 if (!transaction.Commit()) {
560 LOG(ERROR) << "Removing activities from database failed";
564 void CountingPolicy::DoRemoveURLs(const std::vector<GURL>& restrict_urls) {
565 sql::Connection* db = GetDatabaseConnection();
566 if (!db) {
567 LOG(ERROR) << "Unable to connect to database";
568 return;
571 // Flush data first so the URL clearing affects queued-up data as well.
572 activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately);
574 // If no restrictions then then all URLs need to be removed.
575 if (restrict_urls.empty()) {
576 std::string sql_str = base::StringPrintf(
577 "UPDATE %s SET page_url_x=NULL,page_title_x=NULL,arg_url_x=NULL",
578 kTableName);
580 sql::Statement statement;
581 statement.Assign(db->GetCachedStatement(
582 sql::StatementID(SQL_FROM_HERE), sql_str.c_str()));
584 if (!statement.Run()) {
585 LOG(ERROR) << "Removing all URLs from database failed: "
586 << statement.GetSQLStatement();
587 return;
591 // If URLs are specified then restrict to only those URLs.
592 for (size_t i = 0; i < restrict_urls.size(); ++i) {
593 int64 url_id;
594 if (!restrict_urls[i].is_valid() ||
595 !url_table_.StringToInt(db, restrict_urls[i].spec(), &url_id)) {
596 continue;
599 // Remove any that match the page_url.
600 std::string sql_str = base::StringPrintf(
601 "UPDATE %s SET page_url_x=NULL,page_title_x=NULL WHERE page_url_x IS ?",
602 kTableName);
604 sql::Statement statement;
605 statement.Assign(db->GetCachedStatement(
606 sql::StatementID(SQL_FROM_HERE), sql_str.c_str()));
607 statement.BindInt64(0, url_id);
609 if (!statement.Run()) {
610 LOG(ERROR) << "Removing page URL from database failed: "
611 << statement.GetSQLStatement();
612 return;
615 // Remove any that match the arg_url.
616 sql_str = base::StringPrintf(
617 "UPDATE %s SET arg_url_x=NULL WHERE arg_url_x IS ?", kTableName);
619 statement.Assign(db->GetCachedStatement(
620 sql::StatementID(SQL_FROM_HERE), sql_str.c_str()));
621 statement.BindInt64(0, url_id);
623 if (!statement.Run()) {
624 LOG(ERROR) << "Removing arg URL from database failed: "
625 << statement.GetSQLStatement();
626 return;
630 // Clean up unused strings from the strings and urls table to really delete
631 // the urls and page titles. Should be called even if an error occured when
632 // removing a URL as there may some things to clean up.
633 CleanStringTables(db);
636 void CountingPolicy::DoRemoveExtensionData(const std::string& extension_id) {
637 if (extension_id.empty())
638 return;
640 sql::Connection* db = GetDatabaseConnection();
641 if (!db) {
642 LOG(ERROR) << "Unable to connect to database";
643 return;
646 // Make sure any queued in memory are sent to the database before cleaning.
647 activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately);
649 std::string sql_str = base::StringPrintf(
650 "DELETE FROM %s WHERE extension_id_x=?", kTableName);
651 sql::Statement statement(
652 db->GetCachedStatement(sql::StatementID(SQL_FROM_HERE), sql_str.c_str()));
653 int64 id;
654 if (string_table_.StringToInt(db, extension_id, &id)) {
655 statement.BindInt64(0, id);
656 } else {
657 // If the string isn't listed, that means we never recorded anything about
658 // the extension so there's no deletion to do.
659 statement.Clear();
660 return;
662 if (!statement.Run()) {
663 LOG(ERROR) << "Removing URLs for extension "
664 << extension_id << "from database failed: "
665 << statement.GetSQLStatement();
667 CleanStringTables(db);
670 void CountingPolicy::DoDeleteDatabase() {
671 sql::Connection* db = GetDatabaseConnection();
672 if (!db) {
673 LOG(ERROR) << "Unable to connect to database";
674 return;
677 queued_actions_.clear();
679 // Not wrapped in a transaction because a late failure shouldn't undo a
680 // previous deletion.
681 std::string sql_str = base::StringPrintf("DELETE FROM %s", kTableName);
682 sql::Statement statement(db->GetCachedStatement(
683 sql::StatementID(SQL_FROM_HERE),
684 sql_str.c_str()));
685 if (!statement.Run()) {
686 LOG(ERROR) << "Deleting the database failed: "
687 << statement.GetSQLStatement();
688 return;
690 statement.Clear();
691 string_table_.ClearCache();
692 statement.Assign(db->GetCachedStatement(sql::StatementID(SQL_FROM_HERE),
693 "DELETE FROM string_ids"));
694 if (!statement.Run()) {
695 LOG(ERROR) << "Deleting the database failed: "
696 << statement.GetSQLStatement();
697 return;
699 statement.Clear();
700 url_table_.ClearCache();
701 statement.Assign(db->GetCachedStatement(sql::StatementID(SQL_FROM_HERE),
702 "DELETE FROM url_ids"));
703 if (!statement.Run()) {
704 LOG(ERROR) << "Deleting the database failed: "
705 << statement.GetSQLStatement();
706 return;
708 statement.Clear();
709 statement.Assign(db->GetCachedStatement(sql::StatementID(SQL_FROM_HERE),
710 "VACUUM"));
711 if (!statement.Run()) {
712 LOG(ERROR) << "Vacuuming the database failed: "
713 << statement.GetSQLStatement();
717 void CountingPolicy::ReadFilteredData(
718 const std::string& extension_id,
719 const Action::ActionType type,
720 const std::string& api_name,
721 const std::string& page_url,
722 const std::string& arg_url,
723 const int days_ago,
724 const base::Callback
725 <void(scoped_ptr<Action::ActionVector>)>& callback) {
726 BrowserThread::PostTaskAndReplyWithResult(
727 BrowserThread::DB,
728 FROM_HERE,
729 base::Bind(&CountingPolicy::DoReadFilteredData,
730 base::Unretained(this),
731 extension_id,
732 type,
733 api_name,
734 page_url,
735 arg_url,
736 days_ago),
737 callback);
740 void CountingPolicy::RemoveActions(const std::vector<int64>& action_ids) {
741 ScheduleAndForget(this, &CountingPolicy::DoRemoveActions, action_ids);
744 void CountingPolicy::RemoveURLs(const std::vector<GURL>& restrict_urls) {
745 ScheduleAndForget(this, &CountingPolicy::DoRemoveURLs, restrict_urls);
748 void CountingPolicy::RemoveExtensionData(const std::string& extension_id) {
749 ScheduleAndForget(this, &CountingPolicy::DoRemoveExtensionData, extension_id);
752 void CountingPolicy::DeleteDatabase() {
753 ScheduleAndForget(this, &CountingPolicy::DoDeleteDatabase);
756 void CountingPolicy::OnDatabaseFailure() {
757 queued_actions_.clear();
760 void CountingPolicy::OnDatabaseClose() {
761 delete this;
764 // Cleans old records from the activity log database.
765 bool CountingPolicy::CleanOlderThan(sql::Connection* db,
766 const base::Time& cutoff) {
767 std::string clean_statement =
768 "DELETE FROM " + std::string(kTableName) + " WHERE time < ?";
769 sql::Statement cleaner(db->GetCachedStatement(sql::StatementID(SQL_FROM_HERE),
770 clean_statement.c_str()));
771 cleaner.BindInt64(0, cutoff.ToInternalValue());
772 if (!cleaner.Run())
773 return false;
774 return CleanStringTables(db);
777 // Cleans unused interned strings from the database. This should be run after
778 // deleting rows from the main log table to clean out stale values.
779 bool CountingPolicy::CleanStringTables(sql::Connection* db) {
780 sql::Statement cleaner1(db->GetCachedStatement(
781 sql::StatementID(SQL_FROM_HERE), kStringTableCleanup));
782 if (!cleaner1.Run())
783 return false;
784 if (db->GetLastChangeCount() > 0)
785 string_table_.ClearCache();
787 sql::Statement cleaner2(db->GetCachedStatement(
788 sql::StatementID(SQL_FROM_HERE), kUrlTableCleanup));
789 if (!cleaner2.Run())
790 return false;
791 if (db->GetLastChangeCount() > 0)
792 url_table_.ClearCache();
794 return true;
797 void CountingPolicy::Close() {
798 // The policy object should have never been created if there's no DB thread.
799 DCHECK(BrowserThread::IsMessageLoopValid(BrowserThread::DB));
800 ScheduleAndForget(activity_database(), &ActivityDatabase::Close);
803 } // namespace extensions