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

#include "app/sql/connection.h"
#include "app/sql/transaction.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/stl_util-inl.h"
#include "base/string_util.h"
#include "net/base/cache_type.h"
#include "net/base/net_errors.h"
#include "webkit/appcache/appcache.h"
#include "webkit/appcache/appcache_database.h"
#include "webkit/appcache/appcache_entry.h"
#include "webkit/appcache/appcache_group.h"
#include "webkit/appcache/appcache_histograms.h"
#include "webkit/appcache/appcache_policy.h"
#include "webkit/appcache/appcache_response.h"
#include "webkit/appcache/appcache_service.h"
#include "webkit/appcache/appcache_thread.h"

namespace {
// Helper with no return value for use with NewRunnableFunction.
void DeleteDirectory(const FilePath& path) {
  file_util::Delete(path, true);
}
}

namespace appcache {

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

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

class AppCacheStorageImpl::DatabaseTask
    : public base::RefCountedThreadSafe<DatabaseTask> {
 public:
  explicit DatabaseTask(AppCacheStorageImpl* storage)
      : storage_(storage), database_(storage->database_) {}

  virtual ~DatabaseTask() {}

  void AddDelegate(DelegateReference* delegate_reference) {
    delegates_.push_back(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:
  AppCacheStorageImpl* storage_;
  AppCacheDatabase* database_;
  DelegateReferenceVector delegates_;

 private:
  void CallRun();
  void CallRunCompleted();
  void CallDisableStorage();
};

void AppCacheStorageImpl::DatabaseTask::Schedule() {
  DCHECK(storage_);
  DCHECK(AppCacheThread::CurrentlyOn(AppCacheThread::io()));
  if (AppCacheThread::PostTask(AppCacheThread::db(), FROM_HERE,
          NewRunnableMethod(this, &DatabaseTask::CallRun))) {
    storage_->scheduled_database_tasks_.push_back(this);
  } else {
    NOTREACHED() << "The database thread is not running.";
  }
}

void AppCacheStorageImpl::DatabaseTask::CancelCompletion() {
  DCHECK(AppCacheThread::CurrentlyOn(AppCacheThread::io()));
  delegates_.clear();
  storage_ = NULL;
}

void AppCacheStorageImpl::DatabaseTask::CallRun() {
  DCHECK(AppCacheThread::CurrentlyOn(AppCacheThread::db()));
  if (!database_->is_disabled()) {
    Run();
    if (database_->is_disabled()) {
      AppCacheThread::PostTask(AppCacheThread::io(), FROM_HERE,
          NewRunnableMethod(this, &DatabaseTask::CallDisableStorage));
    }
  }
  AppCacheThread::PostTask(AppCacheThread::io(), FROM_HERE,
      NewRunnableMethod(this, &DatabaseTask::CallRunCompleted));
}

void AppCacheStorageImpl::DatabaseTask::CallRunCompleted() {
  if (storage_) {
    DCHECK(AppCacheThread::CurrentlyOn(AppCacheThread::io()));
    DCHECK(storage_->scheduled_database_tasks_.front() == this);
    storage_->scheduled_database_tasks_.pop_front();
    RunCompleted();
    delegates_.clear();
  }
}

void AppCacheStorageImpl::DatabaseTask::CallDisableStorage() {
  if (storage_) {
    DCHECK(AppCacheThread::CurrentlyOn(AppCacheThread::io()));
    storage_->Disable();
  }
}

// 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) {}

  virtual void Run();
  virtual void RunCompleted();

  int64 last_group_id_;
  int64 last_cache_id_;
  int64 last_response_id_;
  int64 last_deletable_response_rowid_;
  std::set<GURL> origins_with_groups_;
};

void AppCacheStorageImpl::InitTask::Run() {
  database_->FindLastStorageIds(
      &last_group_id_, &last_cache_id_, &last_response_id_,
      &last_deletable_response_rowid_);
  database_->FindOriginsWithGroups(&origins_with_groups_);
}

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_->origins_with_groups_.swap(origins_with_groups_);

    const int kDelayMillis = 5 * 60 * 1000;  // Five minutes.
    MessageLoop::current()->PostDelayedTask(FROM_HERE,
      storage_->method_factory_.NewRunnableMethod(
          &AppCacheStorageImpl::DelayedStartDeletingUnusedResponses),
      kDelayMillis);
  }
}

// CloseConnectionTask -------

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

  virtual void Run() { database_->CloseConnection(); }
};

