// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "storage/browser/database/database_tracker.h"

#include <stdint.h>

#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/user_metrics.h"
#include "base/observer_list.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/types/pass_key.h"
#include "components/services/storage/public/cpp/quota_client_callback_wrapper.h"
#include "components/services/storage/public/mojom/quota_client.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "net/base/net_errors.h"
#include "sql/database.h"
#include "sql/meta_table.h"
#include "sql/transaction.h"
#include "storage/browser/database/database_quota_client.h"
#include "storage/browser/database/database_util.h"
#include "storage/browser/database/databases_table.h"
#include "storage/browser/quota/quota_client_type.h"
#include "storage/browser/quota/quota_manager_proxy.h"
#include "storage/browser/quota/special_storage_policy.h"
#include "storage/common/database/database_identifier.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
#include "third_party/sqlite/sqlite3.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace storage {

const base::FilePath::CharType kDatabaseDirectoryName[] =
    FILE_PATH_LITERAL("databases");
const base::FilePath::CharType kIncognitoDatabaseDirectoryName[] =
    FILE_PATH_LITERAL("databases-off-the-record");
const base::FilePath::CharType kTrackerDatabaseFileName[] =
    FILE_PATH_LITERAL("Databases.db");
static const int kDatabaseTrackerCurrentSchemaVersion = 2;
static const int kDatabaseTrackerCompatibleVersion = 1;

const base::FilePath::CharType kTemporaryDirectoryPrefix[] =
    FILE_PATH_LITERAL("DeleteMe");
const base::FilePath::CharType kTemporaryDirectoryPattern[] =
    FILE_PATH_LITERAL("DeleteMe*");

#if defined(OHOS_WEBSTORAGE)
static const std::u16string kBaseDatabaseDir =
    base::UTF8ToUTF16("/data/storage/el2/base/");
static const std::u16string kSuffixStr = base::UTF8ToUTF16(".db");
#endif  // defined(OHOS_WEBSTORAGE)

OriginInfo::OriginInfo() : total_size_(0) {}

OriginInfo::OriginInfo(const OriginInfo& origin_info) = default;

OriginInfo::~OriginInfo() = default;

void OriginInfo::GetAllDatabaseNames(
    std::vector<std::u16string>* databases) const {
  for (const auto& name_and_size : database_sizes_)
    databases->push_back(name_and_size.first);
}

int64_t OriginInfo::GetDatabaseSize(const std::u16string& database_name) const {
  auto it = database_sizes_.find(database_name);
  if (it != database_sizes_.end())
    return it->second;
  return 0;
}

OriginInfo::OriginInfo(const std::string& origin_identifier, int64_t total_size)
    : origin_identifier_(origin_identifier), total_size_(total_size) {}

scoped_refptr<DatabaseTracker> DatabaseTracker::Create(
    const base::FilePath& profile_path,
    bool is_incognito,
    scoped_refptr<SpecialStoragePolicy> special_storage_policy,
    scoped_refptr<QuotaManagerProxy> quota_manager_proxy) {
  auto database_tracker = base::MakeRefCounted<DatabaseTracker>(
      profile_path, is_incognito, std::move(special_storage_policy),
      std::move(quota_manager_proxy), base::PassKey<DatabaseTracker>());
  database_tracker->RegisterQuotaClient();
  return database_tracker;
}

DatabaseTracker::DatabaseTracker(
    const base::FilePath& profile_path,
    bool is_incognito,
    scoped_refptr<SpecialStoragePolicy> special_storage_policy,
    scoped_refptr<QuotaManagerProxy> quota_manager_proxy,
    base::PassKey<DatabaseTracker>)
    : is_incognito_(is_incognito),
      profile_path_(profile_path),
      db_dir_(is_incognito_
                  ? profile_path_.Append(kIncognitoDatabaseDirectoryName)
                  : profile_path_.Append(kDatabaseDirectoryName)),
      db_(std::make_unique<sql::Database>(sql::DatabaseOptions{
          .exclusive_locking = true,
          .page_size = 4096,
          .cache_size = 500,
      })),
      special_storage_policy_(std::move(special_storage_policy)),
      quota_manager_proxy_(std::move(quota_manager_proxy)),
      task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
          {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
           // SKIP_ON_SHUTDOWN cannot be used because Shutdown() needs to run
           // before the destructor, and Shutdown() is ran by PostTask()ing to
           // this sequence. See https://crbug.com/1220191.
           //
           // We may be able to switch to SKIP_ON_SHUTDOWN if we get
           // DatabaseTracker to be used entirely on the database sequence, so
           // the destructor can absorb the logic that is currently in
           // Shutdown().
           base::TaskShutdownBehavior::BLOCK_SHUTDOWN})),
      quota_client_(std::make_unique<DatabaseQuotaClient>(*this)),
      quota_client_wrapper_(
          std::make_unique<QuotaClientCallbackWrapper>(quota_client_.get())),
      quota_client_receiver_(quota_client_wrapper_.get()) {}

