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