|  | // Copyright 2013 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 "storage/browser/quota/quota_database.h" | 
|  |  | 
|  | #include <stddef.h> | 
|  | #include <stdint.h> | 
|  |  | 
|  | #include <tuple> | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/auto_reset.h" | 
|  | #include "base/bind.h" | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/metrics/histogram_macros.h" | 
|  | #include "base/stl_util.h" | 
|  | #include "sql/database.h" | 
|  | #include "sql/meta_table.h" | 
|  | #include "sql/statement.h" | 
|  | #include "sql/transaction.h" | 
|  | #include "storage/browser/quota/special_storage_policy.h" | 
|  | #include "url/gurl.h" | 
|  |  | 
|  | using blink::mojom::StorageType; | 
|  |  | 
|  | namespace storage { | 
|  | namespace { | 
|  |  | 
|  | // Definitions for database schema. | 
|  |  | 
|  | const int kQuotaDatabaseCurrentSchemaVersion = 5; | 
|  | const int kQuotaDatabaseCompatibleVersion = 2; | 
|  |  | 
|  | const char kHostQuotaTable[] = "HostQuotaTable"; | 
|  | const char kOriginInfoTable[] = "OriginInfoTable"; | 
|  | const char kEvictionInfoTable[] = "EvictionInfoTable"; | 
|  | const char kIsOriginTableBootstrapped[] = "IsOriginTableBootstrapped"; | 
|  |  | 
|  | bool VerifyValidQuotaConfig(const char* key) { | 
|  | return (key != nullptr && | 
|  | (!strcmp(key, QuotaDatabase::kDesiredAvailableSpaceKey) || | 
|  | !strcmp(key, QuotaDatabase::kTemporaryQuotaOverrideKey))); | 
|  | } | 
|  |  | 
|  | const int kCommitIntervalMs = 30000; | 
|  |  | 
|  | void LogDaysSinceLastAccess(base::Time this_time, | 
|  | const QuotaDatabase::OriginInfoTableEntry& entry) { | 
|  | base::TimeDelta time_since = this_time - std::max(entry.last_access_time, | 
|  | entry.last_modified_time); | 
|  | if (time_since.InDays() < 1) | 
|  | return; | 
|  | UMA_HISTOGRAM_COUNTS_1000("Quota.DaysSinceLastAccess", time_since.InDays()); | 
|  | } | 
|  |  | 
|  | }  // anonymous namespace | 
|  |  | 
|  | // static | 
|  | const char QuotaDatabase::kDesiredAvailableSpaceKey[] = "DesiredAvailableSpace"; | 
|  | const char QuotaDatabase::kTemporaryQuotaOverrideKey[] = | 
|  | "TemporaryQuotaOverride"; | 
|  |  | 
|  | const QuotaDatabase::TableSchema QuotaDatabase::kTables[] = { | 
|  | {kHostQuotaTable, | 
|  | "(host TEXT NOT NULL," | 
|  | " type INTEGER NOT NULL," | 
|  | " quota INTEGER DEFAULT 0," | 
|  | " UNIQUE(host, type))"}, | 
|  | {kOriginInfoTable, | 
|  | "(origin TEXT NOT NULL," | 
|  | " type INTEGER NOT NULL," | 
|  | " used_count INTEGER DEFAULT 0," | 
|  | " last_access_time INTEGER DEFAULT 0," | 
|  | " last_modified_time INTEGER DEFAULT 0," | 
|  | " UNIQUE(origin, type))"}, | 
|  | {kEvictionInfoTable, | 
|  | "(origin TEXT NOT NULL," | 
|  | " type INTEGER NOT NULL," | 
|  | " last_eviction_time INTEGER DEFAULT 0," | 
|  | " UNIQUE(origin, type))"}}; | 
|  |  | 
|  | // static | 
|  | const QuotaDatabase::IndexSchema QuotaDatabase::kIndexes[] = { | 
|  | { "HostIndex", | 
|  | kHostQuotaTable, | 
|  | "(host)", | 
|  | false }, | 
|  | { "OriginInfoIndex", | 
|  | kOriginInfoTable, | 
|  | "(origin)", | 
|  | false }, | 
|  | { "OriginLastAccessTimeIndex", | 
|  | kOriginInfoTable, | 
|  | "(last_access_time)", | 
|  | false }, | 
|  | { "OriginLastModifiedTimeIndex", | 
|  | kOriginInfoTable, | 
|  | "(last_modified_time)", | 
|  | false }, | 
|  | }; | 
|  |  | 
|  | struct QuotaDatabase::QuotaTableImporter { | 
|  | bool Append(const QuotaTableEntry& entry) { | 
|  | entries.push_back(entry); | 
|  | return true; | 
|  | } | 
|  | std::vector<QuotaTableEntry> entries; | 
|  | }; | 
|  |  | 
|  | // Clang requires explicit out-of-line constructors for them. | 
|  | QuotaDatabase::QuotaTableEntry::QuotaTableEntry() | 
|  | : type(StorageType::kUnknown), quota(0) {} | 
|  |  | 
|  | QuotaDatabase::QuotaTableEntry::QuotaTableEntry(const std::string& host, | 
|  | StorageType type, | 
|  | int64_t quota) | 
|  | : host(host), type(type), quota(quota) {} | 
|  |  | 
|  | QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry() | 
|  | : type(StorageType::kUnknown), used_count(0) {} | 
|  |  | 
|  | QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry( | 
|  | const url::Origin& origin, | 
|  | StorageType type, | 
|  | int used_count, | 
|  | const base::Time& last_access_time, | 
|  | const base::Time& last_modified_time) | 
|  | : origin(origin), | 
|  | type(type), | 
|  | used_count(used_count), | 
|  | last_access_time(last_access_time), | 
|  | last_modified_time(last_modified_time) {} | 
|  |  | 
|  | // QuotaDatabase ------------------------------------------------------------ | 
|  | QuotaDatabase::QuotaDatabase(const base::FilePath& path) | 
|  | : db_file_path_(path), | 
|  | is_recreating_(false), | 
|  | is_disabled_(false) { | 
|  | } | 
|  |  | 
|  | QuotaDatabase::~QuotaDatabase() { | 
|  | if (db_) { | 
|  | db_->CommitTransaction(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void QuotaDatabase::CloseDatabase() { | 
|  | meta_table_.reset(); | 
|  | db_.reset(); | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::GetHostQuota(const std::string& host, | 
|  | StorageType type, | 
|  | int64_t* quota) { | 
|  | DCHECK(quota); | 
|  | if (!LazyOpen(false)) | 
|  | return false; | 
|  |  | 
|  | const char* kSql = | 
|  | "SELECT quota" | 
|  | " FROM HostQuotaTable" | 
|  | " WHERE host = ? AND type = ?"; | 
|  |  | 
|  | sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | 
|  | statement.BindString(0, host); | 
|  | statement.BindInt(1, static_cast<int>(type)); | 
|  |  | 
|  | if (!statement.Step()) | 
|  | return false; | 
|  |  | 
|  | *quota = statement.ColumnInt64(0); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::SetHostQuota(const std::string& host, | 
|  | StorageType type, | 
|  | int64_t quota) { | 
|  | DCHECK_GE(quota, 0); | 
|  | if (!LazyOpen(true)) | 
|  | return false; | 
|  | if (quota == 0) | 
|  | return DeleteHostQuota(host, type); | 
|  | if (!InsertOrReplaceHostQuota(host, type, quota)) | 
|  | return false; | 
|  | ScheduleCommit(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::SetOriginLastAccessTime(const url::Origin& origin, | 
|  | StorageType type, | 
|  | base::Time last_access_time) { | 
|  | if (!LazyOpen(true)) | 
|  | return false; | 
|  |  | 
|  | sql::Statement statement; | 
|  |  | 
|  | OriginInfoTableEntry entry; | 
|  | if (GetOriginInfo(origin, type, &entry)) { | 
|  | LogDaysSinceLastAccess(last_access_time, entry); | 
|  | ++entry.used_count; | 
|  | const char* kSql = | 
|  | "UPDATE OriginInfoTable" | 
|  | " SET used_count = ?, last_access_time = ?" | 
|  | " WHERE origin = ? AND type = ?"; | 
|  | statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | 
|  | } else  { | 
|  | entry.used_count = 1; | 
|  | const char* kSql = | 
|  | "INSERT INTO OriginInfoTable" | 
|  | " (used_count, last_access_time, origin, type, last_modified_time)" | 
|  | " VALUES (?, ?, ?, ?, ?)"; | 
|  | statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | 
|  | statement.BindInt64(4, TimeToSqlValue(last_access_time)); | 
|  | } | 
|  | statement.BindInt(0, entry.used_count); | 
|  | statement.BindInt64(1, TimeToSqlValue(last_access_time)); | 
|  | statement.BindString(2, origin.GetURL().spec()); | 
|  | statement.BindInt(3, static_cast<int>(type)); | 
|  |  | 
|  | if (!statement.Run()) | 
|  | return false; | 
|  |  | 
|  | ScheduleCommit(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::SetOriginLastModifiedTime(const url::Origin& origin, | 
|  | StorageType type, | 
|  | base::Time last_modified_time) { | 
|  | if (!LazyOpen(true)) | 
|  | return false; | 
|  |  | 
|  | sql::Statement statement; | 
|  |  | 
|  | OriginInfoTableEntry entry; | 
|  | if (GetOriginInfo(origin, type, &entry)) { | 
|  | LogDaysSinceLastAccess(last_modified_time, entry); | 
|  | const char* kSql = | 
|  | "UPDATE OriginInfoTable" | 
|  | " SET last_modified_time = ?" | 
|  | " WHERE origin = ? AND type = ?"; | 
|  | statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | 
|  | } else { | 
|  | const char* kSql = | 
|  | "INSERT INTO OriginInfoTable" | 
|  | " (last_modified_time, origin, type, last_access_time)  VALUES (?, ?, ?, ?)"; | 
|  | statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | 
|  | statement.BindInt64(3, TimeToSqlValue(last_modified_time)); | 
|  | } | 
|  | statement.BindInt64(0, TimeToSqlValue(last_modified_time)); | 
|  |  | 
|  | statement.BindString(1, origin.GetURL().spec()); | 
|  | statement.BindInt(2, static_cast<int>(type)); | 
|  |  | 
|  | if (!statement.Run()) | 
|  | return false; | 
|  |  | 
|  | ScheduleCommit(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::GetOriginLastEvictionTime(const url::Origin& origin, | 
|  | StorageType type, | 
|  | base::Time* last_modified_time) { | 
|  | DCHECK(last_modified_time); | 
|  | if (!LazyOpen(false)) | 
|  | return false; | 
|  |  | 
|  | static const char kSql[] = | 
|  | "SELECT last_eviction_time" | 
|  | " FROM EvictionInfoTable" | 
|  | " WHERE origin = ? AND type = ?"; | 
|  |  | 
|  | sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | 
|  | statement.BindString(0, origin.GetURL().spec()); | 
|  | statement.BindInt(1, static_cast<int>(type)); | 
|  |  | 
|  | if (!statement.Step()) | 
|  | return false; | 
|  |  | 
|  | *last_modified_time = TimeFromSqlValue(statement.ColumnInt64(0)); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::SetOriginLastEvictionTime(const url::Origin& origin, | 
|  | StorageType type, | 
|  | base::Time last_modified_time) { | 
|  | if (!LazyOpen(true)) | 
|  | return false; | 
|  |  | 
|  | static const char kSql[] = | 
|  | "INSERT OR REPLACE INTO EvictionInfoTable" | 
|  | " (last_eviction_time, origin, type)" | 
|  | " VALUES (?, ?, ?)"; | 
|  | sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | 
|  | statement.BindInt64(0, TimeToSqlValue(last_modified_time)); | 
|  | statement.BindString(1, origin.GetURL().spec()); | 
|  | statement.BindInt(2, static_cast<int>(type)); | 
|  |  | 
|  | if (!statement.Run()) | 
|  | return false; | 
|  |  | 
|  | ScheduleCommit(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::DeleteOriginLastEvictionTime(const url::Origin& origin, | 
|  | StorageType type) { | 
|  | if (!LazyOpen(false)) | 
|  | return false; | 
|  |  | 
|  | static const char kSql[] = | 
|  | "DELETE FROM EvictionInfoTable" | 
|  | " WHERE origin = ? AND type = ?"; | 
|  |  | 
|  | sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | 
|  | statement.BindString(0, origin.GetURL().spec()); | 
|  | statement.BindInt(1, static_cast<int>(type)); | 
|  |  | 
|  | if (!statement.Run()) | 
|  | return false; | 
|  |  | 
|  | ScheduleCommit(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::RegisterInitialOriginInfo( | 
|  | const std::set<url::Origin>& origins, | 
|  | StorageType type) { | 
|  | if (!LazyOpen(true)) | 
|  | return false; | 
|  |  | 
|  | for (const auto& origin : origins) { | 
|  | const char* kSql = | 
|  | "INSERT OR IGNORE INTO OriginInfoTable" | 
|  | " (origin, type) VALUES (?, ?)"; | 
|  | sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | 
|  | statement.BindString(0, origin.GetURL().spec()); | 
|  | statement.BindInt(1, static_cast<int>(type)); | 
|  |  | 
|  | if (!statement.Run()) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | ScheduleCommit(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::GetOriginInfo(const url::Origin& origin, | 
|  | StorageType type, | 
|  | QuotaDatabase::OriginInfoTableEntry* entry) { | 
|  | if (!LazyOpen(false)) | 
|  | return false; | 
|  |  | 
|  | const char* kSql = | 
|  | "SELECT * FROM OriginInfoTable" | 
|  | " WHERE origin = ? AND type = ?"; | 
|  | sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | 
|  | statement.BindString(0, origin.GetURL().spec()); | 
|  | statement.BindInt(1, static_cast<int>(type)); | 
|  |  | 
|  | if (!statement.Step()) | 
|  | return false; | 
|  |  | 
|  | // TODO(crbug.com/889590): Use helper for url::Origin creation from string. | 
|  | *entry = OriginInfoTableEntry( | 
|  | url::Origin::Create(GURL(statement.ColumnString(0))), | 
|  | static_cast<StorageType>(statement.ColumnInt(1)), statement.ColumnInt(2), | 
|  | TimeFromSqlValue(statement.ColumnInt64(3)), | 
|  | TimeFromSqlValue(statement.ColumnInt64(4))); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::DeleteHostQuota( | 
|  | const std::string& host, StorageType type) { | 
|  | if (!LazyOpen(false)) | 
|  | return false; | 
|  |  | 
|  | const char* kSql = | 
|  | "DELETE FROM HostQuotaTable" | 
|  | " WHERE host = ? AND type = ?"; | 
|  |  | 
|  | sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | 
|  | statement.BindString(0, host); | 
|  | statement.BindInt(1, static_cast<int>(type)); | 
|  |  | 
|  | if (!statement.Run()) | 
|  | return false; | 
|  |  | 
|  | ScheduleCommit(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::DeleteOriginInfo(const url::Origin& origin, | 
|  | StorageType type) { | 
|  | if (!LazyOpen(false)) | 
|  | return false; | 
|  |  | 
|  | const char* kSql = | 
|  | "DELETE FROM OriginInfoTable" | 
|  | " WHERE origin = ? AND type = ?"; | 
|  |  | 
|  | sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | 
|  | statement.BindString(0, origin.GetURL().spec()); | 
|  | statement.BindInt(1, static_cast<int>(type)); | 
|  |  | 
|  | if (!statement.Run()) | 
|  | return false; | 
|  |  | 
|  | ScheduleCommit(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::GetQuotaConfigValue(const char* key, int64_t* value) { | 
|  | if (!LazyOpen(false)) | 
|  | return false; | 
|  | DCHECK(VerifyValidQuotaConfig(key)); | 
|  | return meta_table_->GetValue(key, value); | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::SetQuotaConfigValue(const char* key, int64_t value) { | 
|  | if (!LazyOpen(true)) | 
|  | return false; | 
|  | DCHECK(VerifyValidQuotaConfig(key)); | 
|  | return meta_table_->SetValue(key, value); | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::GetLRUOrigin(StorageType type, | 
|  | const std::set<url::Origin>& exceptions, | 
|  | SpecialStoragePolicy* special_storage_policy, | 
|  | base::Optional<url::Origin>* origin) { | 
|  | DCHECK(origin); | 
|  | if (!LazyOpen(false)) | 
|  | return false; | 
|  |  | 
|  | static const char kSql[] = | 
|  | "SELECT origin FROM OriginInfoTable" | 
|  | " WHERE type = ?" | 
|  | " ORDER BY last_access_time ASC"; | 
|  |  | 
|  | sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | 
|  | statement.BindInt(0, static_cast<int>(type)); | 
|  |  | 
|  | while (statement.Step()) { | 
|  | url::Origin read_origin = | 
|  | url::Origin::Create(GURL(statement.ColumnString(0))); | 
|  | if (base::ContainsKey(exceptions, read_origin)) | 
|  | continue; | 
|  |  | 
|  | if (special_storage_policy && | 
|  | (special_storage_policy->IsStorageDurable(read_origin.GetURL()) || | 
|  | special_storage_policy->IsStorageUnlimited(read_origin.GetURL()))) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | *origin = read_origin; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | origin->reset(); | 
|  | return statement.Succeeded(); | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::GetOriginsModifiedSince(StorageType type, | 
|  | std::set<url::Origin>* origins, | 
|  | base::Time modified_since) { | 
|  | DCHECK(origins); | 
|  | if (!LazyOpen(false)) | 
|  | return false; | 
|  |  | 
|  | const char* kSql = "SELECT origin FROM OriginInfoTable" | 
|  | " WHERE type = ? AND last_modified_time >= ?"; | 
|  |  | 
|  | sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | 
|  | statement.BindInt(0, static_cast<int>(type)); | 
|  | statement.BindInt64(1, TimeToSqlValue(modified_since)); | 
|  |  | 
|  | origins->clear(); | 
|  | while (statement.Step()) | 
|  | origins->insert(url::Origin::Create(GURL(statement.ColumnString(0)))); | 
|  |  | 
|  | return statement.Succeeded(); | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::IsOriginDatabaseBootstrapped() { | 
|  | if (!LazyOpen(true)) | 
|  | return false; | 
|  |  | 
|  | int flag = 0; | 
|  | return meta_table_->GetValue(kIsOriginTableBootstrapped, &flag) && flag; | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::SetOriginDatabaseBootstrapped(bool bootstrap_flag) { | 
|  | if (!LazyOpen(true)) | 
|  | return false; | 
|  |  | 
|  | return meta_table_->SetValue(kIsOriginTableBootstrapped, bootstrap_flag); | 
|  | } | 
|  |  | 
|  | void QuotaDatabase::Commit() { | 
|  | if (!db_) | 
|  | return; | 
|  |  | 
|  | if (timer_.IsRunning()) | 
|  | timer_.Stop(); | 
|  |  | 
|  | DCHECK_EQ(1, db_->transaction_nesting()); | 
|  | db_->CommitTransaction(); | 
|  | DCHECK_EQ(0, db_->transaction_nesting()); | 
|  | db_->BeginTransaction(); | 
|  | DCHECK_EQ(1, db_->transaction_nesting()); | 
|  | } | 
|  |  | 
|  | void QuotaDatabase::ScheduleCommit() { | 
|  | if (timer_.IsRunning()) | 
|  | return; | 
|  | timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kCommitIntervalMs), | 
|  | this, &QuotaDatabase::Commit); | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::LazyOpen(bool create_if_needed) { | 
|  | if (db_) | 
|  | return true; | 
|  |  | 
|  | // If we tried and failed once, don't try again in the same session | 
|  | // to avoid creating an incoherent mess on disk. | 
|  | if (is_disabled_) | 
|  | return false; | 
|  |  | 
|  | bool in_memory_only = db_file_path_.empty(); | 
|  | if (!create_if_needed && | 
|  | (in_memory_only || !base::PathExists(db_file_path_))) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | db_.reset(new sql::Database); | 
|  | meta_table_.reset(new sql::MetaTable); | 
|  |  | 
|  | db_->set_histogram_tag("Quota"); | 
|  |  | 
|  | bool opened = false; | 
|  | if (in_memory_only) { | 
|  | opened = db_->OpenInMemory(); | 
|  | } else if (!base::CreateDirectory(db_file_path_.DirName())) { | 
|  | LOG(ERROR) << "Failed to create quota database directory."; | 
|  | } else { | 
|  | opened = db_->Open(db_file_path_); | 
|  | if (opened) | 
|  | db_->Preload(); | 
|  | } | 
|  |  | 
|  | if (!opened || !EnsureDatabaseVersion()) { | 
|  | LOG(ERROR) << "Could not open the quota database, resetting."; | 
|  | if (!ResetSchema()) { | 
|  | LOG(ERROR) << "Failed to reset the quota database."; | 
|  | is_disabled_ = true; | 
|  | db_.reset(); | 
|  | meta_table_.reset(); | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Start a long-running transaction. | 
|  | db_->BeginTransaction(); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::EnsureDatabaseVersion() { | 
|  | static const size_t kTableCount = base::size(kTables); | 
|  | static const size_t kIndexCount = base::size(kIndexes); | 
|  | if (!sql::MetaTable::DoesTableExist(db_.get())) | 
|  | return CreateSchema(db_.get(), meta_table_.get(), | 
|  | kQuotaDatabaseCurrentSchemaVersion, | 
|  | kQuotaDatabaseCompatibleVersion, kTables, kTableCount, | 
|  | kIndexes, kIndexCount); | 
|  |  | 
|  | if (!meta_table_->Init(db_.get(), kQuotaDatabaseCurrentSchemaVersion, | 
|  | kQuotaDatabaseCompatibleVersion)) | 
|  | return false; | 
|  |  | 
|  | if (meta_table_->GetCompatibleVersionNumber() > | 
|  | kQuotaDatabaseCurrentSchemaVersion) { | 
|  | LOG(WARNING) << "Quota database is too new."; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (meta_table_->GetVersionNumber() < kQuotaDatabaseCurrentSchemaVersion) { | 
|  | if (!UpgradeSchema(meta_table_->GetVersionNumber())) | 
|  | return ResetSchema(); | 
|  | } | 
|  |  | 
|  | #ifndef NDEBUG | 
|  | DCHECK(sql::MetaTable::DoesTableExist(db_.get())); | 
|  | for (size_t i = 0; i < kTableCount; ++i) { | 
|  | DCHECK(db_->DoesTableExist(kTables[i].table_name)); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // static | 
|  | bool QuotaDatabase::CreateSchema(sql::Database* database, | 
|  | sql::MetaTable* meta_table, | 
|  | int schema_version, | 
|  | int compatible_version, | 
|  | const TableSchema* tables, | 
|  | size_t tables_size, | 
|  | const IndexSchema* indexes, | 
|  | size_t indexes_size) { | 
|  | // TODO(kinuko): Factor out the common code to create databases. | 
|  | sql::Transaction transaction(database); | 
|  | if (!transaction.Begin()) | 
|  | return false; | 
|  |  | 
|  | if (!meta_table->Init(database, schema_version, compatible_version)) | 
|  | return false; | 
|  |  | 
|  | for (size_t i = 0; i < tables_size; ++i) { | 
|  | std::string sql("CREATE TABLE "); | 
|  | sql += tables[i].table_name; | 
|  | sql += tables[i].columns; | 
|  | if (!database->Execute(sql.c_str())) { | 
|  | VLOG(1) << "Failed to execute " << sql; | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | for (size_t i = 0; i < indexes_size; ++i) { | 
|  | std::string sql; | 
|  | if (indexes[i].unique) | 
|  | sql += "CREATE UNIQUE INDEX "; | 
|  | else | 
|  | sql += "CREATE INDEX "; | 
|  | sql += indexes[i].index_name; | 
|  | sql += " ON "; | 
|  | sql += indexes[i].table_name; | 
|  | sql += indexes[i].columns; | 
|  | if (!database->Execute(sql.c_str())) { | 
|  | VLOG(1) << "Failed to execute " << sql; | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | return transaction.Commit(); | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::ResetSchema() { | 
|  | DCHECK(!db_file_path_.empty()); | 
|  | DCHECK(base::PathExists(db_file_path_)); | 
|  | DCHECK(!db_ || !db_->transaction_nesting()); | 
|  | VLOG(1) << "Deleting existing quota data and starting over."; | 
|  |  | 
|  | db_.reset(); | 
|  | meta_table_.reset(); | 
|  |  | 
|  | if (!sql::Database::Delete(db_file_path_)) | 
|  | return false; | 
|  |  | 
|  | // So we can't go recursive. | 
|  | if (is_recreating_) | 
|  | return false; | 
|  |  | 
|  | base::AutoReset<bool> auto_reset(&is_recreating_, true); | 
|  | return LazyOpen(true); | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::UpgradeSchema(int current_version) { | 
|  | DCHECK_EQ(0, db_->transaction_nesting()); | 
|  |  | 
|  | if (current_version == 2) { | 
|  | QuotaTableImporter importer; | 
|  | if (!DumpQuotaTable(base::BindRepeating(&QuotaTableImporter::Append, | 
|  | base::Unretained(&importer)))) { | 
|  | return false; | 
|  | } | 
|  | ResetSchema(); | 
|  |  | 
|  | sql::Transaction transaction(db_.get()); | 
|  | if (!transaction.Begin()) | 
|  | return false; | 
|  | for (const auto& entry : importer.entries) { | 
|  | if (!InsertOrReplaceHostQuota(entry.host, entry.type, entry.quota)) | 
|  | return false; | 
|  | } | 
|  | return transaction.Commit(); | 
|  | } else if (current_version < 5) { | 
|  | sql::Transaction transaction(db_.get()); | 
|  | if (!transaction.Begin()) | 
|  | return false; | 
|  |  | 
|  | const QuotaDatabase::TableSchema& eviction_table_schema = kTables[2]; | 
|  | DCHECK_EQ(strcmp(kEvictionInfoTable, eviction_table_schema.table_name), 0); | 
|  |  | 
|  | std::string sql("CREATE TABLE "); | 
|  | sql += eviction_table_schema.table_name; | 
|  | sql += eviction_table_schema.columns; | 
|  | if (!db_->Execute(sql.c_str())) { | 
|  | VLOG(1) << "Failed to execute " << sql; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | meta_table_->SetVersionNumber(5); | 
|  | return transaction.Commit(); | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::InsertOrReplaceHostQuota(const std::string& host, | 
|  | StorageType type, | 
|  | int64_t quota) { | 
|  | DCHECK(db_.get()); | 
|  | const char* kSql = | 
|  | "INSERT OR REPLACE INTO HostQuotaTable" | 
|  | " (quota, host, type)" | 
|  | " VALUES (?, ?, ?)"; | 
|  | sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | 
|  | statement.BindInt64(0, quota); | 
|  | statement.BindString(1, host); | 
|  | statement.BindInt(2, static_cast<int>(type)); | 
|  | return statement.Run(); | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::DumpQuotaTable(const QuotaTableCallback& callback) { | 
|  | if (!LazyOpen(true)) | 
|  | return false; | 
|  |  | 
|  | const char* kSql = "SELECT * FROM HostQuotaTable"; | 
|  | sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | 
|  |  | 
|  | while (statement.Step()) { | 
|  | QuotaTableEntry entry = QuotaTableEntry( | 
|  | statement.ColumnString(0), | 
|  | static_cast<StorageType>(statement.ColumnInt(1)), | 
|  | statement.ColumnInt64(2)); | 
|  |  | 
|  | if (!callback.Run(entry)) | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return statement.Succeeded(); | 
|  | } | 
|  |  | 
|  | bool QuotaDatabase::DumpOriginInfoTable( | 
|  | const OriginInfoTableCallback& callback) { | 
|  |  | 
|  | if (!LazyOpen(true)) | 
|  | return false; | 
|  |  | 
|  | const char* kSql = "SELECT * FROM OriginInfoTable"; | 
|  | sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | 
|  |  | 
|  | while (statement.Step()) { | 
|  | OriginInfoTableEntry entry( | 
|  | url::Origin::Create(GURL(statement.ColumnString(0))), | 
|  | static_cast<StorageType>(statement.ColumnInt(1)), | 
|  | statement.ColumnInt(2), TimeFromSqlValue(statement.ColumnInt64(3)), | 
|  | TimeFromSqlValue(statement.ColumnInt64(4))); | 
|  |  | 
|  | if (!callback.Run(entry)) | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return statement.Succeeded(); | 
|  | } | 
|  |  | 
|  | // static | 
|  | base::Time QuotaDatabase::TimeFromSqlValue(int64_t time) { | 
|  | return base::Time::FromDeltaSinceWindowsEpoch( | 
|  | base::TimeDelta::FromMicroseconds(time)); | 
|  | } | 
|  |  | 
|  | // static | 
|  | int64_t QuotaDatabase::TimeToSqlValue(const base::Time& time) { | 
|  | return time.ToDeltaSinceWindowsEpoch().InMicroseconds(); | 
|  | } | 
|  |  | 
|  | bool operator<(const QuotaDatabase::QuotaTableEntry& lhs, | 
|  | const QuotaDatabase::QuotaTableEntry& rhs) { | 
|  | return std::tie(lhs.host, lhs.type, lhs.quota) < | 
|  | std::tie(rhs.host, rhs.type, rhs.quota); | 
|  | } | 
|  |  | 
|  | bool operator<(const QuotaDatabase::OriginInfoTableEntry& lhs, | 
|  | const QuotaDatabase::OriginInfoTableEntry& rhs) { | 
|  | return std::tie(lhs.origin, lhs.type, lhs.used_count, lhs.last_access_time) < | 
|  | std::tie(rhs.origin, rhs.type, rhs.used_count, rhs.last_access_time); | 
|  | } | 
|  |  | 
|  | }  // namespace storage |