DatabaseTracker::~DatabaseTracker() {
  // base::RefCountedThreadSafe inserts the appropriate barriers to ensure
  // member access in the destructor does not introduce data races.
  DCHECK(dbs_to_be_deleted_.empty());
  DCHECK(deletion_callbacks_.empty());

  DCHECK(!quota_client_);
  DCHECK(!quota_client_wrapper_);
  DCHECK(!quota_client_receiver_.is_bound());
}

void DatabaseTracker::RegisterQuotaClient() {
  if (!quota_manager_proxy_)
    return;

  // QuotaManagerProxy::RegisterClient() must be called synchronously during
  // DatabaseTracker creation until crbug.com/1182630 is fixed.
  mojo::PendingRemote<storage::mojom::QuotaClient> quota_client_remote;
  mojo::PendingReceiver<storage::mojom::QuotaClient> quota_client_receiver =
      quota_client_remote.InitWithNewPipeAndPassReceiver();
  quota_manager_proxy_->RegisterClient(std::move(quota_client_remote),
                                       storage::QuotaClientType::kDatabase,
                                       {blink::mojom::StorageType::kTemporary});

  task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(
          [](scoped_refptr<DatabaseTracker> self,
             mojo::PendingReceiver<storage::mojom::QuotaClient> receiver) {
            self->quota_client_receiver_.Bind(std::move(receiver));
          },
          base::RetainedRef(this), std::move(quota_client_receiver)));
}

void DatabaseTracker::DatabaseOpened(const std::string& origin_identifier,
                                     const std::u16string& database_name,
                                     const std::u16string& database_description,
                                     int64_t* database_size) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  if (shutting_down_ || !LazyInit()) {
    *database_size = 0;
    return;
  }

  if (quota_manager_proxy_.get())
    quota_manager_proxy_->NotifyBucketAccessed(
        BucketLocator::ForDefaultBucket(blink::StorageKey::CreateFirstParty(
            GetOriginFromIdentifier(origin_identifier))),
        base::Time::Now());

  InsertOrUpdateDatabaseDetails(origin_identifier, database_name,
                                database_description);
  if (database_connections_.AddConnection(origin_identifier, database_name)) {
    *database_size = SeedOpenDatabaseInfo(origin_identifier, database_name,
                                          database_description);
    return;
  }
  *database_size = UpdateOpenDatabaseInfoAndNotify(
      origin_identifier, database_name, &database_description);
}

void DatabaseTracker::DatabaseModified(const std::string& origin_identifier,
                                       const std::u16string& database_name) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  if (!LazyInit())
    return;
  UpdateOpenDatabaseSizeAndNotify(origin_identifier, database_name);
}

void DatabaseTracker::DatabaseClosed(const std::string& origin_identifier,
                                     const std::u16string& database_name) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  if (database_connections_.IsEmpty()) {
    DCHECK(!is_initialized_);
    return;
  }

  // We call NotifiyStorageAccessed when a db is opened and also when
  // closed because we don't call it for read while open.
  if (quota_manager_proxy_.get())
    quota_manager_proxy_->NotifyBucketAccessed(
        BucketLocator::ForDefaultBucket(blink::StorageKey::CreateFirstParty(
            GetOriginFromIdentifier(origin_identifier))),
        base::Time::Now());

  UpdateOpenDatabaseSizeAndNotify(origin_identifier, database_name);
  if (database_connections_.RemoveConnection(origin_identifier, database_name))
    DeleteDatabaseIfNeeded(origin_identifier, database_name);
}

void DatabaseTracker::HandleSqliteError(const std::string& origin_identifier,
                                        const std::u16string& database_name,
                                        int error) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  // We only handle errors that indicate corruption and we
  // do so with a heavy hand, we delete it. Any renderers/workers
  // with this database open will receive a message to close it
  // immediately, once all have closed, the files will be deleted.
  // In the interim, all attempts to open a new connection to that
  // database will fail.
  // Note: the client-side filters out all but these two errors as
  // a small optimization, see WebDatabaseObserverImpl::HandleSqliteError.
  if (error == SQLITE_CORRUPT || error == SQLITE_NOTADB) {
    DeleteDatabase(origin_identifier, database_name, base::DoNothing());
  }
}

