summaryrefslogtreecommitdiffstats
path: root/chrome/browser/history/thumbnail_database.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/history/thumbnail_database.cc')
-rw-r--r--chrome/browser/history/thumbnail_database.cc551
1 files changed, 551 insertions, 0 deletions
diff --git a/chrome/browser/history/thumbnail_database.cc b/chrome/browser/history/thumbnail_database.cc
new file mode 100644
index 0000000..8bf203d
--- /dev/null
+++ b/chrome/browser/history/thumbnail_database.cc
@@ -0,0 +1,551 @@
+// 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 "chrome/browser/history/thumbnail_database.h"
+
+#include "app/sql/statement.h"
+#include "app/sql/transaction.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#if defined(OS_MACOSX)
+#include "base/mac_util.h"
+#endif
+#include "base/ref_counted_memory.h"
+#include "base/time.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/diagnostics/sqlite_diagnostics.h"
+#include "chrome/browser/history/history_publisher.h"
+#include "chrome/browser/history/url_database.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/thumbnail_score.h"
+#include "gfx/codec/jpeg_codec.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace history {
+
+// Version number of the database.
+static const int kCurrentVersionNumber = 3;
+static const int kCompatibleVersionNumber = 3;
+
+ThumbnailDatabase::ThumbnailDatabase() : history_publisher_(NULL),
+ use_top_sites_(false) {
+}
+
+ThumbnailDatabase::~ThumbnailDatabase() {
+ // The DBCloseScoper will delete the DB and the cache.
+}
+
+sql::InitStatus ThumbnailDatabase::Init(
+ const FilePath& db_name,
+ const HistoryPublisher* history_publisher) {
+ history_publisher_ = history_publisher;
+ sql::InitStatus status = OpenDatabase(&db_, db_name);
+ if (status != sql::INIT_OK)
+ return status;
+
+ // Scope initialization in a transaction so we can't be partially initialized.
+ sql::Transaction transaction(&db_);
+ transaction.Begin();
+
+#if defined(OS_MACOSX)
+ // Exclude the thumbnails file and its journal from backups.
+ mac_util::SetFileBackupExclusion(db_name, true);
+ FilePath::StringType db_name_string(db_name.value());
+ db_name_string += "-journal";
+ FilePath db_journal_name(db_name_string);
+ mac_util::SetFileBackupExclusion(db_journal_name, true);
+#endif
+
+ // Create the tables.
+ if (!meta_table_.Init(&db_, kCurrentVersionNumber,
+ kCompatibleVersionNumber) ||
+ !InitThumbnailTable() ||
+ !InitFavIconsTable(&db_, false)) {
+ db_.Close();
+ return sql::INIT_FAILURE;
+ }
+ InitFavIconsIndex();
+
+ // Version check. We should not encounter a database too old for us to handle
+ // in the wild, so we try to continue in that case.
+ if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
+ LOG(WARNING) << "Thumbnail database is too new.";
+ return sql::INIT_TOO_NEW;
+ }
+
+ int cur_version = meta_table_.GetVersionNumber();
+ if (cur_version == 2) {
+ if (!UpgradeToVersion3()) {
+ LOG(WARNING) << "Unable to update to thumbnail database to version 3.";
+ db_.Close();
+ return sql::INIT_FAILURE;
+ }
+ ++cur_version;
+ }
+
+ LOG_IF(WARNING, cur_version < kCurrentVersionNumber) <<
+ "Thumbnail database version " << cur_version << " is too old to handle.";
+
+ // Initialization is complete.
+ if (!transaction.Commit()) {
+ db_.Close();
+ return sql::INIT_FAILURE;
+ }
+
+ return sql::INIT_OK;
+}
+
+sql::InitStatus ThumbnailDatabase::OpenDatabase(sql::Connection* db,
+ const FilePath& db_name) {
+ // Set the exceptional sqlite error handler.
+ db->set_error_delegate(GetErrorHandlerForThumbnailDb());
+
+ // Set the database page size to something larger to give us
+ // better performance (we're typically seek rather than bandwidth limited).
+ // This only has an effect before any tables have been created, otherwise
+ // this is a NOP. Must be a power of 2 and a max of 8192. We use a bigger
+ // one because we're storing larger data (4-16K) in it, so we want a few
+ // blocks per element.
+ db->set_page_size(4096);
+
+ // The UI is generally designed to work well when the thumbnail database is
+ // slow, so we can tolerate much less caching. The file is also very large
+ // and so caching won't save a significant percentage of it for us,
+ // reducing the benefit of caching in the first place. With the default cache
+ // size of 2000 pages, it will take >8MB of memory, so reducing it can be a
+ // big savings.
+ db->set_cache_size(64);
+
+ // Run the database in exclusive mode. Nobody else should be accessing the
+ // database while we're running, and this will give somewhat improved perf.
+ db->set_exclusive_locking();
+
+ if (!db->Open(db_name))
+ return sql::INIT_FAILURE;
+
+ return sql::INIT_OK;
+}
+
+bool ThumbnailDatabase::InitThumbnailTable() {
+ if (!db_.DoesTableExist("thumbnails")) {
+ if (CommandLine::ForCurrentProcess()-> HasSwitch(switches::kTopSites)) {
+ use_top_sites_ = true;
+ return true;
+ }
+ if (!db_.Execute("CREATE TABLE thumbnails ("
+ "url_id INTEGER PRIMARY KEY,"
+ "boring_score DOUBLE DEFAULT 1.0,"
+ "good_clipping INTEGER DEFAULT 0,"
+ "at_top INTEGER DEFAULT 0,"
+ "last_updated INTEGER DEFAULT 0,"
+ "data BLOB)"))
+ return false;
+ }
+ return true;
+}
+
+bool ThumbnailDatabase::UpgradeToVersion3() {
+ if (use_top_sites_) {
+ meta_table_.SetVersionNumber(3);
+ meta_table_.SetCompatibleVersionNumber(
+ std::min(3, kCompatibleVersionNumber));
+ return true; // Not needed after migration to TopSites.
+ }
+
+ // sqlite doesn't like the "ALTER TABLE xxx ADD (column_one, two,
+ // three)" syntax, so list out the commands we need to execute:
+ const char* alterations[] = {
+ "ALTER TABLE thumbnails ADD boring_score DOUBLE DEFAULT 1.0",
+ "ALTER TABLE thumbnails ADD good_clipping INTEGER DEFAULT 0",
+ "ALTER TABLE thumbnails ADD at_top INTEGER DEFAULT 0",
+ "ALTER TABLE thumbnails ADD last_updated INTEGER DEFAULT 0",
+ NULL
+ };
+
+ for (int i = 0; alterations[i] != NULL; ++i) {
+ if (!db_.Execute(alterations[i])) {
+ NOTREACHED();
+ return false;
+ }
+ }
+
+ meta_table_.SetVersionNumber(3);
+ meta_table_.SetCompatibleVersionNumber(std::min(3, kCompatibleVersionNumber));
+ return true;
+}
+
+bool ThumbnailDatabase::RecreateThumbnailTable() {
+ if (use_top_sites_)
+ return true; // Not needed after migration to TopSites.
+
+ if (!db_.Execute("DROP TABLE thumbnails"))
+ return false;
+ return InitThumbnailTable();
+}
+
+bool ThumbnailDatabase::InitFavIconsTable(sql::Connection* db,
+ bool is_temporary) {
+ // Note: if you update the schema, don't forget to update
+ // CopyToTemporaryFaviconTable as well.
+ const char* name = is_temporary ? "temp_favicons" : "favicons";
+ if (!db->DoesTableExist(name)) {
+ std::string sql;
+ sql.append("CREATE TABLE ");
+ sql.append(name);
+ sql.append("("
+ "id INTEGER PRIMARY KEY,"
+ "url LONGVARCHAR NOT NULL,"
+ "last_updated INTEGER DEFAULT 0,"
+ "image_data BLOB)");
+ if (!db->Execute(sql.c_str()))
+ return false;
+ }
+ return true;
+}
+
+void ThumbnailDatabase::InitFavIconsIndex() {
+ // Add an index on the url column. We ignore errors. Since this is always
+ // called during startup, the index will normally already exist.
+ db_.Execute("CREATE INDEX favicons_url ON favicons(url)");
+}
+
+void ThumbnailDatabase::BeginTransaction() {
+ db_.BeginTransaction();
+}
+
+void ThumbnailDatabase::CommitTransaction() {
+ db_.CommitTransaction();
+}
+
+void ThumbnailDatabase::Vacuum() {
+ DCHECK(db_.transaction_nesting() == 0) <<
+ "Can not have a transaction when vacuuming.";
+ db_.Execute("VACUUM");
+}
+
+void ThumbnailDatabase::SetPageThumbnail(
+ const GURL& url,
+ URLID id,
+ const SkBitmap& thumbnail,
+ const ThumbnailScore& score,
+ base::Time time) {
+ if (use_top_sites_)
+ return; // Not possible after migration to TopSites.
+
+ if (!thumbnail.isNull()) {
+ bool add_thumbnail = true;
+ ThumbnailScore current_score;
+ if (ThumbnailScoreForId(id, &current_score)) {
+ add_thumbnail = ShouldReplaceThumbnailWith(current_score, score);
+ }
+
+ if (add_thumbnail) {
+ sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
+ "INSERT OR REPLACE INTO thumbnails "
+ "(url_id, boring_score, good_clipping, at_top, last_updated, data) "
+ "VALUES (?,?,?,?,?,?)"));
+ if (!statement)
+ return;
+
+ // We use 90 quality (out of 100) which is pretty high, because
+ // we're very sensitive to artifacts for these small sized,
+ // highly detailed images.
+ std::vector<unsigned char> jpeg_data;
+ SkAutoLockPixels thumbnail_lock(thumbnail);
+ bool encoded = gfx::JPEGCodec::Encode(
+ reinterpret_cast<unsigned char*>(thumbnail.getAddr32(0, 0)),
+ gfx::JPEGCodec::FORMAT_BGRA, thumbnail.width(),
+ thumbnail.height(),
+ static_cast<int>(thumbnail.rowBytes()), 90,
+ &jpeg_data);
+
+ if (encoded) {
+ statement.BindInt64(0, id);
+ statement.BindDouble(1, score.boring_score);
+ statement.BindBool(2, score.good_clipping);
+ statement.BindBool(3, score.at_top);
+ statement.BindInt64(4, score.time_at_snapshot.ToTimeT());
+ statement.BindBlob(5, &jpeg_data[0],
+ static_cast<int>(jpeg_data.size()));
+ if (!statement.Run())
+ NOTREACHED() << db_.GetErrorMessage();
+ }
+
+ // Publish the thumbnail to any indexers listening to us.
+ // The tests may send an invalid url. Hence avoid publishing those.
+ if (url.is_valid() && history_publisher_ != NULL)
+ history_publisher_->PublishPageThumbnail(jpeg_data, url, time);
+ }
+ } else {
+ if (!DeleteThumbnail(id) )
+ DLOG(WARNING) << "Unable to delete thumbnail";
+ }
+}
+
+bool ThumbnailDatabase::GetPageThumbnail(URLID id,
+ std::vector<unsigned char>* data) {
+ if (use_top_sites_)
+ return false; // Not possible after migration to TopSites.
+
+ sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
+ "SELECT data FROM thumbnails WHERE url_id=?"));
+ if (!statement)
+ return false;
+
+ statement.BindInt64(0, id);
+ if (!statement.Step())
+ return false; // don't have a thumbnail for this ID
+
+ statement.ColumnBlobAsVector(0, data);
+ return true;
+}
+
+bool ThumbnailDatabase::DeleteThumbnail(URLID id) {
+ if (use_top_sites_)
+ return true; // Not possible after migration to TopSites.
+
+ sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
+ "DELETE FROM thumbnails WHERE url_id = ?"));
+ if (!statement)
+ return false;
+
+ statement.BindInt64(0, id);
+ return statement.Run();
+}
+
+bool ThumbnailDatabase::ThumbnailScoreForId(URLID id,
+ ThumbnailScore* score) {
+ if (use_top_sites_)
+ return false; // Not possible after migration to TopSites.
+
+ // Fetch the current thumbnail's information to make sure we
+ // aren't replacing a good thumbnail with one that's worse.
+ sql::Statement select_statement(db_.GetCachedStatement(SQL_FROM_HERE,
+ "SELECT boring_score, good_clipping, at_top, last_updated "
+ "FROM thumbnails WHERE url_id=?"));
+ if (!select_statement) {
+ NOTREACHED() << "Couldn't build select statement!";
+ } else {
+ select_statement.BindInt64(0, id);
+ if (select_statement.Step()) {
+ double current_boring_score = select_statement.ColumnDouble(0);
+ bool current_clipping = select_statement.ColumnBool(1);
+ bool current_at_top = select_statement.ColumnBool(2);
+ base::Time last_updated =
+ base::Time::FromTimeT(select_statement.ColumnInt64(3));
+ *score = ThumbnailScore(current_boring_score, current_clipping,
+ current_at_top, last_updated);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool ThumbnailDatabase::SetFavIcon(URLID icon_id,
+ scoped_refptr<RefCountedMemory> icon_data,
+ base::Time time) {
+ DCHECK(icon_id);
+ if (icon_data->size()) {
+ sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
+ "UPDATE favicons SET image_data=?, last_updated=? WHERE id=?"));
+ if (!statement)
+ return 0;
+
+ statement.BindBlob(0, icon_data->front(),
+ static_cast<int>(icon_data->size()));
+ statement.BindInt64(1, time.ToTimeT());
+ statement.BindInt64(2, icon_id);
+ return statement.Run();
+ } else {
+ sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
+ "UPDATE favicons SET image_data=NULL, last_updated=? WHERE id=?"));
+ if (!statement)
+ return 0;
+
+ statement.BindInt64(0, time.ToTimeT());
+ statement.BindInt64(1, icon_id);
+ return statement.Run();
+ }
+}
+
+bool ThumbnailDatabase::SetFavIconLastUpdateTime(FavIconID icon_id,
+ base::Time time) {
+ sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
+ "UPDATE favicons SET last_updated=? WHERE id=?"));
+ if (!statement)
+ return 0;
+
+ statement.BindInt64(0, time.ToTimeT());
+ statement.BindInt64(1, icon_id);
+ return statement.Run();
+}
+
+FavIconID ThumbnailDatabase::GetFavIconIDForFavIconURL(const GURL& icon_url) {
+ sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
+ "SELECT id FROM favicons WHERE url=?"));
+ if (!statement)
+ return 0;
+
+ statement.BindString(0, URLDatabase::GURLToDatabaseURL(icon_url));
+ if (!statement.Step())
+ return 0; // not cached
+
+ return statement.ColumnInt64(0);
+}
+
+bool ThumbnailDatabase::GetFavIcon(
+ FavIconID icon_id,
+ base::Time* last_updated,
+ std::vector<unsigned char>* png_icon_data,
+ GURL* icon_url) {
+ DCHECK(icon_id);
+
+ sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
+ "SELECT last_updated, image_data, url FROM favicons WHERE id=?"));
+ if (!statement)
+ return 0;
+
+ statement.BindInt64(0, icon_id);
+
+ if (!statement.Step())
+ return false; // No entry for the id.
+
+ *last_updated = base::Time::FromTimeT(statement.ColumnInt64(0));
+ if (statement.ColumnByteLength(1) > 0)
+ statement.ColumnBlobAsVector(1, png_icon_data);
+ if (icon_url)
+ *icon_url = GURL(statement.ColumnString(2));
+
+ return true;
+}
+
+FavIconID ThumbnailDatabase::AddFavIcon(const GURL& icon_url) {
+ sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
+ "INSERT INTO favicons (url) VALUES (?)"));
+ if (!statement)
+ return 0;
+
+ statement.BindString(0, URLDatabase::GURLToDatabaseURL(icon_url));
+ if (!statement.Run())
+ return 0;
+ return db_.GetLastInsertRowId();
+}
+
+bool ThumbnailDatabase::DeleteFavIcon(FavIconID id) {
+ sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
+ "DELETE FROM favicons WHERE id = ?"));
+ if (!statement)
+ return false;
+ statement.BindInt64(0, id);
+ return statement.Run();
+}
+
+FavIconID ThumbnailDatabase::CopyToTemporaryFavIconTable(FavIconID source) {
+ sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
+ "INSERT INTO temp_favicons (url, last_updated, image_data)"
+ "SELECT url, last_updated, image_data "
+ "FROM favicons WHERE id = ?"));
+ if (!statement)
+ return 0;
+ statement.BindInt64(0, source);
+ if (!statement.Run())
+ return 0;
+
+ // We return the ID of the newly inserted favicon.
+ return db_.GetLastInsertRowId();
+}
+
+bool ThumbnailDatabase::CommitTemporaryFavIconTable() {
+ // Delete the old favicons table.
+ if (!db_.Execute("DROP TABLE favicons"))
+ return false;
+
+ // Rename the temporary one.
+ if (!db_.Execute("ALTER TABLE temp_favicons RENAME TO favicons"))
+ return false;
+
+ // The renamed table needs the index (the temporary table doesn't have one).
+ InitFavIconsIndex();
+ return true;
+}
+
+bool ThumbnailDatabase::NeedsMigrationToTopSites() {
+ return !use_top_sites_;
+}
+
+bool ThumbnailDatabase::RenameAndDropThumbnails(const FilePath& old_db_file,
+ const FilePath& new_db_file) {
+ // Init favicons table - same schema as the thumbnails.
+ sql::Connection favicons;
+ if (OpenDatabase(&favicons, new_db_file) != sql::INIT_OK)
+ return false;
+
+ if (!InitFavIconsTable(&favicons, false)) {
+ NOTREACHED() << "Couldn't init favicons table.";
+ favicons.Close();
+ return false;
+ }
+ favicons.Close();
+
+ // Can't attach within a transaction.
+ CommitTransaction();
+
+ // Attach new DB.
+ {
+ // This block is needed because otherwise the attach statement is
+ // never cleared from cache and we can't close the DB :P
+ sql::Statement attach(db_.GetUniqueStatement("ATTACH ? AS new_favicons"));
+ if (!attach) {
+ NOTREACHED() << "Unable to attach database.";
+ // Keep the transaction open, even though we failed.
+ BeginTransaction();
+ return false;
+ }
+
+#if defined(OS_POSIX)
+ attach.BindString(0, new_db_file.value());
+#else
+ attach.BindString(0, WideToUTF8(new_db_file.value()));
+#endif
+
+ if (!attach.Run()) {
+ NOTREACHED() << db_.GetErrorMessage();
+ BeginTransaction();
+ return false;
+ }
+ }
+
+ // Move favicons to the new DB.
+ if (!db_.Execute("INSERT OR REPLACE INTO new_favicons.favicons "
+ "SELECT * FROM favicons")) {
+ NOTREACHED() << "Unable to copy favicons.";
+ BeginTransaction();
+ return false;
+ }
+
+ if (!db_.Execute("DETACH new_favicons")) {
+ NOTREACHED() << "Unable to detach database.";
+ BeginTransaction();
+ return false;
+ }
+
+ db_.Close();
+
+ // Reset the DB to point to new file.
+ if (OpenDatabase(&db_, new_db_file) != sql::INIT_OK)
+ return false;
+
+ file_util::Delete(old_db_file, false);
+
+ InitFavIconsIndex();
+
+ // Reopen the transaction.
+ BeginTransaction();
+ use_top_sites_ = true;
+ return true;
+}
+
+} // namespace history