Home | History | Annotate | Download | only in history
      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