counting_policy.cc revision 58537e28ecd584eab876aee8be7156509866d23a
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. 4 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. 17// 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). 27// 28// The activitylog_uncompressed view allows for simpler reading of the activity 29// log contents with identifiers already translated to string values. 30 31#include "chrome/browser/extensions/activity_log/counting_policy.h" 32 33#include <map> 34#include <string> 35#include <vector> 36 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 45using content::BrowserThread; 46 47namespace { 48 49using extensions::Action; 50 51// Delay between cleaning passes (to delete old action records) through the 52// database. 53const int kCleaningDelayInHours = 12; 54 55// We should log the arguments to these API calls. Be careful when 56// constructing this whitelist to not keep arguments that might compromise 57// privacy by logging too much data to the activity log. 58// 59// TODO(mvrable): The contents of this whitelist should be reviewed and 60// expanded as needed. 61struct ApiList { 62 Action::ActionType type; 63 const char* name; 64}; 65 66const ApiList kAlwaysLog[] = { 67 {Action::ACTION_API_CALL, "extension.connect"}, 68 {Action::ACTION_API_CALL, "extension.sendMessage"}, 69 {Action::ACTION_API_CALL, "tabs.executeScript"}, 70 {Action::ACTION_API_CALL, "tabs.insertCSS"}, 71 {Action::ACTION_CONTENT_SCRIPT, ""}, 72 {Action::ACTION_DOM_ACCESS, "XMLHttpRequest.open"}, 73}; 74 75// Columns in the main database table. See the file-level comment for a 76// discussion of how data is stored and the meanings of the _x columns. 77const char* kTableContentFields[] = { 78 "count", "extension_id_x", "time", "action_type", "api_name_x", "args_x", 79 "page_url_x", "page_title_x", "arg_url_x", "other_x"}; 80const char* kTableFieldTypes[] = { 81 "INTEGER NOT NULL DEFAULT 1", "INTEGER NOT NULL", "INTEGER", "INTEGER", 82 "INTEGER", "INTEGER", "INTEGER", "INTEGER", "INTEGER", 83 "INTEGER"}; 84 85// Miscellaneous SQL commands for initializing the database; these should be 86// idempotent. 87static const char kPolicyMiscSetup[] = 88 // The activitylog_uncompressed view performs string lookups for simpler 89 // access to the log data. 90 "DROP VIEW IF EXISTS activitylog_uncompressed;\n" 91 "CREATE VIEW activitylog_uncompressed AS\n" 92 "SELECT count,\n" 93 " x1.value AS extension_id,\n" 94 " time,\n" 95 " action_type,\n" 96 " x2.value AS api_name,\n" 97 " x3.value AS args,\n" 98 " x4.value AS page_url,\n" 99 " x5.value AS page_title,\n" 100 " x6.value AS arg_url,\n" 101 " x7.value AS other\n" 102 "FROM activitylog_compressed\n" 103 " LEFT JOIN string_ids AS x1 ON (x1.id = extension_id_x)\n" 104 " LEFT JOIN string_ids AS x2 ON (x2.id = api_name_x)\n" 105 " LEFT JOIN string_ids AS x3 ON (x3.id = args_x)\n" 106 " LEFT JOIN url_ids AS x4 ON (x4.id = page_url_x)\n" 107 " LEFT JOIN string_ids AS x5 ON (x5.id = page_title_x)\n" 108 " LEFT JOIN url_ids AS x6 ON (x6.id = arg_url_x)\n" 109 " LEFT JOIN string_ids AS x7 ON (x7.id = other_x);\n" 110 // An index on all fields except count and time: all the fields that aren't 111 // changed when incrementing a count. This should accelerate finding the 112 // rows to update (at worst several rows will need to be checked to find 113 // the one in the right time range). 114 "CREATE INDEX IF NOT EXISTS activitylog_compressed_index\n" 115 "ON activitylog_compressed(extension_id_x, action_type, api_name_x,\n" 116 " args_x, page_url_x, page_title_x, arg_url_x, other_x)"; 117 118// SQL statements to clean old, unused entries out of the string and URL id 119// tables. 120static const char kStringTableCleanup[] = 121 "DELETE FROM string_ids WHERE id NOT IN\n" 122 "(SELECT extension_id_x FROM activitylog_compressed\n" 123 " WHERE extension_id_x IS NOT NULL\n" 124 " UNION SELECT api_name_x FROM activitylog_compressed\n" 125 " WHERE api_name_x IS NOT NULL\n" 126 " UNION SELECT args_x FROM activitylog_compressed\n" 127 " WHERE args_x IS NOT NULL\n" 128 " UNION SELECT page_title_x FROM activitylog_compressed\n" 129 " WHERE page_title_x IS NOT NULL\n" 130 " UNION SELECT other_x FROM activitylog_compressed\n" 131 " WHERE other_x IS NOT NULL)"; 132static const char kUrlTableCleanup[] = 133 "DELETE FROM url_ids WHERE id NOT IN\n" 134 "(SELECT page_url_x FROM activitylog_compressed\n" 135 " WHERE page_url_x IS NOT NULL\n" 136 " UNION SELECT arg_url_x FROM activitylog_compressed\n" 137 " WHERE arg_url_x IS NOT NULL)"; 138 139} // namespace 140 141namespace extensions { 142 143const char* CountingPolicy::kTableName = "activitylog_compressed"; 144const char* CountingPolicy::kReadViewName = "activitylog_uncompressed"; 145 146CountingPolicy::CountingPolicy(Profile* profile) 147 : ActivityLogDatabasePolicy( 148 profile, 149 base::FilePath(chrome::kExtensionActivityLogFilename)), 150 string_table_("string_ids"), 151 url_table_("url_ids"), 152 retention_time_(base::TimeDelta::FromHours(60)) { 153 for (size_t i = 0; i < arraysize(kAlwaysLog); i++) { 154 api_arg_whitelist_.insert( 155 std::make_pair(kAlwaysLog[i].type, kAlwaysLog[i].name)); 156 } 157} 158 159CountingPolicy::~CountingPolicy() {} 160 161bool CountingPolicy::InitDatabase(sql::Connection* db) { 162 if (!Util::DropObsoleteTables(db)) 163 return false; 164 165 if (!string_table_.Initialize(db)) 166 return false; 167 if (!url_table_.Initialize(db)) 168 return false; 169 170 // Create the unified activity log entry table. 171 if (!ActivityDatabase::InitializeTable(db, 172 kTableName, 173 kTableContentFields, 174 kTableFieldTypes, 175 arraysize(kTableContentFields))) 176 return false; 177 178 // Create a view for easily accessing the uncompressed form of the data, and 179 // any necessary indexes if needed. 180 return db->Execute(kPolicyMiscSetup); 181} 182 183void CountingPolicy::ProcessAction(scoped_refptr<Action> action) { 184 ScheduleAndForget(this, &CountingPolicy::QueueAction, action); 185} 186 187void CountingPolicy::QueueAction(scoped_refptr<Action> action) { 188 if (activity_database()->is_db_valid()) { 189 action = action->Clone(); 190 Util::StripPrivacySensitiveFields(action); 191 Util::StripArguments(api_arg_whitelist_, action); 192 193 // If the current action falls on a different date than the ones in the 194 // queue, flush the queue out now to prevent any false merging (actions 195 // from different days being merged). 196 base::Time new_date = action->time().LocalMidnight(); 197 if (new_date != queued_actions_date_) 198 activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately); 199 queued_actions_date_ = new_date; 200 201 ActionQueue::iterator queued_entry = queued_actions_.find(action); 202 if (queued_entry == queued_actions_.end()) { 203 queued_actions_[action] = 1; 204 } else { 205 // Update the timestamp in the key to be the latest time seen. Modifying 206 // the time is safe since that field is not involved in key comparisons 207 // in the map. 208 using std::max; 209 queued_entry->first->set_time( 210 max(queued_entry->first->time(), action->time())); 211 queued_entry->second++; 212 } 213 activity_database()->AdviseFlush(queued_actions_.size()); 214 } 215} 216 217bool CountingPolicy::FlushDatabase(sql::Connection* db) { 218 // Columns that must match exactly for database rows to be coalesced. 219 static const char* matched_columns[] = { 220 "extension_id_x", "action_type", "api_name_x", "args_x", "page_url_x", 221 "page_title_x", "arg_url_x", "other_x"}; 222 ActionQueue queue; 223 queue.swap(queued_actions_); 224 225 // Whether to clean old records out of the activity log database. Do this 226 // much less frequently than database flushes since it is expensive, but 227 // always check on the first database flush (since there might be a large 228 // amount of data to clear). 229 bool clean_database = (last_database_cleaning_time_.is_null() || 230 Now() - last_database_cleaning_time_ > 231 base::TimeDelta::FromHours(kCleaningDelayInHours)); 232 233 if (queue.empty() && !clean_database) 234 return true; 235 236 sql::Transaction transaction(db); 237 if (!transaction.Begin()) 238 return false; 239 240 // Adding an Action to the database is a two step process that depends on 241 // whether the count on an existing row can be incremented or a new row needs 242 // to be inserted. 243 // 1. Run the query in locate_str to search for a row which matches and can 244 // have the count incremented. 245 // 2a. If found, increment the count using update_str and the rowid found in 246 // step 1, or 247 // 2b. If not found, insert a new row using insert_str. 248 std::string locate_str = 249 "SELECT rowid FROM " + std::string(kTableName) + 250 " WHERE time >= ? AND time < ?"; 251 std::string insert_str = 252 "INSERT INTO " + std::string(kTableName) + "(count, time"; 253 std::string update_str = 254 "UPDATE " + std::string(kTableName) + 255 " SET count = count + ?, time = max(?, time)" 256 " WHERE rowid = ?"; 257 258 for (size_t i = 0; i < arraysize(matched_columns); i++) { 259 locate_str = base::StringPrintf( 260 "%s AND %s IS ?", locate_str.c_str(), matched_columns[i]); 261 insert_str = 262 base::StringPrintf("%s, %s", insert_str.c_str(), matched_columns[i]); 263 } 264 insert_str += ") VALUES (?, ?"; 265 for (size_t i = 0; i < arraysize(matched_columns); i++) { 266 insert_str += ", ?"; 267 } 268 locate_str += " ORDER BY time DESC LIMIT 1"; 269 insert_str += ")"; 270 271 for (ActionQueue::iterator i = queue.begin(); i != queue.end(); ++i) { 272 const Action& action = *i->first; 273 int count = i->second; 274 275 base::Time day_start = action.time().LocalMidnight(); 276 base::Time next_day = Util::AddDays(day_start, 1); 277 278 // The contents in values must match up with fields in matched_columns. A 279 // value of -1 is used to encode a null database value. 280 int64 id; 281 std::vector<int64> matched_values; 282 283 if (!string_table_.StringToInt(db, action.extension_id(), &id)) 284 return false; 285 matched_values.push_back(id); 286 287 matched_values.push_back(static_cast<int>(action.action_type())); 288 289 if (!string_table_.StringToInt(db, action.api_name(), &id)) 290 return false; 291 matched_values.push_back(id); 292 293 if (action.args()) { 294 std::string args = Util::Serialize(action.args()); 295 // TODO(mvrable): For now, truncate long argument lists. This is a 296 // workaround for excessively-long values coming from DOM logging. When 297 // the V8ValueConverter is fixed to return more reasonable values, we can 298 // drop the truncation. 299 if (args.length() > 10000) { 300 args = "[\"<too_large>\"]"; 301 } 302 if (!string_table_.StringToInt(db, args, &id)) 303 return false; 304 matched_values.push_back(id); 305 } else { 306 matched_values.push_back(-1); 307 } 308 309 std::string page_url_string = action.SerializePageUrl(); 310 if (!page_url_string.empty()) { 311 if (!url_table_.StringToInt(db, page_url_string, &id)) 312 return false; 313 matched_values.push_back(id); 314 } else { 315 matched_values.push_back(-1); 316 } 317 318 // TODO(mvrable): Create a title_table_? 319 if (!action.page_title().empty()) { 320 if (!string_table_.StringToInt(db, action.page_title(), &id)) 321 return false; 322 matched_values.push_back(id); 323 } else { 324 matched_values.push_back(-1); 325 } 326 327 std::string arg_url_string = action.SerializeArgUrl(); 328 if (!arg_url_string.empty()) { 329 if (!url_table_.StringToInt(db, arg_url_string, &id)) 330 return false; 331 matched_values.push_back(id); 332 } else { 333 matched_values.push_back(-1); 334 } 335 336 if (action.other()) { 337 if (!string_table_.StringToInt(db, Util::Serialize(action.other()), &id)) 338 return false; 339 matched_values.push_back(id); 340 } else { 341 matched_values.push_back(-1); 342 } 343 344 // Search for a matching row for this action whose count can be 345 // incremented. 346 sql::Statement locate_statement(db->GetCachedStatement( 347 sql::StatementID(SQL_FROM_HERE), locate_str.c_str())); 348 locate_statement.BindInt64(0, day_start.ToInternalValue()); 349 locate_statement.BindInt64(1, next_day.ToInternalValue()); 350 for (size_t j = 0; j < matched_values.size(); j++) { 351 // A call to BindNull when matched_values contains -1 is likely not 352 // necessary as parameters default to null before they are explicitly 353 // bound. But to be completely clear, and in case a cached statement 354 // ever comes with some values already bound, we bind all parameters 355 // (even null ones) explicitly. 356 if (matched_values[j] == -1) 357 locate_statement.BindNull(j + 2); 358 else 359 locate_statement.BindInt64(j + 2, matched_values[j]); 360 } 361 362 if (locate_statement.Step()) { 363 // A matching row was found. Update the count and time. 364 int64 rowid = locate_statement.ColumnInt64(0); 365 sql::Statement update_statement(db->GetCachedStatement( 366 sql::StatementID(SQL_FROM_HERE), update_str.c_str())); 367 update_statement.BindInt(0, count); 368 update_statement.BindInt64(1, action.time().ToInternalValue()); 369 update_statement.BindInt64(2, rowid); 370 if (!update_statement.Run()) 371 return false; 372 } else if (locate_statement.Succeeded()) { 373 // No matching row was found, so we need to insert one. 374 sql::Statement insert_statement(db->GetCachedStatement( 375 sql::StatementID(SQL_FROM_HERE), insert_str.c_str())); 376 insert_statement.BindInt(0, count); 377 insert_statement.BindInt64(1, action.time().ToInternalValue()); 378 for (size_t j = 0; j < matched_values.size(); j++) { 379 if (matched_values[j] == -1) 380 insert_statement.BindNull(j + 2); 381 else 382 insert_statement.BindInt64(j + 2, matched_values[j]); 383 } 384 if (!insert_statement.Run()) 385 return false; 386 } else { 387 // Database error. 388 return false; 389 } 390 } 391 392 if (clean_database) { 393 base::Time cutoff = (Now() - retention_time()).LocalMidnight(); 394 if (!CleanOlderThan(db, cutoff)) 395 return false; 396 last_database_cleaning_time_ = Now(); 397 } 398 399 if (!transaction.Commit()) 400 return false; 401 402 return true; 403} 404 405scoped_ptr<Action::ActionVector> CountingPolicy::DoReadFilteredData( 406 const std::string& extension_id, 407 const Action::ActionType type, 408 const std::string& api_name, 409 const std::string& page_url, 410 const std::string& arg_url, 411 const int days_ago) { 412 // Ensure data is flushed to the database first so that we query over all 413 // data. 414 activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately); 415 scoped_ptr<Action::ActionVector> actions(new Action::ActionVector()); 416 417 sql::Connection* db = GetDatabaseConnection(); 418 if (!db) 419 return actions.Pass(); 420 421 // Build up the query based on which parameters were specified. 422 std::string where_str = ""; 423 std::string where_next = ""; 424 if (!extension_id.empty()) { 425 where_str += "extension_id=?"; 426 where_next = " AND "; 427 } 428 if (!api_name.empty()) { 429 where_str += where_next + "api_name=?"; 430 where_next = " AND "; 431 } 432 if (type != Action::ACTION_ANY) { 433 where_str += where_next + "action_type=?"; 434 where_next = " AND "; 435 } 436 if (!page_url.empty()) { 437 where_str += where_next + "page_url LIKE ?"; 438 where_next = " AND "; 439 } 440 if (!arg_url.empty()) { 441 where_str += where_next + "arg_url LIKE ?"; 442 where_next = " AND "; 443 } 444 if (days_ago >= 0) 445 where_str += where_next + "time BETWEEN ? AND ?"; 446 447 std::string query_str = base::StringPrintf( 448 "SELECT extension_id,time, action_type, api_name, args, page_url," 449 "page_title, arg_url, other, count FROM %s %s %s ORDER BY count DESC," 450 " time DESC LIMIT 300", 451 kReadViewName, 452 where_str.empty() ? "" : "WHERE", 453 where_str.c_str()); 454 sql::Statement query(db->GetUniqueStatement(query_str.c_str())); 455 int i = -1; 456 if (!extension_id.empty()) 457 query.BindString(++i, extension_id); 458 if (!api_name.empty()) 459 query.BindString(++i, api_name); 460 if (type != Action::ACTION_ANY) 461 query.BindInt(++i, static_cast<int>(type)); 462 if (!page_url.empty()) 463 query.BindString(++i, page_url + "%"); 464 if (!arg_url.empty()) 465 query.BindString(++i, arg_url + "%"); 466 if (days_ago >= 0) { 467 int64 early_bound; 468 int64 late_bound; 469 Util::ComputeDatabaseTimeBounds(Now(), days_ago, &early_bound, &late_bound); 470 query.BindInt64(++i, early_bound); 471 query.BindInt64(++i, late_bound); 472 } 473 474 // Execute the query and get results. 475 while (query.is_valid() && query.Step()) { 476 scoped_refptr<Action> action = 477 new Action(query.ColumnString(0), 478 base::Time::FromInternalValue(query.ColumnInt64(1)), 479 static_cast<Action::ActionType>(query.ColumnInt(2)), 480 query.ColumnString(3)); 481 482 if (query.ColumnType(4) != sql::COLUMN_TYPE_NULL) { 483 scoped_ptr<Value> parsed_value( 484 base::JSONReader::Read(query.ColumnString(4))); 485 if (parsed_value && parsed_value->IsType(Value::TYPE_LIST)) { 486 action->set_args( 487 make_scoped_ptr(static_cast<ListValue*>(parsed_value.release()))); 488 } 489 } 490 491 action->ParsePageUrl(query.ColumnString(5)); 492 action->set_page_title(query.ColumnString(6)); 493 action->ParseArgUrl(query.ColumnString(7)); 494 495 if (query.ColumnType(8) != sql::COLUMN_TYPE_NULL) { 496 scoped_ptr<Value> parsed_value( 497 base::JSONReader::Read(query.ColumnString(8))); 498 if (parsed_value && parsed_value->IsType(Value::TYPE_DICTIONARY)) { 499 action->set_other(make_scoped_ptr( 500 static_cast<DictionaryValue*>(parsed_value.release()))); 501 } 502 } 503 action->set_count(query.ColumnInt(9)); 504 actions->push_back(action); 505 } 506 507 return actions.Pass(); 508} 509 510void CountingPolicy::DoRemoveURLs(const std::vector<GURL>& restrict_urls) { 511 sql::Connection* db = GetDatabaseConnection(); 512 if (!db) { 513 LOG(ERROR) << "Unable to connect to database"; 514 return; 515 } 516 517 // Flush data first so the URL clearing affects queued-up data as well. 518 activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately); 519 520 // If no restrictions then then all URLs need to be removed. 521 if (restrict_urls.empty()) { 522 std::string sql_str = base::StringPrintf( 523 "UPDATE %s SET page_url_x=NULL,page_title_x=NULL,arg_url_x=NULL", 524 kTableName); 525 526 sql::Statement statement; 527 statement.Assign(db->GetCachedStatement( 528 sql::StatementID(SQL_FROM_HERE), sql_str.c_str())); 529 530 if (!statement.Run()) { 531 LOG(ERROR) << "Removing all URLs from database failed: " 532 << statement.GetSQLStatement(); 533 return; 534 } 535 } 536 537 // If URLs are specified then restrict to only those URLs. 538 for (size_t i = 0; i < restrict_urls.size(); ++i) { 539 int64 url_id; 540 if (!restrict_urls[i].is_valid() || 541 !url_table_.StringToInt(db, restrict_urls[i].spec(), &url_id)) { 542 continue; 543 } 544 545 // Remove any that match the page_url. 546 std::string sql_str = base::StringPrintf( 547 "UPDATE %s SET page_url_x=NULL,page_title_x=NULL WHERE page_url_x IS ?", 548 kTableName); 549 550 sql::Statement statement; 551 statement.Assign(db->GetCachedStatement( 552 sql::StatementID(SQL_FROM_HERE), sql_str.c_str())); 553 statement.BindInt64(0, url_id); 554 555 if (!statement.Run()) { 556 LOG(ERROR) << "Removing page URL from database failed: " 557 << statement.GetSQLStatement(); 558 return; 559 } 560 561 // Remove any that match the arg_url. 562 sql_str = base::StringPrintf( 563 "UPDATE %s SET arg_url_x=NULL WHERE arg_url_x IS ?", kTableName); 564 565 statement.Assign(db->GetCachedStatement( 566 sql::StatementID(SQL_FROM_HERE), sql_str.c_str())); 567 statement.BindInt64(0, url_id); 568 569 if (!statement.Run()) { 570 LOG(ERROR) << "Removing arg URL from database failed: " 571 << statement.GetSQLStatement(); 572 return; 573 } 574 } 575 576 // Clean up unused strings from the strings and urls table to really delete 577 // the urls and page titles. Should be called even if an error occured when 578 // removing a URL as there may some things to clean up. 579 CleanStringTables(db); 580} 581 582void CountingPolicy::DoDeleteDatabase() { 583 sql::Connection* db = GetDatabaseConnection(); 584 if (!db) { 585 LOG(ERROR) << "Unable to connect to database"; 586 return; 587 } 588 589 queued_actions_.clear(); 590 591 // Not wrapped in a transaction because a late failure shouldn't undo a 592 // previous deletion. 593 std::string sql_str = base::StringPrintf("DELETE FROM %s", kTableName); 594 sql::Statement statement(db->GetCachedStatement( 595 sql::StatementID(SQL_FROM_HERE), 596 sql_str.c_str())); 597 if (!statement.Run()) { 598 LOG(ERROR) << "Deleting the database failed: " 599 << statement.GetSQLStatement(); 600 return; 601 } 602 statement.Clear(); 603 statement.Assign(db->GetCachedStatement(sql::StatementID(SQL_FROM_HERE), 604 "DELETE FROM string_ids")); 605 if (!statement.Run()) { 606 LOG(ERROR) << "Deleting the database failed: " 607 << statement.GetSQLStatement(); 608 return; 609 } 610 statement.Clear(); 611 statement.Assign(db->GetCachedStatement(sql::StatementID(SQL_FROM_HERE), 612 "DELETE FROM url_ids")); 613 if (!statement.Run()) { 614 LOG(ERROR) << "Deleting the database failed: " 615 << statement.GetSQLStatement(); 616 return; 617 } 618 statement.Clear(); 619 statement.Assign(db->GetCachedStatement(sql::StatementID(SQL_FROM_HERE), 620 "VACUUM")); 621 if (!statement.Run()) { 622 LOG(ERROR) << "Vacuuming the database failed: " 623 << statement.GetSQLStatement(); 624 } 625} 626 627void CountingPolicy::ReadFilteredData( 628 const std::string& extension_id, 629 const Action::ActionType type, 630 const std::string& api_name, 631 const std::string& page_url, 632 const std::string& arg_url, 633 const int days_ago, 634 const base::Callback 635 <void(scoped_ptr<Action::ActionVector>)>& callback) { 636 BrowserThread::PostTaskAndReplyWithResult( 637 BrowserThread::DB, 638 FROM_HERE, 639 base::Bind(&CountingPolicy::DoReadFilteredData, 640 base::Unretained(this), 641 extension_id, 642 type, 643 api_name, 644 page_url, 645 arg_url, 646 days_ago), 647 callback); 648} 649 650void CountingPolicy::RemoveURLs(const std::vector<GURL>& restrict_urls) { 651 ScheduleAndForget(this, &CountingPolicy::DoRemoveURLs, restrict_urls); 652} 653 654void CountingPolicy::DeleteDatabase() { 655 ScheduleAndForget(this, &CountingPolicy::DoDeleteDatabase); 656} 657 658void CountingPolicy::OnDatabaseFailure() { 659 queued_actions_.clear(); 660} 661 662void CountingPolicy::OnDatabaseClose() { 663 delete this; 664} 665 666// Cleans old records from the activity log database. 667bool CountingPolicy::CleanOlderThan(sql::Connection* db, 668 const base::Time& cutoff) { 669 std::string clean_statement = 670 "DELETE FROM " + std::string(kTableName) + " WHERE time < ?"; 671 sql::Statement cleaner(db->GetCachedStatement(sql::StatementID(SQL_FROM_HERE), 672 clean_statement.c_str())); 673 cleaner.BindInt64(0, cutoff.ToInternalValue()); 674 if (!cleaner.Run()) 675 return false; 676 return CleanStringTables(db); 677} 678 679// Cleans unused interned strings from the database. This should be run after 680// deleting rows from the main log table to clean out stale values. 681bool CountingPolicy::CleanStringTables(sql::Connection* db) { 682 sql::Statement cleaner1(db->GetCachedStatement( 683 sql::StatementID(SQL_FROM_HERE), kStringTableCleanup)); 684 if (!cleaner1.Run()) 685 return false; 686 if (db->GetLastChangeCount() > 0) 687 string_table_.ClearCache(); 688 689 sql::Statement cleaner2(db->GetCachedStatement( 690 sql::StatementID(SQL_FROM_HERE), kUrlTableCleanup)); 691 if (!cleaner2.Run()) 692 return false; 693 if (db->GetLastChangeCount() > 0) 694 url_table_.ClearCache(); 695 696 return true; 697} 698 699void CountingPolicy::Close() { 700 // The policy object should have never been created if there's no DB thread. 701 DCHECK(BrowserThread::IsMessageLoopValid(BrowserThread::DB)); 702 ScheduleAndForget(activity_database(), &ActivityDatabase::Close); 703} 704 705} // namespace extensions 706