| Index: chrome/browser/history/top_sites_database.cc
|
| diff --git a/chrome/browser/history/top_sites_database.cc b/chrome/browser/history/top_sites_database.cc
|
| index ff20d4db8e3756beaeec1e264ac13030bced5bce..c25646c6e17fda6a28d8206a02f7c3eca18d08cb 100644
|
| --- a/chrome/browser/history/top_sites_database.cc
|
| +++ b/chrome/browser/history/top_sites_database.cc
|
| @@ -26,8 +26,9 @@
|
| // with the highest rank will be the next one evicted. Forced
|
| // thumbnails have a rank of -1.
|
| // title The title to display under that thumbnail.
|
| -// redirects A space separated list of URLs that are known to redirect
|
| -// to this url.
|
| +// redirects A comma-separated list of URLs that are known to redirect
|
| +// to this url. Each URL is surrounded by quotes, and any
|
| +// existing quote is escaped to two quotes.
|
| // boring_score How "boring" that thumbnail is. See ThumbnailScore.
|
| // good_clipping True if the thumbnail was clipped from the bottom, keeping
|
| // the entire width of the window. See ThumbnailScore.
|
| @@ -49,6 +50,7 @@ namespace {
|
| // fatal (in fact, very old data may be expired immediately at startup
|
| // anyhow).
|
|
|
| +// Version 4: by huangs@chromium.org
|
| // Version 3: b6d6a783/r231648 by beaudoin@chromium.org on 2013-10-29
|
| // Version 2: eb0b24e6/r87284 by satorux@chromium.org on 2011-05-31
|
| // Version 1: 809cc4d8/r64072 by sky@chromium.org on 2010-10-27 (deprecated)
|
| @@ -58,7 +60,7 @@ namespace {
|
| // NOTE(shess): RecoverDatabaseOrRaze() depends on the specific
|
| // version number. The code is subtle and in development, contact me
|
| // if the necessary changes are not obvious.
|
| -static const int kVersionNumber = 3;
|
| +static const int kVersionNumber = 4;
|
| static const int kDeprecatedVersionNumber = 1; // and earlier.
|
|
|
| bool InitTables(sql::Connection* db) {
|
| @@ -78,22 +80,6 @@ bool InitTables(sql::Connection* db) {
|
| return db->Execute(kThumbnailsSql);
|
| }
|
|
|
| -// Encodes redirects into a string.
|
| -std::string GetRedirects(const history::MostVisitedURL& url) {
|
| - std::vector<std::string> redirects;
|
| - for (size_t i = 0; i < url.redirects.size(); i++)
|
| - redirects.push_back(url.redirects[i].spec());
|
| - return JoinString(redirects, ' ');
|
| -}
|
| -
|
| -// Decodes redirects from a string and sets them for the url.
|
| -void SetRedirects(const std::string& redirects, history::MostVisitedURL* url) {
|
| - std::vector<std::string> redirects_vector;
|
| - base::SplitStringAlongWhitespace(redirects, &redirects_vector);
|
| - for (size_t i = 0; i < redirects_vector.size(); ++i)
|
| - url->redirects.push_back(GURL(redirects_vector[i]));
|
| -}
|
| -
|
| // Track various failure (and success) cases in recovery code.
|
| //
|
| // TODO(shess): The recovery code is complete, but by nature runs in challenging
|
| @@ -196,7 +182,7 @@ void FixThumbnailsTable(sql::Connection* db) {
|
| // possible.
|
| void RecoverDatabaseOrRaze(sql::Connection* db, const base::FilePath& db_path) {
|
| // NOTE(shess): If the version changes, review this code.
|
| - DCHECK_EQ(3, kVersionNumber);
|
| + DCHECK_EQ(4, kVersionNumber);
|
|
|
| // It is almost certain that some operation against |db| will fail, prevent
|
| // reentry.
|
| @@ -237,13 +223,13 @@ void RecoverDatabaseOrRaze(sql::Connection* db, const base::FilePath& db_path) {
|
| // TODO(shess): Earlier versions have been deprecated, later versions should
|
| // be impossible. Unrecoverable() seems like a feasible response if this is
|
| // infrequent enough.
|
| - if (version != 2 && version != 3) {
|
| + if (version != 2 && version != 3 && version != 4) {
|
| RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_WRONG_VERSION);
|
| sql::Recovery::Rollback(recovery.Pass());
|
| return;
|
| }
|
|
|
| - // Both v2 and v3 recover to current schema version.
|
| + // v2, v3, and v4 recover to current schema version.
|
| sql::MetaTable recover_meta_table;
|
| if (!recover_meta_table.Init(recovery->db(), kVersionNumber,
|
| kVersionNumber)) {
|
| @@ -413,6 +399,13 @@ bool TopSitesDatabase::InitImpl(const base::FilePath& db_name) {
|
| }
|
| }
|
|
|
| + if (meta_table_.GetVersionNumber() == 3) {
|
| + if (!UpgradeToVersion4()) {
|
| + LOG(WARNING) << "Unable to upgrade top sites database to version 4.";
|
| + return false;
|
| + }
|
| + }
|
| +
|
| // Version check.
|
| if (meta_table_.GetVersionNumber() != kVersionNumber)
|
| return false;
|
| @@ -435,6 +428,49 @@ bool TopSitesDatabase::UpgradeToVersion3() {
|
| return true;
|
| }
|
|
|
| +bool TopSitesDatabase::UpgradeToVersion4() {
|
| + LOG(ERROR) << "Migrating to V4";
|
| +
|
| + // Migrate the "redirects" field from space-separated list in v3 to
|
| + // comma-separated list of quoted strings in v4.
|
| + const char kSelectSql[] = "SELECT redirects, rowid FROM thumbnails";
|
| + sql::Statement select_statement(db_->GetUniqueStatement(kSelectSql));
|
| +
|
| + const char kUpdateSql[] =
|
| + "UPDATE thumbnails SET redirects = ? WHERE rowid = ?";
|
| + sql::Statement update_statement(db_->GetUniqueStatement(kUpdateSql));
|
| +
|
| + while (select_statement.Step()) {
|
| + const std::string encoded_redirects_v3(select_statement.ColumnString(0));
|
| +
|
| + LOG(ERROR) << "Old: " << encoded_redirects_v3;
|
| + // Skip if empty or if previously migrated
|
| + if (encoded_redirects_v3.empty() || encoded_redirects_v3[0] == '"')
|
| + continue;
|
| +
|
| + // Parse v3 redirects, which is a space-separated list of URLs.
|
| + std::vector<std::string> redirects_vector;
|
| + RedirectList redirects;
|
| + base::SplitStringAlongWhitespace(encoded_redirects_v3, &redirects_vector);
|
| + for (size_t i = 0; i < redirects_vector.size(); ++i) {
|
| + GURL redirect_url(redirects_vector[i]);
|
| + if (redirect_url.is_valid())
|
| + redirects.push_back(redirect_url);
|
| + }
|
| +
|
| + std::string encoded_redirects(EncodeRedirects(redirects));
|
| + LOG(ERROR) << "New: " << encoded_redirects;
|
| +
|
| + update_statement.Reset(true);
|
| + update_statement.BindString(0, encoded_redirects);
|
| + update_statement.BindInt64(1, select_statement.ColumnInt64(1));
|
| + update_statement.Run();
|
| + }
|
| +
|
| + meta_table_.SetVersionNumber(4);
|
| + return true;
|
| +}
|
| +
|
| void TopSitesDatabase::GetPageThumbnails(MostVisitedURLList* urls,
|
| URLToImagesMap* thumbnails) {
|
| sql::Statement statement(db_->GetCachedStatement(
|
| @@ -460,8 +496,9 @@ void TopSitesDatabase::GetPageThumbnails(MostVisitedURLList* urls,
|
| url.title = statement.ColumnString16(2);
|
| url.last_forced_time =
|
| base::Time::FromInternalValue(statement.ColumnInt64(10));
|
| - std::string redirects = statement.ColumnString(4);
|
| - SetRedirects(redirects, &url);
|
| + LOG(ERROR) << "Read db: redirects = " << statement.ColumnString(4);
|
| + std::string encoded_redirects = statement.ColumnString(4);
|
| + DecodeRedirects(encoded_redirects, &url.redirects);
|
| urls->push_back(url);
|
|
|
| std::vector<unsigned char> data;
|
| @@ -510,7 +547,7 @@ bool TopSitesDatabase::UpdatePageThumbnail(
|
| statement.BindBlob(1, thumbnail.thumbnail->front(),
|
| static_cast<int>(thumbnail.thumbnail->size()));
|
| }
|
| - statement.BindString(2, GetRedirects(url));
|
| + statement.BindString(2, EncodeRedirects(url.redirects));
|
| const ThumbnailScore& score = thumbnail.thumbnail_score;
|
| statement.BindDouble(3, score.boring_score);
|
| statement.BindBool(4, score.good_clipping);
|
| @@ -539,7 +576,7 @@ void TopSitesDatabase::AddPageThumbnail(const MostVisitedURL& url,
|
| statement.BindBlob(3, thumbnail.thumbnail->front(),
|
| static_cast<int>(thumbnail.thumbnail->size()));
|
| }
|
| - statement.BindString(4, GetRedirects(url));
|
| + statement.BindString(4, EncodeRedirects(url.redirects));
|
| const ThumbnailScore& score = thumbnail.thumbnail_score;
|
| statement.BindDouble(5, score.boring_score);
|
| statement.BindBool(6, score.good_clipping);
|
| @@ -559,6 +596,103 @@ void TopSitesDatabase::AddPageThumbnail(const MostVisitedURL& url,
|
| UpdatePageRankNoTransaction(url, new_rank);
|
| }
|
|
|
| +// static
|
| +std::string TopSitesDatabase::EncodeCSVString(
|
| + const std::vector<std::string> str_list) {
|
| + std::string csv;
|
| + for (std::vector<std::string>::const_iterator it = str_list.begin();
|
| + it != str_list.end(); ++it) {
|
| + const std::string& str = *it;
|
| + if (it != str_list.begin())
|
| + csv += ',';
|
| + csv += '"';
|
| + for (std::string::const_iterator jt = str.begin(); jt != str.end(); ++jt) {
|
| + if (*jt == '"')
|
| + csv += '"';
|
| + csv += *jt;
|
| + }
|
| + csv += '"';
|
| + }
|
| + return csv;
|
| +}
|
| +
|
| +// static
|
| +bool TopSitesDatabase::DecodeCSVString(const std::string csv,
|
| + std::vector<std::string>* str_list) {
|
| + if (csv.empty()) {
|
| + str_list->clear();
|
| + return true;
|
| + }
|
| +
|
| + enum {
|
| + SEEK_QUOTE,
|
| + READ_STRING,
|
| + SAW_ONE_QUOTE
|
| + } state = SEEK_QUOTE;
|
| + std::vector<std::string> out_list;
|
| + std::string str;
|
| + for (std::string::const_iterator it = csv.begin(); it != csv.end(); ++it) {
|
| + const char ch = *it;
|
| + if (state == SEEK_QUOTE) {
|
| + if (ch != '"')
|
| + return false;
|
| + state = READ_STRING;
|
| + } else if (state == READ_STRING) {
|
| + if (ch == '"')
|
| + state = SAW_ONE_QUOTE;
|
| + else
|
| + str += ch;
|
| + } else if (state == SAW_ONE_QUOTE) {
|
| + if (ch == '"') {
|
| + str += ch;
|
| + state = READ_STRING;
|
| + } else if (ch == ',') {
|
| + out_list.push_back(str);
|
| + str.clear();
|
| + // For simplicity, disallow spaces around commas.
|
| + state = SEEK_QUOTE;
|
| + } else {
|
| + return false;
|
| + }
|
| + } else {
|
| + NOTREACHED();
|
| + return false;
|
| + }
|
| + }
|
| + if (state != SAW_ONE_QUOTE)
|
| + return false;
|
| + out_list.push_back(str);
|
| + str_list->swap(out_list);
|
| + return true;
|
| +}
|
| +
|
| +// static
|
| +std::string TopSitesDatabase::EncodeRedirects(const RedirectList& redirects) {
|
| + std::vector<std::string> valid_urls;
|
| + for (size_t i = 0; i < redirects.size(); i++) {
|
| + // Example of invalid URL that may end up here:
|
| + // "data:text/plain,this string contains space".
|
| + if (redirects[i].is_valid())
|
| + valid_urls.push_back(redirects[i].spec());
|
| + }
|
| + return EncodeCSVString(valid_urls);
|
| +}
|
| +
|
| +// static
|
| +void TopSitesDatabase::DecodeRedirects(const std::string& encoded_redirects,
|
| + RedirectList* redirects) {
|
| + std::vector<std::string> redirects_vector;
|
| + if (!DecodeCSVString(encoded_redirects, &redirects_vector)) {
|
| + // Fall back to space-delimited list for backward compatibility.
|
| + base::SplitStringAlongWhitespace(encoded_redirects, &redirects_vector);
|
| + }
|
| + for (size_t i = 0; i < redirects_vector.size(); ++i) {
|
| + GURL redirect_url(redirects_vector[i]);
|
| + if (redirect_url.is_valid())
|
| + redirects->push_back(redirect_url);
|
| + }
|
| +}
|
| +
|
| void TopSitesDatabase::UpdatePageRank(const MostVisitedURL& url,
|
| int new_rank) {
|
| DCHECK((url.last_forced_time.ToInternalValue() == 0) ==
|
|
|