// Copyright (c) 2012 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 "content/browser/appcache/appcache_storage_impl.h"

#include <stddef.h>

#include <algorithm>
#include <functional>
#include <limits>
#include <set>
#include <vector>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/profiler/scoped_tracker.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/appcache/appcache.h"
#include "content/browser/appcache/appcache_database.h"
#include "content/browser/appcache/appcache_entry.h"
#include "content/browser/appcache/appcache_group.h"
#include "content/browser/appcache/appcache_histograms.h"
#include "content/browser/appcache/appcache_quota_client.h"
#include "content/browser/appcache/appcache_response.h"
#include "content/browser/appcache/appcache_service_impl.h"
#include "net/base/cache_type.h"
#include "net/base/net_errors.h"
#include "sql/connection.h"
#include "sql/transaction.h"
#include "storage/browser/quota/quota_client.h"
#include "storage/browser/quota/quota_manager.h"
#include "storage/browser/quota/quota_manager_proxy.h"
#include "storage/browser/quota/special_storage_policy.h"

namespace content {

// Hard coded default when not using quota management.
static const int kDefaultQuota = 5 * 1024 * 1024;

static const int kMaxDiskCacheSize = 250 * 1024 * 1024;
static const int kMaxMemDiskCacheSize = 10 * 1024 * 1024;
static const base::FilePath::CharType kDiskCacheDirectoryName[] =
    FILE_PATH_LITERAL("Cache");

namespace {

// Helpers for clearing data from the AppCacheDatabase.
bool DeleteGroupAndRelatedRecords(
    AppCacheDatabase* database,
    int64_t group_id,
    std::vector<int64_t>* deletable_response_ids) {
  AppCacheDatabase::CacheRecord cache_record;
  bool success = false;
  if (database->FindCacheForGroup(group_id, &cache_record)) {
    database->FindResponseIdsForCacheAsVector(cache_record.cache_id,
                                              deletable_response_ids);
    success =
        database->DeleteGroup(group_id) &&
        database->DeleteCache(cache_record.cache_id) &&
        database->DeleteEntriesForCache(cache_record.cache_id) &&
        database->DeleteNamespacesForCache(cache_record.cache_id) &&
        database->DeleteOnlineWhiteListForCache(cache_record.cache_id) &&
        database->InsertDeletableResponseIds(*deletable_response_ids);
  } else {
    NOTREACHED() << "A existing group without a cache is unexpected";
    success = database->DeleteGroup(group_id);
  }
  return success;
}

// Destroys |database|. If there is appcache data to be deleted
// (|force_keep_session_state| is false), deletes session-only appcache data.
void ClearSessionOnlyOrigins(
    AppCacheDatabase* database,
    scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy,
    bool force_keep_session_state) {
  std::unique_ptr<AppCacheDatabase> database_to_delete(database);

  // If saving session state, only delete the database.
  if (force_keep_session_state)
    return;

  bool has_session_only_appcaches =
      special_storage_policy.get() &&
      special_storage_policy->HasSessionOnlyOrigins();

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

  std::set<GURL> origins;
  database->FindOriginsWithGroups(&origins);
  if (origins.empty())
    return;  // nothing to delete

  sql::Connection* connection = database->db_connection();
  if (!connection) {
    NOTREACHED() << "Missing database connection.";
    return;
  }

  std::set<GURL>::const_iterator origin;
  DCHECK(special_storage_policy.get());
  for (origin = origins.begin(); origin != origins.end(); ++origin) {
    if (!special_storage_policy->IsStorageSessionOnly(*origin))
      continue;
    if (special_storage_policy->IsStorageProtected(*origin))
      continue;

    std::vector<AppCacheDatabase::GroupRecord> groups;
    database->FindGroupsForOrigin(*origin, &groups);
    std::vector<AppCacheDatabase::GroupRecord>::const_iterator group;
    for (group = groups.begin(); group != groups.end(); ++group) {
      sql::Transaction transaction(connection);
      if (!transaction.Begin()) {
        NOTREACHED() << "Failed to start transaction";
        return;
      }
      std::vector<int64_t> deletable_response_ids;
      bool success = DeleteGroupAndRelatedRecords(database,
                                                  group->group_id,
                                                  &deletable_response_ids);
      success = success && transaction.Commit();
      DCHECK(success);
    }  // for each group
  }  // for each origin
}

}  // namespace

// DatabaseTask -----------------------------------------

class AppCacheStorageImpl::DatabaseTask
    : public base::RefCountedThreadSafe<DatabaseTask> {
 public:
  explicit DatabaseTask(AppCacheStorageImpl* storage)
      : storage_(storage),
        database_(storage->database_),
        io_thread_(base::SequencedTaskRunnerHandle::Get()) {
    DCHECK(io_thread_.get());
  }

  void AddDelegate(DelegateReference* delegate_reference) {
    delegates_.push_back(make_scoped_refptr(delegate_reference));
  }

  // Schedules a task to be Run() on the DB thread. Tasks
  // are run in the order in which they are scheduled.
  void Schedule();

  // Called on the DB thread.
  virtual void Run() = 0;

  // Called on the IO thread after Run() has completed.
  virtual void RunCompleted() {}

  // Once scheduled a task cannot be cancelled, but the
  // call to RunCompleted may be. This method should only be
  // called on the IO thread. This is used by AppCacheStorageImpl
  // to cancel the completion calls when AppCacheStorageImpl is
  // destructed. This method may be overriden to release or delete
  // additional data associated with the task that is not DB thread
  // safe. If overriden, this base class method must be called from
  // within the override.
  virtual void CancelCompletion();

 protected:
  friend class base::RefCountedThreadSafe<DatabaseTask>;
  virtual ~DatabaseTask() {}

  AppCacheStorageImpl* storage_;
  AppCacheDatabase* database_;
  DelegateReferenceVector delegates_;

 private:
  void CallRun(base::TimeTicks schedule_time);
  void CallRunCompleted(base::TimeTicks schedule_time);
  void OnFatalError();

  scoped_refptr<base::SequencedTaskRunner> io_thread_;
};

void AppCacheStorageImpl::DatabaseTask::Schedule() {
  DCHECK(storage_);
  DCHECK(io_thread_->RunsTasksInCurrentSequence());
  if (!storage_->database_)
    return;

  if (storage_->db_task_runner_->PostTask(
          FROM_HERE,
          base::Bind(&DatabaseTask::CallRun, this, base::TimeTicks::Now()))) {
    storage_->scheduled_database_tasks_.push_back(this);
  } else {
    NOTREACHED() << "Thread for database tasks is not running.";
  }
}

void AppCacheStorageImpl::DatabaseTask::CancelCompletion() {
  DCHECK(io_thread_->RunsTasksInCurrentSequence());
  delegates_.clear();
  storage_ = NULL;
}

void AppCacheStorageImpl::DatabaseTask::CallRun(
    base::TimeTicks schedule_time) {
  AppCacheHistograms::AddTaskQueueTimeSample(
      base::TimeTicks::Now() - schedule_time);
  if (!database_->is_disabled()) {
    base::TimeTicks run_time = base::TimeTicks::Now();
    Run();
    AppCacheHistograms::AddTaskRunTimeSample(
        base::TimeTicks::Now() - run_time);

    if (database_->was_corruption_detected()) {
      AppCacheHistograms::CountCorruptionDetected();
      database_->Disable();
    }
    if (database_->is_disabled()) {
      io_thread_->PostTask(
          FROM_HERE,
          base::Bind(&DatabaseTask::OnFatalError, this));
    }
  }
  io_thread_->PostTask(
      FROM_HERE,
      base::Bind(&DatabaseTask::CallRunCompleted, this,
                 base::TimeTicks::Now()));
}

void AppCacheStorageImpl::DatabaseTask::CallRunCompleted(
    base::TimeTicks schedule_time) {
  AppCacheHistograms::AddCompletionQueueTimeSample(
      base::TimeTicks::Now() - schedule_time);
  if (storage_) {
    DCHECK(io_thread_->RunsTasksInCurrentSequence());
    DCHECK(storage_->scheduled_database_tasks_.front() == this);
    storage_->scheduled_database_tasks_.pop_front();
    base::TimeTicks run_time = base::TimeTicks::Now();
    RunCompleted();
    AppCacheHistograms::AddCompletionRunTimeSample(
        base::TimeTicks::Now() - run_time);
    delegates_.clear();
  }
}

void AppCacheStorageImpl::DatabaseTask::OnFatalError() {
  if (storage_) {
    DCHECK(io_thread_->RunsTasksInCurrentSequence());
    storage_->Disable();
    storage_->DeleteAndStartOver();
  }
}

// InitTask -------

class AppCacheStorageImpl::InitTask : public DatabaseTask {
 public:
  explicit InitTask(AppCacheStorageImpl* storage)
      : DatabaseTask(storage), last_group_id_(0),
        last_cache_id_(0), last_response_id_(0),
        last_deletable_response_rowid_(0) {
    if (!storage->is_incognito_) {
      db_file_path_ =
          storage->cache_directory_.Append(kAppCacheDatabaseName);
      disk_cache_directory_ =
          storage->cache_directory_.Append(kDiskCacheDirectoryName);
    }
  }

