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

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: address comments. Created 4 years, 9 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
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..150bbce3a704441c73195e27f405e0ea9f37d2e7
--- /dev/null
+++ b/components/offline_pages/offline_page_metadata_store_sql.cc
@@ -0,0 +1,301 @@
+// 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/metrics/histogram_macros.h"
+#include "base/sequenced_task_runner.h"
+#include "base/thread_task_runner_handle.h"
+#include "components/offline_pages/offline_page_item.h"
+#include "sql/connection.h"
+#include "sql/error_delegate_util.h"
+#include "sql/meta_table.h"
+#include "sql/statement.h"
+#include "sql/transaction.h"
+
+namespace offline_pages {
+
+namespace {
+
+const int kCurrentVersion = 1;
+const int kCompatibleVersion = 1;
+
+// this is a #define instead of a const so that
fgorski 2016/03/29 06:50:18 nit: sentence case.
bburns 2016/03/30 16:54:40 Done.
+// I can use it inline in other SQL statements below
fgorski 2016/03/29 06:50:19 so it can be used.
bburns 2016/03/30 16:54:40 Done.
+#define kOfflinePagesTableName "offlinepages"
+const char kOfflinePagesColumns[] =
+ "(offline_id INTEGER PRIMARY_KEY,"
+ " client_namespace VARCHAR(256),"
+ " client_id VARCHAR(256),"
+ " online_url VARCHAR(2048),"
+ " offline_url VARCHAR(2048),"
+ " version INTEGER,"
+ " creation_time INTEGER,"
+ " file_path VARCHAR(1024),"
+ " file_size INTEGER,"
+ " last_access_time INTEGER,"
+ " access_count INTEGER,"
+ " status INTEGER,"
+ " user_initiated BOOLEAN)";
+
+// This is cloned from //content/browser/appcache/appcache_database.cc
+struct TableInfo {
+ const char* table_name;
+ const char* columns;
+};
+
+const TableInfo kOfflinePagesTable{kOfflinePagesTableName,
+ kOfflinePagesColumns};
+
+enum : int {
+ OP_OFFLINE_ID = 0,
+ OP_CLIENT_NAMESPACE,
+ OP_CLIENT_ID,
+ OP_ONLINE_URL,
+ OP_OFFLINE_URL,
+ OP_VERSION,
+ OP_CREATION_TIME,
+ OP_FILE_PATH,
+ OP_FILE_SIZE,
+ OP_LAST_ACCESS_TIME,
+ OP_ACCESS_COUNT,
+ OP_STATUS,
+ OP_USER_INITIATED
+};
+
+bool CreateTable(sql::Connection* db,
+ const char* table_name,
+ const char* columns) {
+ std::string sql("CREATE TABLE ");
+ sql += table_name;
+ sql += columns;
+ return db->Execute(sql.c_str());
+}
+
+bool CreateSchema(sql::MetaTable* meta_table, 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 (!meta_table->Init(db, kCurrentVersion, kCompatibleVersion))
+ return false;
+
+ if (!CreateTable(db, kOfflinePagesTableName, kOfflinePagesColumns))
+ return false;
+
+ // TODO(bburns): indexes here
+ return transaction.Commit();
+}
+
+bool DropTable(sql::Connection* db, const char* table_name) {
+ std::string sql("DROP TABLE ");
+ sql += table_name;
+ return db->Execute(sql.c_str());
+}
+
+bool DeleteByOfflineId(sql::Connection* db, int64_t offline_id) {
+ const char sql[] =
+ "DELETE FROM " kOfflinePagesTableName " WHERE offline_id=?";
+ sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, sql));
+ statement.BindInt64(0, offline_id);
+ return db->Execute(sql);
+}
+
+// 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));
+ base::FilePath path(statement->ColumnString(OP_FILE_PATH));
fgorski 2016/03/29 06:50:19 From the level db store impl: #if defined(OS_POSIX
bburns 2016/03/30 16:54:39 Is this really required? It seems like something
fgorski 2016/03/30 17:19:31 It's actually file path that is weird. class BASE
+ 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->ColumnInt64(OP_VERSION);
+ return item;
+}
+
+bool InsertOrReplace(sql::Connection* db, const OfflinePageItem& item) {
+ const char sql[] =
fgorski 2016/03/29 06:50:19 since it is a const, kSql
bburns 2016/03/30 16:54:40 Done.
+ "INSERT OR REPLACE INTO " kOfflinePagesTableName
+ " (offline_id, online_url, client_namespace, client_id, file_path, "
+ "file_size, creation_time, last_access_time, version)"
+ " VALUES "
+ " (?, ?, ?, ?, ?, ?, ?, ?, ?)";
+
+ sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, sql));
+ 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);
+ statement.BindString(4, item.file_path.value());
+ statement.BindInt64(5, item.file_size);
+ statement.BindInt64(6, item.creation_time.ToInternalValue());
+ statement.BindInt64(7, item.last_access_time.ToInternalValue());
+ statement.BindInt64(8, item.version);
+ return statement.Run();
+}
+} // anonymous namespace
+
+OfflinePageMetadataStoreSQL::OfflinePageMetadataStoreSQL(
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner,
+ const base::FilePath& path,
+ bool in_memory)
+ : background_task_runner_(background_task_runner),
+ db_file_path_(path),
+ use_in_memory_(in_memory),
+ weak_ptr_factory_(this) {}
+
+OfflinePageMetadataStoreSQL::~OfflinePageMetadataStoreSQL() {}
+
+void OfflinePageMetadataStoreSQL::LoadSync(
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ const LoadCallback& callback) {
+ bool opened = false;
+ db_.reset(new sql::Connection);
+ meta_table_.reset(new sql::MetaTable);
+
+ if (use_in_memory_) {
+ opened = db_->OpenInMemory();
+ } else if (!base::CreateDirectory(db_file_path_.DirName())) {
fgorski 2016/03/29 06:50:18 since you are logging here, probably makes sense t
bburns 2016/03/30 16:54:40 Done.
+ LOG(ERROR) << "Failed to create offline pages db directory.";
+ } else {
+ opened = db_->Open(db_file_path_);
+ if (opened)
+ db_->Preload();
+ }
+ if (!opened) {
+ NotifyLoadResult(callback, runner, STORE_INIT_FAILED,
+ std::vector<OfflinePageItem>());
+ }
+
+ if (!sql::MetaTable::DoesTableExist(db_.get())) {
+ if (!CreateSchema(meta_table_.get(), db_.get())) {
+ NotifyLoadResult(callback, runner, STORE_INIT_FAILED,
+ std::vector<OfflinePageItem>());
fgorski 2016/03/29 06:50:19 do you intend to stop execution here? if so return
bburns 2016/03/30 16:54:40 Done.
+ }
+ }
+
+ if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
+ NotifyLoadResult(callback, runner, STORE_INIT_FAILED,
fgorski 2016/03/29 06:50:19 this is a single line, but a multiline body, surro
bburns 2016/03/30 16:54:40 Done.
+ std::vector<OfflinePageItem>());
+
+ if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) {
+ LOG(WARNING) << "Offline database is too new.";
+ NotifyLoadResult(callback, runner, STORE_INIT_FAILED,
+ std::vector<OfflinePageItem>());
fgorski 2016/03/29 06:50:19 return missing
bburns 2016/03/30 16:54:40 Done.
+ }
+
+ const char sql[] = "SELECT * FROM " kOfflinePagesTableName;
+
+ sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, sql));
+
+ std::vector<OfflinePageItem> result;
+ while (statement.Step()) {
+ result.push_back(MakeOfflinePageItem(&statement));
+ }
+
+ if (!statement.Succeeded()) {
+ NotifyLoadResult(callback, runner, STORE_LOAD_FAILED,
+ std::vector<OfflinePageItem>());
+ } else {
+ NotifyLoadResult(callback, runner, LOAD_SUCCEEDED, result);
+ }
+}
+
+void OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePageSync(
+ const OfflinePageItem& offline_page,
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ const UpdateCallback& callback) {
+ bool ok = InsertOrReplace(db_.get(), offline_page);
+ runner->PostTask(FROM_HERE, base::Bind(callback, ok));
+}
+
+void OfflinePageMetadataStoreSQL::RemoveOfflinePagesSync(
+ const std::vector<int64_t>& offline_ids,
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ const UpdateCallback& callback) {
+ // 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_.get());
+
+ for (auto offline_id : offline_ids) {
+ if (!DeleteByOfflineId(db_.get(), offline_id)) {
+ runner->PostTask(FROM_HERE, base::Bind(callback, false));
+ return;
+ }
+ }
+
+ if (!transaction.Commit()) {
fgorski 2016/03/29 06:50:18 This posts the result twice upon failure, because
bburns 2016/03/30 16:54:40 Done.
+ runner->PostTask(FROM_HERE, base::Bind(callback, false));
+ }
+ runner->PostTask(FROM_HERE, base::Bind(callback, true));
+}
+
+void OfflinePageMetadataStoreSQL::Reset(const ResetCallback& callback) {
+ bool success = DropTable(db_.get(), kOfflinePagesTableName);
fgorski 2016/03/29 06:50:19 shouldn't DDL be called on the background thread?
bburns 2016/03/30 16:54:39 If you want. Drop table seemed like it was so fas
fgorski 2016/03/30 17:19:31 Let's stay consistent with choice of threads. If y
Scott Hess - ex-Googler 2016/04/12 21:57:23 While SQLite _can_ be thread-safe in certain ways,
+ callback.Run(success);
+}
+
+void OfflinePageMetadataStoreSQL::NotifyLoadResult(
+ const LoadCallback& callback,
fgorski 2016/03/29 06:50:19 I don't mean to argue either order is better: call
bburns 2016/03/30 16:54:39 Done.
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ LoadStatus status,
+ const std::vector<OfflinePageItem>& result) {
+ 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;
+ db_.reset();
+ }
+ runner->PostTask(FROM_HERE, base::Bind(callback, status, result));
+}
+
+void OfflinePageMetadataStoreSQL::Load(const LoadCallback& callback) {
+ background_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&OfflinePageMetadataStoreSQL::LoadSync,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::ThreadTaskRunnerHandle::Get(), callback));
+}
+
+void OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePage(
+ const OfflinePageItem& offline_page,
+ const UpdateCallback& callback) {
+ background_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePageSync,
+ weak_ptr_factory_.GetWeakPtr(), offline_page,
+ base::ThreadTaskRunnerHandle::Get(), callback));
+}
+
+void OfflinePageMetadataStoreSQL::RemoveOfflinePages(
+ const std::vector<int64_t>& offline_ids,
+ const UpdateCallback& callback) {
+ if (offline_ids.size() == 0) {
+ // Nothing to do.
fgorski 2016/03/29 06:50:19 Good shortcut, but for safety you can post it to t
bburns 2016/03/30 16:54:40 Done.
fgorski 2016/03/30 17:19:31 I meant something like: base::ThreadTaskRunnerHand
+ callback.Run(true);
+ return;
+ }
+
+ background_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&OfflinePageMetadataStoreSQL::RemoveOfflinePagesSync,
+ weak_ptr_factory_.GetWeakPtr(), offline_ids,
+ base::ThreadTaskRunnerHandle::Get(), callback));
+}
+
+} // namespace offline_pages

Powered by Google App Engine
This is Rietveld 408576698