blob: b7eff4853b1b1a80704e4363ccc5715038127e86 [file] [log] [blame]
// Copyright 2016 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 "components/offline_pages/core/offline_page_metadata_store.h"
#include <utility>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/sequenced_task_runner.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/utf_string_conversions.h"
#include "components/offline_pages/core/client_namespace_constants.h"
#include "components/offline_pages/core/offline_page_item.h"
#include "components/offline_pages/core/offline_store_types.h"
#include "components/offline_pages/core/offline_store_utils.h"
#include "sql/database.h"
#include "sql/meta_table.h"
#include "sql/statement.h"
#include "sql/transaction.h"
namespace offline_pages {
const int OfflinePageMetadataStore::kFirstPostLegacyVersion;
const int OfflinePageMetadataStore::kCurrentVersion;
const int OfflinePageMetadataStore::kCompatibleVersion;
namespace {
// This is a macro instead of a const so that
// it can be used inline in other SQL statements below.
#define OFFLINE_PAGES_TABLE_NAME "offlinepages_v1"
void ReportStoreEvent(OfflinePagesStoreEvent event) {
UMA_HISTOGRAM_ENUMERATION("OfflinePages.SQLStorage.StoreEvent", event);
}
bool CreateOfflinePagesTable(sql::Database* db) {
static const char kCreateLatestOfflinePagesTableSql[] =
"CREATE TABLE IF NOT EXISTS " OFFLINE_PAGES_TABLE_NAME
"(offline_id INTEGER PRIMARY KEY NOT NULL,"
" creation_time INTEGER NOT NULL,"
" file_size INTEGER NOT NULL,"
" last_access_time INTEGER NOT NULL,"
" access_count INTEGER NOT NULL,"
" system_download_id INTEGER NOT NULL DEFAULT 0,"
" file_missing_time INTEGER NOT NULL DEFAULT 0,"
// upgrade_attempt is deprecated, and should be removed next time the
// schema needs to be updated.
" upgrade_attempt INTEGER NOT NULL DEFAULT 0,"
" client_namespace VARCHAR NOT NULL,"
" client_id VARCHAR NOT NULL,"
" online_url VARCHAR NOT NULL,"
" file_path VARCHAR NOT NULL,"
" title VARCHAR NOT NULL DEFAULT '',"
" original_url VARCHAR NOT NULL DEFAULT '',"
" request_origin VARCHAR NOT NULL DEFAULT '',"
" digest VARCHAR NOT NULL DEFAULT '',"
" snippet VARCHAR NOT NULL DEFAULT '',"
" attribution VARCHAR NOT NULL DEFAULT ''"
")";
return db->Execute(kCreateLatestOfflinePagesTableSql);
}
bool UpgradeWithQuery(sql::Database* db, const char* upgrade_sql) {
if (!db->Execute("ALTER TABLE " OFFLINE_PAGES_TABLE_NAME
" RENAME TO temp_" OFFLINE_PAGES_TABLE_NAME)) {
return false;
}
static const char kCreateOfflinePagesTableVersion1Sql[] =
"CREATE TABLE IF NOT EXISTS " OFFLINE_PAGES_TABLE_NAME
"(offline_id INTEGER PRIMARY KEY NOT NULL,"
" creation_time INTEGER NOT NULL,"
" file_size INTEGER NOT NULL,"
" last_access_time INTEGER NOT NULL,"
" access_count INTEGER NOT NULL,"
" system_download_id INTEGER NOT NULL DEFAULT 0,"
" file_missing_time INTEGER NOT NULL DEFAULT 0,"
" upgrade_attempt INTEGER NOT NULL DEFAULT 0,"
" client_namespace VARCHAR NOT NULL,"
" client_id VARCHAR NOT NULL,"
" online_url VARCHAR NOT NULL,"
" file_path VARCHAR NOT NULL,"
" title VARCHAR NOT NULL DEFAULT '',"
" original_url VARCHAR NOT NULL DEFAULT '',"
" request_origin VARCHAR NOT NULL DEFAULT '',"
" digest VARCHAR NOT NULL DEFAULT ''"
")";
if (!db->Execute(kCreateOfflinePagesTableVersion1Sql))
return false;
if (!db->Execute(upgrade_sql))
return false;
if (!db->Execute("DROP TABLE IF EXISTS temp_" OFFLINE_PAGES_TABLE_NAME))
return false;
return true;
}
bool UpgradeFrom52(sql::Database* db) {
static const char kSql[] =
"INSERT INTO " OFFLINE_PAGES_TABLE_NAME
" (offline_id, creation_time, file_size, last_access_time, "
"access_count, client_namespace, client_id, "
"online_url, file_path) "
"SELECT "
"offline_id, creation_time, file_size, last_access_time, "
"access_count, client_namespace, client_id, "
"online_url, file_path "
"FROM temp_" OFFLINE_PAGES_TABLE_NAME;
return UpgradeWithQuery(db, kSql);
}
bool UpgradeFrom53(sql::Database* db) {
static const char kSql[] =
"INSERT INTO " OFFLINE_PAGES_TABLE_NAME
" (offline_id, creation_time, file_size, last_access_time, "
"access_count, client_namespace, client_id, online_url, "
"file_path) "
"SELECT "
"offline_id, creation_time, file_size, last_access_time, "
"access_count, client_namespace, client_id, online_url, "
"file_path "
"FROM temp_" OFFLINE_PAGES_TABLE_NAME;
return UpgradeWithQuery(db, kSql);
}
bool UpgradeFrom54(sql::Database* db) {
static const char kSql[] =
"INSERT INTO " OFFLINE_PAGES_TABLE_NAME
" (offline_id, creation_time, file_size, last_access_time, "
"access_count, client_namespace, client_id, online_url, "
"file_path, title) "
"SELECT "
"offline_id, creation_time, file_size, last_access_time, "
"access_count, client_namespace, client_id, online_url, "
"file_path, title "
"FROM temp_" OFFLINE_PAGES_TABLE_NAME;
return UpgradeWithQuery(db, kSql);
}
bool UpgradeFrom55(sql::Database* db) {
static const char kSql[] =
"INSERT INTO " OFFLINE_PAGES_TABLE_NAME
" (offline_id, creation_time, file_size, last_access_time, "
"access_count, client_namespace, client_id, online_url, "
"file_path, title) "
"SELECT "
"offline_id, creation_time, file_size, last_access_time, "
"access_count, client_namespace, client_id, online_url, "
"file_path, title "
"FROM temp_" OFFLINE_PAGES_TABLE_NAME;
return UpgradeWithQuery(db, kSql);
}
bool UpgradeFrom56(sql::Database* db) {
static const char kSql[] =
"INSERT INTO " OFFLINE_PAGES_TABLE_NAME
" (offline_id, creation_time, file_size, last_access_time, "
"access_count, client_namespace, client_id, online_url, "
"file_path, title, original_url) "
"SELECT "
"offline_id, creation_time, file_size, last_access_time, "
"access_count, client_namespace, client_id, online_url, "
"file_path, title, original_url "
"FROM temp_" OFFLINE_PAGES_TABLE_NAME;
return UpgradeWithQuery(db, kSql);
}
bool UpgradeFrom57(sql::Database* db) {
static const char kSql[] =
"INSERT INTO " OFFLINE_PAGES_TABLE_NAME
" (offline_id, creation_time, file_size, last_access_time, "
"access_count, client_namespace, client_id, online_url, "
"file_path, title, original_url) "
"SELECT "
"offline_id, creation_time, file_size, last_access_time, "
"access_count, client_namespace, client_id, online_url, "
"file_path, title, original_url "
"FROM temp_" OFFLINE_PAGES_TABLE_NAME;
return UpgradeWithQuery(db, kSql);
}
bool UpgradeFrom61(sql::Database* db) {
static const char kSql[] =
"INSERT INTO " OFFLINE_PAGES_TABLE_NAME
" (offline_id, creation_time, file_size, last_access_time, "
"access_count, client_namespace, client_id, online_url, "
"file_path, title, original_url, request_origin) "
"SELECT "
"offline_id, creation_time, file_size, last_access_time, "
"access_count, client_namespace, client_id, online_url, "
"file_path, title, original_url, request_origin "
"FROM temp_" OFFLINE_PAGES_TABLE_NAME;
return UpgradeWithQuery(db, kSql);
}
bool CreatePageThumbnailsTable(sql::Database* db) {
// TODO: The next schema change that modifies existing columns on this table
// should also add "DEFAULT x''" to the definition of the "thumbnail" column.
static const char kSql[] =
"CREATE TABLE IF NOT EXISTS page_thumbnails"
" (offline_id INTEGER PRIMARY KEY NOT NULL,"
" expiration INTEGER NOT NULL,"
" thumbnail BLOB NOT NULL,"
" favicon BLOB NOT NULL DEFAULT x''"
")";
return db->Execute(kSql);
}
bool CreateLatestSchema(sql::Database* db) {
sql::Transaction transaction(db);
if (!transaction.Begin())
return false;
// First time database initialization.
if (!CreateOfflinePagesTable(db))
return false;
if (!CreatePageThumbnailsTable(db))
return false;
sql::MetaTable meta_table;
if (!meta_table.Init(db, OfflinePageMetadataStore::kCurrentVersion,
OfflinePageMetadataStore::kCompatibleVersion))
return false;
return transaction.Commit();
}
// Upgrades the database from before the database version was stored in the
// MetaTable. This function should never need to be modified.
bool UpgradeFromLegacyVersion(sql::Database* db) {
sql::Transaction transaction(db);
if (!transaction.Begin())
return false;
// Legacy upgrade section. Details are described in the header file.
if (!db->DoesColumnExist(OFFLINE_PAGES_TABLE_NAME, "expiration_time") &&
!db->DoesColumnExist(OFFLINE_PAGES_TABLE_NAME, "title")) {
if (!UpgradeFrom52(db))
return false;
} else if (!db->DoesColumnExist(OFFLINE_PAGES_TABLE_NAME, "title")) {
if (!UpgradeFrom53(db))
return false;
} else if (db->DoesColumnExist(OFFLINE_PAGES_TABLE_NAME, "offline_url")) {
if (!UpgradeFrom54(db))
return false;
} else if (!db->DoesColumnExist(OFFLINE_PAGES_TABLE_NAME, "original_url")) {
if (!UpgradeFrom55(db))
return false;
} else if (db->DoesColumnExist(OFFLINE_PAGES_TABLE_NAME, "expiration_time")) {
if (!UpgradeFrom56(db))
return false;
} else if (!db->DoesColumnExist(OFFLINE_PAGES_TABLE_NAME, "request_origin")) {
if (!UpgradeFrom57(db))
return false;
} else if (!db->DoesColumnExist(OFFLINE_PAGES_TABLE_NAME, "digest")) {
if (!UpgradeFrom61(db))
return false;
}
sql::MetaTable meta_table;
if (!meta_table.Init(db, OfflinePageMetadataStore::kFirstPostLegacyVersion,
OfflinePageMetadataStore::kCompatibleVersion))
return false;
return transaction.Commit();
}
bool UpgradeFromVersion1ToVersion2(sql::Database* db,
sql::MetaTable* meta_table) {
meta_table->SetVersionNumber(2);
// No actual changes necessary, because upgrade_attempt was deprecated.
return true;
}
bool UpgradeFromVersion2ToVersion3(sql::Database* db,
sql::MetaTable* meta_table) {
sql::Transaction transaction(db);
if (!transaction.Begin())
return false;
static const char kCreatePageThumbnailsSql[] =
"CREATE TABLE IF NOT EXISTS page_thumbnails"
" (offline_id INTEGER PRIMARY KEY NOT NULL,"
"expiration INTEGER NOT NULL,"
"thumbnail BLOB NOT NULL"
")";
if (!db->Execute(kCreatePageThumbnailsSql))
return false;
meta_table->SetVersionNumber(3);
return transaction.Commit();
}
bool UpgradeFromVersion3ToVersion4(sql::Database* db,
sql::MetaTable* meta_table) {
sql::Transaction transaction(db);
if (!transaction.Begin())
return false;
const char kSql[] = "ALTER TABLE " OFFLINE_PAGES_TABLE_NAME
" ADD COLUMN snippet VARCHAR NOT NULL DEFAULT ''; "
"ALTER TABLE " OFFLINE_PAGES_TABLE_NAME
" ADD COLUMN attribution VARCHAR NOT NULL DEFAULT '';";
if (!db->Execute(kSql))
return false;
const char kUpgradeThumbnailsTableSql[] =
"ALTER TABLE page_thumbnails"
" ADD COLUMN favicon BLOB NOT NULL DEFAULT x''";
if (!db->Execute(kUpgradeThumbnailsTableSql))
return false;
meta_table->SetVersionNumber(4);
return transaction.Commit();
}
bool CreateSchema(sql::Database* db) {
if (!sql::MetaTable::DoesTableExist(db)) {
// If this looks like a completely empty DB, simply start from scratch.
if (!db->DoesTableExist(OFFLINE_PAGES_TABLE_NAME))
return CreateLatestSchema(db);
// Otherwise we need to run a legacy upgrade.
if (!UpgradeFromLegacyVersion(db))
return false;
}
sql::MetaTable meta_table;
if (!meta_table.Init(db, OfflinePageMetadataStore::kCurrentVersion,
OfflinePageMetadataStore::kCompatibleVersion))
return false;
for (;;) {
switch (meta_table.GetVersionNumber()) {
case 1:
if (!UpgradeFromVersion1ToVersion2(db, &meta_table))
return false;
break;
case 2:
if (!UpgradeFromVersion2ToVersion3(db, &meta_table))
return false;
break;
case 3:
if (!UpgradeFromVersion3ToVersion4(db, &meta_table))
return false;
break;
case OfflinePageMetadataStore::kCurrentVersion:
return true;
default:
return false;
}
}
}
StoreState InitializationStatusToStoreState(
SqlStoreBase::InitializationStatus status) {
switch (status) {
case SqlStoreBase::InitializationStatus::kNotInitialized:
return StoreState::NOT_LOADED;
case SqlStoreBase::InitializationStatus::kInProgress:
return StoreState::INITIALIZING;
case SqlStoreBase::InitializationStatus::kSuccess:
return StoreState::LOADED;
case SqlStoreBase::InitializationStatus::kFailure:
return StoreState::FAILED_LOADING;
}
}
} // anonymous namespace
OfflinePageMetadataStore::OfflinePageMetadataStore(
scoped_refptr<base::SequencedTaskRunner> background_task_runner)
: SqlStoreBase("OfflinePageMetadata",
std::move(background_task_runner),
base::FilePath()) {}
OfflinePageMetadataStore::OfflinePageMetadataStore(
scoped_refptr<base::SequencedTaskRunner> background_task_runner,
const base::FilePath& path)
: SqlStoreBase("OfflinePageMetadata",
std::move(background_task_runner),
path.AppendASCII("OfflinePages.db")) {}
OfflinePageMetadataStore::~OfflinePageMetadataStore() = default;
base::OnceCallback<bool(sql::Database* db)>
OfflinePageMetadataStore::GetSchemaInitializationFunction() {
return base::BindOnce(&CreateSchema);
}
StoreState OfflinePageMetadataStore::GetStateForTesting() const {
return InitializationStatusToStoreState(initialization_status_for_testing());
}
void OfflinePageMetadataStore::OnOpenStart(base::TimeTicks last_closing_time) {
TRACE_EVENT_ASYNC_BEGIN1("offline_pages", "Metadata Store", this, "is reopen",
!last_closing_time.is_null());
if (!last_closing_time.is_null()) {
ReportStoreEvent(OfflinePagesStoreEvent::kReopened);
UMA_HISTOGRAM_CUSTOM_TIMES("OfflinePages.SQLStorage.TimeFromCloseToOpen",
base::TimeTicks::Now() - last_closing_time,
base::TimeDelta::FromMilliseconds(10),
base::TimeDelta::FromMinutes(10),
50 /* buckets */);
} else {
ReportStoreEvent(OfflinePagesStoreEvent::kOpenedFirstTime);
}
}
void OfflinePageMetadataStore::OnOpenDone(bool success) {
TRACE_EVENT_ASYNC_STEP_PAST1("offline_pages", "Metadata Store", this,
"Initializing", "succeeded", success);
if (!success) {
TRACE_EVENT_ASYNC_END0("offline_pages", "Metadata Store", this);
}
}
void OfflinePageMetadataStore::OnTaskBegin(bool is_initialized) {
TRACE_EVENT_ASYNC_BEGIN1("offline_pages", "Metadata Store: task execution",
this, "is store loaded", is_initialized);
}
void OfflinePageMetadataStore::OnTaskRunComplete() {
// Note: the time recorded for this trace step will include thread hop wait
// times to the background thread and back.
TRACE_EVENT_ASYNC_STEP_PAST0("offline_pages",
"Metadata Store: task execution", this, "Task");
}
void OfflinePageMetadataStore::OnTaskReturnComplete() {
TRACE_EVENT_ASYNC_STEP_PAST0(
"offline_pages", "Metadata Store: task execution", this, "Callback");
TRACE_EVENT_ASYNC_END0("offline_pages", "Metadata Store: task execution",
this);
}
void OfflinePageMetadataStore::OnCloseStart(
InitializationStatus status_before_close) {
if (status_before_close != InitializationStatus::kSuccess) {
ReportStoreEvent(OfflinePagesStoreEvent::kCloseSkipped);
return;
}
TRACE_EVENT_ASYNC_STEP_PAST0("offline_pages", "Metadata Store", this, "Open");
ReportStoreEvent(OfflinePagesStoreEvent::kClosed);
}
void OfflinePageMetadataStore::OnCloseComplete() {
TRACE_EVENT_ASYNC_STEP_PAST0("offline_pages", "Metadata Store", this,
"Closing");
TRACE_EVENT_ASYNC_END0("offline_pages", "Metadata Store", this);
}
} // namespace offline_pages