  // DatabaseTask:
  void Run() override;
  void RunCompleted() override;

 protected:
  ~InitTask() override {}

 private:
  base::FilePath db_file_path_;
  base::FilePath disk_cache_directory_;
  int64_t last_group_id_;
  int64_t last_cache_id_;
  int64_t last_response_id_;
  int64_t last_deletable_response_rowid_;
  std::map<GURL, int64_t> usage_map_;
};

void AppCacheStorageImpl::InitTask::Run() {
  tracked_objects::ScopedTracker tracking_profile(
      FROM_HERE_WITH_EXPLICIT_FUNCTION("AppCacheStorageImpl::InitTask"));
  // If there is no sql database, ensure there is no disk cache either.
  if (!db_file_path_.empty() &&
      !base::PathExists(db_file_path_) &&
      base::DirectoryExists(disk_cache_directory_)) {
    base::DeleteFile(disk_cache_directory_, true);
    if (base::DirectoryExists(disk_cache_directory_)) {
      database_->Disable();  // This triggers OnFatalError handling.
      return;
    }
  }

  database_->FindLastStorageIds(
      &last_group_id_, &last_cache_id_, &last_response_id_,
      &last_deletable_response_rowid_);
  database_->GetAllOriginUsage(&usage_map_);
}

void AppCacheStorageImpl::InitTask::RunCompleted() {
  storage_->last_group_id_ = last_group_id_;
  storage_->last_cache_id_ = last_cache_id_;
  storage_->last_response_id_ = last_response_id_;
  storage_->last_deletable_response_rowid_ = last_deletable_response_rowid_;

  if (!storage_->is_disabled()) {
    storage_->usage_map_.swap(usage_map_);
    const base::TimeDelta kDelay = base::TimeDelta::FromMinutes(5);
    base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
        FROM_HERE,
        base::Bind(&AppCacheStorageImpl::DelayedStartDeletingUnusedResponses,
                   storage_->weak_factory_.GetWeakPtr()),
        kDelay);
  }

  if (storage_->service()->quota_client())
    storage_->service()->quota_client()->NotifyAppCacheReady();
}

// DisableDatabaseTask -------

class AppCacheStorageImpl::DisableDatabaseTask : public DatabaseTask {
 public:
  explicit DisableDatabaseTask(AppCacheStorageImpl* storage)
      : DatabaseTask(storage) {}

  // DatabaseTask:
  void Run() override { database_->Disable(); }

 protected:
  ~DisableDatabaseTask() override {}
};

// GetAllInfoTask -------

class AppCacheStorageImpl::GetAllInfoTask : public DatabaseTask {
 public:
  explicit GetAllInfoTask(AppCacheStorageImpl* storage)
      : DatabaseTask(storage),
        info_collection_(new AppCacheInfoCollection()) {
  }

  // DatabaseTask:
  void Run() override;
  void RunCompleted() override;

 protected:
  ~GetAllInfoTask() override {}

 private:
  scoped_refptr<AppCacheInfoCollection> info_collection_;
};

void AppCacheStorageImpl::GetAllInfoTask::Run() {
  std::set<GURL> origins;
  database_->FindOriginsWithGroups(&origins);
  for (std::set<GURL>::const_iterator origin = origins.begin();
       origin != origins.end(); ++origin) {
    AppCacheInfoVector& infos =
        info_collection_->infos_by_origin[*origin];
    std::vector<AppCacheDatabase::GroupRecord> groups;
    database_->FindGroupsForOrigin(*origin, &groups);
    for (std::vector<AppCacheDatabase::GroupRecord>::const_iterator
         group = groups.begin();
         group != groups.end(); ++group) {
      AppCacheDatabase::CacheRecord cache_record;
      database_->FindCacheForGroup(group->group_id, &cache_record);
      AppCacheInfo info;
      info.manifest_url = group->manifest_url;
      info.creation_time = group->creation_time;
      info.size = cache_record.cache_size;
      info.last_access_time = group->last_access_time;
      info.last_update_time = cache_record.update_time;
      info.cache_id = cache_record.cache_id;
      info.group_id = group->group_id;
      info.is_complete = true;
      infos.push_back(info);
    }
  }
}

void AppCacheStorageImpl::GetAllInfoTask::RunCompleted() {
  DCHECK_EQ(1U, delegates_.size());
  FOR_EACH_DELEGATE(delegates_, OnAllInfo(info_collection_.get()));
}

// StoreOrLoadTask -------

class AppCacheStorageImpl::StoreOrLoadTask : public DatabaseTask {
 protected:
  explicit StoreOrLoadTask(AppCacheStorageImpl* storage)
      : DatabaseTask(storage) {}
  ~StoreOrLoadTask() override {}

  bool FindRelatedCacheRecords(int64_t cache_id);
  void CreateCacheAndGroupFromRecords(
      scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group);

  AppCacheDatabase::GroupRecord group_record_;
  AppCacheDatabase::CacheRecord cache_record_;
  std::vector<AppCacheDatabase::EntryRecord> entry_records_;
  std::vector<AppCacheDatabase::NamespaceRecord>
      intercept_namespace_records_;
  std::vector<AppCacheDatabase::NamespaceRecord>
      fallback_namespace_records_;
  std::vector<AppCacheDatabase::OnlineWhiteListRecord>
      online_whitelist_records_;
};

bool AppCacheStorageImpl::StoreOrLoadTask::FindRelatedCacheRecords(
    int64_t cache_id) {
  return database_->FindEntriesForCache(cache_id, &entry_records_) &&
         database_->FindNamespacesForCache(
             cache_id, &intercept_namespace_records_,
             &fallback_namespace_records_) &&
         database_->FindOnlineWhiteListForCache(
             cache_id, &online_whitelist_records_);
}

void AppCacheStorageImpl::StoreOrLoadTask::CreateCacheAndGroupFromRecords(
    scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group) {
  DCHECK(storage_ && cache && group);

  (*cache) = storage_->working_set_.GetCache(cache_record_.cache_id);
  if (cache->get()) {
    (*group) = cache->get()->owning_group();
    DCHECK(group->get());
    DCHECK_EQ(group_record_.group_id, group->get()->group_id());

    // TODO(michaeln): histogram is fishing for clues to crbug/95101
    if (!cache->get()->GetEntry(group_record_.manifest_url)) {
      AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
          AppCacheHistograms::CALLSITE_0);
    }

    storage_->NotifyStorageAccessed(group_record_.origin);
    return;
  }

  (*cache) = new AppCache(storage_, cache_record_.cache_id);
  cache->get()->InitializeWithDatabaseRecords(
      cache_record_, entry_records_,
      intercept_namespace_records_,
      fallback_namespace_records_,
      online_whitelist_records_);
  cache->get()->set_complete(true);

  (*group) = storage_->working_set_.GetGroup(group_record_.manifest_url);
  if (group->get()) {
    DCHECK(group_record_.group_id == group->get()->group_id());
    group->get()->AddCache(cache->get());

    // TODO(michaeln): histogram is fishing for clues to crbug/95101
    if (!cache->get()->GetEntry(group_record_.manifest_url)) {
      AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
          AppCacheHistograms::CALLSITE_1);
    }
  } else {
    (*group) = new AppCacheGroup(
        storage_, group_record_.manifest_url,
        group_record_.group_id);
    group->get()->set_creation_time(group_record_.creation_time);
    group->get()->set_last_full_update_check_time(
        group_record_.last_full_update_check_time);
    group->get()->set_first_evictable_error_time(
        group_record_.first_evictable_error_time);
    group->get()->AddCache(cache->get());

    // TODO(michaeln): histogram is fishing for clues to crbug/95101
    if (!cache->get()->GetEntry(group_record_.manifest_url)) {
      AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
          AppCacheHistograms::CALLSITE_2);
    }
  }
  DCHECK(group->get()->newest_complete_cache() == cache->get());

  // We have to update foriegn entries if MarkEntryAsForeignTasks
  // are in flight.
  std::vector<GURL> urls;
  storage_->GetPendingForeignMarkingsForCache(cache->get()->cache_id(), &urls);
  for (std::vector<GURL>::iterator iter = urls.begin();
       iter != urls.end(); ++iter) {
    DCHECK(cache->get()->GetEntry(*iter));
    cache->get()->GetEntry(*iter)->add_types(AppCacheEntry::FOREIGN);
  }

  storage_->NotifyStorageAccessed(group_record_.origin);

  // TODO(michaeln): Maybe verify that the responses we expect to exist
  // do actually exist in the disk_cache (and if not then what?)
}

// CacheLoadTask -------

class AppCacheStorageImpl::CacheLoadTask : public StoreOrLoadTask {
 public:
  CacheLoadTask(int64_t cache_id, AppCacheStorageImpl* storage)
      : StoreOrLoadTask(storage), cache_id_(cache_id), success_(false) {}