void DatabaseTracker::CloseDatabases(const DatabaseConnections& connections) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  if (database_connections_.IsEmpty()) {
    DCHECK(!is_initialized_ || connections.IsEmpty());
    return;
  }

  // When being closed by this route, there's a chance that
  // the tracker missed some DatabaseModified calls. This method is used
  // when a renderer crashes to cleanup its open resources.
  // We need to examine what we have in connections for the
  // size of each open databases and notify any differences between the
  // actual file sizes now.
  for (auto& pair : connections.ListConnections())
    UpdateOpenDatabaseSizeAndNotify(pair.first, pair.second);

  for (auto& pair : database_connections_.RemoveConnections(connections))
    DeleteDatabaseIfNeeded(pair.first, pair.second);
}

void DatabaseTracker::DeleteDatabaseIfNeeded(
    const std::string& origin_identifier,
    const std::u16string& database_name) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  DCHECK(!database_connections_.IsDatabaseOpened(origin_identifier,
                                                 database_name));
  if (IsDatabaseScheduledForDeletion(origin_identifier, database_name)) {
    DeleteClosedDatabase(origin_identifier, database_name);
    dbs_to_be_deleted_[origin_identifier].erase(database_name);
    if (dbs_to_be_deleted_[origin_identifier].empty())
      dbs_to_be_deleted_.erase(origin_identifier);

    auto callback = deletion_callbacks_.begin();
    while (callback != deletion_callbacks_.end()) {
      auto found_origin = callback->second.find(origin_identifier);
      if (found_origin != callback->second.end()) {
        std::set<std::u16string>& databases = found_origin->second;
        databases.erase(database_name);
        if (databases.empty()) {
          callback->second.erase(found_origin);
          if (callback->second.empty()) {
            std::move(callback->first).Run(net::OK);
            callback = deletion_callbacks_.erase(callback);
            continue;
          }
        }
      }

      ++callback;
    }
  }
}

void DatabaseTracker::AddObserver(Observer* observer) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  observers_.AddObserver(observer);
}

void DatabaseTracker::RemoveObserver(Observer* observer) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  // When we remove a listener, we do not know which cached information
  // is still needed and which information can be discarded. So we just
  // clear all caches and re-populate them as needed.
  observers_.RemoveObserver(observer);
  ClearAllCachedOriginInfo();
}

void DatabaseTracker::CloseTrackerDatabaseAndClearCaches() {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  ClearAllCachedOriginInfo();

  if (!is_incognito_) {
    meta_table_.reset(nullptr);
    databases_table_.reset(nullptr);
    db_->Close();
    is_initialized_ = false;
  }
}

base::FilePath DatabaseTracker::GetOriginDirectory(
    const std::string& origin_identifier) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());

  std::u16string origin_directory;

  if (!is_incognito_) {
    origin_directory = base::UTF8ToUTF16(origin_identifier);
  } else {
    auto it = incognito_origin_directories_.find(origin_identifier);
    if (it != incognito_origin_directories_.end()) {
      origin_directory = it->second;
    } else {
      origin_directory =
          base::NumberToString16(incognito_origin_directories_generator_++);
      incognito_origin_directories_[origin_identifier] = origin_directory;
    }
  }

#if defined(OHOS_WEBSTORAGE)
  return base::FilePath::FromUTF16Unsafe(kBaseDatabaseDir + origin_directory);
#else
  return db_dir_.Append(base::FilePath::FromUTF16Unsafe(origin_directory));
#endif  // defined(OHOS_WEBSTORAGE)
}

#if defined(OHOS_WEBSTORAGE)
base::FilePath DatabaseTracker::GetFullDBFilePath(
    const std::string& origin_identifier,
    const std::u16string& database_name,
    bool suffix) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  DCHECK(!origin_identifier.empty());
  if (!LazyInit())
    return base::FilePath();

  int64_t id =
      databases_table_->GetDatabaseID(origin_identifier, database_name);
  if (id < 0)
    return base::FilePath();

  return GetOriginDirectory(origin_identifier)
      .AppendASCII((suffix ? base::UTF16ToASCII(database_name + kSuffixStr)
                           : base::UTF16ToASCII(database_name)));
}
#else
base::FilePath DatabaseTracker::GetFullDBFilePath(
    const std::string& origin_identifier,
    const std::u16string& database_name) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  DCHECK(!origin_identifier.empty());
  if (!LazyInit())
    return base::FilePath();

  int64_t id =
      databases_table_->GetDatabaseID(origin_identifier, database_name);
  if (id < 0)
    return base::FilePath();

  return GetOriginDirectory(origin_identifier)
      .AppendASCII(base::NumberToString(id));
}
#endif  // defined(OHOS_WEBSTORAGE)

