| OLD | NEW |
| (Empty) |
| 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 | |
| OLD | NEW |