  // DatabaseTask:
  void Run() override;
  void RunCompleted() override;

 protected:
  ~CacheLoadTask() override {}

 private:
  int64_t cache_id_;
  bool success_;
};

void AppCacheStorageImpl::CacheLoadTask::Run() {
  tracked_objects::ScopedTracker tracking_profile(
      FROM_HERE_WITH_EXPLICIT_FUNCTION("AppCacheStorageImpl::CacheLoadTask"));
  success_ =
      database_->FindCache(cache_id_, &cache_record_) &&
      database_->FindGroup(cache_record_.group_id, &group_record_) &&
      FindRelatedCacheRecords(cache_id_);

  if (success_)
    database_->LazyUpdateLastAccessTime(group_record_.group_id,
                                        base::Time::Now());
}

void AppCacheStorageImpl::CacheLoadTask::RunCompleted() {
  storage_->pending_cache_loads_.erase(cache_id_);
  scoped_refptr<AppCache> cache;
  scoped_refptr<AppCacheGroup> group;
  if (success_ && !storage_->is_disabled()) {
    storage_->LazilyCommitLastAccessTimes();
    DCHECK(cache_record_.cache_id == cache_id_);
    CreateCacheAndGroupFromRecords(&cache, &group);
  }
  FOR_EACH_DELEGATE(delegates_, OnCacheLoaded(cache.get(), cache_id_));
}

// GroupLoadTask -------

class AppCacheStorageImpl::GroupLoadTask : public StoreOrLoadTask {
 public:
  GroupLoadTask(GURL manifest_url, AppCacheStorageImpl* storage)
      : StoreOrLoadTask(storage), manifest_url_(manifest_url),
        success_(false) {}

  // DatabaseTask:
  void Run() override;
  void RunCompleted() override;

 protected:
  ~GroupLoadTask() override {}

 private:
  GURL manifest_url_;
  bool success_;
};

void AppCacheStorageImpl::GroupLoadTask::Run() {
  tracked_objects::ScopedTracker tracking_profile(
      FROM_HERE_WITH_EXPLICIT_FUNCTION("AppCacheStorageImpl::GroupLoadTask"));
  success_ =
      database_->FindGroupForManifestUrl(manifest_url_, &group_record_) &&
      database_->FindCacheForGroup(group_record_.group_id, &cache_record_) &&
      FindRelatedCacheRecords(cache_record_.cache_id);

  if (success_)
    database_->LazyUpdateLastAccessTime(group_record_.group_id,
                                        base::Time::Now());
}

void AppCacheStorageImpl::GroupLoadTask::RunCompleted() {
  storage_->pending_group_loads_.erase(manifest_url_);
  scoped_refptr<AppCacheGroup> group;
  scoped_refptr<AppCache> cache;
  if (!storage_->is_disabled()) {
    if (success_) {
      storage_->LazilyCommitLastAccessTimes();
      DCHECK(group_record_.manifest_url == manifest_url_);
      CreateCacheAndGroupFromRecords(&cache, &group);
    } else {
      group = storage_->working_set_.GetGroup(manifest_url_);
      if (!group.get()) {
        group =
            new AppCacheGroup(storage_, manifest_url_, storage_->NewGroupId());
      }
    }
  }
  FOR_EACH_DELEGATE(delegates_, OnGroupLoaded(group.get(), manifest_url_));
}

// StoreGroupAndCacheTask -------

class AppCacheStorageImpl::StoreGroupAndCacheTask : public StoreOrLoadTask {
 public:
  StoreGroupAndCacheTask(AppCacheStorageImpl* storage, AppCacheGroup* group,
                         AppCache* newest_cache);

  void GetQuotaThenSchedule();
  void OnQuotaCallback(storage::QuotaStatusCode status,
                       int64_t usage,
                       int64_t quota);

  // DatabaseTask:
  void Run() override;
  void RunCompleted() override;
  void CancelCompletion() override;

 protected:
  ~StoreGroupAndCacheTask() override {}

 private:
  scoped_refptr<AppCacheGroup> group_;
  scoped_refptr<AppCache> cache_;
  bool success_;
  bool would_exceed_quota_;
  int64_t space_available_;
  int64_t new_origin_usage_;
  std::vector<int64_t> newly_deletable_response_ids_;
};

AppCacheStorageImpl::StoreGroupAndCacheTask::StoreGroupAndCacheTask(
    AppCacheStorageImpl* storage, AppCacheGroup* group, AppCache* newest_cache)
    : StoreOrLoadTask(storage), group_(group), cache_(newest_cache),
      success_(false), would_exceed_quota_(false),
      space_available_(-1), new_origin_usage_(-1) {
  group_record_.group_id = group->group_id();
  group_record_.manifest_url = group->manifest_url();
  group_record_.origin = group_record_.manifest_url.GetOrigin();
  group_record_.last_full_update_check_time =
      group->last_full_update_check_time();
  group_record_.first_evictable_error_time =
      group->first_evictable_error_time();
  newest_cache->ToDatabaseRecords(
      group,
      &cache_record_, &entry_records_,
      &intercept_namespace_records_,
      &fallback_namespace_records_,
      &online_whitelist_records_);
}

void AppCacheStorageImpl::StoreGroupAndCacheTask::GetQuotaThenSchedule() {
  storage::QuotaManager* quota_manager = NULL;
  if (storage_->service()->quota_manager_proxy()) {
    quota_manager =
        storage_->service()->quota_manager_proxy()->quota_manager();
  }

  if (!quota_manager) {
    if (storage_->service()->special_storage_policy() &&
        storage_->service()->special_storage_policy()->IsStorageUnlimited(
            group_record_.origin))
      space_available_ = std::numeric_limits<int64_t>::max();
    Schedule();
    return;
  }

  // crbug.com/349708
  TRACE_EVENT0(
      "io",
      "AppCacheStorageImpl::StoreGroupAndCacheTask::GetQuotaThenSchedule");

  // We have to ask the quota manager for the value.
  storage_->pending_quota_queries_.insert(this);
  quota_manager->GetUsageAndQuota(
      group_record_.origin,
      storage::kStorageTypeTemporary,
      base::Bind(&StoreGroupAndCacheTask::OnQuotaCallback, this));
}

void AppCacheStorageImpl::StoreGroupAndCacheTask::OnQuotaCallback(
    storage::QuotaStatusCode status,
    int64_t usage,
    int64_t quota) {
  if (storage_) {
    if (status == storage::kQuotaStatusOk)
      space_available_ = std::max(static_cast<int64_t>(0), quota - usage);
    else
      space_available_ = 0;
    storage_->pending_quota_queries_.erase(this);
    Schedule();
  }
}

void AppCacheStorageImpl::StoreGroupAndCacheTask::Run() {
  DCHECK(!success_);
  sql::Connection* connection = database_->db_connection();
  if (!connection)
    return;

  sql::Transaction transaction(connection);
  if (!transaction.Begin())
    return;

  int64_t old_origin_usage = database_->GetOriginUsage(group_record_.origin);

  AppCacheDatabase::GroupRecord existing_group;
  success_ = database_->FindGroup(group_record_.group_id, &existing_group);
  if (!success_) {
    group_record_.creation_time = base::Time::Now();
    group_record_.last_access_time = base::Time::Now();
    success_ = database_->InsertGroup(&group_record_);
  } else {
    DCHECK(group_record_.group_id == existing_group.group_id);
    DCHECK(group_record_.manifest_url == existing_group.manifest_url);
    DCHECK(group_record_.origin == existing_group.origin);

    database_->UpdateLastAccessTime(group_record_.group_id,
                                    base::Time::Now());

    database_->UpdateEvictionTimes(
        group_record_.group_id,
        group_record_.last_full_update_check_time,
        group_record_.first_evictable_error_time);

    AppCacheDatabase::CacheRecord cache;
    if (database_->FindCacheForGroup(group_record_.group_id, &cache)) {
      // Get the set of response ids in the old cache.
      std::set<int64_t> existing_response_ids;
      database_->FindResponseIdsForCacheAsSet(cache.cache_id,
                                              &existing_response_ids);

      // Remove those that remain in the new cache.
      std::vector<AppCacheDatabase::EntryRecord>::const_iterator entry_iter =
          entry_records_.begin();
      while (entry_iter != entry_records_.end()) {
        existing_response_ids.erase(entry_iter->response_id);
        ++entry_iter;
      }

      // The rest are deletable.
      std::set<int64_t>::const_iterator id_iter = existing_response_ids.begin();
      while (id_iter != existing_response_ids.end()) {
        newly_deletable_response_ids_.push_back(*id_iter);
        ++id_iter;
      }

      success_ =
          database_->DeleteCache(cache.cache_id) &&
          database_->DeleteEntriesForCache(cache.cache_id) &&
          database_->DeleteNamespacesForCache(cache.cache_id) &&
          database_->DeleteOnlineWhiteListForCache(cache.cache_id) &&
          database_->InsertDeletableResponseIds(newly_deletable_response_ids_);
          // TODO(michaeln): store group_id too with deletable ids
    } else {
      NOTREACHED() << "A existing group without a cache is unexpected";
    }
  }

  success_ =
      success_ &&
      database_->InsertCache(&cache_record_) &&
      database_->InsertEntryRecords(entry_records_) &&
      database_->InsertNamespaceRecords(intercept_namespace_records_) &&
      database_->InsertNamespaceRecords(fallback_namespace_records_) &&
      database_->InsertOnlineWhiteListRecords(online_whitelist_records_);

  if (!success_)
    return;

  new_origin_usage_ = database_->GetOriginUsage(group_record_.origin);

  // Only check quota when the new usage exceeds the old usage.
  if (new_origin_usage_ <= old_origin_usage) {
    success_ = transaction.Commit();
    return;
  }

  // Use a simple hard-coded value when not using quota management.
  if (space_available_ == -1) {
    if (new_origin_usage_ > kDefaultQuota) {
      would_exceed_quota_ = true;
      success_ = false;
      return;
    }
    success_ = transaction.Commit();
    return;
  }

  // Check limits based on the space availbable given to us via the
  // quota system.
  int64_t delta = new_origin_usage_ - old_origin_usage;
  if (delta > space_available_) {
    would_exceed_quota_ = true;
    success_ = false;
    return;
  }

  success_ = transaction.Commit();
}