bool DatabaseTracker::GetOriginInfo(const std::string& origin_identifier,
                                    OriginInfo* info) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  DCHECK(info);
  CachedOriginInfo* cached_info = GetCachedOriginInfo(origin_identifier);
  if (!cached_info)
    return false;
  *info = OriginInfo(*cached_info);
  return true;
}

bool DatabaseTracker::GetAllOriginIdentifiers(
    std::vector<std::string>* origin_identifiers) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  DCHECK(origin_identifiers);
  DCHECK(origin_identifiers->empty());
  if (!LazyInit())
    return false;
  return databases_table_->GetAllOriginIdentifiers(origin_identifiers);
}

bool DatabaseTracker::GetAllOriginsInfo(std::vector<OriginInfo>* origins_info) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  DCHECK(origins_info);
  DCHECK(origins_info->empty());

  std::vector<std::string> origins;
  if (!GetAllOriginIdentifiers(&origins))
    return false;

  for (const auto& origin : origins) {
    CachedOriginInfo* origin_info = GetCachedOriginInfo(origin);
    if (!origin_info) {
      // Restore 'origins_info' to its initial state.
      origins_info->clear();
      return false;
    }
    origins_info->push_back(OriginInfo(*origin_info));
  }

  return true;
}

bool DatabaseTracker::DeleteClosedDatabase(
    const std::string& origin_identifier,
    const std::u16string& database_name) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  if (!LazyInit())
    return false;

  // Check if the database is opened by any renderer.
  if (database_connections_.IsDatabaseOpened(origin_identifier, database_name))
    return false;

  int64_t db_file_size = quota_manager_proxy_.get()
                             ? GetDBFileSize(origin_identifier, database_name)
                             : 0;

  // Try to delete the file on the hard drive.
  base::FilePath db_file = GetFullDBFilePath(origin_identifier, database_name);
  if (!sql::Database::Delete(db_file))
    return false;

  if (quota_manager_proxy_.get() && db_file_size) {
    quota_manager_proxy_->NotifyBucketModified(
        QuotaClientType::kDatabase,
        BucketLocator::ForDefaultBucket(blink::StorageKey::CreateFirstParty(
            GetOriginFromIdentifier(origin_identifier))),
        -db_file_size, base::Time::Now(),
        base::SequencedTaskRunner::GetCurrentDefault(), base::DoNothing());
  }

  // Clean up the main database and invalidate the cached record.
  databases_table_->DeleteDatabaseDetails(origin_identifier, database_name);
  origins_info_map_.erase(origin_identifier);

  std::vector<DatabaseDetails> details;
  if (databases_table_->GetAllDatabaseDetailsForOriginIdentifier(
          origin_identifier, &details) &&
      details.empty()) {
    // Try to delete the origin in case this was the last database.
    DeleteOrigin(origin_identifier, false);
  }
  return true;
}

bool DatabaseTracker::DeleteOrigin(const std::string& origin_identifier,
                                   bool force) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  if (!LazyInit())
    return false;

  // Check if any database in this origin is opened by any renderer.
  if (database_connections_.IsOriginUsed(origin_identifier) && !force)
    return false;

  int64_t deleted_size = 0;
  if (quota_manager_proxy_.get()) {
    CachedOriginInfo* origin_info = GetCachedOriginInfo(origin_identifier);
    if (origin_info)
      deleted_size = origin_info->TotalSize();
  }

  origins_info_map_.erase(origin_identifier);
  base::FilePath origin_dir = GetOriginDirectory(origin_identifier);

  // Create a temporary directory to move possibly still existing databases to,
  // as we can't delete the origin directory on windows if it contains opened
  // files.
  base::FilePath new_origin_dir;
  base::CreateTemporaryDirInDir(db_dir_, kTemporaryDirectoryPrefix,
                                &new_origin_dir);
  base::FileEnumerator databases(origin_dir, false,
                                 base::FileEnumerator::FILES);
  for (base::FilePath database = databases.Next(); !database.empty();
       database = databases.Next()) {
    base::FilePath new_file = new_origin_dir.Append(database.BaseName());
    base::Move(database, new_file);
  }
  base::DeletePathRecursively(origin_dir);
  base::DeletePathRecursively(new_origin_dir);  // Might fail on windows.

  if (is_incognito_) {
    incognito_origin_directories_.erase(origin_identifier);

    // TODO(jsbell): Consider alternate data structures to avoid this
    // linear scan.
    for (auto it = incognito_file_handles_.begin();
         it != incognito_file_handles_.end();) {
      std::string id;
      if (DatabaseUtil::CrackVfsFileName(it->first, &id, nullptr, nullptr) &&
          id == origin_identifier) {
        delete it->second;
        it = incognito_file_handles_.erase(it);
      } else {
        ++it;
      }
    }
  }

  databases_table_->DeleteOriginIdentifier(origin_identifier);

  if (quota_manager_proxy_.get() && deleted_size) {
    quota_manager_proxy_->NotifyBucketModified(
        QuotaClientType::kDatabase,
        BucketLocator::ForDefaultBucket(blink::StorageKey::CreateFirstParty(
            GetOriginFromIdentifier(origin_identifier))),
        -deleted_size, base::Time::Now(),
        base::SequencedTaskRunner::GetCurrentDefault(), base::DoNothing());
  }

  return true;
}