// DisableDatabaseTask -------

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

  virtual void Run() { database_->Disable(); }
};

// GetAllInfoTask -------

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

  virtual void Run();
  virtual void RunCompleted();

  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);
      infos.push_back(
          AppCacheInfo(
              group->manifest_url, cache_record.cache_size,
              group->creation_time, group->last_access_time,
              cache_record.update_time));
    }
  }
}

void AppCacheStorageImpl::GetAllInfoTask::RunCompleted() {
  DCHECK(delegates_.size() == 1);
  FOR_EACH_DELEGATE(delegates_, OnAllInfo(info_collection_));
}

// StoreOrLoadTask -------

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

  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::FallbackNameSpaceRecord>
      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_->FindFallbackNameSpacesForCache(
             cache_id, &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) = new AppCache(storage_->service_, cache_record_.cache_id);
  cache->get()->InitializeWithDatabaseRecords(
      cache_record_, entry_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());
  } else {
    (*group) = new AppCacheGroup(
        storage_->service_, group_record_.manifest_url,
        group_record_.group_id);
    group->get()->AddCache(cache->get());
  }
  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);
  }

  // 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) {}

  virtual void Run();
  virtual void RunCompleted();

  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_);
    DCHECK(!storage_->working_set_.GetCache(cache_record_.cache_id));
    CreateCacheAndGroupFromRecords(&cache, &group);
  }
  FOR_EACH_DELEGATE(delegates_, OnCacheLoaded(cache, cache_id_));
}

// GroupLoadTask -------

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

  virtual void Run();
  virtual void RunCompleted();

  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_);
      DCHECK(!storage_->working_set_.GetGroup(manifest_url_));
      DCHECK(!storage_->working_set_.GetCache(cache_record_.cache_id));
      CreateCacheAndGroupFromRecords(&cache, &group);
    } else {
      group = new AppCacheGroup(
          storage_->service_, manifest_url_,
          storage_->NewGroupId());
    }
  }
  FOR_EACH_DELEGATE(delegates_, OnGroupLoaded(group, manifest_url_));
}

// StoreGroupAndCacheTask -------

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

  virtual void Run();
  virtual void RunCompleted();
  virtual void CancelCompletion();

  scoped_refptr<AppCacheGroup> group_;
  scoped_refptr<AppCache> cache_;
  bool success_;
  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) {
  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_, &fallback_namespace_records_,
      &online_whitelist_records_);
}

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

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

  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_->DeleteFallbackNameSpacesForCache(cache.cache_id) &&
          database_->DeleteOnlineWhiteListForCache(cache.cache_id) &&
          database_->InsertDeletableResponseIds(newly_deletable_response_ids_);
    } else {
      NOTREACHED() << "A existing group without a cache is unexpected";
    }
  }

  success_ =
      success_ &&
      database_->InsertCache(&cache_record_) &&
      database_->InsertEntryRecords(entry_records_) &&
      database_->InsertFallbackNameSpaceRecords(fallback_namespace_records_)&&
      database_->InsertOnlineWhiteListRecords(online_whitelist_records_) &&
      (database_->GetOriginUsage(group_record_.origin) <=
       database_->GetOriginQuota(group_record_.origin)) &&
      transaction.Commit();
}

void AppCacheStorageImpl::StoreGroupAndCacheTask::RunCompleted() {
  if (success_) {
    storage_->origins_with_groups_.insert(group_->manifest_url().GetOrigin());
    if (cache_ != group_->newest_complete_cache()) {
      cache_->set_complete(true);
      group_->AddCache(cache_);
    }
    group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_);
  }
  FOR_EACH_DELEGATE(delegates_,
                    OnGroupAndNewestCacheStored(group_, cache_, success_));
  group_ = NULL;
  cache_ = NULL;
}

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 -------

class AppCacheStorageImpl::FindMainResponseTask : public DatabaseTask {
 public:
  FindMainResponseTask(AppCacheStorageImpl* storage, const GURL& url,
                       const AppCacheWorkingSet::GroupMap* groups_in_use)
      : DatabaseTask(storage), url_(url), cache_id_(kNoCacheId) {
    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());
      }
    }
  }

  virtual void Run();
  virtual void RunCompleted();

  GURL url_;
  std::set<int64> cache_ids_in_use_;
  AppCacheEntry entry_;
  AppCacheEntry fallback_entry_;
  int64 cache_id_;
  GURL manifest_url_;
};