void AppCacheStorageImpl::StoreGroupAndCacheTask::RunCompleted() {
  if (success_) {
    storage_->UpdateUsageMapAndNotify(
        group_->manifest_url().GetOrigin(), new_origin_usage_);
    if (cache_.get() != group_->newest_complete_cache()) {
      cache_->set_complete(true);
      group_->AddCache(cache_.get());
    }
    if (group_->creation_time().is_null())
      group_->set_creation_time(group_record_.creation_time);
    group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_);
  }
  FOR_EACH_DELEGATE(
      delegates_,
      OnGroupAndNewestCacheStored(
          group_.get(), cache_.get(), success_, would_exceed_quota_));
  group_ = NULL;
  cache_ = NULL;

  // TODO(michaeln): if (would_exceed_quota_) what if the current usage
  // also exceeds the quota? http://crbug.com/83968
}

void AppCacheStorageImpl::StoreGroupAndCacheTask::CancelCompletion() {
  // Overriden to safely drop our reference to the group and cache
  // which are not thread safe refcounted.
  DatabaseTask::CancelCompletion();
  group_ = NULL;
  cache_ = NULL;
}

// FindMainResponseTask -------

// Helpers for FindMainResponseTask::Run()
namespace {
class SortByCachePreference {
 public:
  SortByCachePreference(int64_t preferred_id,
                        const std::set<int64_t>& in_use_ids)
      : preferred_id_(preferred_id), in_use_ids_(in_use_ids) {}
  bool operator()(
      const AppCacheDatabase::EntryRecord& lhs,
      const AppCacheDatabase::EntryRecord& rhs) {
    return compute_value(lhs) > compute_value(rhs);
  }
 private:
  int compute_value(const AppCacheDatabase::EntryRecord& entry) {
    if (entry.cache_id == preferred_id_)
      return 100;
    else if (in_use_ids_.find(entry.cache_id) != in_use_ids_.end())
      return 50;
    return 0;
  }
  int64_t preferred_id_;
  const std::set<int64_t>& in_use_ids_;
};

bool SortByLength(
    const AppCacheDatabase::NamespaceRecord& lhs,
    const AppCacheDatabase::NamespaceRecord& rhs) {
  return lhs.namespace_.namespace_url.spec().length() >
         rhs.namespace_.namespace_url.spec().length();
}

class NetworkNamespaceHelper {
 public:
  explicit NetworkNamespaceHelper(AppCacheDatabase* database)
      : database_(database) {
  }

  bool IsInNetworkNamespace(const GURL& url, int64_t cache_id) {
    typedef std::pair<WhiteListMap::iterator, bool> InsertResult;
    InsertResult result = namespaces_map_.insert(
        WhiteListMap::value_type(cache_id, AppCacheNamespaceVector()));
    if (result.second)
      GetOnlineWhiteListForCache(cache_id, &result.first->second);
    return AppCache::FindNamespace(result.first->second, url) != NULL;
  }

 private:
  void GetOnlineWhiteListForCache(int64_t cache_id,
                                  AppCacheNamespaceVector* namespaces) {
    DCHECK(namespaces && namespaces->empty());
    typedef std::vector<AppCacheDatabase::OnlineWhiteListRecord>
        WhiteListVector;
    WhiteListVector records;
    if (!database_->FindOnlineWhiteListForCache(cache_id, &records))
      return;
    WhiteListVector::const_iterator iter = records.begin();
    while (iter != records.end()) {
      namespaces->push_back(
            AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE, iter->namespace_url,
                GURL(), iter->is_pattern));
      ++iter;
    }
  }

  // Key is cache id
  typedef std::map<int64_t, AppCacheNamespaceVector> WhiteListMap;
  WhiteListMap namespaces_map_;
  AppCacheDatabase* database_;
};

}  // namespace

class AppCacheStorageImpl::FindMainResponseTask : public DatabaseTask {
 public:
  FindMainResponseTask(AppCacheStorageImpl* storage,
                       const GURL& url,
                       const GURL& preferred_manifest_url,
                       const AppCacheWorkingSet::GroupMap* groups_in_use)
      : DatabaseTask(storage), url_(url),
        preferred_manifest_url_(preferred_manifest_url),
        cache_id_(kAppCacheNoCacheId), group_id_(0) {
    if (groups_in_use) {
      for (AppCacheWorkingSet::GroupMap::const_iterator it =
               groups_in_use->begin();
           it != groups_in_use->end(); ++it) {
        AppCacheGroup* group = it->second;
        AppCache* cache = group->newest_complete_cache();
        if (group->is_obsolete() || !cache)
          continue;
        cache_ids_in_use_.insert(cache->cache_id());
      }
    }
  }

  // DatabaseTask:
  void Run() override;
  void RunCompleted() override;

 protected:
  ~FindMainResponseTask() override {}

 private:
  typedef std::vector<AppCacheDatabase::NamespaceRecord*>
      NamespaceRecordPtrVector;

  bool FindExactMatch(int64_t preferred_id);
  bool FindNamespaceMatch(int64_t preferred_id);
  bool FindNamespaceHelper(int64_t preferred_cache_id,
                           AppCacheDatabase::NamespaceRecordVector* namespaces,
                           NetworkNamespaceHelper* network_namespace_helper);
  bool FindFirstValidNamespace(const NamespaceRecordPtrVector& namespaces);

  GURL url_;
  GURL preferred_manifest_url_;
  std::set<int64_t> cache_ids_in_use_;
  AppCacheEntry entry_;
  AppCacheEntry fallback_entry_;
  GURL namespace_entry_url_;
  int64_t cache_id_;
  int64_t group_id_;
  GURL manifest_url_;
};

void AppCacheStorageImpl::FindMainResponseTask::Run() {
  tracked_objects::ScopedTracker tracking_profile(
      FROM_HERE_WITH_EXPLICIT_FUNCTION(
          "AppCacheStorageImpl::FindMainResponseTask"));
  // NOTE: The heuristics around choosing amoungst multiple candidates
  // is underspecified, and just plain not fully understood. This needs
  // to be refined.

  // The 'preferred_manifest_url' is the url of the manifest associated
  // with the page that opened or embedded the page being loaded now.
  // We have a strong preference to use resources from that cache.
  // We also have a lesser bias to use resources from caches that are currently
  // being used by other unrelated pages.
  // TODO(michaeln): come up with a 'preferred_manifest_url' in more cases
  // - when navigating a frame whose current contents are from an appcache
  // - when clicking an href in a frame that is appcached
  int64_t preferred_cache_id = kAppCacheNoCacheId;
  if (!preferred_manifest_url_.is_empty()) {
    AppCacheDatabase::GroupRecord preferred_group;
    AppCacheDatabase::CacheRecord preferred_cache;
    if (database_->FindGroupForManifestUrl(
            preferred_manifest_url_, &preferred_group) &&
        database_->FindCacheForGroup(
            preferred_group.group_id, &preferred_cache)) {
      preferred_cache_id = preferred_cache.cache_id;
    }
  }

  if (FindExactMatch(preferred_cache_id) ||
      FindNamespaceMatch(preferred_cache_id)) {
    // We found something.
    DCHECK(cache_id_ != kAppCacheNoCacheId && !manifest_url_.is_empty() &&
           group_id_ != 0);
    return;
  }

  // We didn't find anything.
  DCHECK(cache_id_ == kAppCacheNoCacheId && manifest_url_.is_empty() &&
         group_id_ == 0);
}

