Chromium Code Reviews| 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 |