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/top_sites_database.h" | 5 #include "chrome/browser/history/top_sites_database.h" |
6 | 6 |
7 #include "base/files/file_util.h" | 7 #include "base/files/file_util.h" |
8 #include "base/memory/ref_counted.h" | 8 #include "base/memory/ref_counted.h" |
9 #include "base/metrics/histogram.h" | 9 #include "base/metrics/histogram.h" |
10 #include "base/strings/string_split.h" | 10 #include "base/strings/string_split.h" |
11 #include "base/strings/string_util.h" | 11 #include "base/strings/string_util.h" |
12 #include "chrome/browser/history/history_types.h" | 12 #include "chrome/browser/history/history_types.h" |
13 #include "chrome/browser/history/top_sites.h" | 13 #include "chrome/browser/history/top_sites.h" |
14 #include "components/history/core/common/thumbnail_score.h" | 14 #include "components/history/core/common/thumbnail_score.h" |
15 #include "sql/connection.h" | 15 #include "sql/connection.h" |
16 #include "sql/recovery.h" | 16 #include "sql/recovery.h" |
17 #include "sql/statement.h" | 17 #include "sql/statement.h" |
18 #include "sql/transaction.h" | 18 #include "sql/transaction.h" |
19 #include "third_party/sqlite/sqlite3.h" | 19 #include "third_party/sqlite/sqlite3.h" |
20 | 20 |
21 // Description of database table: | 21 // Description of database table: |
22 // | 22 // |
23 // thumbnails | 23 // thumbnails |
24 // url URL of the sites for which we have a thumbnail. | 24 // url URL of the sites for which we have a thumbnail. |
25 // url_rank Index of the URL in that thumbnail, 0-based. The thumbnail | 25 // url_rank Index of the URL in that thumbnail, 0-based. The thumbnail |
26 // with the highest rank will be the next one evicted. Forced | 26 // with the highest rank will be the next one evicted. Forced |
27 // thumbnails have a rank of -1. | 27 // thumbnails have a rank of -1. |
28 // title The title to display under that thumbnail. | 28 // title The title to display under that thumbnail. |
29 // redirects A space separated list of URLs that are known to redirect | 29 // redirects A comma-separated list of URLs that are known to redirect |
30 // to this url. | 30 // to this url. Each URL is surrounded by quotes, and any |
| 31 // existing quote is escaped to two quotes. |
31 // boring_score How "boring" that thumbnail is. See ThumbnailScore. | 32 // boring_score How "boring" that thumbnail is. See ThumbnailScore. |
32 // good_clipping True if the thumbnail was clipped from the bottom, keeping | 33 // good_clipping True if the thumbnail was clipped from the bottom, keeping |
33 // the entire width of the window. See ThumbnailScore. | 34 // the entire width of the window. See ThumbnailScore. |
34 // at_top True if the thumbnail was captured at the top of the | 35 // at_top True if the thumbnail was captured at the top of the |
35 // website. | 36 // website. |
36 // last_updated The time at which this thumbnail was last updated. | 37 // last_updated The time at which this thumbnail was last updated. |
37 // load_completed True if the thumbnail was captured after the page load was | 38 // load_completed True if the thumbnail was captured after the page load was |
38 // completed. | 39 // completed. |
39 // last_forced If this is a forced thumbnail, records the last time it | 40 // last_forced If this is a forced thumbnail, records the last time it |
40 // was forced. If it's not a forced thumbnail, 0. | 41 // was forced. If it's not a forced thumbnail, 0. |
41 | 42 |
42 namespace { | 43 namespace { |
43 | 44 |
44 // For this database, schema migrations are deprecated after two | 45 // For this database, schema migrations are deprecated after two |
45 // years. This means that the oldest non-deprecated version should be | 46 // years. This means that the oldest non-deprecated version should be |
46 // two years old or greater (thus the migrations to get there are | 47 // two years old or greater (thus the migrations to get there are |
47 // older). Databases containing deprecated versions will be cleared | 48 // older). Databases containing deprecated versions will be cleared |
48 // at startup. Since this database is a cache, losing old data is not | 49 // at startup. Since this database is a cache, losing old data is not |
49 // fatal (in fact, very old data may be expired immediately at startup | 50 // fatal (in fact, very old data may be expired immediately at startup |
50 // anyhow). | 51 // anyhow). |
51 | 52 |
| 53 // Version 4: by huangs@chromium.org |
52 // Version 3: b6d6a783/r231648 by beaudoin@chromium.org on 2013-10-29 | 54 // Version 3: b6d6a783/r231648 by beaudoin@chromium.org on 2013-10-29 |
53 // Version 2: eb0b24e6/r87284 by satorux@chromium.org on 2011-05-31 | 55 // Version 2: eb0b24e6/r87284 by satorux@chromium.org on 2011-05-31 |
54 // Version 1: 809cc4d8/r64072 by sky@chromium.org on 2010-10-27 (deprecated) | 56 // Version 1: 809cc4d8/r64072 by sky@chromium.org on 2010-10-27 (deprecated) |
55 | 57 |
56 // NOTE(shess): When changing the version, add a new golden file for | 58 // NOTE(shess): When changing the version, add a new golden file for |
57 // the new version and a test to verify that Init() works with it. | 59 // the new version and a test to verify that Init() works with it. |
58 // NOTE(shess): RecoverDatabaseOrRaze() depends on the specific | 60 // NOTE(shess): RecoverDatabaseOrRaze() depends on the specific |
59 // version number. The code is subtle and in development, contact me | 61 // version number. The code is subtle and in development, contact me |
60 // if the necessary changes are not obvious. | 62 // if the necessary changes are not obvious. |
61 static const int kVersionNumber = 3; | 63 static const int kVersionNumber = 4; |
62 static const int kDeprecatedVersionNumber = 1; // and earlier. | 64 static const int kDeprecatedVersionNumber = 1; // and earlier. |
63 | 65 |
64 bool InitTables(sql::Connection* db) { | 66 bool InitTables(sql::Connection* db) { |
65 const char kThumbnailsSql[] = | 67 const char kThumbnailsSql[] = |
66 "CREATE TABLE IF NOT EXISTS thumbnails (" | 68 "CREATE TABLE IF NOT EXISTS thumbnails (" |
67 "url LONGVARCHAR PRIMARY KEY," | 69 "url LONGVARCHAR PRIMARY KEY," |
68 "url_rank INTEGER," | 70 "url_rank INTEGER," |
69 "title LONGVARCHAR," | 71 "title LONGVARCHAR," |
70 "thumbnail BLOB," | 72 "thumbnail BLOB," |
71 "redirects LONGVARCHAR," | 73 "redirects LONGVARCHAR," |
72 "boring_score DOUBLE DEFAULT 1.0," | 74 "boring_score DOUBLE DEFAULT 1.0," |
73 "good_clipping INTEGER DEFAULT 0," | 75 "good_clipping INTEGER DEFAULT 0," |
74 "at_top INTEGER DEFAULT 0," | 76 "at_top INTEGER DEFAULT 0," |
75 "last_updated INTEGER DEFAULT 0," | 77 "last_updated INTEGER DEFAULT 0," |
76 "load_completed INTEGER DEFAULT 0," | 78 "load_completed INTEGER DEFAULT 0," |
77 "last_forced INTEGER DEFAULT 0)"; | 79 "last_forced INTEGER DEFAULT 0)"; |
78 return db->Execute(kThumbnailsSql); | 80 return db->Execute(kThumbnailsSql); |
79 } | 81 } |
80 | 82 |
81 // Encodes redirects into a string. | |
82 std::string GetRedirects(const history::MostVisitedURL& url) { | |
83 std::vector<std::string> redirects; | |
84 for (size_t i = 0; i < url.redirects.size(); i++) | |
85 redirects.push_back(url.redirects[i].spec()); | |
86 return JoinString(redirects, ' '); | |
87 } | |
88 | |
89 // Decodes redirects from a string and sets them for the url. | |
90 void SetRedirects(const std::string& redirects, history::MostVisitedURL* url) { | |
91 std::vector<std::string> redirects_vector; | |
92 base::SplitStringAlongWhitespace(redirects, &redirects_vector); | |
93 for (size_t i = 0; i < redirects_vector.size(); ++i) | |
94 url->redirects.push_back(GURL(redirects_vector[i])); | |
95 } | |
96 | |
97 // Track various failure (and success) cases in recovery code. | 83 // Track various failure (and success) cases in recovery code. |
98 // | 84 // |
99 // TODO(shess): The recovery code is complete, but by nature runs in challenging | 85 // TODO(shess): The recovery code is complete, but by nature runs in challenging |
100 // circumstances, so initially the default error response is to leave the | 86 // circumstances, so initially the default error response is to leave the |
101 // existing database in place. This histogram is intended to expose the | 87 // existing database in place. This histogram is intended to expose the |
102 // failures seen in the fleet. Frequent failure cases can be explored more | 88 // failures seen in the fleet. Frequent failure cases can be explored more |
103 // deeply to see if the complexity to fix them is warranted. Infrequent failure | 89 // deeply to see if the complexity to fix them is warranted. Infrequent failure |
104 // cases can be resolved by marking the database unrecoverable (which will | 90 // cases can be resolved by marking the database unrecoverable (which will |
105 // delete the data). | 91 // delete the data). |
106 // | 92 // |
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
189 ++next_rank; | 175 ++next_rank; |
190 } | 176 } |
191 if (adjusted) | 177 if (adjusted) |
192 RecordRecoveryEvent(RECOVERY_EVENT_INVARIANT_CONTIGUOUS); | 178 RecordRecoveryEvent(RECOVERY_EVENT_INVARIANT_CONTIGUOUS); |
193 } | 179 } |
194 | 180 |
195 // Recover the database to the extent possible, razing it if recovery is not | 181 // Recover the database to the extent possible, razing it if recovery is not |
196 // possible. | 182 // possible. |
197 void RecoverDatabaseOrRaze(sql::Connection* db, const base::FilePath& db_path) { | 183 void RecoverDatabaseOrRaze(sql::Connection* db, const base::FilePath& db_path) { |
198 // NOTE(shess): If the version changes, review this code. | 184 // NOTE(shess): If the version changes, review this code. |
199 DCHECK_EQ(3, kVersionNumber); | 185 DCHECK_EQ(4, kVersionNumber); |
200 | 186 |
201 // It is almost certain that some operation against |db| will fail, prevent | 187 // It is almost certain that some operation against |db| will fail, prevent |
202 // reentry. | 188 // reentry. |
203 db->reset_error_callback(); | 189 db->reset_error_callback(); |
204 | 190 |
205 // For generating histogram stats. | 191 // For generating histogram stats. |
206 size_t thumbnails_recovered = 0; | 192 size_t thumbnails_recovered = 0; |
207 int64 original_size = 0; | 193 int64 original_size = 0; |
208 base::GetFileSize(db_path, &original_size); | 194 base::GetFileSize(db_path, &original_size); |
209 | 195 |
(...skipping 20 matching lines...) Expand all Loading... |
230 // to raze the database. | 216 // to raze the database. |
231 if (version <= kDeprecatedVersionNumber) { | 217 if (version <= kDeprecatedVersionNumber) { |
232 sql::Recovery::Unrecoverable(recovery.Pass()); | 218 sql::Recovery::Unrecoverable(recovery.Pass()); |
233 RecordRecoveryEvent(RECOVERY_EVENT_DEPRECATED); | 219 RecordRecoveryEvent(RECOVERY_EVENT_DEPRECATED); |
234 return; | 220 return; |
235 } | 221 } |
236 | 222 |
237 // TODO(shess): Earlier versions have been deprecated, later versions should | 223 // TODO(shess): Earlier versions have been deprecated, later versions should |
238 // be impossible. Unrecoverable() seems like a feasible response if this is | 224 // be impossible. Unrecoverable() seems like a feasible response if this is |
239 // infrequent enough. | 225 // infrequent enough. |
240 if (version != 2 && version != 3) { | 226 if (version != 2 && version != 3 && version != 4) { |
241 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_WRONG_VERSION); | 227 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_WRONG_VERSION); |
242 sql::Recovery::Rollback(recovery.Pass()); | 228 sql::Recovery::Rollback(recovery.Pass()); |
243 return; | 229 return; |
244 } | 230 } |
245 | 231 |
246 // Both v2 and v3 recover to current schema version. | 232 // v2, v3, and v4 recover to current schema version. |
247 sql::MetaTable recover_meta_table; | 233 sql::MetaTable recover_meta_table; |
248 if (!recover_meta_table.Init(recovery->db(), kVersionNumber, | 234 if (!recover_meta_table.Init(recovery->db(), kVersionNumber, |
249 kVersionNumber)) { | 235 kVersionNumber)) { |
250 sql::Recovery::Rollback(recovery.Pass()); | 236 sql::Recovery::Rollback(recovery.Pass()); |
251 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_INIT); | 237 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_INIT); |
252 return; | 238 return; |
253 } | 239 } |
254 | 240 |
255 // Create a fresh version of the schema. The recovery code uses | 241 // Create a fresh version of the schema. The recovery code uses |
256 // conflict-resolution to handle duplicates, so any indices are necessary. | 242 // conflict-resolution to handle duplicates, so any indices are necessary. |
(...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
406 if (!InitTables(db_.get())) | 392 if (!InitTables(db_.get())) |
407 return false; | 393 return false; |
408 | 394 |
409 if (meta_table_.GetVersionNumber() == 2) { | 395 if (meta_table_.GetVersionNumber() == 2) { |
410 if (!UpgradeToVersion3()) { | 396 if (!UpgradeToVersion3()) { |
411 LOG(WARNING) << "Unable to upgrade top sites database to version 3."; | 397 LOG(WARNING) << "Unable to upgrade top sites database to version 3."; |
412 return false; | 398 return false; |
413 } | 399 } |
414 } | 400 } |
415 | 401 |
| 402 if (meta_table_.GetVersionNumber() == 3) { |
| 403 if (!UpgradeToVersion4()) { |
| 404 LOG(WARNING) << "Unable to upgrade top sites database to version 4."; |
| 405 return false; |
| 406 } |
| 407 } |
| 408 |
416 // Version check. | 409 // Version check. |
417 if (meta_table_.GetVersionNumber() != kVersionNumber) | 410 if (meta_table_.GetVersionNumber() != kVersionNumber) |
418 return false; | 411 return false; |
419 | 412 |
420 // Initialization is complete. | 413 // Initialization is complete. |
421 if (!transaction.Commit()) | 414 if (!transaction.Commit()) |
422 return false; | 415 return false; |
423 | 416 |
424 return true; | 417 return true; |
425 } | 418 } |
426 | 419 |
427 bool TopSitesDatabase::UpgradeToVersion3() { | 420 bool TopSitesDatabase::UpgradeToVersion3() { |
428 // Add 'last_forced' column. | 421 // Add 'last_forced' column. |
429 if (!db_->Execute( | 422 if (!db_->Execute( |
430 "ALTER TABLE thumbnails ADD last_forced INTEGER DEFAULT 0")) { | 423 "ALTER TABLE thumbnails ADD last_forced INTEGER DEFAULT 0")) { |
431 NOTREACHED(); | 424 NOTREACHED(); |
432 return false; | 425 return false; |
433 } | 426 } |
434 meta_table_.SetVersionNumber(3); | 427 meta_table_.SetVersionNumber(3); |
435 return true; | 428 return true; |
436 } | 429 } |
437 | 430 |
| 431 bool TopSitesDatabase::UpgradeToVersion4() { |
| 432 LOG(ERROR) << "Migrating to V4"; |
| 433 |
| 434 // Migrate the "redirects" field from space-separated list in v3 to |
| 435 // comma-separated list of quoted strings in v4. |
| 436 const char kSelectSql[] = "SELECT redirects, rowid FROM thumbnails"; |
| 437 sql::Statement select_statement(db_->GetUniqueStatement(kSelectSql)); |
| 438 |
| 439 const char kUpdateSql[] = |
| 440 "UPDATE thumbnails SET redirects = ? WHERE rowid = ?"; |
| 441 sql::Statement update_statement(db_->GetUniqueStatement(kUpdateSql)); |
| 442 |
| 443 while (select_statement.Step()) { |
| 444 const std::string encoded_redirects_v3(select_statement.ColumnString(0)); |
| 445 |
| 446 LOG(ERROR) << "Old: " << encoded_redirects_v3; |
| 447 // Skip if empty or if previously migrated |
| 448 if (encoded_redirects_v3.empty() || encoded_redirects_v3[0] == '"') |
| 449 continue; |
| 450 |
| 451 // Parse v3 redirects, which is a space-separated list of URLs. |
| 452 std::vector<std::string> redirects_vector; |
| 453 RedirectList redirects; |
| 454 base::SplitStringAlongWhitespace(encoded_redirects_v3, &redirects_vector); |
| 455 for (size_t i = 0; i < redirects_vector.size(); ++i) { |
| 456 GURL redirect_url(redirects_vector[i]); |
| 457 if (redirect_url.is_valid()) |
| 458 redirects.push_back(redirect_url); |
| 459 } |
| 460 |
| 461 std::string encoded_redirects(EncodeRedirects(redirects)); |
| 462 LOG(ERROR) << "New: " << encoded_redirects; |
| 463 |
| 464 update_statement.Reset(true); |
| 465 update_statement.BindString(0, encoded_redirects); |
| 466 update_statement.BindInt64(1, select_statement.ColumnInt64(1)); |
| 467 update_statement.Run(); |
| 468 } |
| 469 |
| 470 meta_table_.SetVersionNumber(4); |
| 471 return true; |
| 472 } |
| 473 |
438 void TopSitesDatabase::GetPageThumbnails(MostVisitedURLList* urls, | 474 void TopSitesDatabase::GetPageThumbnails(MostVisitedURLList* urls, |
439 URLToImagesMap* thumbnails) { | 475 URLToImagesMap* thumbnails) { |
440 sql::Statement statement(db_->GetCachedStatement( | 476 sql::Statement statement(db_->GetCachedStatement( |
441 SQL_FROM_HERE, | 477 SQL_FROM_HERE, |
442 "SELECT url, url_rank, title, thumbnail, redirects, " | 478 "SELECT url, url_rank, title, thumbnail, redirects, " |
443 "boring_score, good_clipping, at_top, last_updated, load_completed, " | 479 "boring_score, good_clipping, at_top, last_updated, load_completed, " |
444 "last_forced FROM thumbnails ORDER BY url_rank, last_forced")); | 480 "last_forced FROM thumbnails ORDER BY url_rank, last_forced")); |
445 | 481 |
446 if (!statement.is_valid()) { | 482 if (!statement.is_valid()) { |
447 LOG(WARNING) << db_->GetErrorMessage(); | 483 LOG(WARNING) << db_->GetErrorMessage(); |
448 return; | 484 return; |
449 } | 485 } |
450 | 486 |
451 urls->clear(); | 487 urls->clear(); |
452 thumbnails->clear(); | 488 thumbnails->clear(); |
453 | 489 |
454 while (statement.Step()) { | 490 while (statement.Step()) { |
455 // Results are sorted by url_rank. For forced thumbnails with url_rank = -1, | 491 // Results are sorted by url_rank. For forced thumbnails with url_rank = -1, |
456 // thumbnails are sorted by last_forced. | 492 // thumbnails are sorted by last_forced. |
457 MostVisitedURL url; | 493 MostVisitedURL url; |
458 GURL gurl(statement.ColumnString(0)); | 494 GURL gurl(statement.ColumnString(0)); |
459 url.url = gurl; | 495 url.url = gurl; |
460 url.title = statement.ColumnString16(2); | 496 url.title = statement.ColumnString16(2); |
461 url.last_forced_time = | 497 url.last_forced_time = |
462 base::Time::FromInternalValue(statement.ColumnInt64(10)); | 498 base::Time::FromInternalValue(statement.ColumnInt64(10)); |
463 std::string redirects = statement.ColumnString(4); | 499 LOG(ERROR) << "Read db: redirects = " << statement.ColumnString(4); |
464 SetRedirects(redirects, &url); | 500 std::string encoded_redirects = statement.ColumnString(4); |
| 501 DecodeRedirects(encoded_redirects, &url.redirects); |
465 urls->push_back(url); | 502 urls->push_back(url); |
466 | 503 |
467 std::vector<unsigned char> data; | 504 std::vector<unsigned char> data; |
468 statement.ColumnBlobAsVector(3, &data); | 505 statement.ColumnBlobAsVector(3, &data); |
469 Images thumbnail; | 506 Images thumbnail; |
470 if (!data.empty()) | 507 if (!data.empty()) |
471 thumbnail.thumbnail = base::RefCountedBytes::TakeVector(&data); | 508 thumbnail.thumbnail = base::RefCountedBytes::TakeVector(&data); |
472 thumbnail.thumbnail_score.boring_score = statement.ColumnDouble(5); | 509 thumbnail.thumbnail_score.boring_score = statement.ColumnDouble(5); |
473 thumbnail.thumbnail_score.good_clipping = statement.ColumnBool(6); | 510 thumbnail.thumbnail_score.good_clipping = statement.ColumnBool(6); |
474 thumbnail.thumbnail_score.at_top = statement.ColumnBool(7); | 511 thumbnail.thumbnail_score.at_top = statement.ColumnBool(7); |
(...skipping 28 matching lines...) Expand all Loading... |
503 "UPDATE thumbnails SET " | 540 "UPDATE thumbnails SET " |
504 "title = ?, thumbnail = ?, redirects = ?, " | 541 "title = ?, thumbnail = ?, redirects = ?, " |
505 "boring_score = ?, good_clipping = ?, at_top = ?, last_updated = ?, " | 542 "boring_score = ?, good_clipping = ?, at_top = ?, last_updated = ?, " |
506 "load_completed = ?, last_forced = ?" | 543 "load_completed = ?, last_forced = ?" |
507 "WHERE url = ? ")); | 544 "WHERE url = ? ")); |
508 statement.BindString16(0, url.title); | 545 statement.BindString16(0, url.title); |
509 if (thumbnail.thumbnail.get() && thumbnail.thumbnail->front()) { | 546 if (thumbnail.thumbnail.get() && thumbnail.thumbnail->front()) { |
510 statement.BindBlob(1, thumbnail.thumbnail->front(), | 547 statement.BindBlob(1, thumbnail.thumbnail->front(), |
511 static_cast<int>(thumbnail.thumbnail->size())); | 548 static_cast<int>(thumbnail.thumbnail->size())); |
512 } | 549 } |
513 statement.BindString(2, GetRedirects(url)); | 550 statement.BindString(2, EncodeRedirects(url.redirects)); |
514 const ThumbnailScore& score = thumbnail.thumbnail_score; | 551 const ThumbnailScore& score = thumbnail.thumbnail_score; |
515 statement.BindDouble(3, score.boring_score); | 552 statement.BindDouble(3, score.boring_score); |
516 statement.BindBool(4, score.good_clipping); | 553 statement.BindBool(4, score.good_clipping); |
517 statement.BindBool(5, score.at_top); | 554 statement.BindBool(5, score.at_top); |
518 statement.BindInt64(6, score.time_at_snapshot.ToInternalValue()); | 555 statement.BindInt64(6, score.time_at_snapshot.ToInternalValue()); |
519 statement.BindBool(7, score.load_completed); | 556 statement.BindBool(7, score.load_completed); |
520 statement.BindInt64(8, url.last_forced_time.ToInternalValue()); | 557 statement.BindInt64(8, url.last_forced_time.ToInternalValue()); |
521 statement.BindString(9, url.url.spec()); | 558 statement.BindString(9, url.url.spec()); |
522 | 559 |
523 return statement.Run(); | 560 return statement.Run(); |
524 } | 561 } |
525 | 562 |
526 void TopSitesDatabase::AddPageThumbnail(const MostVisitedURL& url, | 563 void TopSitesDatabase::AddPageThumbnail(const MostVisitedURL& url, |
527 int new_rank, | 564 int new_rank, |
528 const Images& thumbnail) { | 565 const Images& thumbnail) { |
529 sql::Statement statement(db_->GetCachedStatement( | 566 sql::Statement statement(db_->GetCachedStatement( |
530 SQL_FROM_HERE, | 567 SQL_FROM_HERE, |
531 "INSERT OR REPLACE INTO thumbnails " | 568 "INSERT OR REPLACE INTO thumbnails " |
532 "(url, url_rank, title, thumbnail, redirects, " | 569 "(url, url_rank, title, thumbnail, redirects, " |
533 "boring_score, good_clipping, at_top, last_updated, load_completed, " | 570 "boring_score, good_clipping, at_top, last_updated, load_completed, " |
534 "last_forced) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")); | 571 "last_forced) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")); |
535 statement.BindString(0, url.url.spec()); | 572 statement.BindString(0, url.url.spec()); |
536 statement.BindInt(1, kRankOfForcedURL); // Fist make it a forced thumbnail. | 573 statement.BindInt(1, kRankOfForcedURL); // Fist make it a forced thumbnail. |
537 statement.BindString16(2, url.title); | 574 statement.BindString16(2, url.title); |
538 if (thumbnail.thumbnail.get() && thumbnail.thumbnail->front()) { | 575 if (thumbnail.thumbnail.get() && thumbnail.thumbnail->front()) { |
539 statement.BindBlob(3, thumbnail.thumbnail->front(), | 576 statement.BindBlob(3, thumbnail.thumbnail->front(), |
540 static_cast<int>(thumbnail.thumbnail->size())); | 577 static_cast<int>(thumbnail.thumbnail->size())); |
541 } | 578 } |
542 statement.BindString(4, GetRedirects(url)); | 579 statement.BindString(4, EncodeRedirects(url.redirects)); |
543 const ThumbnailScore& score = thumbnail.thumbnail_score; | 580 const ThumbnailScore& score = thumbnail.thumbnail_score; |
544 statement.BindDouble(5, score.boring_score); | 581 statement.BindDouble(5, score.boring_score); |
545 statement.BindBool(6, score.good_clipping); | 582 statement.BindBool(6, score.good_clipping); |
546 statement.BindBool(7, score.at_top); | 583 statement.BindBool(7, score.at_top); |
547 statement.BindInt64(8, score.time_at_snapshot.ToInternalValue()); | 584 statement.BindInt64(8, score.time_at_snapshot.ToInternalValue()); |
548 statement.BindBool(9, score.load_completed); | 585 statement.BindBool(9, score.load_completed); |
549 int64 last_forced = url.last_forced_time.ToInternalValue(); | 586 int64 last_forced = url.last_forced_time.ToInternalValue(); |
550 DCHECK((last_forced == 0) == (new_rank != kRankOfForcedURL)) | 587 DCHECK((last_forced == 0) == (new_rank != kRankOfForcedURL)) |
551 << "Thumbnail without a forced time stamp has a forced rank, or the " | 588 << "Thumbnail without a forced time stamp has a forced rank, or the " |
552 << "opposite."; | 589 << "opposite."; |
553 statement.BindInt64(10, last_forced); | 590 statement.BindInt64(10, last_forced); |
554 if (!statement.Run()) | 591 if (!statement.Run()) |
555 return; | 592 return; |
556 | 593 |
557 // Update rank if this is not a forced thumbnail. | 594 // Update rank if this is not a forced thumbnail. |
558 if (new_rank != kRankOfForcedURL) | 595 if (new_rank != kRankOfForcedURL) |
559 UpdatePageRankNoTransaction(url, new_rank); | 596 UpdatePageRankNoTransaction(url, new_rank); |
560 } | 597 } |
561 | 598 |
| 599 // static |
| 600 std::string TopSitesDatabase::EncodeCSVString( |
| 601 const std::vector<std::string> str_list) { |
| 602 std::string csv; |
| 603 for (std::vector<std::string>::const_iterator it = str_list.begin(); |
| 604 it != str_list.end(); ++it) { |
| 605 const std::string& str = *it; |
| 606 if (it != str_list.begin()) |
| 607 csv += ','; |
| 608 csv += '"'; |
| 609 for (std::string::const_iterator jt = str.begin(); jt != str.end(); ++jt) { |
| 610 if (*jt == '"') |
| 611 csv += '"'; |
| 612 csv += *jt; |
| 613 } |
| 614 csv += '"'; |
| 615 } |
| 616 return csv; |
| 617 } |
| 618 |
| 619 // static |
| 620 bool TopSitesDatabase::DecodeCSVString(const std::string csv, |
| 621 std::vector<std::string>* str_list) { |
| 622 if (csv.empty()) { |
| 623 str_list->clear(); |
| 624 return true; |
| 625 } |
| 626 |
| 627 enum { |
| 628 SEEK_QUOTE, |
| 629 READ_STRING, |
| 630 SAW_ONE_QUOTE |
| 631 } state = SEEK_QUOTE; |
| 632 std::vector<std::string> out_list; |
| 633 std::string str; |
| 634 for (std::string::const_iterator it = csv.begin(); it != csv.end(); ++it) { |
| 635 const char ch = *it; |
| 636 if (state == SEEK_QUOTE) { |
| 637 if (ch != '"') |
| 638 return false; |
| 639 state = READ_STRING; |
| 640 } else if (state == READ_STRING) { |
| 641 if (ch == '"') |
| 642 state = SAW_ONE_QUOTE; |
| 643 else |
| 644 str += ch; |
| 645 } else if (state == SAW_ONE_QUOTE) { |
| 646 if (ch == '"') { |
| 647 str += ch; |
| 648 state = READ_STRING; |
| 649 } else if (ch == ',') { |
| 650 out_list.push_back(str); |
| 651 str.clear(); |
| 652 // For simplicity, disallow spaces around commas. |
| 653 state = SEEK_QUOTE; |
| 654 } else { |
| 655 return false; |
| 656 } |
| 657 } else { |
| 658 NOTREACHED(); |
| 659 return false; |
| 660 } |
| 661 } |
| 662 if (state != SAW_ONE_QUOTE) |
| 663 return false; |
| 664 out_list.push_back(str); |
| 665 str_list->swap(out_list); |
| 666 return true; |
| 667 } |
| 668 |
| 669 // static |
| 670 std::string TopSitesDatabase::EncodeRedirects(const RedirectList& redirects) { |
| 671 std::vector<std::string> valid_urls; |
| 672 for (size_t i = 0; i < redirects.size(); i++) { |
| 673 // Example of invalid URL that may end up here: |
| 674 // "data:text/plain,this string contains space". |
| 675 if (redirects[i].is_valid()) |
| 676 valid_urls.push_back(redirects[i].spec()); |
| 677 } |
| 678 return EncodeCSVString(valid_urls); |
| 679 } |
| 680 |
| 681 // static |
| 682 void TopSitesDatabase::DecodeRedirects(const std::string& encoded_redirects, |
| 683 RedirectList* redirects) { |
| 684 std::vector<std::string> redirects_vector; |
| 685 if (!DecodeCSVString(encoded_redirects, &redirects_vector)) { |
| 686 // Fall back to space-delimited list for backward compatibility. |
| 687 base::SplitStringAlongWhitespace(encoded_redirects, &redirects_vector); |
| 688 } |
| 689 for (size_t i = 0; i < redirects_vector.size(); ++i) { |
| 690 GURL redirect_url(redirects_vector[i]); |
| 691 if (redirect_url.is_valid()) |
| 692 redirects->push_back(redirect_url); |
| 693 } |
| 694 } |
| 695 |
562 void TopSitesDatabase::UpdatePageRank(const MostVisitedURL& url, | 696 void TopSitesDatabase::UpdatePageRank(const MostVisitedURL& url, |
563 int new_rank) { | 697 int new_rank) { |
564 DCHECK((url.last_forced_time.ToInternalValue() == 0) == | 698 DCHECK((url.last_forced_time.ToInternalValue() == 0) == |
565 (new_rank != kRankOfForcedURL)) | 699 (new_rank != kRankOfForcedURL)) |
566 << "Thumbnail without a forced time stamp has a forced rank, or the " | 700 << "Thumbnail without a forced time stamp has a forced rank, or the " |
567 << "opposite."; | 701 << "opposite."; |
568 sql::Transaction transaction(db_.get()); | 702 sql::Transaction transaction(db_.get()); |
569 transaction.Begin(); | 703 transaction.Begin(); |
570 UpdatePageRankNoTransaction(url, new_rank); | 704 UpdatePageRankNoTransaction(url, new_rank); |
571 transaction.Commit(); | 705 transaction.Commit(); |
(...skipping 152 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
724 db.get(), db_name)); | 858 db.get(), db_name)); |
725 db->set_page_size(4096); | 859 db->set_page_size(4096); |
726 db->set_cache_size(32); | 860 db->set_cache_size(32); |
727 | 861 |
728 if (!db->Open(db_name)) | 862 if (!db->Open(db_name)) |
729 return NULL; | 863 return NULL; |
730 return db.release(); | 864 return db.release(); |
731 } | 865 } |
732 | 866 |
733 } // namespace history | 867 } // namespace history |
OLD | NEW |