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