// Copyright (c) 2009 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "app/sql/connection.h" #include "app/sql/transaction.h" #include "base/file_util.h" #include "base/string_split.h" #include "base/string_util.h" #include "chrome/browser/diagnostics/sqlite_diagnostics.h" #include "chrome/browser/history/history_types.h" #include "chrome/browser/history/top_sites.h" #include "chrome/browser/history/top_sites_database.h" namespace history { static const int kVersionNumber = 1; TopSitesDatabase::TopSitesDatabase() : may_need_history_migration_(false) { } TopSitesDatabase::~TopSitesDatabase() { } bool TopSitesDatabase::Init(const FilePath& db_name) { bool file_existed = file_util::PathExists(db_name); if (!file_existed) may_need_history_migration_ = true; db_.reset(CreateDB(db_name)); if (!db_.get()) return false; bool does_meta_exist = sql::MetaTable::DoesTableExist(db_.get()); if (!does_meta_exist && file_existed) { may_need_history_migration_ = true; // If the meta file doesn't exist, this version is old. We could remove all // the entries as they are no longer applicable, but it's safest to just // remove the file and start over. db_.reset(NULL); if (!file_util::Delete(db_name, false) && !file_util::Delete(db_name, false)) { // Try to delete twice. If we can't, fail. LOG(ERROR) << "unable to delete old TopSites file"; return false; } db_.reset(CreateDB(db_name)); if (!db_.get()) return false; } if (!meta_table_.Init(db_.get(), kVersionNumber, kVersionNumber)) return false; if (!InitThumbnailTable()) return false; // Version check. if (meta_table_.GetVersionNumber() != kVersionNumber) return false; return true; } bool TopSitesDatabase::InitThumbnailTable() { if (!db_->DoesTableExist("thumbnails")) { if (!db_->Execute("CREATE TABLE thumbnails (" "url LONGVARCHAR PRIMARY KEY," "url_rank INTEGER ," "title LONGVARCHAR," "thumbnail BLOB," "redirects LONGVARCHAR," "boring_score DOUBLE DEFAULT 1.0, " "good_clipping INTEGER DEFAULT 0, " "at_top INTEGER DEFAULT 0, " "last_updated INTEGER DEFAULT 0) ")) { LOG(WARNING) << db_->GetErrorMessage(); return false; } } return true; } void TopSitesDatabase::GetPageThumbnails(MostVisitedURLList* urls, URLToImagesMap* thumbnails) { sql::Statement statement(db_->GetCachedStatement( SQL_FROM_HERE, "SELECT url, url_rank, title, thumbnail, redirects, " "boring_score, good_clipping, at_top, last_updated " "FROM thumbnails ORDER BY url_rank ")); if (!statement) { LOG(WARNING) << db_->GetErrorMessage(); return; } urls->clear(); thumbnails->clear(); while (statement.Step()) { // Results are sorted by url_rank. MostVisitedURL url; GURL gurl(statement.ColumnString(0)); url.url = gurl; url.title = statement.ColumnString16(2); std::string redirects = statement.ColumnString(4); SetRedirects(redirects, &url); urls->push_back(url); std::vector data; statement.ColumnBlobAsVector(3, &data); Images thumbnail; thumbnail.thumbnail = RefCountedBytes::TakeVector(&data); thumbnail.thumbnail_score.boring_score = statement.ColumnDouble(5); thumbnail.thumbnail_score.good_clipping = statement.ColumnBool(6); thumbnail.thumbnail_score.at_top = statement.ColumnBool(7); thumbnail.thumbnail_score.time_at_snapshot = base::Time::FromInternalValue(statement.ColumnInt64(8)); (*thumbnails)[gurl] = thumbnail; } } // static std::string TopSitesDatabase::GetRedirects(const MostVisitedURL& url) { std::vector redirects; for (size_t i = 0; i < url.redirects.size(); i++) redirects.push_back(url.redirects[i].spec()); return JoinString(redirects, ' '); } // static void TopSitesDatabase::SetRedirects(const std::string& redirects, MostVisitedURL* url) { std::vector 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])); } void TopSitesDatabase::SetPageThumbnail(const MostVisitedURL& url, int new_rank, const Images& thumbnail) { sql::Transaction transaction(db_.get()); transaction.Begin(); int rank = GetURLRank(url); if (rank == -1) { AddPageThumbnail(url, new_rank, thumbnail); } else { UpdatePageRankNoTransaction(url, new_rank); UpdatePageThumbnail(url, thumbnail); } transaction.Commit(); } void TopSitesDatabase::UpdatePageThumbnail( const MostVisitedURL& url, const Images& thumbnail) { sql::Statement statement(db_->GetCachedStatement( SQL_FROM_HERE, "UPDATE thumbnails SET " "title = ?, thumbnail = ?, redirects = ?, " "boring_score = ?, good_clipping = ?, at_top = ?, last_updated = ? " "WHERE url = ? ")); if (!statement) return; statement.BindString16(0, url.title); if (thumbnail.thumbnail.get() && thumbnail.thumbnail->front()) { statement.BindBlob(1, thumbnail.thumbnail->front(), static_cast(thumbnail.thumbnail->size())); } statement.BindString(2, GetRedirects(url)); const ThumbnailScore& score = thumbnail.thumbnail_score; statement.BindDouble(3, score.boring_score); statement.BindBool(4, score.good_clipping); statement.BindBool(5, score.at_top); statement.BindInt64(6, score.time_at_snapshot.ToInternalValue()); statement.BindString(7, url.url.spec()); if (!statement.Run()) NOTREACHED() << db_->GetErrorMessage(); } void TopSitesDatabase::AddPageThumbnail(const MostVisitedURL& url, int new_rank, const Images& thumbnail) { int count = GetRowCount(); sql::Statement statement(db_->GetCachedStatement( SQL_FROM_HERE, "INSERT OR REPLACE INTO thumbnails " "(url, url_rank, title, thumbnail, redirects, " "boring_score, good_clipping, at_top, last_updated) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")); if (!statement) return; statement.BindString(0, url.url.spec()); statement.BindInt(1, count); // Make it the last url. statement.BindString16(2, url.title); if (thumbnail.thumbnail.get() && thumbnail.thumbnail->front()) { statement.BindBlob(3, thumbnail.thumbnail->front(), static_cast(thumbnail.thumbnail->size())); } statement.BindString(4, GetRedirects(url)); const ThumbnailScore& score = thumbnail.thumbnail_score; statement.BindDouble(5, score.boring_score); statement.BindBool(6, score.good_clipping); statement.BindBool(7, score.at_top); statement.BindInt64(8, score.time_at_snapshot.ToInternalValue()); if (!statement.Run()) NOTREACHED() << db_->GetErrorMessage(); UpdatePageRankNoTransaction(url, new_rank); } void TopSitesDatabase::UpdatePageRank(const MostVisitedURL& url, int new_rank) { sql::Transaction transaction(db_.get()); transaction.Begin(); UpdatePageRankNoTransaction(url, new_rank); transaction.Commit(); } // Caller should have a transaction open. void TopSitesDatabase::UpdatePageRankNoTransaction( const MostVisitedURL& url, int new_rank) { int prev_rank = GetURLRank(url); if (prev_rank == -1) { LOG(WARNING) << "Updating rank of an unknown URL: " << url.url.spec(); return; } // Shift the ranks. if (prev_rank > new_rank) { // Shift up sql::Statement shift_statement(db_->GetCachedStatement( SQL_FROM_HERE, "UPDATE thumbnails " "SET url_rank = url_rank + 1 " "WHERE url_rank >= ? AND url_rank < ?")); shift_statement.BindInt(0, new_rank); shift_statement.BindInt(1, prev_rank); if (shift_statement) shift_statement.Run(); } else if (prev_rank < new_rank) { // Shift down sql::Statement shift_statement(db_->GetCachedStatement( SQL_FROM_HERE, "UPDATE thumbnails " "SET url_rank = url_rank - 1 " "WHERE url_rank > ? AND url_rank <= ?")); shift_statement.BindInt(0, prev_rank); shift_statement.BindInt(1, new_rank); if (shift_statement) shift_statement.Run(); } // Set the url's rank. sql::Statement set_statement(db_->GetCachedStatement( SQL_FROM_HERE, "UPDATE thumbnails " "SET url_rank = ? " "WHERE url == ?")); set_statement.BindInt(0, new_rank); set_statement.BindString(1, url.url.spec()); if (set_statement) set_statement.Run(); } bool TopSitesDatabase::GetPageThumbnail(const GURL& url, Images* thumbnail) { sql::Statement statement(db_->GetCachedStatement( SQL_FROM_HERE, "SELECT thumbnail, boring_score, good_clipping, at_top, last_updated " "FROM thumbnails WHERE url=?")); if (!statement) { LOG(WARNING) << db_->GetErrorMessage(); return false; } statement.BindString(0, url.spec()); if (!statement.Step()) return false; std::vector data; statement.ColumnBlobAsVector(0, &data); thumbnail->thumbnail = RefCountedBytes::TakeVector(&data); thumbnail->thumbnail_score.boring_score = statement.ColumnDouble(1); thumbnail->thumbnail_score.good_clipping = statement.ColumnBool(2); thumbnail->thumbnail_score.at_top = statement.ColumnBool(3); thumbnail->thumbnail_score.time_at_snapshot = base::Time::FromInternalValue(statement.ColumnInt64(4)); return true; } int TopSitesDatabase::GetRowCount() { int result = 0; sql::Statement select_statement(db_->GetCachedStatement( SQL_FROM_HERE, "SELECT COUNT (url) FROM thumbnails")); if (!select_statement) { LOG(WARNING) << db_->GetErrorMessage(); return result; } if (select_statement.Step()) result = select_statement.ColumnInt(0); return result; } int TopSitesDatabase::GetURLRank(const MostVisitedURL& url) { int result = -1; sql::Statement select_statement(db_->GetCachedStatement( SQL_FROM_HERE, "SELECT url_rank " "FROM thumbnails WHERE url=?")); if (!select_statement) { LOG(WARNING) << db_->GetErrorMessage(); return result; } select_statement.BindString(0, url.url.spec()); if (select_statement.Step()) result = select_statement.ColumnInt(0); return result; } // Remove the record for this URL. Returns true iff removed successfully. bool TopSitesDatabase::RemoveURL(const MostVisitedURL& url) { int old_rank = GetURLRank(url); if (old_rank < 0) return false; sql::Transaction transaction(db_.get()); transaction.Begin(); // Decrement all following ranks. sql::Statement shift_statement(db_->GetCachedStatement( SQL_FROM_HERE, "UPDATE thumbnails " "SET url_rank = url_rank - 1 " "WHERE url_rank > ?")); if (!shift_statement) return false; shift_statement.BindInt(0, old_rank); shift_statement.Run(); sql::Statement delete_statement( db_->GetCachedStatement(SQL_FROM_HERE, "DELETE FROM thumbnails WHERE url = ?")); if (!delete_statement) return false; delete_statement.BindString(0, url.url.spec()); delete_statement.Run(); return transaction.Commit(); } sql::Connection* TopSitesDatabase::CreateDB(const FilePath& db_name) { scoped_ptr db(new sql::Connection()); // Settings copied from ThumbnailDatabase. db->set_error_delegate(GetErrorHandlerForThumbnailDb()); db->set_page_size(4096); db->set_cache_size(32); if (!db->Open(db_name)) { LOG(ERROR) << db->GetErrorMessage(); return NULL; } return db.release(); } } // namespace history