bool DatabaseTracker::IsDatabaseScheduledForDeletion(
    const std::string& origin_identifier,
    const std::u16string& database_name) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  auto it = dbs_to_be_deleted_.find(origin_identifier);
  if (it == dbs_to_be_deleted_.end())
    return false;

  const std::set<std::u16string>& databases = it->second;
  return (databases.find(database_name) != databases.end());
}

bool DatabaseTracker::LazyInit() {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  if (!is_initialized_ && !shutting_down_) {
    DCHECK(!db_->is_open());
    DCHECK(!databases_table_.get());
    DCHECK(!meta_table_.get());

    // If there are left-over directories from failed deletion attempts, clean
    // them up.
    if (base::DirectoryExists(db_dir_)) {
      base::FileEnumerator directories(db_dir_, false,
                                       base::FileEnumerator::DIRECTORIES,
                                       kTemporaryDirectoryPattern);
      for (base::FilePath directory = directories.Next(); !directory.empty();
           directory = directories.Next()) {
        base::DeletePathRecursively(directory);
      }
    }

    db_->set_histogram_tag("DatabaseTracker");

    // If the tracker database exists, but it's corrupt or doesn't
    // have a meta table, delete the database directory.
    const base::FilePath kTrackerDatabaseFullPath =
        db_dir_.Append(base::FilePath(kTrackerDatabaseFileName));
    if (base::DirectoryExists(db_dir_) &&
        base::PathExists(kTrackerDatabaseFullPath) &&
        (!db_->Open(kTrackerDatabaseFullPath) ||
         !sql::MetaTable::DoesTableExist(db_.get()))) {
      db_->Close();
      if (!base::DeletePathRecursively(db_dir_))
        return false;
    }

    databases_table_ = std::make_unique<DatabasesTable>(db_.get());
    meta_table_ = std::make_unique<sql::MetaTable>();

    is_initialized_ = (is_incognito_ ? true : base::CreateDirectory(db_dir_)) &&
                      (db_->is_open() ||
                       (is_incognito_ ? db_->OpenInMemory()
                                      : db_->Open(kTrackerDatabaseFullPath))) &&
                      UpgradeToCurrentVersion();
    if (!is_initialized_) {
      databases_table_.reset(nullptr);
      meta_table_.reset(nullptr);
      db_->Close();
    }
  }
  return is_initialized_;
}

bool DatabaseTracker::UpgradeToCurrentVersion() {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  sql::Transaction transaction(db_.get());
  if (!transaction.Begin() ||
      !meta_table_->Init(db_.get(), kDatabaseTrackerCurrentSchemaVersion,
                         kDatabaseTrackerCompatibleVersion) ||
      (meta_table_->GetCompatibleVersionNumber() >
       kDatabaseTrackerCurrentSchemaVersion) ||
      !databases_table_->Init())
    return false;

  if (meta_table_->GetVersionNumber() < kDatabaseTrackerCurrentSchemaVersion &&
      !meta_table_->SetVersionNumber(kDatabaseTrackerCurrentSchemaVersion)) {
    return false;
  }

  return transaction.Commit();
}

