root/webkit/browser/appcache/appcache_storage_impl.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. DeleteGroupAndRelatedRecords
  2. ClearSessionOnlyOrigins
  3. io_thread_
  4. AddDelegate
  5. RunCompleted
  6. Schedule
  7. CancelCompletion
  8. CallRun
  9. CallRunCompleted
  10. OnFatalError
  11. last_deletable_response_rowid_
  12. InitTask
  13. Run
  14. RunCompleted
  15. DisableDatabaseTask
  16. Run
  17. DisableDatabaseTask
  18. info_collection_
  19. GetAllInfoTask
  20. Run
  21. RunCompleted
  22. StoreOrLoadTask
  23. StoreOrLoadTask
  24. FindRelatedCacheRecords
  25. CreateCacheAndGroupFromRecords
  26. success_
  27. CacheLoadTask
  28. Run
  29. RunCompleted
  30. success_
  31. GroupLoadTask
  32. Run
  33. RunCompleted
  34. StoreGroupAndCacheTask
  35. new_origin_usage_
  36. GetQuotaThenSchedule
  37. OnQuotaCallback
  38. Run
  39. RunCompleted
  40. CancelCompletion
  41. in_use_ids_
  42. compute_value
  43. SortByLength
  44. IsInNetworkNamespace
  45. GetOnlineWhiteListForCache
  46. group_id_
  47. FindMainResponseTask
  48. Run
  49. FindExactMatch
  50. FindNamespaceMatch
  51. FindNamespaceHelper
  52. FindFirstValidNamespace
  53. RunCompleted
  54. entry_url_
  55. MarkEntryAsForeignTask
  56. Run
  57. RunCompleted
  58. MakeGroupObsoleteTask
  59. new_origin_usage_
  60. Run
  61. RunCompleted
  62. CancelCompletion
  63. max_rowid_
  64. GetDeletableResponseIdsTask
  65. Run
  66. RunCompleted
  67. InsertDeletableResponseIdsTask
  68. InsertDeletableResponseIdsTask
  69. Run
  70. DeleteDeletableResponseIdsTask
  71. DeleteDeletableResponseIdsTask
  72. Run
  73. last_access_time_
  74. UpdateGroupLastAccessTimeTask
  75. Run
  76. weak_factory_
  77. Initialize
  78. Disable
  79. GetAllInfo
  80. LoadCache
  81. LoadOrCreateGroup
  82. StoreGroupAndNewestCache
  83. FindResponseForMainRequest
  84. FindResponseForMainRequestInGroup
  85. DeliverShortCircuitedFindMainResponse
  86. CallOnMainResponseFound
  87. FindResponseForSubRequest
  88. MarkEntryAsForeign
  89. MakeGroupObsolete
  90. CreateResponseReader
  91. CreateResponseWriter
  92. DoomResponses
  93. DeleteResponses
  94. DelayedStartDeletingUnusedResponses
  95. StartDeletingResponses
  96. ScheduleDeleteOneResponse
  97. DeleteOneResponse
  98. OnDeletedOneResponse
  99. GetPendingCacheLoadTask
  100. GetPendingGroupLoadTask
  101. GetPendingForeignMarkingsForCache
  102. ScheduleSimpleTask
  103. RunOnePendingSimpleTask
  104. disk_cache
  105. OnDiskCacheInitialized
  106. DeleteAndStartOver
  107. DeleteAndStartOverPart2
  108. CallScheduleReinitialize

// 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();
}

// 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,
                        int response_code);

  // 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_;
  int response_code_;
  int64 new_origin_usage_;
  std::vector<int64> 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 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,
                                            int response_code) {
  DCHECK(group && delegate);
  scoped_refptr<MakeGroupObsoleteTask> task(
      new MakeGroupObsoleteTask(this, group, response_code));
  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::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

/* [<][>][^][v][top][bottom][index][help] */