bool AppCacheStorageImpl::FindMainResponseTask::FindExactMatch(
    int64_t preferred_cache_id) {
  std::vector<AppCacheDatabase::EntryRecord> entries;
  if (database_->FindEntriesForUrl(url_, &entries) && !entries.empty()) {
    // Sort them in order of preference, from the preferred_cache first,
    // followed by hits from caches that are 'in use', then the rest.
    std::sort(entries.begin(), entries.end(),
              SortByCachePreference(preferred_cache_id, cache_ids_in_use_));

    // Take the first with a valid, non-foreign entry.
    std::vector<AppCacheDatabase::EntryRecord>::iterator iter;
    for (iter = entries.begin(); iter < entries.end(); ++iter) {
      AppCacheDatabase::GroupRecord group_record;
      if ((iter->flags & AppCacheEntry::FOREIGN) ||
          !database_->FindGroupForCache(iter->cache_id, &group_record)) {
        continue;
      }
      manifest_url_ = group_record.manifest_url;
      group_id_ = group_record.group_id;
      entry_ = AppCacheEntry(iter->flags, iter->response_id);
      cache_id_ = iter->cache_id;
      return true;  // We found an exact match.
    }
  }
  return false;
}

bool AppCacheStorageImpl::FindMainResponseTask::FindNamespaceMatch(
    int64_t preferred_cache_id) {
  AppCacheDatabase::NamespaceRecordVector all_intercepts;
  AppCacheDatabase::NamespaceRecordVector all_fallbacks;
  if (!database_->FindNamespacesForOrigin(
          url_.GetOrigin(), &all_intercepts, &all_fallbacks)
      || (all_intercepts.empty() && all_fallbacks.empty())) {
    return false;
  }

  NetworkNamespaceHelper network_namespace_helper(database_);
  if (FindNamespaceHelper(preferred_cache_id,
                          &all_intercepts,
                          &network_namespace_helper) ||
      FindNamespaceHelper(preferred_cache_id,
                          &all_fallbacks,
                          &network_namespace_helper)) {
    return true;
  }
  return false;
}

bool AppCacheStorageImpl::FindMainResponseTask::FindNamespaceHelper(
    int64_t preferred_cache_id,
    AppCacheDatabase::NamespaceRecordVector* namespaces,
    NetworkNamespaceHelper* network_namespace_helper) {
  // Sort them by length, longer matches within the same cache/bucket take
  // precedence.
  std::sort(namespaces->begin(), namespaces->end(), SortByLength);

  NamespaceRecordPtrVector preferred_namespaces;
  NamespaceRecordPtrVector inuse_namespaces;
  NamespaceRecordPtrVector other_namespaces;
  std::vector<AppCacheDatabase::NamespaceRecord>::iterator iter;
  for (iter = namespaces->begin(); iter < namespaces->end(); ++iter) {
    // Skip those that aren't a match.
    if (!iter->namespace_.IsMatch(url_))
      continue;

    // Skip namespaces where the requested url falls into a network
    // namespace of its containing appcache.
    if (network_namespace_helper->IsInNetworkNamespace(url_, iter->cache_id))
      continue;

    // Bin them into one of our three buckets.
    if (iter->cache_id == preferred_cache_id)
      preferred_namespaces.push_back(&(*iter));
    else if (cache_ids_in_use_.find(iter->cache_id) != cache_ids_in_use_.end())
      inuse_namespaces.push_back(&(*iter));
    else
      other_namespaces.push_back(&(*iter));
  }

  if (FindFirstValidNamespace(preferred_namespaces) ||
      FindFirstValidNamespace(inuse_namespaces) ||
      FindFirstValidNamespace(other_namespaces))
    return true;  // We found one.

  // We didn't find anything.
  return false;
}

bool AppCacheStorageImpl::
FindMainResponseTask::FindFirstValidNamespace(
    const NamespaceRecordPtrVector& namespaces) {
  // Take the first with a valid, non-foreign entry.
  NamespaceRecordPtrVector::const_iterator iter;
  for (iter = namespaces.begin(); iter < namespaces.end();  ++iter) {
    AppCacheDatabase::EntryRecord entry_record;
    if (database_->FindEntry((*iter)->cache_id, (*iter)->namespace_.target_url,
                             &entry_record)) {
      AppCacheDatabase::GroupRecord group_record;
      if ((entry_record.flags & AppCacheEntry::FOREIGN) ||
          !database_->FindGroupForCache(entry_record.cache_id, &group_record)) {
        continue;
      }
      manifest_url_ = group_record.manifest_url;
      group_id_ = group_record.group_id;
      cache_id_ = (*iter)->cache_id;
      namespace_entry_url_ = (*iter)->namespace_.target_url;
      if ((*iter)->namespace_.type == APPCACHE_FALLBACK_NAMESPACE)
        fallback_entry_ = AppCacheEntry(entry_record.flags,
                                        entry_record.response_id);
      else
        entry_ = AppCacheEntry(entry_record.flags, entry_record.response_id);
      return true;  // We found one.
    }
  }
  return false;  // We didn't find a match.
}

void AppCacheStorageImpl::FindMainResponseTask::RunCompleted() {
  storage_->CallOnMainResponseFound(
      &delegates_, url_, entry_, namespace_entry_url_, fallback_entry_,
      cache_id_, group_id_, manifest_url_);
}

// MarkEntryAsForeignTask -------

class AppCacheStorageImpl::MarkEntryAsForeignTask : public DatabaseTask {
 public:
  MarkEntryAsForeignTask(AppCacheStorageImpl* storage,
                         const GURL& url,
                         int64_t cache_id)
      : DatabaseTask(storage), cache_id_(cache_id), entry_url_(url) {}

  // DatabaseTask:
  void Run() override;
  void RunCompleted() override;

 protected:
  ~MarkEntryAsForeignTask() override {}

 private:
  int64_t cache_id_;
  GURL entry_url_;
};

void AppCacheStorageImpl::MarkEntryAsForeignTask::Run() {
  database_->AddEntryFlags(entry_url_, cache_id_, AppCacheEntry::FOREIGN);
}

void AppCacheStorageImpl::MarkEntryAsForeignTask::RunCompleted() {
  DCHECK(storage_->pending_foreign_markings_.front().first == entry_url_ &&
         storage_->pending_foreign_markings_.front().second == cache_id_);
  storage_->pending_foreign_markings_.pop_front();
}

// MakeGroupObsoleteTask -------

class AppCacheStorageImpl::MakeGroupObsoleteTask : public DatabaseTask {
 public:
  MakeGroupObsoleteTask(AppCacheStorageImpl* storage,
                        AppCacheGroup* group,
                        int response_code);

  // DatabaseTask:
  void Run() override;
  void RunCompleted() override;
  void CancelCompletion() override;

 protected:
  ~MakeGroupObsoleteTask() override {}

 private:
  scoped_refptr<AppCacheGroup> group_;
  int64_t group_id_;
  GURL origin_;
  bool success_;
  int response_code_;
  int64_t new_origin_usage_;
  std::vector<int64_t> newly_deletable_response_ids_;
};

AppCacheStorageImpl::MakeGroupObsoleteTask::MakeGroupObsoleteTask(
    AppCacheStorageImpl* storage,
    AppCacheGroup* group,
    int response_code)
    : DatabaseTask(storage),
      group_(group),
      group_id_(group->group_id()),
      origin_(group->manifest_url().GetOrigin()),
      success_(false),
      response_code_(response_code),
      new_origin_usage_(-1) {}

void AppCacheStorageImpl::MakeGroupObsoleteTask::Run() {
  DCHECK(!success_);
  sql::Connection* connection = database_->db_connection();
  if (!connection)
    return;

  sql::Transaction transaction(connection);
  if (!transaction.Begin())
    return;

  AppCacheDatabase::GroupRecord group_record;
  if (!database_->FindGroup(group_id_, &group_record)) {
    // This group doesn't exists in the database, nothing todo here.
    new_origin_usage_ = database_->GetOriginUsage(origin_);
    success_ = true;
    return;
  }

  DCHECK_EQ(group_record.origin, origin_);
  success_ = DeleteGroupAndRelatedRecords(database_,
                                          group_id_,
                                          &newly_deletable_response_ids_);

  new_origin_usage_ = database_->GetOriginUsage(origin_);
  success_ = success_ && transaction.Commit();
}

void AppCacheStorageImpl::MakeGroupObsoleteTask::RunCompleted() {
  if (success_) {
    group_->set_obsolete(true);
    if (!storage_->is_disabled()) {
      storage_->UpdateUsageMapAndNotify(origin_, new_origin_usage_);
      group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_);

      // Also remove from the working set, caches for an 'obsolete' group
      // may linger in use, but the group itself cannot be looked up by
      // 'manifest_url' in the working set any longer.
      storage_->working_set()->RemoveGroup(group_.get());
    }
  }
  FOR_EACH_DELEGATE(
      delegates_, OnGroupMadeObsolete(group_.get(), success_, response_code_));
  group_ = NULL;
}

