Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/history/download_database.h" | 5 #include "chrome/browser/history/download_database.h" |
| 6 | 6 |
| 7 #include <limits> | 7 #include <limits> |
| 8 #include <string> | 8 #include <string> |
| 9 #include <vector> | 9 #include <vector> |
| 10 | 10 |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 36 DROPPED_REASON_BAD_ID = 2, | 36 DROPPED_REASON_BAD_ID = 2, |
| 37 DROPPED_REASON_DUPLICATE_ID = 3, | 37 DROPPED_REASON_DUPLICATE_ID = 3, |
| 38 DROPPED_REASON_MAX | 38 DROPPED_REASON_MAX |
| 39 }; | 39 }; |
| 40 | 40 |
| 41 static const char kSchema[] = | 41 static const char kSchema[] = |
| 42 "CREATE TABLE downloads (" | 42 "CREATE TABLE downloads (" |
| 43 "id INTEGER PRIMARY KEY," // Primary key. | 43 "id INTEGER PRIMARY KEY," // Primary key. |
| 44 "current_path LONGVARCHAR NOT NULL," // Current disk location | 44 "current_path LONGVARCHAR NOT NULL," // Current disk location |
| 45 "target_path LONGVARCHAR NOT NULL," // Final disk location | 45 "target_path LONGVARCHAR NOT NULL," // Final disk location |
| 46 "mime_type VARCHAR(255) NOT NULL," // Mime type. | |
| 47 "original_mime_type VARCHAR(255) NOT NULL," // Original mime type. | |
|
asanka
2013/12/17 19:14:13
Can you just use VARCHAR? I understand that CHAR w
| |
| 46 "start_time INTEGER NOT NULL," // When the download was started. | 48 "start_time INTEGER NOT NULL," // When the download was started. |
| 47 "received_bytes INTEGER NOT NULL," // Total size downloaded. | 49 "received_bytes INTEGER NOT NULL," // Total size downloaded. |
| 48 "total_bytes INTEGER NOT NULL," // Total size of the download. | 50 "total_bytes INTEGER NOT NULL," // Total size of the download. |
| 49 "state INTEGER NOT NULL," // 1=complete, 4=interrupted | 51 "state INTEGER NOT NULL," // 1=complete, 4=interrupted |
| 50 "danger_type INTEGER NOT NULL, " // Danger type, validated. | 52 "danger_type INTEGER NOT NULL, " // Danger type, validated. |
| 51 "interrupt_reason INTEGER NOT NULL," // content::DownloadInterruptReason | 53 "interrupt_reason INTEGER NOT NULL," // content::DownloadInterruptReason |
| 52 "end_time INTEGER NOT NULL," // When the download completed. | 54 "end_time INTEGER NOT NULL," // When the download completed. |
| 53 "opened INTEGER NOT NULL," // 1 if it has ever been opened else 0 | 55 "opened INTEGER NOT NULL," // 1 if it has ever been opened else 0 |
| 54 "referrer VARCHAR NOT NULL," // HTTP Referrer | 56 "referrer VARCHAR NOT NULL," // HTTP Referrer |
| 55 "by_ext_id VARCHAR NOT NULL," // ID of extension that started the | 57 "by_ext_id VARCHAR NOT NULL," // ID of extension that started the |
| (...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 206 DownloadDatabase::~DownloadDatabase() { | 208 DownloadDatabase::~DownloadDatabase() { |
| 207 } | 209 } |
| 208 | 210 |
| 209 bool DownloadDatabase::EnsureColumnExists( | 211 bool DownloadDatabase::EnsureColumnExists( |
| 210 const std::string& name, const std::string& type) { | 212 const std::string& name, const std::string& type) { |
| 211 std::string add_col = "ALTER TABLE downloads ADD COLUMN " + name + " " + type; | 213 std::string add_col = "ALTER TABLE downloads ADD COLUMN " + name + " " + type; |
| 212 return GetDB().DoesColumnExist("downloads", name.c_str()) || | 214 return GetDB().DoesColumnExist("downloads", name.c_str()) || |
| 213 GetDB().Execute(add_col.c_str()); | 215 GetDB().Execute(add_col.c_str()); |
| 214 } | 216 } |
| 215 | 217 |
| 218 bool DownloadDatabase::MigrateMimeType() { | |
| 219 return EnsureColumnExists("mime_type", "VARCHAR(255) NOT NULL" | |
| 220 " DEFAULT \"\"") && | |
| 221 EnsureColumnExists("original_mime_type", "VARCHAR(255) NOT NULL" | |
| 222 " DEFAULT \"\""); | |
|
asanka
2013/12/17 19:14:13
Same here.
| |
| 223 } | |
| 224 | |
| 216 bool DownloadDatabase::MigrateDownloadsState() { | 225 bool DownloadDatabase::MigrateDownloadsState() { |
| 217 sql::Statement statement(GetDB().GetUniqueStatement( | 226 sql::Statement statement(GetDB().GetUniqueStatement( |
| 218 "UPDATE downloads SET state=? WHERE state=?")); | 227 "UPDATE downloads SET state=? WHERE state=?")); |
| 219 statement.BindInt(0, kStateInterrupted); | 228 statement.BindInt(0, kStateInterrupted); |
| 220 statement.BindInt(1, kStateBug140687); | 229 statement.BindInt(1, kStateBug140687); |
| 221 return statement.Run(); | 230 return statement.Run(); |
| 222 } | 231 } |
| 223 | 232 |
| 224 bool DownloadDatabase::MigrateDownloadsReasonPathsAndDangerType() { | 233 bool DownloadDatabase::MigrateDownloadsReasonPathsAndDangerType() { |
| 225 // We need to rename the table and copy back from it because SQLite | 234 // We need to rename the table and copy back from it because SQLite |
| 226 // provides no way to rename or delete a column. | 235 // provides no way to rename or delete a column. |
| 227 if (!GetDB().Execute("ALTER TABLE downloads RENAME TO downloads_tmp")) | 236 if (!GetDB().Execute("ALTER TABLE downloads RENAME TO downloads_tmp")) |
| 228 return false; | 237 return false; |
| 229 | 238 |
| 230 // Recreate main table. | 239 // Recreate main table. |
| 231 if (!GetDB().Execute(kSchema)) | 240 if (!GetDB().Execute(kSchema)) |
| 232 return false; | 241 return false; |
| 233 | 242 |
| 234 // Populate it. As we do so, we transform the time values from time_t | 243 // 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 | 244 // (seconds since 1/1/1970 UTC), to our internal measure (microseconds |
| 236 // since the Windows Epoch). Note that this is dependent on the | 245 // since the Windows Epoch). Note that this is dependent on the |
| 237 // internal representation of base::Time and needs to change if that changes. | 246 // internal representation of base::Time and needs to change if that changes. |
| 238 sql::Statement statement_populate(GetDB().GetUniqueStatement( | 247 sql::Statement statement_populate(GetDB().GetUniqueStatement( |
| 239 "INSERT INTO downloads " | 248 "INSERT INTO downloads " |
| 240 "( id, current_path, target_path, start_time, received_bytes, " | 249 "( id, current_path, target_path, start_time, received_bytes, " |
| 241 " total_bytes, state, danger_type, interrupt_reason, end_time, opened, " | 250 " total_bytes, state, danger_type, interrupt_reason, end_time, opened, " |
| 242 " referrer, by_ext_id, by_ext_name, etag, last_modified ) " | 251 " referrer, by_ext_id, by_ext_name, etag, last_modified, mime_type, " |
| 252 " original_mime_type ) " | |
|
Randy Smith (Not in Mondays)
2013/12/17 18:48:58
This brings up a philosophical question, which may
sky
2013/12/17 19:22:47
You are correct. Migrations needs to move from ver
| |
| 243 "SELECT id, full_path, full_path, " | 253 "SELECT id, full_path, full_path, " |
| 244 " CASE start_time WHEN 0 THEN 0 ELSE " | 254 " CASE start_time WHEN 0 THEN 0 ELSE " |
| 245 " (start_time + 11644473600) * 1000000 END, " | 255 " (start_time + 11644473600) * 1000000 END, " |
| 246 " received_bytes, total_bytes, " | 256 " received_bytes, total_bytes, " |
| 247 " state, ?, ?, " | 257 " state, ?, ?, " |
| 248 " CASE end_time WHEN 0 THEN 0 ELSE " | 258 " CASE end_time WHEN 0 THEN 0 ELSE " |
| 249 " (end_time + 11644473600) * 1000000 END, " | 259 " (end_time + 11644473600) * 1000000 END, " |
| 250 " opened, \"\", \"\", \"\", \"\", \"\" " | 260 " opened, \"\", \"\", \"\", \"\", \"\", \"\", \"\" " |
| 251 "FROM downloads_tmp")); | 261 "FROM downloads_tmp")); |
| 252 statement_populate.BindInt(0, content::DOWNLOAD_INTERRUPT_REASON_NONE); | 262 statement_populate.BindInt(0, content::DOWNLOAD_INTERRUPT_REASON_NONE); |
| 253 statement_populate.BindInt(1, kDangerTypeNotDangerous); | 263 statement_populate.BindInt(1, kDangerTypeNotDangerous); |
| 254 if (!statement_populate.Run()) | 264 if (!statement_populate.Run()) |
| 255 return false; | 265 return false; |
| 256 | 266 |
| 257 // Create new chain table and populate it. | 267 // Create new chain table and populate it. |
| 258 if (!GetDB().Execute(kUrlChainSchema)) | 268 if (!GetDB().Execute(kUrlChainSchema)) |
| 259 return false; | 269 return false; |
| 260 | 270 |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 281 | 291 |
| 282 bool DownloadDatabase::MigrateDownloadValidators() { | 292 bool DownloadDatabase::MigrateDownloadValidators() { |
| 283 return EnsureColumnExists("etag", "VARCHAR NOT NULL DEFAULT \"\"") && | 293 return EnsureColumnExists("etag", "VARCHAR NOT NULL DEFAULT \"\"") && |
| 284 EnsureColumnExists("last_modified", "VARCHAR NOT NULL DEFAULT \"\""); | 294 EnsureColumnExists("last_modified", "VARCHAR NOT NULL DEFAULT \"\""); |
| 285 } | 295 } |
| 286 | 296 |
| 287 bool DownloadDatabase::InitDownloadTable() { | 297 bool DownloadDatabase::InitDownloadTable() { |
| 288 if (GetDB().DoesTableExist("downloads")) { | 298 if (GetDB().DoesTableExist("downloads")) { |
| 289 return EnsureColumnExists("end_time", "INTEGER NOT NULL DEFAULT 0") && | 299 return EnsureColumnExists("end_time", "INTEGER NOT NULL DEFAULT 0") && |
| 290 EnsureColumnExists("opened", "INTEGER NOT NULL DEFAULT 0"); | 300 EnsureColumnExists("opened", "INTEGER NOT NULL DEFAULT 0"); |
| 301 | |
|
Randy Smith (Not in Mondays)
2013/12/17 18:48:58
nit: Why the extra line?
| |
| 291 } else { | 302 } else { |
| 292 // If the "downloads" table doesn't exist, the downloads_url_chain | 303 // If the "downloads" table doesn't exist, the downloads_url_chain |
| 293 // table better not. | 304 // table better not. |
| 294 return (!GetDB().DoesTableExist("downloads_url_chain") && | 305 return (!GetDB().DoesTableExist("downloads_url_chain") && |
| 295 GetDB().Execute(kSchema) && GetDB().Execute(kUrlChainSchema)); | 306 GetDB().Execute(kSchema) && GetDB().Execute(kUrlChainSchema)); |
| 296 } | 307 } |
| 297 } | 308 } |
| 298 | 309 |
| 299 void DownloadDatabase::GetNextDownloadId(uint32* id) { | 310 void DownloadDatabase::GetNextDownloadId(uint32* id) { |
| 300 sql::Statement select_max_id(GetDB().GetUniqueStatement( | 311 sql::Statement select_max_id(GetDB().GetUniqueStatement( |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 323 void DownloadDatabase::QueryDownloads( | 334 void DownloadDatabase::QueryDownloads( |
| 324 std::vector<DownloadRow>* results) { | 335 std::vector<DownloadRow>* results) { |
| 325 EnsureInProgressEntriesCleanedUp(); | 336 EnsureInProgressEntriesCleanedUp(); |
| 326 | 337 |
| 327 results->clear(); | 338 results->clear(); |
| 328 std::set<uint32> ids; | 339 std::set<uint32> ids; |
| 329 | 340 |
| 330 std::map<uint32, DownloadRow*> info_map; | 341 std::map<uint32, DownloadRow*> info_map; |
| 331 | 342 |
| 332 sql::Statement statement_main(GetDB().GetCachedStatement(SQL_FROM_HERE, | 343 sql::Statement statement_main(GetDB().GetCachedStatement(SQL_FROM_HERE, |
| 333 "SELECT id, current_path, target_path, start_time, received_bytes, " | 344 "SELECT id, current_path, target_path, " |
| 345 "mime_type, original_mime_type, " | |
| 346 "start_time, received_bytes, " | |
| 334 "total_bytes, state, danger_type, interrupt_reason, end_time, opened, " | 347 "total_bytes, state, danger_type, interrupt_reason, end_time, opened, " |
| 335 "referrer, by_ext_id, by_ext_name, etag, last_modified " | 348 "referrer, by_ext_id, by_ext_name, etag, last_modified " |
| 336 "FROM downloads ORDER BY start_time")); | 349 "FROM downloads ORDER BY start_time")); |
| 337 | 350 |
| 338 while (statement_main.Step()) { | 351 while (statement_main.Step()) { |
| 339 scoped_ptr<DownloadRow> info(new DownloadRow()); | 352 scoped_ptr<DownloadRow> info(new DownloadRow()); |
| 340 int column = 0; | 353 int column = 0; |
| 341 | 354 |
| 342 // SQLITE does not have unsigned integers, so explicitly handle negative | 355 // SQLITE does not have unsigned integers, so explicitly handle negative |
| 343 // |id|s instead of casting them to very large uint32s, which would break | 356 // |id|s instead of casting them to very large uint32s, which would break |
| 344 // the max(id) logic in GetNextDownloadId(). | 357 // the max(id) logic in GetNextDownloadId(). |
| 345 int64 signed_id = statement_main.ColumnInt64(column++); | 358 int64 signed_id = statement_main.ColumnInt64(column++); |
| 346 info->id = static_cast<uint32>(signed_id); | 359 info->id = static_cast<uint32>(signed_id); |
| 347 info->current_path = ColumnFilePath(statement_main, column++); | 360 info->current_path = ColumnFilePath(statement_main, column++); |
| 348 info->target_path = ColumnFilePath(statement_main, column++); | 361 info->target_path = ColumnFilePath(statement_main, column++); |
| 362 info->mime_type = statement_main.ColumnString(column++); | |
| 363 info->original_mime_type = statement_main.ColumnString(column++); | |
| 349 info->start_time = base::Time::FromInternalValue( | 364 info->start_time = base::Time::FromInternalValue( |
| 350 statement_main.ColumnInt64(column++)); | 365 statement_main.ColumnInt64(column++)); |
| 351 info->received_bytes = statement_main.ColumnInt64(column++); | 366 info->received_bytes = statement_main.ColumnInt64(column++); |
| 352 info->total_bytes = statement_main.ColumnInt64(column++); | 367 info->total_bytes = statement_main.ColumnInt64(column++); |
| 353 int state = statement_main.ColumnInt(column++); | 368 int state = statement_main.ColumnInt(column++); |
| 354 info->state = IntToState(state); | 369 info->state = IntToState(state); |
| 355 if (info->state == DownloadItem::MAX_DOWNLOAD_STATE) | 370 if (info->state == DownloadItem::MAX_DOWNLOAD_STATE) |
| 356 UMA_HISTOGRAM_COUNTS("Download.DatabaseInvalidState", state); | 371 UMA_HISTOGRAM_COUNTS("Download.DatabaseInvalidState", state); |
| 357 info->danger_type = IntToDangerType(statement_main.ColumnInt(column++)); | 372 info->danger_type = IntToDangerType(statement_main.ColumnInt(column++)); |
| 358 info->interrupt_reason = static_cast<content::DownloadInterruptReason>( | 373 info->interrupt_reason = static_cast<content::DownloadInterruptReason>( |
| (...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 459 return false; | 474 return false; |
| 460 } | 475 } |
| 461 int danger_type = DangerTypeToInt(data.danger_type); | 476 int danger_type = DangerTypeToInt(data.danger_type); |
| 462 if (danger_type == kDangerTypeInvalid) { | 477 if (danger_type == kDangerTypeInvalid) { |
| 463 NOTREACHED(); | 478 NOTREACHED(); |
| 464 return false; | 479 return false; |
| 465 } | 480 } |
| 466 | 481 |
| 467 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, | 482 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, |
| 468 "UPDATE downloads " | 483 "UPDATE downloads " |
| 469 "SET current_path=?, target_path=?, received_bytes=?, state=?, " | 484 "SET current_path=?, target_path=?, " |
| 485 "mime_type=?, original_mime_type=?, " | |
| 486 "received_bytes=?, state=?, " | |
| 470 "danger_type=?, interrupt_reason=?, end_time=?, total_bytes=?, " | 487 "danger_type=?, interrupt_reason=?, end_time=?, total_bytes=?, " |
| 471 "opened=?, by_ext_id=?, by_ext_name=?, etag=?, last_modified=? " | 488 "opened=?, by_ext_id=?, by_ext_name=?, etag=?, last_modified=? " |
| 472 "WHERE id=?")); | 489 "WHERE id=?")); |
| 473 int column = 0; | 490 int column = 0; |
| 474 BindFilePath(statement, data.current_path, column++); | 491 BindFilePath(statement, data.current_path, column++); |
| 475 BindFilePath(statement, data.target_path, column++); | 492 BindFilePath(statement, data.target_path, column++); |
| 493 statement.BindString(column++, data.mime_type); | |
| 494 statement.BindString(column++, data.original_mime_type); | |
| 476 statement.BindInt64(column++, data.received_bytes); | 495 statement.BindInt64(column++, data.received_bytes); |
| 477 statement.BindInt(column++, state); | 496 statement.BindInt(column++, state); |
| 478 statement.BindInt(column++, danger_type); | 497 statement.BindInt(column++, danger_type); |
| 479 statement.BindInt(column++, static_cast<int>(data.interrupt_reason)); | 498 statement.BindInt(column++, static_cast<int>(data.interrupt_reason)); |
| 480 statement.BindInt64(column++, data.end_time.ToInternalValue()); | 499 statement.BindInt64(column++, data.end_time.ToInternalValue()); |
| 481 statement.BindInt64(column++, data.total_bytes); | 500 statement.BindInt64(column++, data.total_bytes); |
| 482 statement.BindInt(column++, (data.opened ? 1 : 0)); | 501 statement.BindInt(column++, (data.opened ? 1 : 0)); |
| 483 statement.BindString(column++, data.by_ext_id); | 502 statement.BindString(column++, data.by_ext_id); |
| 484 statement.BindString(column++, data.by_ext_name); | 503 statement.BindString(column++, data.by_ext_name); |
| 485 statement.BindString(column++, data.etag); | 504 statement.BindString(column++, data.etag); |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 515 return false; | 534 return false; |
| 516 | 535 |
| 517 int danger_type = DangerTypeToInt(info.danger_type); | 536 int danger_type = DangerTypeToInt(info.danger_type); |
| 518 if (danger_type == kDangerTypeInvalid) | 537 if (danger_type == kDangerTypeInvalid) |
| 519 return false; | 538 return false; |
| 520 | 539 |
| 521 { | 540 { |
| 522 sql::Statement statement_insert(GetDB().GetCachedStatement( | 541 sql::Statement statement_insert(GetDB().GetCachedStatement( |
| 523 SQL_FROM_HERE, | 542 SQL_FROM_HERE, |
| 524 "INSERT INTO downloads " | 543 "INSERT INTO downloads " |
| 525 "(id, current_path, target_path, start_time, " | 544 "(id, current_path, target_path, " |
| 545 "mime_type, original_mime_type, " | |
| 546 "start_time, " | |
|
asanka
2013/12/17 19:14:13
Also micro nit: One extra space in the first colum
| |
| 526 " received_bytes, total_bytes, state, danger_type, interrupt_reason, " | 547 " received_bytes, total_bytes, state, danger_type, interrupt_reason, " |
| 527 " end_time, opened, referrer, by_ext_id, by_ext_name, etag, " | 548 " end_time, opened, referrer, by_ext_id, by_ext_name, etag, " |
| 528 " last_modified) " | 549 " last_modified) " |
| 529 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")); | 550 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")); |
| 530 | 551 |
| 531 int column = 0; | 552 int column = 0; |
| 532 statement_insert.BindInt(column++, info.id); | 553 statement_insert.BindInt(column++, info.id); |
| 533 BindFilePath(statement_insert, info.current_path, column++); | 554 BindFilePath(statement_insert, info.current_path, column++); |
| 534 BindFilePath(statement_insert, info.target_path, column++); | 555 BindFilePath(statement_insert, info.target_path, column++); |
| 556 statement_insert.BindString(column++, info.mime_type); | |
| 557 statement_insert.BindString(column++, info.original_mime_type); | |
| 535 statement_insert.BindInt64(column++, info.start_time.ToInternalValue()); | 558 statement_insert.BindInt64(column++, info.start_time.ToInternalValue()); |
| 536 statement_insert.BindInt64(column++, info.received_bytes); | 559 statement_insert.BindInt64(column++, info.received_bytes); |
| 537 statement_insert.BindInt64(column++, info.total_bytes); | 560 statement_insert.BindInt64(column++, info.total_bytes); |
| 538 statement_insert.BindInt(column++, state); | 561 statement_insert.BindInt(column++, state); |
| 539 statement_insert.BindInt(column++, danger_type); | 562 statement_insert.BindInt(column++, danger_type); |
| 540 statement_insert.BindInt(column++, info.interrupt_reason); | 563 statement_insert.BindInt(column++, info.interrupt_reason); |
| 541 statement_insert.BindInt64(column++, info.end_time.ToInternalValue()); | 564 statement_insert.BindInt64(column++, info.end_time.ToInternalValue()); |
| 542 statement_insert.BindInt(column++, info.opened ? 1 : 0); | 565 statement_insert.BindInt(column++, info.opened ? 1 : 0); |
| 543 statement_insert.BindString(column++, info.referrer_url.spec()); | 566 statement_insert.BindString(column++, info.referrer_url.spec()); |
| 544 statement_insert.BindString(column++, info.by_ext_id); | 567 statement_insert.BindString(column++, info.by_ext_id); |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 620 size_t DownloadDatabase::CountDownloads() { | 643 size_t DownloadDatabase::CountDownloads() { |
| 621 EnsureInProgressEntriesCleanedUp(); | 644 EnsureInProgressEntriesCleanedUp(); |
| 622 | 645 |
| 623 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, | 646 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, |
| 624 "SELECT count(*) from downloads")); | 647 "SELECT count(*) from downloads")); |
| 625 statement.Step(); | 648 statement.Step(); |
| 626 return statement.ColumnInt(0); | 649 return statement.ColumnInt(0); |
| 627 } | 650 } |
| 628 | 651 |
| 629 } // namespace history | 652 } // namespace history |
| OLD | NEW |