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/history/download_database.h" 6 7 #include <limits> 8 #include <string> 9 #include <vector> 10 11 #include "base/debug/alias.h" 12 #include "base/files/file_path.h" 13 #include "base/memory/scoped_ptr.h" 14 #include "base/metrics/histogram.h" 15 #include "base/stl_util.h" 16 #include "base/strings/stringprintf.h" 17 #include "base/strings/utf_string_conversions.h" 18 #include "base/time/time.h" 19 #include "build/build_config.h" 20 #include "chrome/browser/history/download_row.h" 21 #include "chrome/browser/history/history_types.h" 22 #include "content/public/browser/download_interrupt_reasons.h" 23 #include "content/public/browser/download_item.h" 24 #include "sql/statement.h" 25 26 using content::DownloadItem; 27 28 namespace history { 29 30 namespace { 31 32 // Reason for dropping a particular record. 33 enum DroppedReason { 34 DROPPED_REASON_BAD_STATE = 0, 35 DROPPED_REASON_BAD_DANGER_TYPE = 1, 36 DROPPED_REASON_BAD_ID = 2, 37 DROPPED_REASON_DUPLICATE_ID = 3, 38 DROPPED_REASON_MAX 39 }; 40 41 static const char kSchema[] = 42 "CREATE TABLE downloads (" 43 "id INTEGER PRIMARY KEY," // Primary key. 44 "current_path LONGVARCHAR NOT NULL," // Current disk location 45 "target_path LONGVARCHAR NOT NULL," // Final disk location 46 "start_time INTEGER NOT NULL," // When the download was started. 47 "received_bytes INTEGER NOT NULL," // Total size downloaded. 48 "total_bytes INTEGER NOT NULL," // Total size of the download. 49 "state INTEGER NOT NULL," // 1=complete, 4=interrupted 50 "danger_type INTEGER NOT NULL, " // Danger type, validated. 51 "interrupt_reason INTEGER NOT NULL," // content::DownloadInterruptReason 52 "end_time INTEGER NOT NULL," // When the download completed. 53 "opened INTEGER NOT NULL," // 1 if it has ever been opened else 0 54 "referrer VARCHAR NOT NULL," // HTTP Referrer 55 "by_ext_id VARCHAR NOT NULL," // ID of extension that started the 56 // download 57 "by_ext_name VARCHAR NOT NULL," // name of extension 58 "etag VARCHAR NOT NULL," // ETag 59 "last_modified VARCHAR NOT NULL)"; // Last-Modified header 60 61 static const char kUrlChainSchema[] = 62 "CREATE TABLE downloads_url_chains (" 63 "id INTEGER NOT NULL," // downloads.id. 64 "chain_index INTEGER NOT NULL," // Index of url in chain 65 // 0 is initial target, 66 // MAX is target after redirects. 67 "url LONGVARCHAR NOT NULL, " // URL. 68 "PRIMARY KEY (id, chain_index) )"; 69 70 #if defined(OS_POSIX) 71 72 // Binds/reads the given file path to the given column of the given statement. 73 void BindFilePath(sql::Statement& statement, const base::FilePath& path, 74 int col) { 75 statement.BindString(col, path.value()); 76 } 77 base::FilePath ColumnFilePath(sql::Statement& statement, int col) { 78 return base::FilePath(statement.ColumnString(col)); 79 } 80 81 #else 82 83 // See above. 84 void BindFilePath(sql::Statement& statement, const base::FilePath& path, 85 int col) { 86 statement.BindString16(col, path.value()); 87 } 88 base::FilePath ColumnFilePath(sql::Statement& statement, int col) { 89 return base::FilePath(statement.ColumnString16(col)); 90 } 91 92 #endif 93 94 } // namespace 95 96 // These constants and the transformation functions below are used to allow 97 // DownloadItem::DownloadState and DownloadDangerType to change without 98 // breaking the database schema. 99 // They guarantee that the values of the |state| field in the database are one 100 // of the values returned by StateToInt, and that the values of the |state| 101 // field of the DownloadRows returned by QueryDownloads() are one of the values 102 // returned by IntToState(). 103 const int DownloadDatabase::kStateInvalid = -1; 104 const int DownloadDatabase::kStateInProgress = 0; 105 const int DownloadDatabase::kStateComplete = 1; 106 const int DownloadDatabase::kStateCancelled = 2; 107 const int DownloadDatabase::kStateBug140687 = 3; 108 const int DownloadDatabase::kStateInterrupted = 4; 109 110 const int DownloadDatabase::kDangerTypeInvalid = -1; 111 const int DownloadDatabase::kDangerTypeNotDangerous = 0; 112 const int DownloadDatabase::kDangerTypeDangerousFile = 1; 113 const int DownloadDatabase::kDangerTypeDangerousUrl = 2; 114 const int DownloadDatabase::kDangerTypeDangerousContent = 3; 115 const int DownloadDatabase::kDangerTypeMaybeDangerousContent = 4; 116 const int DownloadDatabase::kDangerTypeUncommonContent = 5; 117 const int DownloadDatabase::kDangerTypeUserValidated = 6; 118 const int DownloadDatabase::kDangerTypeDangerousHost = 7; 119 const int DownloadDatabase::kDangerTypePotentiallyUnwanted = 8; 120 121 int DownloadDatabase::StateToInt(DownloadItem::DownloadState state) { 122 switch (state) { 123 case DownloadItem::IN_PROGRESS: return DownloadDatabase::kStateInProgress; 124 case DownloadItem::COMPLETE: return DownloadDatabase::kStateComplete; 125 case DownloadItem::CANCELLED: return DownloadDatabase::kStateCancelled; 126 case DownloadItem::INTERRUPTED: return DownloadDatabase::kStateInterrupted; 127 case DownloadItem::MAX_DOWNLOAD_STATE: 128 NOTREACHED(); 129 return DownloadDatabase::kStateInvalid; 130 } 131 NOTREACHED(); 132 return DownloadDatabase::kStateInvalid; 133 } 134 135 DownloadItem::DownloadState DownloadDatabase::IntToState(int state) { 136 switch (state) { 137 case DownloadDatabase::kStateInProgress: return DownloadItem::IN_PROGRESS; 138 case DownloadDatabase::kStateComplete: return DownloadItem::COMPLETE; 139 case DownloadDatabase::kStateCancelled: return DownloadItem::CANCELLED; 140 // We should not need kStateBug140687 here because MigrateDownloadsState() 141 // is called in HistoryDatabase::Init(). 142 case DownloadDatabase::kStateInterrupted: return DownloadItem::INTERRUPTED; 143 default: return DownloadItem::MAX_DOWNLOAD_STATE; 144 } 145 } 146 147 int DownloadDatabase::DangerTypeToInt(content::DownloadDangerType danger_type) { 148 switch (danger_type) { 149 case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS: 150 return DownloadDatabase::kDangerTypeNotDangerous; 151 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: 152 return DownloadDatabase::kDangerTypeDangerousFile; 153 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: 154 return DownloadDatabase::kDangerTypeDangerousUrl; 155 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT: 156 return DownloadDatabase::kDangerTypeDangerousContent; 157 case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT: 158 return DownloadDatabase::kDangerTypeMaybeDangerousContent; 159 case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: 160 return DownloadDatabase::kDangerTypeUncommonContent; 161 case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED: 162 return DownloadDatabase::kDangerTypeUserValidated; 163 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: 164 return DownloadDatabase::kDangerTypeDangerousHost; 165 case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: 166 return DownloadDatabase::kDangerTypePotentiallyUnwanted; 167 case content::DOWNLOAD_DANGER_TYPE_MAX: 168 NOTREACHED(); 169 return DownloadDatabase::kDangerTypeInvalid; 170 } 171 NOTREACHED(); 172 return DownloadDatabase::kDangerTypeInvalid; 173 } 174 175 content::DownloadDangerType DownloadDatabase::IntToDangerType(int danger_type) { 176 switch (danger_type) { 177 case DownloadDatabase::kDangerTypeNotDangerous: 178 return content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS; 179 case DownloadDatabase::kDangerTypeDangerousFile: 180 return content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE; 181 case DownloadDatabase::kDangerTypeDangerousUrl: 182 return content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL; 183 case DownloadDatabase::kDangerTypeDangerousContent: 184 return content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT; 185 case DownloadDatabase::kDangerTypeMaybeDangerousContent: 186 return content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT; 187 case DownloadDatabase::kDangerTypeUncommonContent: 188 return content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT; 189 case DownloadDatabase::kDangerTypeUserValidated: 190 return content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED; 191 case DownloadDatabase::kDangerTypeDangerousHost: 192 return content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST; 193 case DownloadDatabase::kDangerTypePotentiallyUnwanted: 194 return content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED; 195 default: 196 return content::DOWNLOAD_DANGER_TYPE_MAX; 197 } 198 } 199 200 DownloadDatabase::DownloadDatabase() 201 : owning_thread_set_(false), 202 owning_thread_(0), 203 in_progress_entry_cleanup_completed_(false) { 204 } 205 206 DownloadDatabase::~DownloadDatabase() { 207 } 208 209 bool DownloadDatabase::EnsureColumnExists( 210 const std::string& name, const std::string& type) { 211 std::string add_col = "ALTER TABLE downloads ADD COLUMN " + name + " " + type; 212 return GetDB().DoesColumnExist("downloads", name.c_str()) || 213 GetDB().Execute(add_col.c_str()); 214 } 215 216 bool DownloadDatabase::MigrateDownloadsState() { 217 sql::Statement statement(GetDB().GetUniqueStatement( 218 "UPDATE downloads SET state=? WHERE state=?")); 219 statement.BindInt(0, kStateInterrupted); 220 statement.BindInt(1, kStateBug140687); 221 return statement.Run(); 222 } 223 224 bool DownloadDatabase::MigrateDownloadsReasonPathsAndDangerType() { 225 // We need to rename the table and copy back from it because SQLite 226 // provides no way to rename or delete a column. 227 if (!GetDB().Execute("ALTER TABLE downloads RENAME TO downloads_tmp")) 228 return false; 229 230 // Recreate main table. 231 if (!GetDB().Execute(kSchema)) 232 return false; 233 234 // Populate it. As we do so, we transform the time values from time_t 235 // (seconds since 1/1/1970 UTC), to our internal measure (microseconds 236 // since the Windows Epoch). Note that this is dependent on the 237 // internal representation of base::Time and needs to change if that changes. 238 sql::Statement statement_populate(GetDB().GetUniqueStatement( 239 "INSERT INTO downloads " 240 "( id, current_path, target_path, start_time, received_bytes, " 241 " total_bytes, state, danger_type, interrupt_reason, end_time, opened, " 242 " referrer, by_ext_id, by_ext_name, etag, last_modified ) " 243 "SELECT id, full_path, full_path, " 244 " CASE start_time WHEN 0 THEN 0 ELSE " 245 " (start_time + 11644473600) * 1000000 END, " 246 " received_bytes, total_bytes, " 247 " state, ?, ?, " 248 " CASE end_time WHEN 0 THEN 0 ELSE " 249 " (end_time + 11644473600) * 1000000 END, " 250 " opened, \"\", \"\", \"\", \"\", \"\" " 251 "FROM downloads_tmp")); 252 statement_populate.BindInt(0, content::DOWNLOAD_INTERRUPT_REASON_NONE); 253 statement_populate.BindInt(1, kDangerTypeNotDangerous); 254 if (!statement_populate.Run()) 255 return false; 256 257 // Create new chain table and populate it. 258 if (!GetDB().Execute(kUrlChainSchema)) 259 return false; 260 261 if (!GetDB().Execute("INSERT INTO downloads_url_chains " 262 " ( id, chain_index, url) " 263 " SELECT id, 0, url from downloads_tmp")) 264 return false; 265 266 // Get rid of temporary table. 267 if (!GetDB().Execute("DROP TABLE downloads_tmp")) 268 return false; 269 270 return true; 271 } 272 273 bool DownloadDatabase::MigrateReferrer() { 274 return EnsureColumnExists("referrer", "VARCHAR NOT NULL DEFAULT \"\""); 275 } 276 277 bool DownloadDatabase::MigrateDownloadedByExtension() { 278 return EnsureColumnExists("by_ext_id", "VARCHAR NOT NULL DEFAULT \"\"") && 279 EnsureColumnExists("by_ext_name", "VARCHAR NOT NULL DEFAULT \"\""); 280 } 281 282 bool DownloadDatabase::MigrateDownloadValidators() { 283 return EnsureColumnExists("etag", "VARCHAR NOT NULL DEFAULT \"\"") && 284 EnsureColumnExists("last_modified", "VARCHAR NOT NULL DEFAULT \"\""); 285 } 286 287 bool DownloadDatabase::InitDownloadTable() { 288 if (GetDB().DoesTableExist("downloads")) { 289 return EnsureColumnExists("end_time", "INTEGER NOT NULL DEFAULT 0") && 290 EnsureColumnExists("opened", "INTEGER NOT NULL DEFAULT 0"); 291 } else { 292 // If the "downloads" table doesn't exist, the downloads_url_chain 293 // table better not. 294 return (!GetDB().DoesTableExist("downloads_url_chain") && 295 GetDB().Execute(kSchema) && GetDB().Execute(kUrlChainSchema)); 296 } 297 } 298 299 void DownloadDatabase::GetNextDownloadId(uint32* id) { 300 sql::Statement select_max_id(GetDB().GetUniqueStatement( 301 "SELECT max(id) FROM downloads")); 302 if (!select_max_id.Step()) { 303 DCHECK(false); 304 *id = content::DownloadItem::kInvalidId + 1; 305 return; 306 } 307 // If there are zero records in the downloads table, then max(id) will return 308 // 0 = kInvalidId, so GetNextDownloadId() will set *id = kInvalidId + 1. 309 // If there is at least one record but all of the |id|s are <= kInvalidId, 310 // then max(id) will return <= kInvalidId, so GetNextDownloadId should return 311 // kInvalidId + 1. Note that any records with |id <= kInvalidId| will be 312 // dropped in QueryDownloads() 313 // SQLITE doesn't have unsigned integers. 314 *id = 1 + static_cast<uint32>(std::max( 315 static_cast<int64>(content::DownloadItem::kInvalidId), 316 select_max_id.ColumnInt64(0))); 317 } 318 319 bool DownloadDatabase::DropDownloadTable() { 320 return GetDB().Execute("DROP TABLE downloads"); 321 } 322 323 void DownloadDatabase::QueryDownloads( 324 std::vector<DownloadRow>* results) { 325 EnsureInProgressEntriesCleanedUp(); 326 327 results->clear(); 328 std::set<uint32> ids; 329 330 std::map<uint32, DownloadRow*> info_map; 331 332 sql::Statement statement_main(GetDB().GetCachedStatement(SQL_FROM_HERE, 333 "SELECT id, current_path, target_path, start_time, received_bytes, " 334 "total_bytes, state, danger_type, interrupt_reason, end_time, opened, " 335 "referrer, by_ext_id, by_ext_name, etag, last_modified " 336 "FROM downloads ORDER BY start_time")); 337 338 while (statement_main.Step()) { 339 scoped_ptr<DownloadRow> info(new DownloadRow()); 340 int column = 0; 341 342 // SQLITE does not have unsigned integers, so explicitly handle negative 343 // |id|s instead of casting them to very large uint32s, which would break 344 // the max(id) logic in GetNextDownloadId(). 345 int64 signed_id = statement_main.ColumnInt64(column++); 346 info->id = static_cast<uint32>(signed_id); 347 info->current_path = ColumnFilePath(statement_main, column++); 348 info->target_path = ColumnFilePath(statement_main, column++); 349 info->start_time = base::Time::FromInternalValue( 350 statement_main.ColumnInt64(column++)); 351 info->received_bytes = statement_main.ColumnInt64(column++); 352 info->total_bytes = statement_main.ColumnInt64(column++); 353 int state = statement_main.ColumnInt(column++); 354 info->state = IntToState(state); 355 if (info->state == DownloadItem::MAX_DOWNLOAD_STATE) 356 UMA_HISTOGRAM_COUNTS("Download.DatabaseInvalidState", state); 357 info->danger_type = IntToDangerType(statement_main.ColumnInt(column++)); 358 info->interrupt_reason = static_cast<content::DownloadInterruptReason>( 359 statement_main.ColumnInt(column++)); 360 info->end_time = base::Time::FromInternalValue( 361 statement_main.ColumnInt64(column++)); 362 info->opened = statement_main.ColumnInt(column++) != 0; 363 info->referrer_url = GURL(statement_main.ColumnString(column++)); 364 info->by_ext_id = statement_main.ColumnString(column++); 365 info->by_ext_name = statement_main.ColumnString(column++); 366 info->etag = statement_main.ColumnString(column++); 367 info->last_modified = statement_main.ColumnString(column++); 368 369 // If the record is corrupted, note that and drop it. 370 // http://crbug.com/251269 371 DroppedReason dropped_reason = DROPPED_REASON_MAX; 372 if (signed_id <= static_cast<int64>(content::DownloadItem::kInvalidId)) { 373 // SQLITE doesn't have unsigned integers. 374 dropped_reason = DROPPED_REASON_BAD_ID; 375 } else if (!ids.insert(info->id).second) { 376 dropped_reason = DROPPED_REASON_DUPLICATE_ID; 377 NOTREACHED() << info->id; 378 } else if (info->state == DownloadItem::MAX_DOWNLOAD_STATE) { 379 dropped_reason = DROPPED_REASON_BAD_STATE; 380 } else if (info->danger_type == content::DOWNLOAD_DANGER_TYPE_MAX) { 381 dropped_reason = DROPPED_REASON_BAD_DANGER_TYPE; 382 } 383 if (dropped_reason != DROPPED_REASON_MAX) { 384 UMA_HISTOGRAM_ENUMERATION("Download.DatabaseRecordDropped", 385 dropped_reason, 386 DROPPED_REASON_MAX + 1); 387 } else { 388 DCHECK(!ContainsKey(info_map, info->id)); 389 uint32 id = info->id; 390 info_map[id] = info.release(); 391 } 392 } 393 394 sql::Statement statement_chain(GetDB().GetCachedStatement( 395 SQL_FROM_HERE, 396 "SELECT id, chain_index, url FROM downloads_url_chains " 397 "ORDER BY id, chain_index")); 398 399 while (statement_chain.Step()) { 400 int column = 0; 401 // See the comment above about SQLITE lacking unsigned integers. 402 int64 signed_id = statement_chain.ColumnInt64(column++); 403 int chain_index = statement_chain.ColumnInt(column++); 404 405 if (signed_id <= static_cast<int64>(content::DownloadItem::kInvalidId)) 406 continue; 407 uint32 id = static_cast<uint32>(signed_id); 408 409 // Note that these DCHECKs may trip as a result of corrupted databases. 410 // We have them because in debug builds the chances are higher there's 411 // an actual bug than that the database is corrupt, but we handle the 412 // DB corruption case in production code. 413 414 // Confirm the id has already been seen--if it hasn't, discard the 415 // record. 416 DCHECK(ContainsKey(info_map, id)); 417 if (!ContainsKey(info_map, id)) 418 continue; 419 420 // Confirm all previous URLs in the chain have already been seen; 421 // if not, fill in with null or discard record. 422 int current_chain_size = info_map[id]->url_chain.size(); 423 std::vector<GURL>* url_chain(&info_map[id]->url_chain); 424 DCHECK_EQ(chain_index, current_chain_size); 425 while (current_chain_size < chain_index) { 426 url_chain->push_back(GURL()); 427 current_chain_size++; 428 } 429 if (current_chain_size > chain_index) 430 continue; 431 432 // Save the record. 433 url_chain->push_back(GURL(statement_chain.ColumnString(2))); 434 } 435 436 for (std::map<uint32, DownloadRow*>::iterator 437 it = info_map.begin(); it != info_map.end(); ++it) { 438 DownloadRow* row = it->second; 439 bool empty_url_chain = row->url_chain.empty(); 440 UMA_HISTOGRAM_BOOLEAN("Download.DatabaseEmptyUrlChain", empty_url_chain); 441 if (empty_url_chain) { 442 RemoveDownload(row->id); 443 } else { 444 // Copy the contents of the stored info. 445 results->push_back(*row); 446 } 447 delete row; 448 it->second = NULL; 449 } 450 } 451 452 bool DownloadDatabase::UpdateDownload(const DownloadRow& data) { 453 EnsureInProgressEntriesCleanedUp(); 454 455 DCHECK_NE(content::DownloadItem::kInvalidId, data.id); 456 int state = StateToInt(data.state); 457 if (state == kStateInvalid) { 458 NOTREACHED(); 459 return false; 460 } 461 int danger_type = DangerTypeToInt(data.danger_type); 462 if (danger_type == kDangerTypeInvalid) { 463 NOTREACHED(); 464 return false; 465 } 466 467 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, 468 "UPDATE downloads " 469 "SET current_path=?, target_path=?, received_bytes=?, state=?, " 470 "danger_type=?, interrupt_reason=?, end_time=?, total_bytes=?, " 471 "opened=?, by_ext_id=?, by_ext_name=?, etag=?, last_modified=? " 472 "WHERE id=?")); 473 int column = 0; 474 BindFilePath(statement, data.current_path, column++); 475 BindFilePath(statement, data.target_path, column++); 476 statement.BindInt64(column++, data.received_bytes); 477 statement.BindInt(column++, state); 478 statement.BindInt(column++, danger_type); 479 statement.BindInt(column++, static_cast<int>(data.interrupt_reason)); 480 statement.BindInt64(column++, data.end_time.ToInternalValue()); 481 statement.BindInt64(column++, data.total_bytes); 482 statement.BindInt(column++, (data.opened ? 1 : 0)); 483 statement.BindString(column++, data.by_ext_id); 484 statement.BindString(column++, data.by_ext_name); 485 statement.BindString(column++, data.etag); 486 statement.BindString(column++, data.last_modified); 487 statement.BindInt(column++, data.id); 488 489 return statement.Run(); 490 } 491 492 void DownloadDatabase::EnsureInProgressEntriesCleanedUp() { 493 if (in_progress_entry_cleanup_completed_) 494 return; 495 496 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, 497 "UPDATE downloads SET state=?, interrupt_reason=? WHERE state=?")); 498 statement.BindInt(0, kStateInterrupted); 499 statement.BindInt(1, content::DOWNLOAD_INTERRUPT_REASON_CRASH); 500 statement.BindInt(2, kStateInProgress); 501 502 statement.Run(); 503 in_progress_entry_cleanup_completed_ = true; 504 } 505 506 bool DownloadDatabase::CreateDownload(const DownloadRow& info) { 507 DCHECK_NE(content::DownloadItem::kInvalidId, info.id); 508 EnsureInProgressEntriesCleanedUp(); 509 510 if (info.url_chain.empty()) 511 return false; 512 513 int state = StateToInt(info.state); 514 if (state == kStateInvalid) 515 return false; 516 517 int danger_type = DangerTypeToInt(info.danger_type); 518 if (danger_type == kDangerTypeInvalid) 519 return false; 520 521 { 522 sql::Statement statement_insert(GetDB().GetCachedStatement( 523 SQL_FROM_HERE, 524 "INSERT INTO downloads " 525 "(id, current_path, target_path, start_time, " 526 " received_bytes, total_bytes, state, danger_type, interrupt_reason, " 527 " end_time, opened, referrer, by_ext_id, by_ext_name, etag, " 528 " last_modified) " 529 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")); 530 531 int column = 0; 532 statement_insert.BindInt(column++, info.id); 533 BindFilePath(statement_insert, info.current_path, column++); 534 BindFilePath(statement_insert, info.target_path, column++); 535 statement_insert.BindInt64(column++, info.start_time.ToInternalValue()); 536 statement_insert.BindInt64(column++, info.received_bytes); 537 statement_insert.BindInt64(column++, info.total_bytes); 538 statement_insert.BindInt(column++, state); 539 statement_insert.BindInt(column++, danger_type); 540 statement_insert.BindInt(column++, info.interrupt_reason); 541 statement_insert.BindInt64(column++, info.end_time.ToInternalValue()); 542 statement_insert.BindInt(column++, info.opened ? 1 : 0); 543 statement_insert.BindString(column++, info.referrer_url.spec()); 544 statement_insert.BindString(column++, info.by_ext_id); 545 statement_insert.BindString(column++, info.by_ext_name); 546 statement_insert.BindString(column++, info.etag); 547 statement_insert.BindString(column++, info.last_modified); 548 if (!statement_insert.Run()) { 549 // GetErrorCode() returns a bitmask where the lower byte is a more general 550 // code and the upper byte is a more specific code. In order to save 551 // memory, take the general code, of which there are fewer than 50. See 552 // also sql/connection.cc 553 // http://www.sqlite.org/c3ref/c_abort_rollback.html 554 UMA_HISTOGRAM_ENUMERATION("Download.DatabaseMainInsertError", 555 GetDB().GetErrorCode() & 0xff, 50); 556 return false; 557 } 558 } 559 560 { 561 sql::Statement count_urls(GetDB().GetCachedStatement(SQL_FROM_HERE, 562 "SELECT count(*) FROM downloads_url_chains WHERE id=?")); 563 count_urls.BindInt(0, info.id); 564 if (count_urls.Step()) { 565 bool corrupt_urls = count_urls.ColumnInt(0) > 0; 566 UMA_HISTOGRAM_BOOLEAN("Download.DatabaseCorruptUrls", corrupt_urls); 567 if (corrupt_urls) { 568 // There should not be any URLs in downloads_url_chains for this 569 // info.id. If there are, we don't want them to interfere with 570 // inserting the correct URLs, so just remove them. 571 RemoveDownloadURLs(info.id); 572 } 573 } 574 } 575 576 sql::Statement statement_insert_chain( 577 GetDB().GetCachedStatement(SQL_FROM_HERE, 578 "INSERT INTO downloads_url_chains " 579 "(id, chain_index, url) " 580 "VALUES (?, ?, ?)")); 581 for (size_t i = 0; i < info.url_chain.size(); ++i) { 582 statement_insert_chain.BindInt(0, info.id); 583 statement_insert_chain.BindInt(1, i); 584 statement_insert_chain.BindString(2, info.url_chain[i].spec()); 585 if (!statement_insert_chain.Run()) { 586 UMA_HISTOGRAM_ENUMERATION("Download.DatabaseURLChainInsertError", 587 GetDB().GetErrorCode() & 0xff, 50); 588 RemoveDownload(info.id); 589 return false; 590 } 591 statement_insert_chain.Reset(true); 592 } 593 return true; 594 } 595 596 void DownloadDatabase::RemoveDownload(uint32 id) { 597 EnsureInProgressEntriesCleanedUp(); 598 599 sql::Statement downloads_statement(GetDB().GetCachedStatement(SQL_FROM_HERE, 600 "DELETE FROM downloads WHERE id=?")); 601 downloads_statement.BindInt(0, id); 602 if (!downloads_statement.Run()) { 603 UMA_HISTOGRAM_ENUMERATION("Download.DatabaseMainDeleteError", 604 GetDB().GetErrorCode() & 0xff, 50); 605 return; 606 } 607 RemoveDownloadURLs(id); 608 } 609 610 void DownloadDatabase::RemoveDownloadURLs(uint32 id) { 611 sql::Statement urlchain_statement(GetDB().GetCachedStatement(SQL_FROM_HERE, 612 "DELETE FROM downloads_url_chains WHERE id=?")); 613 urlchain_statement.BindInt(0, id); 614 if (!urlchain_statement.Run()) { 615 UMA_HISTOGRAM_ENUMERATION("Download.DatabaseURLChainDeleteError", 616 GetDB().GetErrorCode() & 0xff, 50); 617 } 618 } 619 620 size_t DownloadDatabase::CountDownloads() { 621 EnsureInProgressEntriesCleanedUp(); 622 623 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, 624 "SELECT count(*) from downloads")); 625 statement.Step(); 626 return statement.ColumnInt(0); 627 } 628 629 } // namespace history 630