namespace {

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

}

void AppCacheStorageImpl::FindMainResponseTask::Run() {
  // We have a bias for hits from caches that are in use.

  // TODO(michaeln): The heuristics around choosing amoungst
  // multiple candidates is under specified, and just plain
  // not fully understood. Refine these over time. In particular,
  // * prefer candidates from newer caches
  // * take into account the cache associated with the document
  //   that initiated the navigation
  // * take into account the cache associated with the document
  //   currently residing in the frame being navigated

  // First look for an exact match. We don't worry about whether
  // the containing cache is in-use in this loop because the
  // storage class's FindResponseForMainRequest method does that
  // as a pre-optimization.
  std::vector<AppCacheDatabase::EntryRecord> entries;
  if (database_->FindEntriesForUrl(url_, &entries) && !entries.empty()) {
    std::vector<AppCacheDatabase::EntryRecord>::iterator iter;
    for (iter = entries.begin(); iter < entries.end(); ++iter) {
      if (iter->flags & AppCacheEntry::FOREIGN)
        continue;

      AppCacheDatabase::GroupRecord group_record;
      if (!database_->FindGroupForCache(iter->cache_id, &group_record)) {
        NOTREACHED() << "A cache without a group is not expected.";
        continue;
      }
      entry_ = AppCacheEntry(iter->flags, iter->response_id);
      cache_id_ = iter->cache_id;
      manifest_url_ = group_record.manifest_url;
      return;
    }
  }

  // No exact matches, look at the fallback namespaces for this origin.
  std::vector<AppCacheDatabase::FallbackNameSpaceRecord> fallbacks;
  if (!database_->FindFallbackNameSpacesForOrigin(url_.GetOrigin(), &fallbacks)
      || fallbacks.empty()) {
    return;
  }

  // Sort by namespace url string length, longest to shortest,
  // since longer matches trump when matching a url to a namespace.
  std::sort(fallbacks.begin(), fallbacks.end(), SortByLength);

  bool has_candidate = false;
  GURL candidate_fallback_namespace;
  std::vector<AppCacheDatabase::FallbackNameSpaceRecord>::iterator iter;
  for (iter = fallbacks.begin(); iter < fallbacks.end(); ++iter) {
    if (has_candidate &&
        (candidate_fallback_namespace.spec().length() >
         iter->namespace_url.spec().length())) {
      break;  // Stop iterating since longer namespace prefix matches win.
    }

    if (StartsWithASCII(url_.spec(), iter->namespace_url.spec(), true)) {
      bool is_cache_in_use = cache_ids_in_use_.find(iter->cache_id) !=
                             cache_ids_in_use_.end();

      bool take_new_candidate = !has_candidate || is_cache_in_use;

      AppCacheDatabase::EntryRecord entry_record;
      if (take_new_candidate &&
          database_->FindEntry(iter->cache_id, iter->fallback_entry_url,
                               &entry_record)) {
        AppCacheDatabase::GroupRecord group_record;
        if (!database_->FindGroupForCache(iter->cache_id, &group_record)) {
          NOTREACHED() << "A cache without a group is not expected.";
          continue;
        }
        cache_id_ = iter->cache_id;
        manifest_url_ = group_record.manifest_url;
        fallback_entry_ = AppCacheEntry(
            entry_record.flags, entry_record.response_id);
        if (is_cache_in_use)
          break;  // Stop iterating since we favor hits from in-use caches.
        candidate_fallback_namespace = iter->namespace_url;
        has_candidate = true;
      }
    }
  }
}

void AppCacheStorageImpl::FindMainResponseTask::RunCompleted() {
  storage_->CheckPolicyAndCallOnMainResponseFound(
      &delegates_, url_, entry_, fallback_entry_, cache_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) {}

  virtual void Run();
  virtual void RunCompleted();

  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);

  virtual void Run();
  virtual void RunCompleted();
  virtual void CancelCompletion();

  scoped_refptr<AppCacheGroup> group_;
  int64 group_id_;
  bool success_;
  std::set<GURL> origins_with_groups_;
  std::vector<int64> newly_deletable_response_ids_;
};