void AppCacheStorageImpl::MakeGroupObsoleteTask::CancelCompletion() {
  // Overriden to safely drop our reference to the group
  // which is not thread safe refcounted.
  DatabaseTask::CancelCompletion();
  group_ = NULL;
}

// GetDeletableResponseIdsTask -------

class AppCacheStorageImpl::GetDeletableResponseIdsTask : public DatabaseTask {
 public:
  GetDeletableResponseIdsTask(AppCacheStorageImpl* storage, int64_t max_rowid)
      : DatabaseTask(storage), max_rowid_(max_rowid) {}

  // DatabaseTask:
  void Run() override;
  void RunCompleted() override;

 protected:
  ~GetDeletableResponseIdsTask() override {}

 private:
  int64_t max_rowid_;
  std::vector<int64_t> response_ids_;
};

void AppCacheStorageImpl::GetDeletableResponseIdsTask::Run() {
  const int kSqlLimit = 1000;
  database_->GetDeletableResponseIds(&response_ids_, max_rowid_, kSqlLimit);
  // TODO(michaeln): retrieve group_ids too
}

void AppCacheStorageImpl::GetDeletableResponseIdsTask::RunCompleted() {
  if (!response_ids_.empty())
    storage_->StartDeletingResponses(response_ids_);
}

// InsertDeletableResponseIdsTask -------

class AppCacheStorageImpl::InsertDeletableResponseIdsTask
    : public DatabaseTask {
 public:
  explicit InsertDeletableResponseIdsTask(AppCacheStorageImpl* storage)
      : DatabaseTask(storage) {}

  // DatabaseTask:
  void Run() override;

  std::vector<int64_t> response_ids_;

 protected:
  ~InsertDeletableResponseIdsTask() override {}
};

void AppCacheStorageImpl::InsertDeletableResponseIdsTask::Run() {
  database_->InsertDeletableResponseIds(response_ids_);
  // TODO(michaeln): store group_ids too
}

// DeleteDeletableResponseIdsTask -------

class AppCacheStorageImpl::DeleteDeletableResponseIdsTask
    : public DatabaseTask {
 public:
  explicit DeleteDeletableResponseIdsTask(AppCacheStorageImpl* storage)
      : DatabaseTask(storage) {}

  // DatabaseTask:
  void Run() override;

  std::vector<int64_t> response_ids_;

 protected:
  ~DeleteDeletableResponseIdsTask() override {}
};

void AppCacheStorageImpl::DeleteDeletableResponseIdsTask::Run() {
  database_->DeleteDeletableResponseIds(response_ids_);
}

// LazyUpdateLastAccessTimeTask -------

class AppCacheStorageImpl::LazyUpdateLastAccessTimeTask
    : public DatabaseTask {
 public:
  LazyUpdateLastAccessTimeTask(
      AppCacheStorageImpl* storage, AppCacheGroup* group, base::Time time)
      : DatabaseTask(storage), group_id_(group->group_id()),
        last_access_time_(time) {
    storage->NotifyStorageAccessed(group->manifest_url().GetOrigin());
  }

  // DatabaseTask:
  void Run() override;
  void RunCompleted() override;

 protected:
  ~LazyUpdateLastAccessTimeTask() override {}

 private:
  int64_t group_id_;
  base::Time last_access_time_;
};

void AppCacheStorageImpl::LazyUpdateLastAccessTimeTask::Run() {
  tracked_objects::ScopedTracker tracking_profile(
      FROM_HERE_WITH_EXPLICIT_FUNCTION(
          "AppCacheStorageImpl::LazyUpdateLastAccessTimeTask"));
  database_->LazyUpdateLastAccessTime(group_id_, last_access_time_);
}

void AppCacheStorageImpl::LazyUpdateLastAccessTimeTask::RunCompleted() {
  storage_->LazilyCommitLastAccessTimes();
}

// CommitLastAccessTimesTask -------

class AppCacheStorageImpl::CommitLastAccessTimesTask
    : public DatabaseTask {
 public:
  CommitLastAccessTimesTask(AppCacheStorageImpl* storage)
      : DatabaseTask(storage) {}

  // DatabaseTask:
  void Run() override {
    tracked_objects::ScopedTracker tracking_profile(
        FROM_HERE_WITH_EXPLICIT_FUNCTION(
            "AppCacheStorageImpl::CommitLastAccessTimesTask"));
    database_->CommitLazyLastAccessTimes();
  }

 protected:
  ~CommitLastAccessTimesTask() override {}
};

// UpdateEvictionTimes -------

class AppCacheStorageImpl::UpdateEvictionTimesTask
    : public DatabaseTask {
 public:
  UpdateEvictionTimesTask(
      AppCacheStorageImpl* storage, AppCacheGroup* group)
      : DatabaseTask(storage), group_id_(group->group_id()),
        last_full_update_check_time_(group->last_full_update_check_time()),
        first_evictable_error_time_(group->first_evictable_error_time()) {
  }

  // DatabaseTask:
  void Run() override;

 protected:
  ~UpdateEvictionTimesTask() override {}

 private:
  int64_t group_id_;
  base::Time last_full_update_check_time_;
  base::Time first_evictable_error_time_;
};

void AppCacheStorageImpl::UpdateEvictionTimesTask::Run() {
  tracked_objects::ScopedTracker tracking_profile(
      FROM_HERE_WITH_EXPLICIT_FUNCTION(
          "AppCacheStorageImpl::UpdateEvictionTimes"));
  database_->UpdateEvictionTimes(group_id_,
                                 last_full_update_check_time_,
                                 first_evictable_error_time_);
}

// AppCacheStorageImpl ---------------------------------------------------

AppCacheStorageImpl::AppCacheStorageImpl(AppCacheServiceImpl* service)
    : AppCacheStorage(service),
      is_incognito_(false),
      is_response_deletion_scheduled_(false),
      did_start_deleting_responses_(false),
      last_deletable_response_rowid_(0),
      database_(NULL),
      is_disabled_(false),
      weak_factory_(this) {
}

AppCacheStorageImpl::~AppCacheStorageImpl() {
  for (auto* task : pending_quota_queries_)
    task->CancelCompletion();
  for (auto* task : scheduled_database_tasks_)
    task->CancelCompletion();

  if (database_ &&
      !db_task_runner_->PostTask(
          FROM_HERE,
          base::Bind(&ClearSessionOnlyOrigins, database_,
                     make_scoped_refptr(service_->special_storage_policy()),
                     service()->force_keep_session_state()))) {
    delete database_;
  }
  database_ = NULL;  // So no further database tasks can be scheduled.
}

void AppCacheStorageImpl::Initialize(
    const base::FilePath& cache_directory,
    const scoped_refptr<base::SequencedTaskRunner>& db_task_runner,
    const scoped_refptr<base::SingleThreadTaskRunner>& cache_thread) {
  cache_directory_ = cache_directory;
  is_incognito_ = cache_directory_.empty();

  base::FilePath db_file_path;
  if (!is_incognito_)
    db_file_path = cache_directory_.Append(kAppCacheDatabaseName);
  database_ = new AppCacheDatabase(db_file_path);

  db_task_runner_ = db_task_runner;
  cache_thread_ = cache_thread;

  scoped_refptr<InitTask> task(new InitTask(this));
  task->Schedule();
}

void AppCacheStorageImpl::Disable() {
  if (is_disabled_)
    return;
  VLOG(1) << "Disabling appcache storage.";
  is_disabled_ = true;
  ClearUsageMapAndNotify();
  working_set()->Disable();
  if (disk_cache_)
    disk_cache_->Disable();
  scoped_refptr<DisableDatabaseTask> task(new DisableDatabaseTask(this));
  task->Schedule();
}

void AppCacheStorageImpl::GetAllInfo(Delegate* delegate) {
  DCHECK(delegate);
  scoped_refptr<GetAllInfoTask> task(new GetAllInfoTask(this));
  task->AddDelegate(GetOrCreateDelegateReference(delegate));
  task->Schedule();
}

void AppCacheStorageImpl::LoadCache(int64_t id, Delegate* delegate) {
  DCHECK(delegate);
  if (is_disabled_) {
    delegate->OnCacheLoaded(NULL, id);
    return;
  }

  AppCache* cache = working_set_.GetCache(id);
  if (cache) {
    delegate->OnCacheLoaded(cache, id);
    if (cache->owning_group()) {
      scoped_refptr<DatabaseTask> update_task(
          new LazyUpdateLastAccessTimeTask(
              this, cache->owning_group(), base::Time::Now()));
      update_task->Schedule();
    }
    return;
  }
  scoped_refptr<CacheLoadTask> task(GetPendingCacheLoadTask(id));
  if (task.get()) {
    task->AddDelegate(GetOrCreateDelegateReference(delegate));
    return;
  }
  task = new CacheLoadTask(id, this);
  task->AddDelegate(GetOrCreateDelegateReference(delegate));
  task->Schedule();
  pending_cache_loads_[id] = task.get();
}

