// 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<unsigned char> 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<std::string> 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<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]));
}
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<int>(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<int>(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<unsigned char> 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<sql::Connection> 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