AppCacheStorageImpl::MakeGroupObsoleteTask::MakeGroupObsoleteTask(
    AppCacheStorageImpl* storage, AppCacheGroup* group)
    : DatabaseTask(storage), group_(group), group_id_(group->group_id()),
      success_(false) {
}

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.
    success_ = true;
    return;
  }

  AppCacheDatabase::CacheRecord cache_record;
  if (database_->FindCacheForGroup(group_id_, &cache_record)) {
    database_->FindResponseIdsForCacheAsVector(cache_record.cache_id,
                                               &newly_deletable_response_ids_);
    success_ =
        database_->DeleteGroup(group_id_) &&
        database_->DeleteCache(cache_record.cache_id) &&
        database_->DeleteEntriesForCache(cache_record.cache_id) &&
        database_->DeleteFallbackNameSpacesForCache(cache_record.cache_id) &&
        database_->DeleteOnlineWhiteListForCache(cache_record.cache_id) &&
        database_->InsertDeletableResponseIds(newly_deletable_response_ids_);
  } else {
    NOTREACHED() << "A existing group without a cache is unexpected";
    success_ = database_->DeleteGroup(group_id_);
  }

  success_ = success_ &&
             database_->FindOriginsWithGroups(&origins_with_groups_) &&
             transaction.Commit();
}

void AppCacheStorageImpl::MakeGroupObsoleteTask::RunCompleted() {
  if (success_) {
    group_->set_obsolete(true);
    if (!storage_->is_disabled()) {
      storage_->origins_with_groups_.swap(origins_with_groups_);
      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_);
    }
  }
  FOR_EACH_DELEGATE(delegates_, OnGroupMadeObsolete(group_, 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) {}

  virtual void Run();
  virtual void RunCompleted();

  int64 max_rowid_;
  std::vector<int64> response_ids_;
};

void AppCacheStorageImpl::GetDeletableResponseIdsTask::Run() {
  const int kSqlLimit = 1000;
  database_->GetDeletableResponseIds(&response_ids_, max_rowid_, kSqlLimit);
}

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) {}
  virtual void Run();
  std::vector<int64> response_ids_;
};

void AppCacheStorageImpl::InsertDeletableResponseIdsTask::Run() {
  database_->InsertDeletableResponseIds(response_ids_);
}

// DeleteDeletableResponseIdsTask -------

class AppCacheStorageImpl::DeleteDeletableResponseIdsTask
    : public DatabaseTask {
 public:
  explicit DeleteDeletableResponseIdsTask(AppCacheStorageImpl* storage)
      : DatabaseTask(storage) {}
  virtual void Run();
  std::vector<int64> response_ids_;
};

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

// UpdateGroupLastAccessTimeTask -------

class AppCacheStorageImpl::UpdateGroupLastAccessTimeTask
    : public DatabaseTask {
 public:
  UpdateGroupLastAccessTimeTask(
      AppCacheStorageImpl* storage, int64 group_id, base::Time time)
      : DatabaseTask(storage), group_id_(group_id), last_access_time_(time) {}

  virtual void Run();

  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),
      ALLOW_THIS_IN_INITIALIZER_LIST(doom_callback_(
          this, &AppCacheStorageImpl::OnDeletedOneResponse)),
      ALLOW_THIS_IN_INITIALIZER_LIST(init_callback_(
          this, &AppCacheStorageImpl::OnDiskCacheInitialized)),
      database_(NULL), is_disabled_(false),
      ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
}

AppCacheStorageImpl::~AppCacheStorageImpl() {
  STLDeleteElements(&pending_simple_tasks_);

  std::for_each(scheduled_database_tasks_.begin(),
                scheduled_database_tasks_.end(),
                std::mem_fun(&DatabaseTask::CancelCompletion));

  if (database_)
    AppCacheThread::DeleteSoon(AppCacheThread::db(), FROM_HERE, database_);
}