void DatabaseTracker::InsertOrUpdateDatabaseDetails(
    const std::string& origin_identifier,
    const std::u16string& database_name,
    const std::u16string& database_description) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  DatabaseDetails details;
  if (!databases_table_->GetDatabaseDetails(origin_identifier, database_name,
                                            &details)) {
    details.origin_identifier = origin_identifier;
    details.database_name = database_name;
    details.description = database_description;
    databases_table_->InsertDatabaseDetails(details);
  } else if (details.description != database_description) {
    details.description = database_description;
    databases_table_->UpdateDatabaseDetails(details);
  }
}

void DatabaseTracker::ClearAllCachedOriginInfo() {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  origins_info_map_.clear();
}

DatabaseTracker::CachedOriginInfo* DatabaseTracker::MaybeGetCachedOriginInfo(
    const std::string& origin_identifier,
    bool create_if_needed) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  if (!LazyInit())
    return nullptr;

  // Populate the cache with data for this origin if needed.
  if (origins_info_map_.find(origin_identifier) == origins_info_map_.end()) {
    if (!create_if_needed)
      return nullptr;

    std::vector<DatabaseDetails> details;
    if (!databases_table_->GetAllDatabaseDetailsForOriginIdentifier(
            origin_identifier, &details)) {
      return nullptr;
    }

    CachedOriginInfo& origin_info = origins_info_map_[origin_identifier];
    origin_info.SetOriginIdentifier(origin_identifier);
    for (const auto& db : details) {
      int64_t db_file_size;
      if (database_connections_.IsDatabaseOpened(origin_identifier,
                                                 db.database_name)) {
        db_file_size = database_connections_.GetOpenDatabaseSize(
            origin_identifier, db.database_name);
      } else {
        db_file_size = GetDBFileSize(origin_identifier, db.database_name);
      }
      origin_info.SetDatabaseSize(db.database_name, db_file_size);

      base::FilePath path =
#if defined(OHOS_WEBSTORAGE)
          GetFullDBFilePath(origin_identifier, db.database_name, true);
#else
          GetFullDBFilePath(origin_identifier, db.database_name);
#endif  // defined(OHOS_WEBSTORAGE)
      base::File::Info file_info;
      // TODO(jsbell): Avoid duplicate base::GetFileInfo calls between this and
      // the GetDBFileSize() call above.
      if (base::GetFileInfo(path, &file_info)) {
        origin_info.UpdateLastModified(file_info.last_modified);
      }
    }
  }

  return &origins_info_map_[origin_identifier];
}

int64_t DatabaseTracker::GetDBFileSize(const std::string& origin_identifier,
                                       const std::u16string& database_name) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  base::FilePath db_file_name =
#if defined(OHOS_WEBSTORAGE)
      GetFullDBFilePath(origin_identifier, database_name, true);
#else
      GetFullDBFilePath(origin_identifier, database_name);
#endif  // defined(OHOS_WEBSTORAGE)
  int64_t db_file_size = 0;
  if (!base::GetFileSize(db_file_name, &db_file_size))
    db_file_size = 0;
  return db_file_size;
}

int64_t DatabaseTracker::SeedOpenDatabaseInfo(
    const std::string& origin_id,
    const std::u16string& name,
    const std::u16string& description) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  DCHECK(database_connections_.IsDatabaseOpened(origin_id, name));
  int64_t size = GetDBFileSize(origin_id, name);
  database_connections_.SetOpenDatabaseSize(origin_id, name, size);
  CachedOriginInfo* info = MaybeGetCachedOriginInfo(origin_id, false);
  if (info) {
    info->SetDatabaseSize(name, size);
  }
  return size;
}

int64_t DatabaseTracker::UpdateOpenDatabaseInfoAndNotify(
    const std::string& origin_id,
    const std::u16string& name,
    const std::u16string* opt_description) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  DCHECK(database_connections_.IsDatabaseOpened(origin_id, name));
  int64_t new_size = GetDBFileSize(origin_id, name);
  int64_t old_size = database_connections_.GetOpenDatabaseSize(origin_id, name);
  CachedOriginInfo* info = MaybeGetCachedOriginInfo(origin_id, false);
  if (old_size != new_size) {
    database_connections_.SetOpenDatabaseSize(origin_id, name, new_size);
    if (info)
      info->SetDatabaseSize(name, new_size);

    if (quota_manager_proxy_.get()) {
      quota_manager_proxy_->NotifyBucketModified(
          QuotaClientType::kDatabase,
          BucketLocator::ForDefaultBucket(blink::StorageKey::CreateFirstParty(
              GetOriginFromIdentifier(origin_id))),
          new_size - old_size, base::Time::Now(),
          base::SequencedTaskRunner::GetCurrentDefault(), base::DoNothing());
    }

    for (auto& observer : observers_)
      observer.OnDatabaseSizeChanged(origin_id, name, new_size);
  }
  return new_size;
}