void AppCacheStorageImpl::LoadOrCreateGroup(
    const GURL& manifest_url, Delegate* delegate) {
  DCHECK(delegate);
  if (is_disabled_) {
    delegate->OnGroupLoaded(NULL, manifest_url);
    return;
  }

  AppCacheGroup* group = working_set_.GetGroup(manifest_url);
  if (group) {
    delegate->OnGroupLoaded(group, manifest_url);
    scoped_refptr<DatabaseTask> update_task(
        new LazyUpdateLastAccessTimeTask(
            this, group, base::Time::Now()));
    update_task->Schedule();
    return;
  }

  scoped_refptr<GroupLoadTask> task(GetPendingGroupLoadTask(manifest_url));
  if (task.get()) {
    task->AddDelegate(GetOrCreateDelegateReference(delegate));
    return;
  }

  if (usage_map_.find(manifest_url.GetOrigin()) == usage_map_.end()) {
    // No need to query the database, return a new group immediately.
    scoped_refptr<AppCacheGroup> group(new AppCacheGroup(
        this, manifest_url, NewGroupId()));
    delegate->OnGroupLoaded(group.get(), manifest_url);
    return;
  }

  task = new GroupLoadTask(manifest_url, this);
  task->AddDelegate(GetOrCreateDelegateReference(delegate));
  task->Schedule();
  pending_group_loads_[manifest_url] = task.get();
}

void AppCacheStorageImpl::StoreGroupAndNewestCache(
    AppCacheGroup* group, AppCache* newest_cache, Delegate* delegate) {
  // TODO(michaeln): distinguish between a simple update of an existing
  // cache that just adds new master entry(s), and the insertion of a
  // whole new cache. The StoreGroupAndCacheTask as written will handle
  // the simple update case in a very heavy weight way (delete all and
  // the reinsert all over again).
  DCHECK(group && delegate && newest_cache);
  scoped_refptr<StoreGroupAndCacheTask> task(
      new StoreGroupAndCacheTask(this, group, newest_cache));
  task->AddDelegate(GetOrCreateDelegateReference(delegate));
  task->GetQuotaThenSchedule();

  // TODO(michaeln): histogram is fishing for clues to crbug/95101
  if (!newest_cache->GetEntry(group->manifest_url())) {
    AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
        AppCacheHistograms::CALLSITE_3);
  }
}