void AppCacheStorageImpl::Initialize(const FilePath& cache_directory,
                                     base::MessageLoopProxy* cache_thread) {
  cache_directory_ = cache_directory;
  cache_thread_ = cache_thread;
  is_incognito_ = cache_directory_.empty();

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

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

void AppCacheStorageImpl::Disable() {
  if (is_disabled_)
    return;
  LOG(INFO) << "Disabling appcache storage.";
  is_disabled_ = true;
  origins_with_groups_.clear();
  working_set()->Disable();
  if (disk_cache_.get())
    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()->group_id(), base::Time::Now());
      update_task->Schedule();
    }
    return;
  }
  scoped_refptr<CacheLoadTask> task = GetPendingCacheLoadTask(id);
  if (task) {
    task->AddDelegate(GetOrCreateDelegateReference(delegate));
    return;
  }
  task = new CacheLoadTask(id, this);
  task->AddDelegate(GetOrCreateDelegateReference(delegate));
  task->Schedule();
  pending_cache_loads_[id] = task;
}

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->group_id(), base::Time::Now());
    update_task->Schedule();
    return;
  }

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

  if (origins_with_groups_.find(manifest_url.GetOrigin()) ==
      origins_with_groups_.end()) {
    // No need to query the database, return a new group immediately.
    scoped_refptr<AppCacheGroup> group = new AppCacheGroup(
        service_, manifest_url, NewGroupId());
    delegate->OnGroupLoaded(group, 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->Schedule();
}

void AppCacheStorageImpl::FindResponseForMainRequest(
    const GURL& 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) {
    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;

      AppCacheEntry* entry = cache->GetEntry(*url_ptr);
      if (entry && !entry->IsForeign()) {
        ScheduleSimpleTask(method_factory_.NewRunnableMethod(
            &AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse,
            url, *entry, make_scoped_refptr(group), make_scoped_refptr(cache),
            make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
        return;
      }
    }
  }

  if (IsInitTaskComplete() &&
      origins_with_groups_.find(origin) == origins_with_groups_.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(method_factory_.NewRunnableMethod(
        &AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse,
        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, groups_in_use);
  task->AddDelegate(GetOrCreateDelegateReference(delegate));
  task->Schedule();
}

void AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse(
    const GURL& url, 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);
    CheckPolicyAndCallOnMainResponseFound(
        &delegates, url, found_entry, AppCacheEntry(),
        cache.get() ? cache->cache_id() : kNoCacheId,
        group.get() ? group->manifest_url() : GURL());
  }
}

void AppCacheStorageImpl::CheckPolicyAndCallOnMainResponseFound(
    DelegateReferenceVector* delegates, const GURL& url,
    const AppCacheEntry& entry, const AppCacheEntry& fallback_entry,
    int64 cache_id, const GURL& manifest_url) {
  if (!manifest_url.is_empty()) {
    // Check the policy prior to returning a main resource from the appcache.
    AppCachePolicy* policy = service()->appcache_policy();
    if (policy && !policy->CanLoadAppCache(manifest_url)) {
      FOR_EACH_DELEGATE(
          (*delegates),
          OnMainResponseFound(url, AppCacheEntry(), AppCacheEntry(),
                              kNoCacheId, manifest_url, true));
      return;
    }
  }

  FOR_EACH_DELEGATE(
      (*delegates),
      OnMainResponseFound(url, entry, fallback_entry,
                          cache_id, manifest_url, false));
}

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;
  cache->FindResponseForRequest(
      url, found_entry, 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 response_id) {
  return new AppCacheResponseReader(response_id, disk_cache());
}

AppCacheResponseWriter* AppCacheStorageImpl::CreateResponseWriter(
    const GURL& manifest_url) {
  return new AppCacheResponseWriter(NewResponseId(), 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 int kDelayMillis = 10;
  MessageLoop::current()->PostDelayedTask(FROM_HERE,
      method_factory_.NewRunnableMethod(
          &AppCacheStorageImpl::DeleteOneResponse),
      kDelayMillis);
  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;
  }

  int64 id = deletable_response_ids_.front();
  int rv = disk_cache_->DoomEntry(id, &doom_callback_);
  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(Task* task) {
  pending_simple_tasks_.push_back(task);
  MessageLoop::current()->PostTask(FROM_HERE,
      method_factory_.NewRunnableMethod(
          &AppCacheStorageImpl::RunOnePendingSimpleTask));
}

void AppCacheStorageImpl::RunOnePendingSimpleTask() {
  DCHECK(!pending_simple_tasks_.empty());
  Task* task = pending_simple_tasks_.front();
  pending_simple_tasks_.pop_front();
  task->Run();
  delete task;
}

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

  if (is_disabled_)
    return NULL;

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

    // We should not keep this reference around.
    cache_thread_ = NULL;

    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 disabling the appcache for this
    // browser session and deleting the directory on disk. The next browser
    // session should start with a clean slate.
    Disable();
    if (!is_incognito_) {
      LOG(INFO) << "Deleting existing appcache data and starting over.";
      AppCacheThread::PostTask(AppCacheThread::db(), FROM_HERE,
          NewRunnableFunction(DeleteDirectory, cache_directory_));
    }
  }
}

}  // namespace appcache