text: // 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 "webkit/browser/appcache/appcache_storage_impl.h"
  
  #include <algorithm>
  #include <functional>
  #include <set>
  #include <vector>
  
  #include "base/bind.h"
  #include "base/bind_helpers.h"
  #include "base/file_util.h"
  #include "base/logging.h"
  #include "base/message_loop/message_loop.h"
  #include "base/stl_util.h"
  #include "base/strings/string_util.h"
  #include "net/base/cache_type.h"
  #include "net/base/net_errors.h"
  #include "sql/connection.h"
  #include "sql/transaction.h"
  #include "webkit/browser/appcache/appcache.h"
  #include "webkit/browser/appcache/appcache_database.h"
  #include "webkit/browser/appcache/appcache_entry.h"
  #include "webkit/browser/appcache/appcache_group.h"
  #include "webkit/browser/appcache/appcache_histograms.h"
  #include "webkit/browser/appcache/appcache_quota_client.h"
  #include "webkit/browser/appcache/appcache_response.h"
  #include "webkit/browser/appcache/appcache_service.h"
  #include "webkit/browser/quota/quota_client.h"
  #include "webkit/browser/quota/quota_manager.h"
  #include "webkit/browser/quota/quota_manager_proxy.h"
  #include "webkit/browser/quota/special_storage_policy.h"
  
  namespace appcache {
  
  // 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 group_id,
                                    std::vector<int64>* 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<quota::SpecialStoragePolicy> special_storage_policy,
      bool force_keep_session_state) {
    scoped_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;
    for (origin = origins.begin(); origin != origins.end(); ++origin) {
      if (!special_storage_policy->IsStorageSessionOnly(*origin))
        continue;
      if (special_storage_policy.get() &&
          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> 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::MessageLoopProxy::current()) {
      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::MessageLoopProxy> io_thread_;
  };
  
  void AppCacheStorageImpl::DatabaseTask::Schedule() {
    DCHECK(storage_);
    DCHECK(io_thread_->BelongsToCurrentThread());
    if (!storage_->database_)
      return;
  
    if (storage_->db_thread_->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_->BelongsToCurrentThread());
    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_->BelongsToCurrentThread());
      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_->BelongsToCurrentThread());
      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:
    virtual void Run() OVERRIDE;
    virtual void RunCompleted() OVERRIDE;
  
   protected:
    virtual ~InitTask() {}
  
   private:
    base::FilePath db_file_path_;
    base::FilePath disk_cache_directory_;
    int64 last_group_id_;
    int64 last_cache_id_;
    int64 last_response_id_;
    int64 last_deletable_response_rowid_;
    std::map<GURL, int64> usage_map_;
  };
  
  void AppCacheStorageImpl::InitTask::Run() {
    // 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::MessageLoop::current()->PostDelayedTask(
          FROM_HERE,
          base::Bind(&AppCacheStorageImpl::DelayedStartDeletingUnusedResponses,
                     storage_->weak_factory_.GetWeakPtr()),
          kDelay);
    }
  
    if (storage_->service()->quota_client())
      storage_->service()->quota_client()->NotifyAppCacheReady();
  }
  
  // CloseConnectionTask -------
  
  class AppCacheStorageImpl::CloseConnectionTask : public DatabaseTask {
   public:
    explicit CloseConnectionTask(AppCacheStorageImpl* storage)
        : DatabaseTask(storage) {}
  
    // DatabaseTask:
    virtual void Run() OVERRIDE { database_->CloseConnection(); }
  
   protected:
    virtual ~CloseConnectionTask() {}
  };
  
  // DisableDatabaseTask -------
  
  class AppCacheStorageImpl::DisableDatabaseTask : public DatabaseTask {
   public:
    explicit DisableDatabaseTask(AppCacheStorageImpl* storage)
        : DatabaseTask(storage) {}
  
    // DatabaseTask:
    virtual void Run() OVERRIDE { database_->Disable(); }
  
   protected:
    virtual ~DisableDatabaseTask() {}
  };
  
  // GetAllInfoTask -------
  
  class AppCacheStorageImpl::GetAllInfoTask : public DatabaseTask {
   public:
    explicit GetAllInfoTask(AppCacheStorageImpl* storage)
        : DatabaseTask(storage),
          info_collection_(new AppCacheInfoCollection()) {
    }
  
    // DatabaseTask:
    virtual void Run() OVERRIDE;
    virtual void RunCompleted() OVERRIDE;
  
   protected:
    virtual ~GetAllInfoTask() {}
  
   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(delegates_.size() == 1);
    FOR_EACH_DELEGATE(delegates_, OnAllInfo(info_collection_.get()));
  }
  
  // StoreOrLoadTask -------
  
  class AppCacheStorageImpl::StoreOrLoadTask : public DatabaseTask {
   protected:
    explicit StoreOrLoadTask(AppCacheStorageImpl* storage)
        : DatabaseTask(storage) {}
    virtual ~StoreOrLoadTask() {}
  
    bool FindRelatedCacheRecords(int64 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 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()->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 cache_id, AppCacheStorageImpl* storage)
        : StoreOrLoadTask(storage), cache_id_(cache_id),
          success_(false) {}
  
    // DatabaseTask:
    virtual void Run() OVERRIDE;
    virtual void RunCompleted() OVERRIDE;
  
   protected:
    virtual ~CacheLoadTask() {}
  
   private:
    int64 cache_id_;
    bool success_;
  };
  
  void AppCacheStorageImpl::CacheLoadTask::Run() {
    success_ =
        database_->FindCache(cache_id_, &cache_record_) &&
        database_->FindGroup(cache_record_.group_id, &group_record_) &&
        FindRelatedCacheRecords(cache_id_);
  
    if (success_)
      database_->UpdateGroupLastAccessTime(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()) {
      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:
    virtual void Run() OVERRIDE;
    virtual void RunCompleted() OVERRIDE;
  
   protected:
    virtual ~GroupLoadTask() {}
  
   private:
    GURL manifest_url_;
    bool success_;
  };
  
  void AppCacheStorageImpl::GroupLoadTask::Run() {
    success_ =
        database_->FindGroupForManifestUrl(manifest_url_, &group_record_) &&
        database_->FindCacheForGroup(group_record_.group_id, &cache_record_) &&
        FindRelatedCacheRecords(cache_record_.cache_id);
  
    if (success_)
      database_->UpdateGroupLastAccessTime(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_) {
        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(
        quota::QuotaStatusCode status, int64 usage, int64 quota);
  
    // DatabaseTask:
    virtual void Run() OVERRIDE;
    virtual void RunCompleted() OVERRIDE;
    virtual void CancelCompletion() OVERRIDE;
  
   protected:
    virtual ~StoreGroupAndCacheTask() {}
  
   private:
    scoped_refptr<AppCacheGroup> group_;
    scoped_refptr<AppCache> cache_;
    bool success_;
    bool would_exceed_quota_;
    int64 space_available_;
    int64 new_origin_usage_;
    std::vector<int64> 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();
    newest_cache->ToDatabaseRecords(
        group,
        &cache_record_, &entry_records_,
        &intercept_namespace_records_,
        &fallback_namespace_records_,
        &online_whitelist_records_);
  }
  
  void AppCacheStorageImpl::StoreGroupAndCacheTask::GetQuotaThenSchedule() {
    quota::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_ = kint64max;
      Schedule();
      return;
    }
  
    // We have to ask the quota manager for the value.
    storage_->pending_quota_queries_.insert(this);
    quota_manager->GetUsageAndQuota(
        group_record_.origin, quota::kStorageTypeTemporary,
        base::Bind(&StoreGroupAndCacheTask::OnQuotaCallback, this));
  }
  
  void AppCacheStorageImpl::StoreGroupAndCacheTask::OnQuotaCallback(
      quota::QuotaStatusCode status, int64 usage, int64 quota) {
    if (storage_) {
      if (status == quota::kQuotaStatusOk)
        space_available_ = std::max(static_cast<int64>(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 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_->UpdateGroupLastAccessTime(group_record_.group_id,
                                           base::Time::Now());
  
      AppCacheDatabase::CacheRecord cache;
      if (database_->FindCacheForGroup(group_record_.group_id, &cache)) {
        // Get the set of response ids in the old cache.
        std::set<int64> 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>::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 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 std::binary_function<
          AppCacheDatabase::EntryRecord,
          AppCacheDatabase::EntryRecord,
          bool> {
   public:
    SortByCachePreference(int64 preferred_id, const std::set<int64>& 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 preferred_id_;
    const std::set<int64>& 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 cache_id) {
      typedef std::pair<WhiteListMap::iterator, bool> InsertResult;
      InsertResult result = namespaces_map_.insert(
          WhiteListMap::value_type(cache_id, NamespaceVector()));
      if (result.second)
        GetOnlineWhiteListForCache(cache_id, &result.first->second);
      return AppCache::FindNamespace(result.first->second, url) != NULL;
    }
  
   private:
    void GetOnlineWhiteListForCache(
        int64 cache_id, NamespaceVector* 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(
              Namespace(NETWORK_NAMESPACE, iter->namespace_url, GURL(),
                        iter->is_pattern));
        ++iter;
      }
    }
  
    // Key is cache id
    typedef std::map<int64, NamespaceVector> 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_(kNoCacheId), 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:
    virtual void Run() OVERRIDE;
    virtual void RunCompleted() OVERRIDE;
  
   protected:
    virtual ~FindMainResponseTask() {}
  
   private:
    typedef std::vector<AppCacheDatabase::NamespaceRecord*>
        NamespaceRecordPtrVector;
  
    bool FindExactMatch(int64 preferred_id);
    bool FindNamespaceMatch(int64 preferred_id);
    bool FindNamespaceHelper(
        int64 preferred_cache_id,
        AppCacheDatabase::NamespaceRecordVector* namespaces,
        NetworkNamespaceHelper* network_namespace_helper);
    bool FindFirstValidNamespace(const NamespaceRecordPtrVector& namespaces);
  
    GURL url_;
    GURL preferred_manifest_url_;
    std::set<int64> cache_ids_in_use_;
    AppCacheEntry entry_;
    AppCacheEntry fallback_entry_;
    GURL namespace_entry_url_;
    int64 cache_id_;
    int64 group_id_;
    GURL manifest_url_;
  };
  
  void AppCacheStorageImpl::FindMainResponseTask::Run() {
    // 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 preferred_cache_id = kNoCacheId;
    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_ != kNoCacheId && !manifest_url_.is_empty() &&
             group_id_ != 0);
      return;
    }
  
    // We didn't find anything.
    DCHECK(cache_id_ == kNoCacheId && manifest_url_.is_empty() &&
           group_id_ == 0);
  }
  
  bool AppCacheStorageImpl::
  FindMainResponseTask::FindExactMatch(int64 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 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 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 == 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 cache_id)
        : DatabaseTask(storage), cache_id_(cache_id), entry_url_(url) {}
  
    // DatabaseTask:
    virtual void Run() OVERRIDE;
    virtual void RunCompleted() OVERRIDE;
  
   protected:
    virtual ~MarkEntryAsForeignTask() {}
  
   private:
    int64 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);
  
    // DatabaseTask:
    virtual void Run() OVERRIDE;
    virtual void RunCompleted() OVERRIDE;
    virtual void CancelCompletion() OVERRIDE;
  
   protected:
    virtual ~MakeGroupObsoleteTask() {}
  
   private:
    scoped_refptr<AppCacheGroup> group_;
    int64 group_id_;
    GURL origin_;
    bool success_;
    int64 new_origin_usage_;
    std::vector<int64> newly_deletable_response_ids_;
  };
  
  AppCacheStorageImpl::MakeGroupObsoleteTask::MakeGroupObsoleteTask(
      AppCacheStorageImpl* storage, AppCacheGroup* group)
      : DatabaseTask(storage), group_(group), group_id_(group->group_id()),
        origin_(group->manifest_url().GetOrigin()),
        success_(false), 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_));
    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 max_rowid)
        : DatabaseTask(storage), max_rowid_(max_rowid) {}
  
    // DatabaseTask:
    virtual void Run() OVERRIDE;
    virtual void RunCompleted() OVERRIDE;
  
   protected:
    virtual ~GetDeletableResponseIdsTask() {}
  
   private:
    int64 max_rowid_;
    std::vector<int64> 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:
    virtual void Run() OVERRIDE;
  
    std::vector<int64> response_ids_;
  
   protected:
    virtual ~InsertDeletableResponseIdsTask() {}
  };
  
  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:
    virtual void Run() OVERRIDE;
  
    std::vector<int64> response_ids_;
  
   protected:
    virtual ~DeleteDeletableResponseIdsTask() {}
  };
  
  void AppCacheStorageImpl::DeleteDeletableResponseIdsTask::Run() {
    database_->DeleteDeletableResponseIds(response_ids_);
  }
  
  // UpdateGroupLastAccessTimeTask -------
  
  class AppCacheStorageImpl::UpdateGroupLastAccessTimeTask
      : public DatabaseTask {
   public:
    UpdateGroupLastAccessTimeTask(
        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:
    virtual void Run() OVERRIDE;
  
   protected:
    virtual ~UpdateGroupLastAccessTimeTask() {}
  
   private:
    int64 group_id_;
    base::Time last_access_time_;
  };
  
  void AppCacheStorageImpl::UpdateGroupLastAccessTimeTask::Run() {
    database_->UpdateGroupLastAccessTime(group_id_, last_access_time_);
  }
  
  
  // AppCacheStorageImpl ---------------------------------------------------
  
  AppCacheStorageImpl::AppCacheStorageImpl(AppCacheService* 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() {
    std::for_each(pending_quota_queries_.begin(),
                  pending_quota_queries_.end(),
                  std::mem_fun(&DatabaseTask::CancelCompletion));
    std::for_each(scheduled_database_tasks_.begin(),
                  scheduled_database_tasks_.end(),
                  std::mem_fun(&DatabaseTask::CancelCompletion));
  
    if (database_ &&
        !db_thread_->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,
                                       base::MessageLoopProxy* db_thread,
                                       base::MessageLoopProxy* cache_thread) {
    DCHECK(db_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_thread_ = db_thread;
    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 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 UpdateGroupLastAccessTimeTask(
                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 UpdateGroupLastAccessTimeTask(
              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() : kNoCacheId,
          group.get() ? group->group_id() : kNoCacheId,
          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 cache_id, int64 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 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) {
    DCHECK(group && delegate);
    scoped_refptr<MakeGroupObsoleteTask> task(
        new MakeGroupObsoleteTask(this, group));
    task->AddDelegate(GetOrCreateDelegateReference(delegate));
    task->Schedule();
  }
  
  AppCacheResponseReader* AppCacheStorageImpl::CreateResponseReader(
      const GURL& manifest_url, int64 group_id, int64 response_id) {
    return new AppCacheResponseReader(response_id, group_id, disk_cache());
  }
  
  AppCacheResponseWriter* AppCacheStorageImpl::CreateResponseWriter(
      const GURL& manifest_url, int64 group_id) {
    return new AppCacheResponseWriter(NewResponseId(), group_id, disk_cache());
  }
  
  void AppCacheStorageImpl::DoomResponses(
      const GURL& manifest_url, const std::vector<int64>& 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>& response_ids) {
    if (response_ids.empty())
      return;
    StartDeletingResponses(response_ids);
  }
  
  void AppCacheStorageImpl::PurgeMemory() {
    scoped_refptr<CloseConnectionTask> task(new CloseConnectionTask(this));
    task->Schedule();
  }
  
  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>& 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 kDelay = base::TimeDelta::FromMilliseconds(10);
    base::MessageLoop::current()->PostDelayedTask(
        FROM_HERE,
        base::Bind(&AppCacheStorageImpl::DeleteOneResponse,
                   weak_factory_.GetWeakPtr()),
        kDelay);
    is_response_deletion_scheduled_ = true;
  }
  
  void AppCacheStorageImpl::DeleteOneResponse() {
    DCHECK(is_response_deletion_scheduled_);
    DCHECK(!deletable_response_ids_.empty());
  
    if (!disk_cache()) {
      DCHECK(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 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 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 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 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::MessageLoop::current()->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());
  
    if (is_disabled_)
      return NULL;
  
    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_thread_->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.
  }
  
  }  // namespace appcache
  