void DatabaseTracker::ScheduleDatabaseForDeletion(
    const std::string& origin_identifier,
    const std::u16string& database_name) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  DCHECK(
      database_connections_.IsDatabaseOpened(origin_identifier, database_name));
  dbs_to_be_deleted_[origin_identifier].insert(database_name);
  for (auto& observer : observers_)
    observer.OnDatabaseScheduledForDeletion(origin_identifier, database_name);
}

void DatabaseTracker::ScheduleDatabasesForDeletion(
    const DatabaseSet& databases,
    net::CompletionOnceCallback callback) {
  DCHECK(!databases.empty());

  if (!callback.is_null())
    deletion_callbacks_.emplace_back(std::move(callback), databases);
  for (const auto& origin_dbs : databases) {
    for (const std::u16string& db : origin_dbs.second)
      ScheduleDatabaseForDeletion(origin_dbs.first, db);
  }
}

void DatabaseTracker::DeleteDatabase(const std::string& origin_identifier,
                                     const std::u16string& database_name,
                                     net::CompletionOnceCallback callback) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  DCHECK(!callback.is_null());
  if (!LazyInit()) {
    std::move(callback).Run(net::ERR_FAILED);
    return;
  }

  if (database_connections_.IsDatabaseOpened(origin_identifier,
                                             database_name)) {
    DatabaseSet set;
    set[origin_identifier].insert(database_name);
    deletion_callbacks_.emplace_back(std::move(callback), std::move(set));
    ScheduleDatabaseForDeletion(origin_identifier, database_name);
    return;
  }

  DeleteClosedDatabase(origin_identifier, database_name);
  std::move(callback).Run(net::OK);
}

void DatabaseTracker::DeleteDataModifiedSince(
    const base::Time& cutoff,
    net::CompletionOnceCallback callback) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  DCHECK(!callback.is_null());
  if (!LazyInit()) {
    std::move(callback).Run(net::ERR_FAILED);
    return;
  }

  std::vector<std::string> origins_identifiers;
  if (!databases_table_->GetAllOriginIdentifiers(&origins_identifiers)) {
    std::move(callback).Run(net::ERR_FAILED);
    return;
  }

  DatabaseSet to_be_deleted;
  int rv = net::OK;
  for (const auto& origin : origins_identifiers) {
    if (special_storage_policy_.get() &&
        special_storage_policy_->IsStorageProtected(
            GetOriginURLFromIdentifier(origin))) {
      continue;
    }

    std::vector<DatabaseDetails> details;
    if (!databases_table_->GetAllDatabaseDetailsForOriginIdentifier(origin,
                                                                    &details)) {
      rv = net::ERR_FAILED;
    }
    for (const DatabaseDetails& db : details) {
#if defined(OHOS_WEBSTORAGE)
      base::FilePath db_file =
          GetFullDBFilePath(origin, db.database_name, true);
#else
      base::FilePath db_file = GetFullDBFilePath(origin, db.database_name);
#endif  // defined(OHOS_WEBSTORAGE)
      base::File::Info file_info;
      base::GetFileInfo(db_file, &file_info);
      if (file_info.last_modified < cutoff)
        continue;

      // Check if the database is opened by any renderer.
      if (database_connections_.IsDatabaseOpened(origin, db.database_name)) {
        to_be_deleted[origin].insert(db.database_name);
      } else {
        DeleteClosedDatabase(origin, db.database_name);
      }
    }
  }

  if (rv != net::OK) {
    DCHECK_EQ(rv, net::ERR_FAILED);
    std::move(callback).Run(rv);
    return;
  }

  if (!to_be_deleted.empty()) {
    ScheduleDatabasesForDeletion(to_be_deleted, std::move(callback));
    return;
  }

  std::move(callback).Run(net::OK);
}

