Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(899)

Unified Diff: components/offline_pages/offline_page_metadata_store_sql.cc

Issue 1834563002: initial add of SQL based storage (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: rebase Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « components/offline_pages/offline_page_metadata_store_sql.h ('k') | tools/metrics/histograms/histograms.xml » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: components/offline_pages/offline_page_metadata_store_sql.cc
diff --git a/components/offline_pages/offline_page_metadata_store_sql.cc b/components/offline_pages/offline_page_metadata_store_sql.cc
new file mode 100644
index 0000000000000000000000000000000000000000..7ece2fc53a978656e233121af4ac70730595ca89
--- /dev/null
+++ b/components/offline_pages/offline_page_metadata_store_sql.cc
@@ -0,0 +1,331 @@
+// 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/offline_page_metadata_store_sql.h"
+
+#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/strings/utf_string_conversions.h"
+#include "base/thread_task_runner_handle.h"
+#include "components/offline_pages/offline_page_item.h"
+#include "sql/connection.h"
+#include "sql/statement.h"
+#include "sql/transaction.h"
+
+namespace offline_pages {
+
+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"
+
+const char kOfflinePagesColumns[] =
+ "(offline_id INTEGER PRIMARY KEY NOT NULL,"
+ " creation_time INTEGER NOT NULL,"
+ " file_size INTEGER NOT NULL,"
+ " version INTEGER NOT NULL,"
+ " last_access_time INTEGER NOT NULL,"
+ " access_count INTEGER NOT NULL,"
+ " status INTEGER NOT NULL DEFAULT 0,"
+ // A note on this field: It will be NULL for now and is reserved for
+ // later use. We will treat NULL as "Unknown" in any subsequent queries
+ // for user_initiated values.
+ " user_initiated INTEGER," // this is actually a boolean
+ " client_namespace VARCHAR NOT NULL,"
+ " client_id VARCHAR NOT NULL,"
+ " online_url VARCHAR NOT NULL,"
+ " offline_url VARCHAR NOT NULL DEFAULT '',"
+ " file_path VARCHAR NOT NULL"
+ ")";
+
+// This is cloned from //content/browser/appcache/appcache_database.cc
+struct TableInfo {
+ const char* table_name;
+ const char* columns;
+};
+
+const TableInfo kOfflinePagesTable{OFFLINE_PAGES_TABLE_NAME,
+ kOfflinePagesColumns};
+
+// This enum is used to define the indices for the columns in each row
+// that hold the different pieces of offline page.
+enum : int {
+ OP_OFFLINE_ID = 0,
+ OP_CREATION_TIME,
+ OP_FILE_SIZE,
+ OP_VERSION,
+ OP_LAST_ACCESS_TIME,
+ OP_ACCESS_COUNT,
+ OP_STATUS,
+ OP_USER_INITIATED,
+ OP_CLIENT_NAMESPACE,
+ OP_CLIENT_ID,
+ OP_ONLINE_URL,
+ OP_OFFLINE_URL,
+ OP_FILE_PATH
+};
+
+bool CreateTable(sql::Connection* db, const TableInfo& table_info) {
+ std::string sql("CREATE TABLE ");
+ sql += table_info.table_name;
+ sql += table_info.columns;
+ return db->Execute(sql.c_str());
+}
+
+bool CreateSchema(sql::Connection* db) {
+ // If you create a transaction but don't Commit() it is automatically
+ // rolled back by its destructor when it falls out of scope.
+ sql::Transaction transaction(db);
+ if (!transaction.Begin())
+ return false;
+ if (db->DoesTableExist(kOfflinePagesTable.table_name))
+ return true;
+
+ if (!CreateTable(db, kOfflinePagesTable))
+ return false;
+
+ // TODO(bburns): Add indices here.
+ return transaction.Commit();
+}
+
+bool DeleteByOfflineId(sql::Connection* db, int64_t offline_id) {
+ static const char kSql[] =
+ "DELETE FROM " OFFLINE_PAGES_TABLE_NAME " WHERE offline_id=?";
+ sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
+ statement.BindInt64(0, offline_id);
+ return statement.Run();
+}
+
+// Create an offline page item from a SQL result. Expects complete rows with
+// all columns present.
+OfflinePageItem MakeOfflinePageItem(sql::Statement* statement) {
+ int64_t id = statement->ColumnInt64(OP_OFFLINE_ID);
+ GURL url(statement->ColumnString(OP_ONLINE_URL));
+ ClientId client_id(statement->ColumnString(OP_CLIENT_NAMESPACE),
+ statement->ColumnString(OP_CLIENT_ID));
+#if defined(OS_POSIX)
+ base::FilePath path(statement->ColumnString(OP_FILE_PATH));
+#elif defined(OS_WIN)
+ base::FilePath path(base::UTF8ToWide(statement->ColumnString(OP_FILE_PATH)));
+#else
+#error Unknown OS
+#endif
+ int64_t file_size = statement->ColumnInt64(OP_FILE_SIZE);
+ base::Time creation_time =
+ base::Time::FromInternalValue(statement->ColumnInt64(OP_CREATION_TIME));
+
+ OfflinePageItem item(url, id, client_id, path, file_size, creation_time);
+ item.last_access_time = base::Time::FromInternalValue(
+ statement->ColumnInt64(OP_LAST_ACCESS_TIME));
+ item.version = statement->ColumnInt(OP_VERSION);
+ item.access_count = statement->ColumnInt(OP_ACCESS_COUNT);
+ return item;
+}
+
+bool InsertOrReplace(sql::Connection* db, const OfflinePageItem& item) {
+ const char kSql[] =
+ "INSERT OR REPLACE INTO " OFFLINE_PAGES_TABLE_NAME
+ " (offline_id, online_url, client_namespace, client_id, file_path, "
+ "file_size, creation_time, last_access_time, version, access_count)"
+ " VALUES "
+ " (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+
+ sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
+ statement.BindInt64(0, item.offline_id);
+ statement.BindString(1, item.url.spec());
+ statement.BindString(2, item.client_id.name_space);
+ statement.BindString(3, item.client_id.id);
+#if defined(OS_POSIX)
+ std::string path_string = item.file_path.value();
+#elif defined(OS_WIN)
+ std::string path_string = base::WideToUTF8(item.file_path.value());
+#else
+#error Unknown OS
+#endif
+ statement.BindString(4, path_string);
+ statement.BindInt64(5, item.file_size);
+ statement.BindInt64(6, item.creation_time.ToInternalValue());
+ statement.BindInt64(7, item.last_access_time.ToInternalValue());
+ statement.BindInt(8, item.version);
+ statement.BindInt(9, item.access_count);
+ return statement.Run();
+}
+
+bool InitDatabase(sql::Connection* db, base::FilePath path) {
+ db->set_page_size(4096);
+ db->set_cache_size(500);
+ db->set_histogram_tag("OfflinePageMetadata");
+ db->set_exclusive_locking();
+
+ base::File::Error err;
+ if (!base::CreateDirectoryAndGetError(path.DirName(), &err)) {
+ LOG(ERROR) << "Failed to create offline pages db directory: "
+ << base::File::ErrorToString(err);
+ return false;
+ }
+ if (!db->Open(path)) {
+ LOG(ERROR) << "Failed to open database";
+ return false;
+ }
+ db->Preload();
+
+ return CreateSchema(db);
+}
+
+} // anonymous namespace
+
+OfflinePageMetadataStoreSQL::OfflinePageMetadataStoreSQL(
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner,
+ const base::FilePath& path)
+ : background_task_runner_(std::move(background_task_runner)),
+ db_file_path_(path.AppendASCII("OfflinePages.db")) {}
+
+OfflinePageMetadataStoreSQL::~OfflinePageMetadataStoreSQL() {
+ if (db_.get() &&
+ !background_task_runner_->DeleteSoon(FROM_HERE, db_.release())) {
+ DLOG(WARNING) << "SQL database will not be deleted.";
+ }
+}
+
+void OfflinePageMetadataStoreSQL::LoadSync(
+ sql::Connection* db,
+ const base::FilePath& path,
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ const LoadCallback& callback) {
+ if (!InitDatabase(db, path)) {
+ NotifyLoadResult(runner, callback, STORE_INIT_FAILED,
+ std::vector<OfflinePageItem>());
+ return;
+ }
+
+ const char kSql[] = "SELECT * FROM " OFFLINE_PAGES_TABLE_NAME;
+
+ sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
+
+ std::vector<OfflinePageItem> result;
+ while (statement.Step()) {
+ result.push_back(MakeOfflinePageItem(&statement));
+ }
+
+ if (statement.Succeeded()) {
+ NotifyLoadResult(runner, callback, LOAD_SUCCEEDED, result);
+ } else {
+ NotifyLoadResult(runner, callback, STORE_LOAD_FAILED,
+ std::vector<OfflinePageItem>());
+ }
+}
+
+void OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePageSync(
+ const OfflinePageItem& offline_page,
+ sql::Connection* db,
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ const UpdateCallback& callback) {
+ // TODO(bburns): add UMA metrics here (and for levelDB).
+ bool ok = InsertOrReplace(db, offline_page);
+ runner->PostTask(FROM_HERE, base::Bind(callback, ok));
+}
+
+void OfflinePageMetadataStoreSQL::RemoveOfflinePagesSync(
+ const std::vector<int64_t>& offline_ids,
+ sql::Connection* db,
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ const UpdateCallback& callback) {
+ // TODO(bburns): add UMA metrics here (and for levelDB).
+
+ // If you create a transaction but don't Commit() it is automatically
+ // rolled back by its destructor when it falls out of scope.
+ sql::Transaction transaction(db);
+ if (!transaction.Begin()) {
+ runner->PostTask(FROM_HERE, base::Bind(callback, false));
+ return;
+ }
+ for (auto offline_id : offline_ids) {
+ if (!DeleteByOfflineId(db, offline_id)) {
+ runner->PostTask(FROM_HERE, base::Bind(callback, false));
+ return;
+ }
+ }
+
+ bool success = transaction.Commit();
+ runner->PostTask(FROM_HERE, base::Bind(callback, success));
+}
+
+void OfflinePageMetadataStoreSQL::ResetSync(
+ std::unique_ptr<sql::Connection> db,
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ const ResetCallback& callback) {
+ const char kSql[] = "DELETE * FROM " OFFLINE_PAGES_TABLE_NAME;
+ sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
+ runner->PostTask(FROM_HERE, base::Bind(callback, statement.Run()));
+}
+
+void OfflinePageMetadataStoreSQL::NotifyLoadResult(
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ const LoadCallback& callback,
+ LoadStatus status,
+ const std::vector<OfflinePageItem>& result) {
+ // TODO(bburns): Switch to SQL specific UMA metrics.
+ UMA_HISTOGRAM_ENUMERATION("OfflinePages.LoadStatus", status,
+ OfflinePageMetadataStore::LOAD_STATUS_COUNT);
+ if (status == LOAD_SUCCEEDED) {
+ UMA_HISTOGRAM_COUNTS("OfflinePages.SavedPageCount", result.size());
+ } else {
+ DVLOG(1) << "Offline pages database loading failed: " << status;
+ }
+ runner->PostTask(FROM_HERE, base::Bind(callback, status, result));
+}
+
+void OfflinePageMetadataStoreSQL::Load(const LoadCallback& callback) {
+ db_.reset(new sql::Connection());
+ background_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&OfflinePageMetadataStoreSQL::LoadSync, db_.get(),
+ db_file_path_, base::ThreadTaskRunnerHandle::Get(), callback));
+}
+
+void OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePage(
+ const OfflinePageItem& offline_page,
+ const UpdateCallback& callback) {
+ DCHECK(db_.get());
+ background_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePageSync,
+ offline_page, db_.get(), base::ThreadTaskRunnerHandle::Get(),
+ callback));
+}
+
+void OfflinePageMetadataStoreSQL::RemoveOfflinePages(
+ const std::vector<int64_t>& offline_ids,
+ const UpdateCallback& callback) {
+ DCHECK(db_.get());
+
+ if (offline_ids.empty()) {
+ // Nothing to do, but post a callback instead of calling directly
+ // to preserve the async style behavior to prevent bugs.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ base::Bind(callback, true));
+ return;
+ }
+
+ background_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&OfflinePageMetadataStoreSQL::RemoveOfflinePagesSync,
+ offline_ids, db_.get(), base::ThreadTaskRunnerHandle::Get(),
+ callback));
+}
+
+void OfflinePageMetadataStoreSQL::Reset(const ResetCallback& callback) {
+ background_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&OfflinePageMetadataStoreSQL::ResetSync, base::Passed(&db_),
+ base::ThreadTaskRunnerHandle::Get(), callback));
+}
+
+} // namespace offline_pages
« no previous file with comments | « components/offline_pages/offline_page_metadata_store_sql.h ('k') | tools/metrics/histograms/histograms.xml » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698