void AppCacheStorageImpl::FindResponseForMainRequest(
    const GURL& url, const GURL& preferred_manifest_url,
    Delegate* delegate) {
  DCHECK(delegate);

  const GURL* url_ptr = &url;
  GURL url_no_ref;
  if (url.has_ref()) {
    GURL::Replacements replacements;
    replacements.ClearRef();
    url_no_ref = url.ReplaceComponents(replacements);
    url_ptr = &url_no_ref;
  }

  const GURL origin = url.GetOrigin();

  // First look in our working set for a direct hit without having to query
  // the database.
  const AppCacheWorkingSet::GroupMap* groups_in_use =
      working_set()->GetGroupsInOrigin(origin);
  if (groups_in_use) {
    if (!preferred_manifest_url.is_empty()) {
      AppCacheWorkingSet::GroupMap::const_iterator found =
          groups_in_use->find(preferred_manifest_url);
      if (found != groups_in_use->end() &&
          FindResponseForMainRequestInGroup(
              found->second, *url_ptr, delegate)) {
          return;
      }
    } else {
      for (AppCacheWorkingSet::GroupMap::const_iterator it =
              groups_in_use->begin();
           it != groups_in_use->end(); ++it) {
        if (FindResponseForMainRequestInGroup(
                it->second, *url_ptr, delegate)) {
          return;
        }
      }
    }
  }

  if (IsInitTaskComplete() &&  usage_map_.find(origin) == usage_map_.end()) {
    // No need to query the database, return async'ly but without going thru
    // the DB thread.
    scoped_refptr<AppCacheGroup> no_group;
    scoped_refptr<AppCache> no_cache;
    ScheduleSimpleTask(
        base::Bind(&AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse,
                   weak_factory_.GetWeakPtr(), url, AppCacheEntry(), no_group,
                   no_cache,
                   make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
    return;
  }

  // We have to query the database, schedule a database task to do so.
  scoped_refptr<FindMainResponseTask> task(
      new FindMainResponseTask(this, *url_ptr, preferred_manifest_url,
                               groups_in_use));
  task->AddDelegate(GetOrCreateDelegateReference(delegate));
  task->Schedule();
}

bool AppCacheStorageImpl::FindResponseForMainRequestInGroup(
    AppCacheGroup* group,  const GURL& url, Delegate* delegate) {
  AppCache* cache = group->newest_complete_cache();
  if (group->is_obsolete() || !cache)
    return false;

  AppCacheEntry* entry = cache->GetEntry(url);
  if (!entry || entry->IsForeign())
    return false;

  ScheduleSimpleTask(
      base::Bind(&AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse,
                 weak_factory_.GetWeakPtr(), url, *entry,
                 make_scoped_refptr(group), make_scoped_refptr(cache),
                 make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
  return true;
}

void AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse(
    const GURL& url,
    const AppCacheEntry& found_entry,
    scoped_refptr<AppCacheGroup> group,
    scoped_refptr<AppCache> cache,
    scoped_refptr<DelegateReference> delegate_ref) {
  if (delegate_ref->delegate) {
    DelegateReferenceVector delegates(1, delegate_ref);
    CallOnMainResponseFound(
        &delegates, url, found_entry,
        GURL(), AppCacheEntry(),
        cache.get() ? cache->cache_id() : kAppCacheNoCacheId,
        group.get() ? group->group_id() : kAppCacheNoCacheId,
        group.get() ? group->manifest_url() : GURL());
  }
}

void AppCacheStorageImpl::CallOnMainResponseFound(
    DelegateReferenceVector* delegates,
    const GURL& url,
    const AppCacheEntry& entry,
    const GURL& namespace_entry_url,
    const AppCacheEntry& fallback_entry,
    int64_t cache_id,
    int64_t group_id,
    const GURL& manifest_url) {
  FOR_EACH_DELEGATE(
      (*delegates),
      OnMainResponseFound(url, entry,
                          namespace_entry_url, fallback_entry,
                          cache_id, group_id, manifest_url));
}

void AppCacheStorageImpl::FindResponseForSubRequest(
    AppCache* cache, const GURL& url,
    AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry,
    bool* found_network_namespace) {
  DCHECK(cache && cache->is_complete());

  // When a group is forcibly deleted, all subresource loads for pages
  // using caches in the group will result in a synthesized network errors.
  // Forcible deletion is not a function that is covered by the HTML5 spec.
  if (cache->owning_group()->is_being_deleted()) {
    *found_entry = AppCacheEntry();
    *found_fallback_entry = AppCacheEntry();
    *found_network_namespace = false;
    return;
  }

  GURL fallback_namespace_not_used;
  GURL intercept_namespace_not_used;
  cache->FindResponseForRequest(
      url, found_entry, &intercept_namespace_not_used,
      found_fallback_entry, &fallback_namespace_not_used,
      found_network_namespace);
}

void AppCacheStorageImpl::MarkEntryAsForeign(const GURL& entry_url,
                                             int64_t cache_id) {
  AppCache* cache = working_set_.GetCache(cache_id);
  if (cache) {
    AppCacheEntry* entry = cache->GetEntry(entry_url);
    DCHECK(entry);
    if (entry)
      entry->add_types(AppCacheEntry::FOREIGN);
  }
  scoped_refptr<MarkEntryAsForeignTask> task(
      new MarkEntryAsForeignTask(this, entry_url, cache_id));
  task->Schedule();
  pending_foreign_markings_.push_back(std::make_pair(entry_url, cache_id));
}

void AppCacheStorageImpl::MakeGroupObsolete(AppCacheGroup* group,
                                            Delegate* delegate,
                                            int response_code) {
  DCHECK(group && delegate);
  scoped_refptr<MakeGroupObsoleteTask> task(
      new MakeGroupObsoleteTask(this, group, response_code));
  task->AddDelegate(GetOrCreateDelegateReference(delegate));
  task->Schedule();
}

void AppCacheStorageImpl::StoreEvictionTimes(AppCacheGroup* group) {
  scoped_refptr<UpdateEvictionTimesTask> task(
      new UpdateEvictionTimesTask(this, group));
  task->Schedule();
}

AppCacheResponseReader* AppCacheStorageImpl::CreateResponseReader(
    const GURL& manifest_url,
    int64_t response_id) {
  return new AppCacheResponseReader(
      response_id, is_disabled_ ? nullptr : disk_cache()->GetWeakPtr());
}

AppCacheResponseWriter* AppCacheStorageImpl::CreateResponseWriter(
    const GURL& manifest_url) {
  return new AppCacheResponseWriter(
      NewResponseId(), is_disabled_ ? nullptr : disk_cache()->GetWeakPtr());
}

AppCacheResponseMetadataWriter*
AppCacheStorageImpl::CreateResponseMetadataWriter(int64_t response_id) {
  return new AppCacheResponseMetadataWriter(
      response_id, is_disabled_ ? nullptr : disk_cache()->GetWeakPtr());
}

void AppCacheStorageImpl::DoomResponses(
    const GURL& manifest_url,
    const std::vector<int64_t>& response_ids) {
  if (response_ids.empty())
    return;

  // Start deleting them from the disk cache lazily.
  StartDeletingResponses(response_ids);

  // Also schedule a database task to record these ids in the
  // deletable responses table.
  // TODO(michaeln): There is a race here. If the browser crashes
  // prior to committing these rows to the database and prior to us
  // having deleted them from the disk cache, we'll never delete them.
  scoped_refptr<InsertDeletableResponseIdsTask> task(
      new InsertDeletableResponseIdsTask(this));
  task->response_ids_ = response_ids;
  task->Schedule();
}

void AppCacheStorageImpl::DeleteResponses(
    const GURL& manifest_url,
    const std::vector<int64_t>& response_ids) {
  if (response_ids.empty())
    return;
  StartDeletingResponses(response_ids);
}

bool AppCacheStorageImpl::IsInitialized() {
  return IsInitTaskComplete();
}

void AppCacheStorageImpl::DelayedStartDeletingUnusedResponses() {
  // Only if we haven't already begun.
  if (!did_start_deleting_responses_) {
    scoped_refptr<GetDeletableResponseIdsTask> task(
        new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_));
    task->Schedule();
  }
}

void AppCacheStorageImpl::StartDeletingResponses(
    const std::vector<int64_t>& response_ids) {
  DCHECK(!response_ids.empty());
  did_start_deleting_responses_ = true;
  deletable_response_ids_.insert(
      deletable_response_ids_.end(),
      response_ids.begin(), response_ids.end());
  if (!is_response_deletion_scheduled_)
    ScheduleDeleteOneResponse();
}

void AppCacheStorageImpl::ScheduleDeleteOneResponse() {
  DCHECK(!is_response_deletion_scheduled_);
  const base::TimeDelta kBriefDelay = base::TimeDelta::FromMilliseconds(10);
  base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
      FROM_HERE,
      base::Bind(&AppCacheStorageImpl::DeleteOneResponse,
                 weak_factory_.GetWeakPtr()),
      kBriefDelay);
  is_response_deletion_scheduled_ = true;
}

void AppCacheStorageImpl::DeleteOneResponse() {
  DCHECK(is_response_deletion_scheduled_);
  DCHECK(!deletable_response_ids_.empty());

  if (is_disabled_) {
    deletable_response_ids_.clear();
    deleted_response_ids_.clear();
    is_response_deletion_scheduled_ = false;
    return;
  }

  // TODO(michaeln): add group_id to DoomEntry args
  int64_t id = deletable_response_ids_.front();
  int rv = disk_cache()->DoomEntry(
      id, base::Bind(&AppCacheStorageImpl::OnDeletedOneResponse,
                     base::Unretained(this)));
  if (rv != net::ERR_IO_PENDING)
    OnDeletedOneResponse(rv);
}

void AppCacheStorageImpl::OnDeletedOneResponse(int rv) {
  is_response_deletion_scheduled_ = false;
  if (is_disabled_)
    return;

  int64_t id = deletable_response_ids_.front();
  deletable_response_ids_.pop_front();
  if (rv != net::ERR_ABORTED)
    deleted_response_ids_.push_back(id);

  const size_t kBatchSize = 50U;
  if (deleted_response_ids_.size() >= kBatchSize ||
      deletable_response_ids_.empty()) {
    scoped_refptr<DeleteDeletableResponseIdsTask> task(
        new DeleteDeletableResponseIdsTask(this));
    task->response_ids_.swap(deleted_response_ids_);
    task->Schedule();
  }

  if (deletable_response_ids_.empty()) {
    scoped_refptr<GetDeletableResponseIdsTask> task(
        new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_));
    task->Schedule();
    return;
  }

  ScheduleDeleteOneResponse();
}

AppCacheStorageImpl::CacheLoadTask*
AppCacheStorageImpl::GetPendingCacheLoadTask(int64_t cache_id) {
  PendingCacheLoads::iterator found = pending_cache_loads_.find(cache_id);
  if (found != pending_cache_loads_.end())
    return found->second;
  return NULL;
}

AppCacheStorageImpl::GroupLoadTask*
AppCacheStorageImpl::GetPendingGroupLoadTask(const GURL& manifest_url) {
  PendingGroupLoads::iterator found = pending_group_loads_.find(manifest_url);
  if (found != pending_group_loads_.end())
    return found->second;
  return NULL;
}

void AppCacheStorageImpl::GetPendingForeignMarkingsForCache(
    int64_t cache_id,
    std::vector<GURL>* urls) {
  PendingForeignMarkings::iterator iter = pending_foreign_markings_.begin();
  while (iter != pending_foreign_markings_.end()) {
    if (iter->second == cache_id)
      urls->push_back(iter->first);
    ++iter;
  }
}

void AppCacheStorageImpl::ScheduleSimpleTask(const base::Closure& task) {
  pending_simple_tasks_.push_back(task);
  base::SequencedTaskRunnerHandle::Get()->PostTask(
      FROM_HERE, base::Bind(&AppCacheStorageImpl::RunOnePendingSimpleTask,
                            weak_factory_.GetWeakPtr()));
}

void AppCacheStorageImpl::RunOnePendingSimpleTask() {
  DCHECK(!pending_simple_tasks_.empty());
  base::Closure task = pending_simple_tasks_.front();
  pending_simple_tasks_.pop_front();
  task.Run();
}

AppCacheDiskCache* AppCacheStorageImpl::disk_cache() {
  DCHECK(IsInitTaskComplete());
  DCHECK(!is_disabled_);

  if (!disk_cache_) {
    int rv = net::OK;
    disk_cache_.reset(new AppCacheDiskCache);
    if (is_incognito_) {
      rv = disk_cache_->InitWithMemBackend(
          kMaxMemDiskCacheSize,
          base::Bind(&AppCacheStorageImpl::OnDiskCacheInitialized,
                     base::Unretained(this)));
    } else {
      rv = disk_cache_->InitWithDiskBackend(
          cache_directory_.Append(kDiskCacheDirectoryName),
          kMaxDiskCacheSize,
          false,
          cache_thread_.get(),
          base::Bind(&AppCacheStorageImpl::OnDiskCacheInitialized,
                     base::Unretained(this)));
    }

    if (rv != net::ERR_IO_PENDING)
      OnDiskCacheInitialized(rv);
  }
  return disk_cache_.get();
}

void AppCacheStorageImpl::OnDiskCacheInitialized(int rv) {
  if (rv != net::OK) {
    LOG(ERROR) << "Failed to open the appcache diskcache.";
    AppCacheHistograms::CountInitResult(AppCacheHistograms::DISK_CACHE_ERROR);

    // We're unable to open the disk cache, this is a fatal error that we can't
    // really recover from. We handle it by temporarily disabling the appcache
    // deleting the directory on disk and reinitializing the appcache system.
    Disable();
    if (rv != net::ERR_ABORTED)
      DeleteAndStartOver();
  }
}

void AppCacheStorageImpl::DeleteAndStartOver() {
  DCHECK(is_disabled_);
  if (!is_incognito_) {
    VLOG(1) << "Deleting existing appcache data and starting over.";
    // We can have tasks in flight to close file handles on both the db
    // and cache threads, we need to allow those tasks to cycle thru
    // prior to deleting the files and calling reinit.
    cache_thread_->PostTaskAndReply(
        FROM_HERE,
        base::Bind(&base::DoNothing),
        base::Bind(&AppCacheStorageImpl::DeleteAndStartOverPart2,
                   weak_factory_.GetWeakPtr()));
  }
}

void AppCacheStorageImpl::DeleteAndStartOverPart2() {
  db_task_runner_->PostTaskAndReply(
      FROM_HERE,
      base::Bind(base::IgnoreResult(&base::DeleteFile), cache_directory_, true),
      base::Bind(&AppCacheStorageImpl::CallScheduleReinitialize,
                 weak_factory_.GetWeakPtr()));
}

void AppCacheStorageImpl::CallScheduleReinitialize() {
  service_->ScheduleReinitialize();
  // note: 'this' may be deleted at this point.
}

void AppCacheStorageImpl::LazilyCommitLastAccessTimes() {
  if (lazy_commit_timer_.IsRunning())
    return;
  const base::TimeDelta kDelay = base::TimeDelta::FromMinutes(5);
  lazy_commit_timer_.Start(
      FROM_HERE, kDelay,
      base::Bind(&AppCacheStorageImpl::OnLazyCommitTimer,
                 weak_factory_.GetWeakPtr()));
}

void AppCacheStorageImpl::OnLazyCommitTimer() {
  lazy_commit_timer_.Stop();
  if (is_disabled())
    return;
  scoped_refptr<DatabaseTask> task(new CommitLastAccessTimesTask(this));
  task->Schedule();
}

}  // namespace content
