blob: 369c8c2751706f8bad9b9ad290545924e2cfd880 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/history/download_database.h"
#include <limits>
#include <string>
#include <vector>
#include "base/debug/alias.h"
#include "base/files/file_path.h"
#include "base/memory/scoped_ptr.h"
#include "base/metrics/histogram.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/history/download_row.h"
#include "chrome/browser/history/history_types.h"
#include "content/public/browser/download_interrupt_reasons.h"
#include "content/public/browser/download_item.h"
#include "sql/statement.h"
using content::DownloadItem;
namespace history {
namespace {
// Reason for dropping a particular record.
enum DroppedReason {
DROPPED_REASON_BAD_STATE = 0,
DROPPED_REASON_BAD_DANGER_TYPE = 1,
DROPPED_REASON_BAD_ID = 2,
DROPPED_REASON_DUPLICATE_ID = 3,
DROPPED_REASON_MAX
};
static const char kSchema[] =
"CREATE TABLE downloads ("
"id INTEGER PRIMARY KEY," // Primary key.
"current_path LONGVARCHAR NOT NULL," // Current disk location
"target_path LONGVARCHAR NOT NULL," // Final disk location
"start_time INTEGER NOT NULL," // When the download was started.
"received_bytes INTEGER NOT NULL," // Total size downloaded.
"total_bytes INTEGER NOT NULL," // Total size of the download.
"state INTEGER NOT NULL," // 1=complete, 4=interrupted
"danger_type INTEGER NOT NULL, " // Danger type, validated.
"interrupt_reason INTEGER NOT NULL," // content::DownloadInterruptReason
"end_time INTEGER NOT NULL," // When the download completed.
"opened INTEGER NOT NULL," // 1 if it has ever been opened else 0
"referrer VARCHAR NOT NULL," // HTTP Referrer
"by_ext_id VARCHAR NOT NULL," // ID of extension that started the
// download
"by_ext_name VARCHAR NOT NULL," // name of extension
"etag VARCHAR NOT NULL," // ETag
"last_modified VARCHAR NOT NULL)"; // Last-Modified header
static const char kUrlChainSchema[] =
"CREATE TABLE downloads_url_chains ("
"id INTEGER NOT NULL," // downloads.id.
"chain_index INTEGER NOT NULL," // Index of url in chain
// 0 is initial target,
// MAX is target after redirects.
"url LONGVARCHAR NOT NULL, " // URL.
"PRIMARY KEY (id, chain_index) )";
#if defined(OS_POSIX)
// Binds/reads the given file path to the given column of the given statement.
void BindFilePath(sql::Statement& statement, const base::FilePath& path,
int col) {
statement.BindString(col, path.value());
}
base::FilePath ColumnFilePath(sql::Statement& statement, int col) {
return base::FilePath(statement.ColumnString(col));
}
#else
// See above.
void BindFilePath(sql::Statement& statement, const base::FilePath& path,
int col) {
statement.BindString16(col, path.value());
}
base::FilePath ColumnFilePath(sql::Statement& statement, int col) {
return base::FilePath(statement.ColumnString16(col));
}
#endif
} // namespace
// These constants and the transformation functions below are used to allow
// DownloadItem::DownloadState and DownloadDangerType to change without
// breaking the database schema.
// They guarantee that the values of the |state| field in the database are one
// of the values returned by StateToInt, and that the values of the |state|
// field of the DownloadRows returned by QueryDownloads() are one of the values
// returned by IntToState().
const int DownloadDatabase::kStateInvalid = -1;
const int DownloadDatabase::kStateInProgress = 0;
const int DownloadDatabase::kStateComplete = 1;
const int DownloadDatabase::kStateCancelled = 2;
const int DownloadDatabase::kStateBug140687 = 3;
const int DownloadDatabase::kStateInterrupted = 4;
const int DownloadDatabase::kDangerTypeInvalid = -1;
const int DownloadDatabase::kDangerTypeNotDangerous = 0;
const int DownloadDatabase::kDangerTypeDangerousFile = 1;
const int DownloadDatabase::kDangerTypeDangerousUrl = 2;
const int DownloadDatabase::kDangerTypeDangerousContent = 3;
const int DownloadDatabase::kDangerTypeMaybeDangerousContent = 4;
const int DownloadDatabase::kDangerTypeUncommonContent = 5;
const int DownloadDatabase::kDangerTypeUserValidated = 6;
const int DownloadDatabase::kDangerTypeDangerousHost = 7;
const int DownloadDatabase::kDangerTypePotentiallyUnwanted = 8;
int DownloadDatabase::StateToInt(DownloadItem::DownloadState state) {
switch (state) {
case DownloadItem::IN_PROGRESS: return DownloadDatabase::kStateInProgress;
case DownloadItem::COMPLETE: return DownloadDatabase::kStateComplete;
case DownloadItem::CANCELLED: return DownloadDatabase::kStateCancelled;
case DownloadItem::INTERRUPTED: return DownloadDatabase::kStateInterrupted;
case DownloadItem::MAX_DOWNLOAD_STATE:
NOTREACHED();
return DownloadDatabase::kStateInvalid;
}
NOTREACHED();
return DownloadDatabase::kStateInvalid;
}
DownloadItem::DownloadState DownloadDatabase::IntToState(int state) {
switch (state) {
case DownloadDatabase::kStateInProgress: return DownloadItem::IN_PROGRESS;
case DownloadDatabase::kStateComplete: return DownloadItem::COMPLETE;
case DownloadDatabase::kStateCancelled: return DownloadItem::CANCELLED;
// We should not need kStateBug140687 here because MigrateDownloadsState()
// is called in HistoryDatabase::Init().
case DownloadDatabase::kStateInterrupted: return DownloadItem::INTERRUPTED;
default: return DownloadItem::MAX_DOWNLOAD_STATE;
}
}
int DownloadDatabase::DangerTypeToInt(content::DownloadDangerType danger_type) {
switch (danger_type) {
case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
return DownloadDatabase::kDangerTypeNotDangerous;
case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
return DownloadDatabase::kDangerTypeDangerousFile;
case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
return DownloadDatabase::kDangerTypeDangerousUrl;
case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
return DownloadDatabase::kDangerTypeDangerousContent;
case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
return DownloadDatabase::kDangerTypeMaybeDangerousContent;
case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT:
return DownloadDatabase::kDangerTypeUncommonContent;
case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
return DownloadDatabase::kDangerTypeUserValidated;
case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
return DownloadDatabase::kDangerTypeDangerousHost;
case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED:
return DownloadDatabase::kDangerTypePotentiallyUnwanted;
case content::DOWNLOAD_DANGER_TYPE_MAX:
NOTREACHED();
return DownloadDatabase::kDangerTypeInvalid;
}
NOTREACHED();
return DownloadDatabase::kDangerTypeInvalid;
}
content::DownloadDangerType DownloadDatabase::IntToDangerType(int danger_type) {
switch (danger_type) {
case DownloadDatabase::kDangerTypeNotDangerous:
return content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS;
case DownloadDatabase::kDangerTypeDangerousFile:
return content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE;
case DownloadDatabase::kDangerTypeDangerousUrl:
return content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL;
case DownloadDatabase::kDangerTypeDangerousContent:
return content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT;
case DownloadDatabase::kDangerTypeMaybeDangerousContent:
return content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT;
case DownloadDatabase::kDangerTypeUncommonContent:
return content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT;
case DownloadDatabase::kDangerTypeUserValidated:
return content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED;
case DownloadDatabase::kDangerTypeDangerousHost:
return content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST;
case DownloadDatabase::kDangerTypePotentiallyUnwanted:
return content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED;
default:
return content::DOWNLOAD_DANGER_TYPE_MAX;
}
}
DownloadDatabase::DownloadDatabase()
: owning_thread_set_(false),
owning_thread_(0),
in_progress_entry_cleanup_completed_(false) {
}
DownloadDatabase::~DownloadDatabase() {
}
bool DownloadDatabase::EnsureColumnExists(
const std::string& name, const std::string& type) {
std::string add_col = "ALTER TABLE downloads ADD COLUMN " + name + " " + type;
return GetDB().DoesColumnExist("downloads", name.c_str()) ||
GetDB().Execute(add_col.c_str());
}
bool DownloadDatabase::MigrateDownloadsState() {
sql::Statement statement(GetDB().GetUniqueStatement(
"UPDATE downloads SET state=? WHERE state=?"));
statement.BindInt(0, kStateInterrupted);
statement.BindInt(1, kStateBug140687);
return statement.Run();
}
bool DownloadDatabase::MigrateDownloadsReasonPathsAndDangerType() {
// We need to rename the table and copy back from it because SQLite
// provides no way to rename or delete a column.
if (!GetDB().Execute("ALTER TABLE downloads RENAME TO downloads_tmp"))
return false;
// Recreate main table.
if (!GetDB().Execute(kSchema))
return false;
// Populate it. As we do so, we transform the time values from time_t
// (seconds since 1/1/1970 UTC), to our internal measure (microseconds
// since the Windows Epoch). Note that this is dependent on the
// internal representation of base::Time and needs to change if that changes.
sql::Statement statement_populate(GetDB().GetUniqueStatement(
"INSERT INTO downloads "
"( id, current_path, target_path, start_time, received_bytes, "
" total_bytes, state, danger_type, interrupt_reason, end_time, opened, "
" referrer, by_ext_id, by_ext_name, etag, last_modified ) "
"SELECT id, full_path, full_path, "
" CASE start_time WHEN 0 THEN 0 ELSE "
" (start_time + 11644473600) * 1000000 END, "
" received_bytes, total_bytes, "
" state, ?, ?, "
" CASE end_time WHEN 0 THEN 0 ELSE "
" (end_time + 11644473600) * 1000000 END, "
" opened, \"\", \"\", \"\", \"\", \"\" "
"FROM downloads_tmp"));
statement_populate.BindInt(0, content::DOWNLOAD_INTERRUPT_REASON_NONE);
statement_populate.BindInt(1, kDangerTypeNotDangerous);
if (!statement_populate.Run())
return false;
// Create new chain table and populate it.
if (!GetDB().Execute(kUrlChainSchema))
return false;
if (!GetDB().Execute("INSERT INTO downloads_url_chains "
" ( id, chain_index, url) "
" SELECT id, 0, url from downloads_tmp"))
return false;
// Get rid of temporary table.
if (!GetDB().Execute("DROP TABLE downloads_tmp"))
return false;
return true;
}
bool DownloadDatabase::MigrateReferrer() {
return EnsureColumnExists("referrer", "VARCHAR NOT NULL DEFAULT \"\"");
}
bool DownloadDatabase::MigrateDownloadedByExtension() {
return EnsureColumnExists("by_ext_id", "VARCHAR NOT NULL DEFAULT \"\"") &&
EnsureColumnExists("by_ext_name", "VARCHAR NOT NULL DEFAULT \"\"");
}
bool DownloadDatabase::MigrateDownloadValidators() {
return EnsureColumnExists("etag", "VARCHAR NOT NULL DEFAULT \"\"") &&
EnsureColumnExists("last_modified", "VARCHAR NOT NULL DEFAULT \"\"");
}
bool DownloadDatabase::InitDownloadTable() {
if (GetDB().DoesTableExist("downloads")) {
return EnsureColumnExists("end_time", "INTEGER NOT NULL DEFAULT 0") &&
EnsureColumnExists("opened", "INTEGER NOT NULL DEFAULT 0");
} else {
// If the "downloads" table doesn't exist, the downloads_url_chain
// table better not.
return (!GetDB().DoesTableExist("downloads_url_chain") &&
GetDB().Execute(kSchema) && GetDB().Execute(kUrlChainSchema));
}
}
void DownloadDatabase::GetNextDownloadId(uint32* id) {
sql::Statement select_max_id(GetDB().GetUniqueStatement(
"SELECT max(id) FROM downloads"));
if (!select_max_id.Step()) {
DCHECK(false);
*id = content::DownloadItem::kInvalidId + 1;
return;
}
// If there are zero records in the downloads table, then max(id) will return
// 0 = kInvalidId, so GetNextDownloadId() will set *id = kInvalidId + 1.
// If there is at least one record but all of the |id|s are <= kInvalidId,
// then max(id) will return <= kInvalidId, so GetNextDownloadId should return
// kInvalidId + 1. Note that any records with |id <= kInvalidId| will be
// dropped in QueryDownloads()
// SQLITE doesn't have unsigned integers.
*id = 1 + static_cast<uint32>(std::max(
static_cast<int64>(content::DownloadItem::kInvalidId),
select_max_id.ColumnInt64(0)));
}
bool DownloadDatabase::DropDownloadTable() {
return GetDB().Execute("DROP TABLE downloads");
}
void DownloadDatabase::QueryDownloads(
std::vector<DownloadRow>* results) {
EnsureInProgressEntriesCleanedUp();
results->clear();
std::set<uint32> ids;
std::map<uint32, DownloadRow*> info_map;
sql::Statement statement_main(GetDB().GetCachedStatement(SQL_FROM_HERE,
"SELECT id, current_path, target_path, start_time, received_bytes, "
"total_bytes, state, danger_type, interrupt_reason, end_time, opened, "
"referrer, by_ext_id, by_ext_name, etag, last_modified "
"FROM downloads ORDER BY start_time"));
while (statement_main.Step()) {
scoped_ptr<DownloadRow> info(new DownloadRow());
int column = 0;
// SQLITE does not have unsigned integers, so explicitly handle negative
// |id|s instead of casting them to very large uint32s, which would break
// the max(id) logic in GetNextDownloadId().
int64 signed_id = statement_main.ColumnInt64(column++);
info->id = static_cast<uint32>(signed_id);
info->current_path = ColumnFilePath(statement_main, column++);
info->target_path = ColumnFilePath(statement_main, column++);
info->start_time = base::Time::FromInternalValue(
statement_main.ColumnInt64(column++));
info->received_bytes = statement_main.ColumnInt64(column++);
info->total_bytes = statement_main.ColumnInt64(column++);
int state = statement_main.ColumnInt(column++);
info->state = IntToState(state);
if (info->state == DownloadItem::MAX_DOWNLOAD_STATE)
UMA_HISTOGRAM_COUNTS("Download.DatabaseInvalidState", state);
info->danger_type = IntToDangerType(statement_main.ColumnInt(column++));
info->interrupt_reason = static_cast<content::DownloadInterruptReason>(
statement_main.ColumnInt(column++));
info->end_time = base::Time::FromInternalValue(
statement_main.ColumnInt64(column++));
info->opened = statement_main.ColumnInt(column++) != 0;
info->referrer_url = GURL(statement_main.ColumnString(column++));
info->by_ext_id = statement_main.ColumnString(column++);
info->by_ext_name = statement_main.ColumnString(column++);
info->etag = statement_main.ColumnString(column++);
info->last_modified = statement_main.ColumnString(column++);
// If the record is corrupted, note that and drop it.
// http://crbug.com/251269
DroppedReason dropped_reason = DROPPED_REASON_MAX;
if (signed_id <= static_cast<int64>(content::DownloadItem::kInvalidId)) {
// SQLITE doesn't have unsigned integers.
dropped_reason = DROPPED_REASON_BAD_ID;
} else if (!ids.insert(info->id).second) {
dropped_reason = DROPPED_REASON_DUPLICATE_ID;
NOTREACHED() << info->id;
} else if (info->state == DownloadItem::MAX_DOWNLOAD_STATE) {
dropped_reason = DROPPED_REASON_BAD_STATE;
} else if (info->danger_type == content::DOWNLOAD_DANGER_TYPE_MAX) {
dropped_reason = DROPPED_REASON_BAD_DANGER_TYPE;
}
if (dropped_reason != DROPPED_REASON_MAX) {
UMA_HISTOGRAM_ENUMERATION("Download.DatabaseRecordDropped",
dropped_reason,
DROPPED_REASON_MAX + 1);
} else {
DCHECK(!ContainsKey(info_map, info->id));
uint32 id = info->id;
info_map[id] = info.release();
}
}
sql::Statement statement_chain(GetDB().GetCachedStatement(
SQL_FROM_HERE,
"SELECT id, chain_index, url FROM downloads_url_chains "
"ORDER BY id, chain_index"));
while (statement_chain.Step()) {
int column = 0;
// See the comment above about SQLITE lacking unsigned integers.
int64 signed_id = statement_chain.ColumnInt64(column++);
int chain_index = statement_chain.ColumnInt(column++);
if (signed_id <= static_cast<int64>(content::DownloadItem::kInvalidId))
continue;
uint32 id = static_cast<uint32>(signed_id);
// Note that these DCHECKs may trip as a result of corrupted databases.
// We have them because in debug builds the chances are higher there's
// an actual bug than that the database is corrupt, but we handle the
// DB corruption case in production code.
// Confirm the id has already been seen--if it hasn't, discard the
// record.
DCHECK(ContainsKey(info_map, id));
if (!ContainsKey(info_map, id))
continue;
// Confirm all previous URLs in the chain have already been seen;
// if not, fill in with null or discard record.
int current_chain_size = info_map[id]->url_chain.size();
std::vector<GURL>* url_chain(&info_map[id]->url_chain);
DCHECK_EQ(chain_index, current_chain_size);
while (current_chain_size < chain_index) {
url_chain->push_back(GURL());
current_chain_size++;
}
if (current_chain_size > chain_index)
continue;
// Save the record.
url_chain->push_back(GURL(statement_chain.ColumnString(2)));
}
for (std::map<uint32, DownloadRow*>::iterator
it = info_map.begin(); it != info_map.end(); ++it) {
DownloadRow* row = it->second;
bool empty_url_chain = row->url_chain.empty();
UMA_HISTOGRAM_BOOLEAN("Download.DatabaseEmptyUrlChain", empty_url_chain);
if (empty_url_chain) {
RemoveDownload(row->id);
} else {
// Copy the contents of the stored info.
results->push_back(*row);
}
delete row;
it->second = NULL;
}
}
bool DownloadDatabase::UpdateDownload(const DownloadRow& data) {
EnsureInProgressEntriesCleanedUp();
DCHECK_NE(content::DownloadItem::kInvalidId, data.id);
int state = StateToInt(data.state);
if (state == kStateInvalid) {
NOTREACHED();
return false;
}
int danger_type = DangerTypeToInt(data.danger_type);
if (danger_type == kDangerTypeInvalid) {
NOTREACHED();
return false;
}
sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
"UPDATE downloads "
"SET current_path=?, target_path=?, received_bytes=?, state=?, "
"danger_type=?, interrupt_reason=?, end_time=?, total_bytes=?, "
"opened=?, by_ext_id=?, by_ext_name=?, etag=?, last_modified=? "
"WHERE id=?"));
int column = 0;
BindFilePath(statement, data.current_path, column++);
BindFilePath(statement, data.target_path, column++);
statement.BindInt64(column++, data.received_bytes);
statement.BindInt(column++, state);
statement.BindInt(column++, danger_type);
statement.BindInt(column++, static_cast<int>(data.interrupt_reason));
statement.BindInt64(column++, data.end_time.ToInternalValue());
statement.BindInt64(column++, data.total_bytes);
statement.BindInt(column++, (data.opened ? 1 : 0));
statement.BindString(column++, data.by_ext_id);
statement.BindString(column++, data.by_ext_name);
statement.BindString(column++, data.etag);
statement.BindString(column++, data.last_modified);
statement.BindInt(column++, data.id);
return statement.Run();
}
void DownloadDatabase::EnsureInProgressEntriesCleanedUp() {
if (in_progress_entry_cleanup_completed_)
return;
sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
"UPDATE downloads SET state=?, interrupt_reason=? WHERE state=?"));
statement.BindInt(0, kStateInterrupted);
statement.BindInt(1, content::DOWNLOAD_INTERRUPT_REASON_CRASH);
statement.BindInt(2, kStateInProgress);
statement.Run();
in_progress_entry_cleanup_completed_ = true;
}
bool DownloadDatabase::CreateDownload(const DownloadRow& info) {
DCHECK_NE(content::DownloadItem::kInvalidId, info.id);
EnsureInProgressEntriesCleanedUp();
if (info.url_chain.empty())
return false;
int state = StateToInt(info.state);
if (state == kStateInvalid)
return false;
int danger_type = DangerTypeToInt(info.danger_type);
if (danger_type == kDangerTypeInvalid)
return false;
{
sql::Statement statement_insert(GetDB().GetCachedStatement(
SQL_FROM_HERE,
"INSERT INTO downloads "
"(id, current_path, target_path, start_time, "
" received_bytes, total_bytes, state, danger_type, interrupt_reason, "
" end_time, opened, referrer, by_ext_id, by_ext_name, etag, "
" last_modified) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
int column = 0;
statement_insert.BindInt(column++, info.id);
BindFilePath(statement_insert, info.current_path, column++);
BindFilePath(statement_insert, info.target_path, column++);
statement_insert.BindInt64(column++, info.start_time.ToInternalValue());
statement_insert.BindInt64(column++, info.received_bytes);
statement_insert.BindInt64(column++, info.total_bytes);
statement_insert.BindInt(column++, state);
statement_insert.BindInt(column++, danger_type);
statement_insert.BindInt(column++, info.interrupt_reason);
statement_insert.BindInt64(column++, info.end_time.ToInternalValue());
statement_insert.BindInt(column++, info.opened ? 1 : 0);
statement_insert.BindString(column++, info.referrer_url.spec());
statement_insert.BindString(column++, info.by_ext_id);
statement_insert.BindString(column++, info.by_ext_name);
statement_insert.BindString(column++, info.etag);
statement_insert.BindString(column++, info.last_modified);
if (!statement_insert.Run()) {
// GetErrorCode() returns a bitmask where the lower byte is a more general
// code and the upper byte is a more specific code. In order to save
// memory, take the general code, of which there are fewer than 50. See
// also sql/connection.cc
// http://www.sqlite.org/c3ref/c_abort_rollback.html
UMA_HISTOGRAM_ENUMERATION("Download.DatabaseMainInsertError",
GetDB().GetErrorCode() & 0xff, 50);
return false;
}
}
{
sql::Statement count_urls(GetDB().GetCachedStatement(SQL_FROM_HERE,
"SELECT count(*) FROM downloads_url_chains WHERE id=?"));
count_urls.BindInt(0, info.id);
if (count_urls.Step()) {
bool corrupt_urls = count_urls.ColumnInt(0) > 0;
UMA_HISTOGRAM_BOOLEAN("Download.DatabaseCorruptUrls", corrupt_urls);
if (corrupt_urls) {
// There should not be any URLs in downloads_url_chains for this
// info.id. If there are, we don't want them to interfere with
// inserting the correct URLs, so just remove them.
RemoveDownloadURLs(info.id);
}
}
}
sql::Statement statement_insert_chain(
GetDB().GetCachedStatement(SQL_FROM_HERE,
"INSERT INTO downloads_url_chains "
"(id, chain_index, url) "
"VALUES (?, ?, ?)"));
for (size_t i = 0; i < info.url_chain.size(); ++i) {
statement_insert_chain.BindInt(0, info.id);
statement_insert_chain.BindInt(1, i);
statement_insert_chain.BindString(2, info.url_chain[i].spec());
if (!statement_insert_chain.Run()) {
UMA_HISTOGRAM_ENUMERATION("Download.DatabaseURLChainInsertError",
GetDB().GetErrorCode() & 0xff, 50);
RemoveDownload(info.id);
return false;
}
statement_insert_chain.Reset(true);
}
return true;
}
void DownloadDatabase::RemoveDownload(uint32 id) {
EnsureInProgressEntriesCleanedUp();
sql::Statement downloads_statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM downloads WHERE id=?"));
downloads_statement.BindInt(0, id);
if (!downloads_statement.Run()) {
UMA_HISTOGRAM_ENUMERATION("Download.DatabaseMainDeleteError",
GetDB().GetErrorCode() & 0xff, 50);
return;
}
RemoveDownloadURLs(id);
}
void DownloadDatabase::RemoveDownloadURLs(uint32 id) {
sql::Statement urlchain_statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM downloads_url_chains WHERE id=?"));
urlchain_statement.BindInt(0, id);
if (!urlchain_statement.Run()) {
UMA_HISTOGRAM_ENUMERATION("Download.DatabaseURLChainDeleteError",
GetDB().GetErrorCode() & 0xff, 50);
}
}
size_t DownloadDatabase::CountDownloads() {
EnsureInProgressEntriesCleanedUp();
sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
"SELECT count(*) from downloads"));
statement.Step();
return statement.ColumnInt(0);
}
} // namespace history