void DatabaseTracker::DeleteDataForOrigin(
    const url::Origin& origin,
    net::CompletionOnceCallback callback) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  DCHECK(!callback.is_null());
  if (!LazyInit()) {
    std::move(callback).Run(net::ERR_FAILED);
    return;
  }

  const std::string identifier = GetIdentifierFromOrigin(origin);

  std::vector<DatabaseDetails> details;
  if (!databases_table_->GetAllDatabaseDetailsForOriginIdentifier(identifier,
                                                                  &details)) {
    std::move(callback).Run(net::ERR_FAILED);
    return;
  }

  DatabaseSet to_be_deleted;
  for (const DatabaseDetails& db : details) {
    // Check if the database is opened by any renderer.
    if (database_connections_.IsDatabaseOpened(identifier, db.database_name)) {
      to_be_deleted[identifier].insert(db.database_name);
    } else {
      DeleteClosedDatabase(identifier, db.database_name);
    }
  }

  if (!to_be_deleted.empty()) {
    ScheduleDatabasesForDeletion(to_be_deleted, std::move(callback));
    return;
  }

  std::move(callback).Run(net::OK);
}

const base::File* DatabaseTracker::GetIncognitoFile(
    const std::u16string& vfs_file_name) const {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  DCHECK(is_incognito_);
  auto it = incognito_file_handles_.find(vfs_file_name);
  if (it != incognito_file_handles_.end())
    return it->second;

  return nullptr;
}

const base::File* DatabaseTracker::SaveIncognitoFile(
    const std::u16string& vfs_file_name,
    base::File file) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  DCHECK(is_incognito_);
  if (!file.IsValid())
    return nullptr;

  base::File* to_insert = new base::File(std::move(file));
  auto rv =
      incognito_file_handles_.insert(std::make_pair(vfs_file_name, to_insert));
  DCHECK(rv.second);
  base::RecordAction(base::UserMetricsAction("IncognitoWebSQL_Created"));
  return rv.first->second;
}

void DatabaseTracker::CloseIncognitoFileHandle(
    const std::u16string& vfs_file_name) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  DCHECK(is_incognito_);
  DCHECK(incognito_file_handles_.find(vfs_file_name) !=
         incognito_file_handles_.end());

  auto it = incognito_file_handles_.find(vfs_file_name);
  if (it != incognito_file_handles_.end()) {
    delete it->second;
    incognito_file_handles_.erase(it);
  }
  base::RecordAction(base::UserMetricsAction("IncognitoWebSQL_Released"));
}

bool DatabaseTracker::HasSavedIncognitoFileHandle(
    const std::u16string& vfs_file_name) const {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  return (incognito_file_handles_.find(vfs_file_name) !=
          incognito_file_handles_.end());
}

void DatabaseTracker::DeleteIncognitoDBDirectory() {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  is_initialized_ = false;

  for (auto& pair : incognito_file_handles_)
    delete pair.second;

  base::FilePath incognito_db_dir =
      profile_path_.Append(kIncognitoDatabaseDirectoryName);
  if (base::DirectoryExists(incognito_db_dir))
    base::DeletePathRecursively(incognito_db_dir);
}

void DatabaseTracker::ClearSessionOnlyOrigins() {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  bool has_session_only_databases =
      special_storage_policy_.get() &&
      special_storage_policy_->HasSessionOnlyOrigins();

  // Clearing only session-only databases, and there are none.
  if (!has_session_only_databases)
    return;

  if (!LazyInit())
    return;

  std::vector<std::string> origin_identifiers;
  GetAllOriginIdentifiers(&origin_identifiers);

  for (const auto& origin : origin_identifiers) {
    GURL origin_url = GetOriginURLFromIdentifier(origin);
    if (!special_storage_policy_->IsStorageSessionOnly(origin_url))
      continue;
    if (special_storage_policy_->IsStorageProtected(origin_url))
      continue;
    OriginInfo origin_info;
    std::vector<std::u16string> databases;
    GetOriginInfo(origin, &origin_info);
    origin_info.GetAllDatabaseNames(&databases);

    for (const auto& database : databases) {
      base::File file(
          GetFullDBFilePath(origin, database),
          base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_WIN_SHARE_DELETE |
              base::File::FLAG_DELETE_ON_CLOSE | base::File::FLAG_READ);
    }
    DeleteOrigin(origin, true);
  }
}

void DatabaseTracker::Shutdown() {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  if (shutting_down_) {
    NOTREACHED();
    return;
  }
  shutting_down_ = true;

  // The mojo receiver must be reset before the instance it calls into is
  // destroyed.
  quota_client_receiver_.reset();
  quota_client_wrapper_.reset();
  quota_client_.reset();

  if (is_incognito_)
    DeleteIncognitoDBDirectory();
  else if (!force_keep_session_state_)
    ClearSessionOnlyOrigins();
  CloseTrackerDatabaseAndClearCaches();
}

void DatabaseTracker::SetForceKeepSessionState() {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  force_keep_session_state_ = true;
}

}  // namespace storage