// Copyright 2013 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 "storage/browser/quota/quota_manager.h"

#include <stddef.h>
#include <stdint.h>

#include <algorithm>
#include <functional>
#include <limits>
#include <memory>
#include <utility>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/profiler/scoped_tracker.h"
#include "base/sequenced_task_runner.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/sys_info.h"
#include "base/task_runner_util.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "net/base/url_util.h"
#include "storage/browser/quota/client_usage_tracker.h"
#include "storage/browser/quota/quota_manager_proxy.h"
#include "storage/browser/quota/quota_temporary_storage_evictor.h"
#include "storage/browser/quota/storage_monitor.h"
#include "storage/browser/quota/usage_tracker.h"
#include "storage/common/quota/quota_types.h"

#define UMA_HISTOGRAM_MBYTES(name, sample)          \
  UMA_HISTOGRAM_CUSTOM_COUNTS(                      \
      (name), static_cast<int>((sample) / kMBytes), \
      1, 10 * 1024 * 1024 /* 10TB */, 100)

namespace storage {

namespace {

const int64_t kMBytes = 1024 * 1024;
const int kMinutesInMilliSeconds = 60 * 1000;

const int64_t kReportHistogramInterval = 60 * 60 * 1000;  // 1 hour
const double kTemporaryQuotaRatioToAvail = 1.0 / 3.0;  // 33%

}  // namespace

// Arbitrary for now, but must be reasonably small so that
// in-memory databases can fit.
// TODO(kinuko): Refer SysInfo::AmountOfPhysicalMemory() to determine this.
const int64_t QuotaManager::kIncognitoDefaultQuotaLimit = 100 * kMBytes;

const int64_t QuotaManager::kNoLimit = INT64_MAX;

const int QuotaManager::kPerHostTemporaryPortion = 5;  // 20%

// Cap size for per-host persistent quota determined by the histogram.
// This is a bit lax value because the histogram says nothing about per-host
// persistent storage usage and we determined by global persistent storage
// usage that is less than 10GB for almost all users.
const int64_t QuotaManager::kPerHostPersistentQuotaLimit = 10 * 1024 * kMBytes;

const char QuotaManager::kDatabaseName[] = "QuotaManager";

const int QuotaManager::kThresholdOfErrorsToBeBlacklisted = 3;

// Preserve kMinimumPreserveForSystem disk space for system book-keeping
// when returning the quota to unlimited apps/extensions.
// TODO(kinuko): This should be like 10% of the actual disk space.
// For now we simply use a constant as getting the disk size needs
// platform-dependent code. (http://crbug.com/178976)
int64_t QuotaManager::kMinimumPreserveForSystem = 1024 * kMBytes;

const int QuotaManager::kEvictionIntervalInMilliSeconds =
    30 * kMinutesInMilliSeconds;

const char QuotaManager::kTimeBetweenRepeatedOriginEvictionsHistogram[] =
    "Quota.TimeBetweenRepeatedOriginEvictions";
const char QuotaManager::kEvictedOriginAccessedCountHistogram[] =
    "Quota.EvictedOriginAccessCount";
const char QuotaManager::kEvictedOriginTimeSinceAccessHistogram[] =
    "Quota.EvictedOriginTimeSinceAccess";

// Heuristics: assuming average cloud server allows a few Gigs storage
// on the server side and the storage needs to be shared for user data
// and by multiple apps.
int64_t QuotaManager::kSyncableStorageDefaultHostQuota = 500 * kMBytes;

namespace {

void CountOriginType(const std::set<GURL>& origins,
                     SpecialStoragePolicy* policy,
                     size_t* protected_origins,
                     size_t* unlimited_origins) {
  DCHECK(protected_origins);
  DCHECK(unlimited_origins);
  *protected_origins = 0;
  *unlimited_origins = 0;
  if (!policy)
    return;
  for (std::set<GURL>::const_iterator itr = origins.begin();
       itr != origins.end();
       ++itr) {
    if (policy->IsStorageProtected(*itr))
      ++*protected_origins;
    if (policy->IsStorageUnlimited(*itr))
      ++*unlimited_origins;
  }
}

bool SetTemporaryGlobalOverrideQuotaOnDBThread(int64_t* new_quota,
                                               QuotaDatabase* database) {
  DCHECK(database);
  if (!database->SetQuotaConfigValue(
          QuotaDatabase::kTemporaryQuotaOverrideKey, *new_quota)) {
    *new_quota = -1;
    return false;
  }
  return true;
}

bool GetPersistentHostQuotaOnDBThread(const std::string& host,
                                      int64_t* quota,
                                      QuotaDatabase* database) {
  DCHECK(database);
  database->GetHostQuota(host, kStorageTypePersistent, quota);
  return true;
}

bool SetPersistentHostQuotaOnDBThread(const std::string& host,
                                      int64_t* new_quota,
                                      QuotaDatabase* database) {
  DCHECK(database);
  if (database->SetHostQuota(host, kStorageTypePersistent, *new_quota))
    return true;
  *new_quota = 0;
  return false;
}

bool InitializeOnDBThread(int64_t* temporary_quota_override,
                          int64_t* desired_available_space,
                          QuotaDatabase* database) {
  DCHECK(database);
  database->GetQuotaConfigValue(QuotaDatabase::kTemporaryQuotaOverrideKey,
                                temporary_quota_override);
  database->GetQuotaConfigValue(QuotaDatabase::kDesiredAvailableSpaceKey,
                                desired_available_space);
  return true;
}

bool GetLRUOriginOnDBThread(StorageType type,
                            const std::set<GURL>& exceptions,
                            SpecialStoragePolicy* policy,
                            GURL* url,
                            QuotaDatabase* database) {
  DCHECK(database);
  database->GetLRUOrigin(type, exceptions, policy, url);
  return true;
}

bool DeleteOriginInfoOnDBThread(const GURL& origin,
                                StorageType type,
                                bool is_eviction,
                                QuotaDatabase* database) {
  DCHECK(database);

  base::Time now = base::Time::Now();

  if (is_eviction) {
    QuotaDatabase::OriginInfoTableEntry entry;
    database->GetOriginInfo(origin, type, &entry);
    UMA_HISTOGRAM_COUNTS(QuotaManager::kEvictedOriginAccessedCountHistogram,
                         entry.used_count);
    UMA_HISTOGRAM_LONG_TIMES(
        QuotaManager::kEvictedOriginTimeSinceAccessHistogram,
        now - entry.last_access_time);
  }

  if (!database->DeleteOriginInfo(origin, type))
    return false;

  // If the deletion is not due to an eviction, delete the entry in the eviction
  // table as well due to privacy concerns.
  if (!is_eviction)
    return database->DeleteOriginLastEvictionTime(origin, type);

  base::Time last_eviction_time;
  database->GetOriginLastEvictionTime(origin, type, &last_eviction_time);

  if (last_eviction_time != base::Time()) {
    UMA_HISTOGRAM_LONG_TIMES(
        QuotaManager::kTimeBetweenRepeatedOriginEvictionsHistogram,
        now - last_eviction_time);
  }

  return database->SetOriginLastEvictionTime(origin, type, now);
}

bool InitializeTemporaryOriginsInfoOnDBThread(const std::set<GURL>* origins,
                                              QuotaDatabase* database) {
  DCHECK(database);
  if (database->IsOriginDatabaseBootstrapped())
    return true;

  // Register existing origins with 0 last time access.
  if (database->RegisterInitialOriginInfo(*origins, kStorageTypeTemporary)) {
    database->SetOriginDatabaseBootstrapped(true);
    return true;
  }
  return false;
}

bool UpdateAccessTimeOnDBThread(const GURL& origin,
                                StorageType type,
                                base::Time accessed_time,
                                QuotaDatabase* database) {
  DCHECK(database);
  return database->SetOriginLastAccessTime(origin, type, accessed_time);
}

bool UpdateModifiedTimeOnDBThread(const GURL& origin,
                                  StorageType type,
                                  base::Time modified_time,
                                  QuotaDatabase* database) {
  DCHECK(database);
  return database->SetOriginLastModifiedTime(origin, type, modified_time);
}

int64_t CalculateTemporaryGlobalQuota(int64_t global_limited_usage,
                                      int64_t available_space) {
  DCHECK_GE(global_limited_usage, 0);
  int64_t avail_space = available_space;
  if (avail_space <
      std::numeric_limits<int64_t>::max() - global_limited_usage) {
    // We basically calculate the temporary quota by
    // [available_space + space_used_for_temp] * kTempQuotaRatio,
    // but make sure we'll have no overflow.
    avail_space += global_limited_usage;
  }
  int64_t pool_size = avail_space * kTemporaryQuotaRatioToAvail;
  UMA_HISTOGRAM_MBYTES("Quota.GlobalTemporaryPoolSize", pool_size);
  return pool_size;
}

void DispatchTemporaryGlobalQuotaCallback(
    const QuotaCallback& callback,
    QuotaStatusCode status,
    const UsageAndQuota& usage_and_quota) {
  if (status != kQuotaStatusOk) {
    callback.Run(status, 0);
    return;
  }

  callback.Run(status, CalculateTemporaryGlobalQuota(
      usage_and_quota.global_limited_usage,
      usage_and_quota.available_disk_space));
}

int64_t CalculateQuotaWithDiskSpace(int64_t available_disk_space,
                                    int64_t usage,
                                    int64_t quota) {
  if (available_disk_space < QuotaManager::kMinimumPreserveForSystem) {
    LOG(WARNING)
        << "Running out of disk space for profile."
        << " QuotaManager starts forbidding further quota consumption.";
    return usage;
  }

  if (quota < usage) {
    // No more space; cap the quota to the current usage.
    return usage;
  }

  available_disk_space -= QuotaManager::kMinimumPreserveForSystem;
  if (available_disk_space < quota - usage)
    return available_disk_space + usage;

  return quota;
}

int64_t CalculateTemporaryHostQuota(int64_t host_usage,
                                    int64_t global_quota,
                                    int64_t global_limited_usage) {
  DCHECK_GE(global_limited_usage, 0);
  int64_t host_quota = global_quota / QuotaManager::kPerHostTemporaryPortion;
  if (global_limited_usage > global_quota)
    host_quota = std::min(host_quota, host_usage);
  return host_quota;
}

void DispatchUsageAndQuotaForWebApps(
    StorageType type,
    bool is_incognito,
    bool is_unlimited,
    bool can_query_disk_size,
    const QuotaManager::GetUsageAndQuotaCallback& callback,
    QuotaStatusCode status,
    const UsageAndQuota& usage_and_quota) {
  if (status != kQuotaStatusOk) {
    callback.Run(status, 0, 0);
    return;
  }

  int64_t usage = usage_and_quota.usage;
  int64_t quota = usage_and_quota.quota;

  if (type == kStorageTypeTemporary && !is_unlimited) {
    quota = CalculateTemporaryHostQuota(
        usage, quota, usage_and_quota.global_limited_usage);
  }

  if (is_incognito) {
    quota = std::min(quota, QuotaManager::kIncognitoDefaultQuotaLimit);
    callback.Run(status, usage, quota);
    return;
  }

  // For apps with unlimited permission or can_query_disk_size is true (and not
  // in incognito mode).
  // We assume we can expose the actual disk size for them and cap the quota by
  // the available disk space.
  if (is_unlimited || can_query_disk_size) {
    quota = CalculateQuotaWithDiskSpace(
        usage_and_quota.available_disk_space,
        usage, quota);
  }

  callback.Run(status, usage, quota);

  if (type == kStorageTypeTemporary && !is_unlimited)
    UMA_HISTOGRAM_MBYTES("Quota.QuotaForOrigin", quota);
}

}  // namespace

UsageAndQuota::UsageAndQuota()
    : usage(0),
      global_limited_usage(0),
      quota(0),
      available_disk_space(0) {
}

UsageAndQuota::UsageAndQuota(int64_t usage,
                             int64_t global_limited_usage,
                             int64_t quota,
                             int64_t available_disk_space)
    : usage(usage),
      global_limited_usage(global_limited_usage),
      quota(quota),
      available_disk_space(available_disk_space) {}

class UsageAndQuotaCallbackDispatcher
    : public QuotaTask,
      public base::SupportsWeakPtr<UsageAndQuotaCallbackDispatcher> {
 public:
  explicit UsageAndQuotaCallbackDispatcher(QuotaManager* manager)
      : QuotaTask(manager),
        has_usage_(false),
        has_global_limited_usage_(false),
        has_quota_(false),
        has_available_disk_space_(false),
        status_(kQuotaStatusUnknown),
        usage_and_quota_(-1, -1, -1, -1),
        waiting_callbacks_(1) {}

  ~UsageAndQuotaCallbackDispatcher() override {}

  void WaitForResults(const QuotaManager::UsageAndQuotaCallback& callback) {
    callback_ = callback;
    Start();
  }

  void set_usage(int64_t usage) {
    usage_and_quota_.usage = usage;
    has_usage_ = true;
  }

  void set_global_limited_usage(int64_t global_limited_usage) {
    usage_and_quota_.global_limited_usage = global_limited_usage;
    has_global_limited_usage_ = true;
  }

  void set_quota(int64_t quota) {
    usage_and_quota_.quota = quota;
    has_quota_ = true;
  }

  void set_available_disk_space(int64_t available_disk_space) {
    usage_and_quota_.available_disk_space = available_disk_space;
    has_available_disk_space_ = true;
  }

  UsageCallback GetHostUsageCallback() {
    ++waiting_callbacks_;
    has_usage_ = true;
    return base::Bind(&UsageAndQuotaCallbackDispatcher::DidGetHostUsage,
                      AsWeakPtr());
  }

  UsageCallback GetGlobalLimitedUsageCallback() {
    ++waiting_callbacks_;
    has_global_limited_usage_ = true;
    return base::Bind(
        &UsageAndQuotaCallbackDispatcher::DidGetGlobalLimitedUsage,
        AsWeakPtr());
  }

  QuotaCallback GetQuotaCallback() {
    ++waiting_callbacks_;
    has_quota_ = true;
    return base::Bind(&UsageAndQuotaCallbackDispatcher::DidGetQuota,
                      AsWeakPtr());
  }

  QuotaCallback GetAvailableSpaceCallback() {
    ++waiting_callbacks_;
    has_available_disk_space_ = true;
    return base::Bind(&UsageAndQuotaCallbackDispatcher::DidGetAvailableSpace,
                      AsWeakPtr());
  }

 private:
  void DidGetHostUsage(int64_t usage) {
    if (status_ == kQuotaStatusUnknown)
      status_ = kQuotaStatusOk;
    usage_and_quota_.usage = usage;
    CheckCompleted();
  }

  void DidGetGlobalLimitedUsage(int64_t limited_usage) {
    if (status_ == kQuotaStatusUnknown)
      status_ = kQuotaStatusOk;
    usage_and_quota_.global_limited_usage = limited_usage;
    CheckCompleted();
  }

  void DidGetQuota(QuotaStatusCode status, int64_t quota) {
    if (status_ == kQuotaStatusUnknown || status_ == kQuotaStatusOk)
      status_ = status;
    usage_and_quota_.quota = quota;
    CheckCompleted();
  }

  void DidGetAvailableSpace(QuotaStatusCode status, int64_t space) {
    // crbug.com/349708
    TRACE_EVENT0(
        "io", "UsageAndQuotaCallbackDispatcher::DidGetAvailableSpace");

    DCHECK_GE(space, 0);
    if (status_ == kQuotaStatusUnknown || status_ == kQuotaStatusOk)
      status_ = status;
    usage_and_quota_.available_disk_space = space;
    CheckCompleted();
  }

  void Run() override {
    // We initialize waiting_callbacks to 1 so that we won't run
    // the completion callback until here even some of the callbacks
    // are dispatched synchronously.
    CheckCompleted();
  }

  void Aborted() override {
    callback_.Run(kQuotaErrorAbort, UsageAndQuota());
    DeleteSoon();
  }

  void Completed() override {
    // crbug.com/349708
    TRACE_EVENT0("io", "UsageAndQuotaCallbackDispatcher::Completed");

    DCHECK(!has_usage_ || usage_and_quota_.usage >= 0);
    DCHECK(!has_global_limited_usage_ ||
           usage_and_quota_.global_limited_usage >= 0);
    DCHECK(!has_quota_ || usage_and_quota_.quota >= 0);
    DCHECK(!has_available_disk_space_ ||
           usage_and_quota_.available_disk_space >= 0);

    callback_.Run(status_, usage_and_quota_);
    DeleteSoon();
  }

  void CheckCompleted() {
    if (--waiting_callbacks_ <= 0)
      CallCompleted();
  }

  // For sanity checks, they're checked only when DCHECK is on.
  bool has_usage_;
  bool has_global_limited_usage_;
  bool has_quota_;
  bool has_available_disk_space_;

  QuotaStatusCode status_;
  UsageAndQuota usage_and_quota_;
  QuotaManager::UsageAndQuotaCallback callback_;
  int waiting_callbacks_;

  DISALLOW_COPY_AND_ASSIGN(UsageAndQuotaCallbackDispatcher);
};

class QuotaManager::GetUsageInfoTask : public QuotaTask {
 public:
  GetUsageInfoTask(
      QuotaManager* manager,
      const GetUsageInfoCallback& callback)
      : QuotaTask(manager),
        callback_(callback),
        weak_factory_(this) {
  }

 protected:
  void Run() override {
    // crbug.com/349708
    TRACE_EVENT0("io", "QuotaManager::GetUsageInfoTask::Run");

    remaining_trackers_ = 3;
    // This will populate cached hosts and usage info.
    manager()->GetUsageTracker(kStorageTypeTemporary)->GetGlobalUsage(
        base::Bind(&GetUsageInfoTask::DidGetGlobalUsage,
                   weak_factory_.GetWeakPtr(),
                   kStorageTypeTemporary));
    manager()->GetUsageTracker(kStorageTypePersistent)->GetGlobalUsage(
        base::Bind(&GetUsageInfoTask::DidGetGlobalUsage,
                   weak_factory_.GetWeakPtr(),
                   kStorageTypePersistent));
    manager()->GetUsageTracker(kStorageTypeSyncable)->GetGlobalUsage(
        base::Bind(&GetUsageInfoTask::DidGetGlobalUsage,
                   weak_factory_.GetWeakPtr(),
                   kStorageTypeSyncable));
  }

  void Completed() override {
    // crbug.com/349708
    TRACE_EVENT0("io", "QuotaManager::GetUsageInfoTask::Completed");

    callback_.Run(entries_);
    DeleteSoon();
  }

  void Aborted() override {
    callback_.Run(UsageInfoEntries());
    DeleteSoon();
  }

 private:
  void AddEntries(StorageType type, UsageTracker* tracker) {
    std::map<std::string, int64_t> host_usage;
    tracker->GetCachedHostsUsage(&host_usage);
    for (std::map<std::string, int64_t>::const_iterator iter =
             host_usage.begin();
         iter != host_usage.end(); ++iter) {
      entries_.push_back(UsageInfo(iter->first, type, iter->second));
    }
    if (--remaining_trackers_ == 0)
      CallCompleted();
  }

  void DidGetGlobalUsage(StorageType type, int64_t, int64_t) {
    DCHECK(manager()->GetUsageTracker(type));
    AddEntries(type, manager()->GetUsageTracker(type));
  }

  QuotaManager* manager() const {
    return static_cast<QuotaManager*>(observer());
  }

  GetUsageInfoCallback callback_;
  UsageInfoEntries entries_;
  int remaining_trackers_;
  base::WeakPtrFactory<GetUsageInfoTask> weak_factory_;

  DISALLOW_COPY_AND_ASSIGN(GetUsageInfoTask);
};

class QuotaManager::OriginDataDeleter : public QuotaTask {
 public:
  OriginDataDeleter(QuotaManager* manager,
                    const GURL& origin,
                    StorageType type,
                    int quota_client_mask,
                    bool is_eviction,
                    const StatusCallback& callback)
      : QuotaTask(manager),
        origin_(origin),
        type_(type),
        quota_client_mask_(quota_client_mask),
        error_count_(0),
        remaining_clients_(-1),
        skipped_clients_(0),
        is_eviction_(is_eviction),
        callback_(callback),
        weak_factory_(this) {}

 protected:
  void Run() override {
    error_count_ = 0;
    remaining_clients_ = manager()->clients_.size();
    for (QuotaClientList::iterator iter = manager()->clients_.begin();
         iter != manager()->clients_.end(); ++iter) {
      if (quota_client_mask_ & (*iter)->id()) {
        (*iter)->DeleteOriginData(
            origin_, type_,
            base::Bind(&OriginDataDeleter::DidDeleteOriginData,
                       weak_factory_.GetWeakPtr()));
      } else {
        ++skipped_clients_;
        if (--remaining_clients_ == 0)
          CallCompleted();
      }
    }
  }

  void Completed() override {
    if (error_count_ == 0) {
      // crbug.com/349708
      TRACE_EVENT0("io", "QuotaManager::OriginDataDeleter::Completed Ok");

      // Only remove the entire origin if we didn't skip any client types.
      if (skipped_clients_ == 0)
        manager()->DeleteOriginFromDatabase(origin_, type_, is_eviction_);
      callback_.Run(kQuotaStatusOk);
    } else {
      // crbug.com/349708
      TRACE_EVENT0("io", "QuotaManager::OriginDataDeleter::Completed Error");

      callback_.Run(kQuotaErrorInvalidModification);
    }
    DeleteSoon();
  }

  void Aborted() override {
    callback_.Run(kQuotaErrorAbort);
    DeleteSoon();
  }

 private:
  void DidDeleteOriginData(QuotaStatusCode status) {
    DCHECK_GT(remaining_clients_, 0);

    if (status != kQuotaStatusOk)
      ++error_count_;

    if (--remaining_clients_ == 0)
      CallCompleted();
  }

  QuotaManager* manager() const {
    return static_cast<QuotaManager*>(observer());
  }

  GURL origin_;
  StorageType type_;
  int quota_client_mask_;
  int error_count_;
  int remaining_clients_;
  int skipped_clients_;
  bool is_eviction_;
  StatusCallback callback_;

  base::WeakPtrFactory<OriginDataDeleter> weak_factory_;
  DISALLOW_COPY_AND_ASSIGN(OriginDataDeleter);
};

class QuotaManager::HostDataDeleter : public QuotaTask {
 public:
  HostDataDeleter(QuotaManager* manager,
                  const std::string& host,
                  StorageType type,
                  int quota_client_mask,
                  const StatusCallback& callback)
      : QuotaTask(manager),
        host_(host),
        type_(type),
        quota_client_mask_(quota_client_mask),
        error_count_(0),
        remaining_clients_(-1),
        remaining_deleters_(-1),
        callback_(callback),
        weak_factory_(this) {}

 protected:
  void Run() override {
    error_count_ = 0;
    remaining_clients_ = manager()->clients_.size();
    for (QuotaClientList::iterator iter = manager()->clients_.begin();
         iter != manager()->clients_.end(); ++iter) {
      (*iter)->GetOriginsForHost(
          type_, host_,
          base::Bind(&HostDataDeleter::DidGetOriginsForHost,
                     weak_factory_.GetWeakPtr()));
    }
  }

  void Completed() override {
    if (error_count_ == 0) {
      // crbug.com/349708
      TRACE_EVENT0("io", "QuotaManager::HostDataDeleter::Completed Ok");

      callback_.Run(kQuotaStatusOk);
    } else {
      // crbug.com/349708
      TRACE_EVENT0("io", "QuotaManager::HostDataDeleter::Completed Error");

      callback_.Run(kQuotaErrorInvalidModification);
    }
    DeleteSoon();
  }

  void Aborted() override {
    callback_.Run(kQuotaErrorAbort);
    DeleteSoon();
  }

 private:
  void DidGetOriginsForHost(const std::set<GURL>& origins) {
    DCHECK_GT(remaining_clients_, 0);

    origins_.insert(origins.begin(), origins.end());

    if (--remaining_clients_ == 0) {
      if (!origins_.empty())
        ScheduleOriginsDeletion();
      else
        CallCompleted();
    }
  }

  void ScheduleOriginsDeletion() {
    remaining_deleters_ = origins_.size();
    for (std::set<GURL>::const_iterator p = origins_.begin();
         p != origins_.end();
         ++p) {
      OriginDataDeleter* deleter = new OriginDataDeleter(
          manager(), *p, type_, quota_client_mask_, false,
          base::Bind(&HostDataDeleter::DidDeleteOriginData,
                     weak_factory_.GetWeakPtr()));
      deleter->Start();
    }
  }

  void DidDeleteOriginData(QuotaStatusCode status) {
    DCHECK_GT(remaining_deleters_, 0);

    if (status != kQuotaStatusOk)
      ++error_count_;

    if (--remaining_deleters_ == 0)
      CallCompleted();
  }

  QuotaManager* manager() const {
    return static_cast<QuotaManager*>(observer());
  }

  std::string host_;
  StorageType type_;
  int quota_client_mask_;
  std::set<GURL> origins_;
  int error_count_;
  int remaining_clients_;
  int remaining_deleters_;
  StatusCallback callback_;

  base::WeakPtrFactory<HostDataDeleter> weak_factory_;
  DISALLOW_COPY_AND_ASSIGN(HostDataDeleter);
};

class QuotaManager::GetModifiedSinceHelper {
 public:
  bool GetModifiedSinceOnDBThread(StorageType type,
                                  base::Time modified_since,
                                  QuotaDatabase* database) {
    DCHECK(database);
    return database->GetOriginsModifiedSince(type, &origins_, modified_since);
  }

  void DidGetModifiedSince(const base::WeakPtr<QuotaManager>& manager,
                           const GetOriginsCallback& callback,
                           StorageType type,
                           bool success) {
    if (!manager) {
      // The operation was aborted.
      callback.Run(std::set<GURL>(), type);
      return;
    }
    manager->DidDatabaseWork(success);
    callback.Run(origins_, type);
  }

 private:
  std::set<GURL> origins_;
};

class QuotaManager::DumpQuotaTableHelper {
 public:
  bool DumpQuotaTableOnDBThread(QuotaDatabase* database) {
    DCHECK(database);
    return database->DumpQuotaTable(
        base::Bind(&DumpQuotaTableHelper::AppendEntry, base::Unretained(this)));
  }

  void DidDumpQuotaTable(const base::WeakPtr<QuotaManager>& manager,
                         const DumpQuotaTableCallback& callback,
                         bool success) {
    if (!manager) {
      // The operation was aborted.
      callback.Run(QuotaTableEntries());
      return;
    }
    manager->DidDatabaseWork(success);
    callback.Run(entries_);
  }

 private:
  bool AppendEntry(const QuotaTableEntry& entry) {
    entries_.push_back(entry);
    return true;
  }

  QuotaTableEntries entries_;
};

class QuotaManager::DumpOriginInfoTableHelper {
 public:
  bool DumpOriginInfoTableOnDBThread(QuotaDatabase* database) {
    DCHECK(database);
    return database->DumpOriginInfoTable(
        base::Bind(&DumpOriginInfoTableHelper::AppendEntry,
                   base::Unretained(this)));
  }

  void DidDumpOriginInfoTable(const base::WeakPtr<QuotaManager>& manager,
                              const DumpOriginInfoTableCallback& callback,
                              bool success) {
    if (!manager) {
      // The operation was aborted.
      callback.Run(OriginInfoTableEntries());
      return;
    }
    manager->DidDatabaseWork(success);
    callback.Run(entries_);
  }

 private:
  bool AppendEntry(const OriginInfoTableEntry& entry) {
    entries_.push_back(entry);
    return true;
  }

  OriginInfoTableEntries entries_;
};

// QuotaManager ---------------------------------------------------------------

QuotaManager::QuotaManager(
    bool is_incognito,
    const base::FilePath& profile_path,
    const scoped_refptr<base::SingleThreadTaskRunner>& io_thread,
    const scoped_refptr<base::SequencedTaskRunner>& db_thread,
    const scoped_refptr<SpecialStoragePolicy>& special_storage_policy)
    : is_incognito_(is_incognito),
      profile_path_(profile_path),
      proxy_(new QuotaManagerProxy(this, io_thread)),
      db_disabled_(false),
      eviction_disabled_(false),
      io_thread_(io_thread),
      db_thread_(db_thread),
      is_getting_eviction_origin_(false),
      temporary_quota_initialized_(false),
      temporary_quota_override_(-1),
      special_storage_policy_(special_storage_policy),
      get_volume_info_fn_(&QuotaManager::GetVolumeInfo),
      storage_monitor_(new StorageMonitor(this)),
      weak_factory_(this) {}

void QuotaManager::GetUsageInfo(const GetUsageInfoCallback& callback) {
  LazyInitialize();
  GetUsageInfoTask* get_usage_info = new GetUsageInfoTask(this, callback);
  get_usage_info->Start();
}

void QuotaManager::GetUsageAndQuotaForWebApps(
    const GURL& origin,
    StorageType type,
    const GetUsageAndQuotaCallback& callback) {
  // TODO(pkasting): Remove ScopedTracker below once crbug.com/477117 is fixed.
  tracked_objects::ScopedTracker tracking_profile(
      FROM_HERE_WITH_EXPLICIT_FUNCTION(
          "477117 QuotaManager::GetUsageAndQuotaForWebApps"));
  if (type != kStorageTypeTemporary &&
      type != kStorageTypePersistent &&
      type != kStorageTypeSyncable) {
    callback.Run(kQuotaErrorNotSupported, 0, 0);
    return;
  }

  LazyInitialize();

  bool unlimited = IsStorageUnlimited(origin, type);
  bool can_query_disk_size = CanQueryDiskSize(origin);

  UsageAndQuotaCallbackDispatcher* dispatcher =
      new UsageAndQuotaCallbackDispatcher(this);

  if (unlimited) {
    dispatcher->set_quota(kNoLimit);
  } else {
    if (type == kStorageTypeTemporary) {
      GetUsageTracker(type)->GetGlobalLimitedUsage(
          dispatcher->GetGlobalLimitedUsageCallback());
      GetTemporaryGlobalQuota(dispatcher->GetQuotaCallback());
    } else if (type == kStorageTypePersistent) {
      GetPersistentHostQuota(net::GetHostOrSpecFromURL(origin),
                             dispatcher->GetQuotaCallback());
    } else {
      dispatcher->set_quota(kSyncableStorageDefaultHostQuota);
    }
  }

  DCHECK(GetUsageTracker(type));
  GetUsageTracker(type)->GetHostUsage(net::GetHostOrSpecFromURL(origin),
                                      dispatcher->GetHostUsageCallback());

  if (!is_incognito_ && (unlimited || can_query_disk_size))
    GetAvailableSpace(dispatcher->GetAvailableSpaceCallback());

  dispatcher->WaitForResults(base::Bind(
      &DispatchUsageAndQuotaForWebApps,
      type, is_incognito_, unlimited, can_query_disk_size,
      callback));
}

void QuotaManager::GetUsageAndQuota(
    const GURL& origin, StorageType type,
    const GetUsageAndQuotaCallback& callback) {
  DCHECK(origin == origin.GetOrigin());

  if (IsStorageUnlimited(origin, type)) {
    callback.Run(kQuotaStatusOk, 0, kNoLimit);
    return;
  }

  GetUsageAndQuotaForWebApps(origin, type, callback);
}

void QuotaManager::NotifyStorageAccessed(
    QuotaClient::ID client_id,
    const GURL& origin, StorageType type) {
  DCHECK(origin == origin.GetOrigin());
  NotifyStorageAccessedInternal(client_id, origin, type, base::Time::Now());
}

void QuotaManager::NotifyStorageModified(QuotaClient::ID client_id,
                                         const GURL& origin,
                                         StorageType type,
                                         int64_t delta) {
  DCHECK(origin == origin.GetOrigin());
  NotifyStorageModifiedInternal(client_id, origin, type, delta,
                                base::Time::Now());
}

void QuotaManager::NotifyOriginInUse(const GURL& origin) {
  DCHECK(io_thread_->BelongsToCurrentThread());
  origins_in_use_[origin]++;
}

void QuotaManager::NotifyOriginNoLongerInUse(const GURL& origin) {
  DCHECK(io_thread_->BelongsToCurrentThread());
  DCHECK(IsOriginInUse(origin));
  int& count = origins_in_use_[origin];
  if (--count == 0)
    origins_in_use_.erase(origin);
}

void QuotaManager::SetUsageCacheEnabled(QuotaClient::ID client_id,
                                        const GURL& origin,
                                        StorageType type,
                                        bool enabled) {
  LazyInitialize();
  DCHECK(GetUsageTracker(type));
  GetUsageTracker(type)->SetUsageCacheEnabled(client_id, origin, enabled);
}

void QuotaManager::SetTemporaryStorageEvictionPolicy(
    std::unique_ptr<QuotaEvictionPolicy> policy) {
  temporary_storage_eviction_policy_ = std::move(policy);
}

void QuotaManager::DeleteOriginData(const GURL& origin,
                                    StorageType type,
                                    int quota_client_mask,
                                    const StatusCallback& callback) {
  DeleteOriginDataInternal(origin, type, quota_client_mask, false, callback);
}

void QuotaManager::DeleteHostData(const std::string& host,
                                  StorageType type,
                                  int quota_client_mask,
                                  const StatusCallback& callback) {
  LazyInitialize();

  if (host.empty() || clients_.empty()) {
    callback.Run(kQuotaStatusOk);
    return;
  }

  HostDataDeleter* deleter =
      new HostDataDeleter(this, host, type, quota_client_mask, callback);
  deleter->Start();
}

void QuotaManager::GetAvailableSpace(const AvailableSpaceCallback& callback) {
  if (!available_space_callbacks_.Add(callback))
    return;
  // crbug.com/349708
  TRACE_EVENT0("io", "QuotaManager::GetAvailableSpace");

  PostTaskAndReplyWithResult(
      db_thread_.get(),
      FROM_HERE,
      base::Bind(&QuotaManager::CallGetAmountOfFreeDiskSpace,
                 get_volume_info_fn_, profile_path_),
      base::Bind(&QuotaManager::DidGetAvailableSpace,
                 weak_factory_.GetWeakPtr()));
}

void QuotaManager::GetTemporaryGlobalQuota(const QuotaCallback& callback) {
  LazyInitialize();
  if (!temporary_quota_initialized_) {
    db_initialization_callbacks_.Add(base::Bind(
        &QuotaManager::GetTemporaryGlobalQuota,
        weak_factory_.GetWeakPtr(), callback));
    return;
  }

  if (temporary_quota_override_ > 0) {
    callback.Run(kQuotaStatusOk, temporary_quota_override_);
    return;
  }

  UsageAndQuotaCallbackDispatcher* dispatcher =
      new UsageAndQuotaCallbackDispatcher(this);
  GetUsageTracker(kStorageTypeTemporary)->
      GetGlobalLimitedUsage(dispatcher->GetGlobalLimitedUsageCallback());
  GetAvailableSpace(dispatcher->GetAvailableSpaceCallback());
  dispatcher->WaitForResults(
      base::Bind(&DispatchTemporaryGlobalQuotaCallback, callback));
}

void QuotaManager::SetTemporaryGlobalOverrideQuota(
    int64_t new_quota,
    const QuotaCallback& callback) {
  LazyInitialize();

  if (new_quota < 0) {
    if (!callback.is_null())
      callback.Run(kQuotaErrorInvalidModification, -1);
    return;
  }

  if (db_disabled_) {
    if (!callback.is_null())
      callback.Run(kQuotaErrorInvalidAccess, -1);
    return;
  }

  int64_t* new_quota_ptr = new int64_t(new_quota);
  PostTaskAndReplyWithResultForDBThread(
      FROM_HERE,
      base::Bind(&SetTemporaryGlobalOverrideQuotaOnDBThread,
                 base::Unretained(new_quota_ptr)),
      base::Bind(&QuotaManager::DidSetTemporaryGlobalOverrideQuota,
                 weak_factory_.GetWeakPtr(),
                 callback,
                 base::Owned(new_quota_ptr)));
}

void QuotaManager::GetPersistentHostQuota(const std::string& host,
                                          const QuotaCallback& callback) {
  LazyInitialize();
  if (host.empty()) {
    // This could happen if we are called on file:///.
    // TODO(kinuko) We may want to respect --allow-file-access-from-files
    // command line switch.
    callback.Run(kQuotaStatusOk, 0);
    return;
  }

  if (!persistent_host_quota_callbacks_.Add(host, callback))
    return;

  int64_t* quota_ptr = new int64_t(0);
  PostTaskAndReplyWithResultForDBThread(
      FROM_HERE,
      base::Bind(&GetPersistentHostQuotaOnDBThread,
                 host,
                 base::Unretained(quota_ptr)),
      base::Bind(&QuotaManager::DidGetPersistentHostQuota,
                 weak_factory_.GetWeakPtr(),
                 host,
                 base::Owned(quota_ptr)));
}

void QuotaManager::SetPersistentHostQuota(const std::string& host,
                                          int64_t new_quota,
                                          const QuotaCallback& callback) {
  LazyInitialize();
  if (host.empty()) {
    // This could happen if we are called on file:///.
    callback.Run(kQuotaErrorNotSupported, 0);
    return;
  }

  if (new_quota < 0) {
    callback.Run(kQuotaErrorInvalidModification, -1);
    return;
  }

  if (kPerHostPersistentQuotaLimit < new_quota) {
    // Cap the requested size at the per-host quota limit.
    new_quota = kPerHostPersistentQuotaLimit;
  }

  if (db_disabled_) {
    callback.Run(kQuotaErrorInvalidAccess, -1);
    return;
  }

  int64_t* new_quota_ptr = new int64_t(new_quota);
  PostTaskAndReplyWithResultForDBThread(
      FROM_HERE,
      base::Bind(&SetPersistentHostQuotaOnDBThread,
                 host,
                 base::Unretained(new_quota_ptr)),
      base::Bind(&QuotaManager::DidSetPersistentHostQuota,
                 weak_factory_.GetWeakPtr(),
                 host,
                 callback,
                 base::Owned(new_quota_ptr)));
}

void QuotaManager::GetGlobalUsage(StorageType type,
                                  const GlobalUsageCallback& callback) {
  LazyInitialize();
  DCHECK(GetUsageTracker(type));
  GetUsageTracker(type)->GetGlobalUsage(callback);
}

void QuotaManager::GetHostUsage(const std::string& host,
                                StorageType type,
                                const UsageCallback& callback) {
  LazyInitialize();
  DCHECK(GetUsageTracker(type));
  GetUsageTracker(type)->GetHostUsage(host, callback);
}

void QuotaManager::GetHostUsage(const std::string& host,
                                StorageType type,
                                QuotaClient::ID client_id,
                                const UsageCallback& callback) {
  LazyInitialize();
  DCHECK(GetUsageTracker(type));
  ClientUsageTracker* tracker =
      GetUsageTracker(type)->GetClientTracker(client_id);
  if (!tracker) {
    callback.Run(0);
    return;
  }
  tracker->GetHostUsage(host, callback);
}

bool QuotaManager::IsTrackingHostUsage(StorageType type,
                                       QuotaClient::ID client_id) const {
  UsageTracker* tracker = GetUsageTracker(type);
  return tracker && tracker->GetClientTracker(client_id);
}

void QuotaManager::GetStatistics(
    std::map<std::string, std::string>* statistics) {
  DCHECK(statistics);
  if (temporary_storage_evictor_) {
    std::map<std::string, int64_t> stats;
    temporary_storage_evictor_->GetStatistics(&stats);
    for (std::map<std::string, int64_t>::iterator p = stats.begin();
         p != stats.end(); ++p) {
      (*statistics)[p->first] = base::Int64ToString(p->second);
    }
  }
}

bool QuotaManager::IsStorageUnlimited(const GURL& origin,
                                      StorageType type) const {
  // For syncable storage we should always enforce quota (since the
  // quota must be capped by the server limit).
  if (type == kStorageTypeSyncable)
    return false;
  if (type == kStorageTypeQuotaNotManaged)
    return true;
  return special_storage_policy_.get() &&
         special_storage_policy_->IsStorageUnlimited(origin);
}

void QuotaManager::GetOriginsModifiedSince(StorageType type,
                                           base::Time modified_since,
                                           const GetOriginsCallback& callback) {
  LazyInitialize();
  GetModifiedSinceHelper* helper = new GetModifiedSinceHelper;
  PostTaskAndReplyWithResultForDBThread(
      FROM_HERE,
      base::Bind(&GetModifiedSinceHelper::GetModifiedSinceOnDBThread,
                 base::Unretained(helper),
                 type,
                 modified_since),
      base::Bind(&GetModifiedSinceHelper::DidGetModifiedSince,
                 base::Owned(helper),
                 weak_factory_.GetWeakPtr(),
                 callback,
                 type));
}

bool QuotaManager::ResetUsageTracker(StorageType type) {
  DCHECK(GetUsageTracker(type));
  if (GetUsageTracker(type)->IsWorking())
    return false;
  switch (type) {
    case kStorageTypeTemporary:
      temporary_usage_tracker_.reset(new UsageTracker(
          clients_, kStorageTypeTemporary, special_storage_policy_.get(),
          storage_monitor_.get()));
      return true;
    case kStorageTypePersistent:
      persistent_usage_tracker_.reset(new UsageTracker(
          clients_, kStorageTypePersistent, special_storage_policy_.get(),
          storage_monitor_.get()));
      return true;
    case kStorageTypeSyncable:
      syncable_usage_tracker_.reset(new UsageTracker(
          clients_, kStorageTypeSyncable, special_storage_policy_.get(),
          storage_monitor_.get()));
      return true;
    default:
      NOTREACHED();
  }
  return true;
}

void QuotaManager::AddStorageObserver(
    StorageObserver* observer, const StorageObserver::MonitorParams& params) {
  DCHECK(observer);
  storage_monitor_->AddObserver(observer, params);
}

void QuotaManager::RemoveStorageObserver(StorageObserver* observer) {
  DCHECK(observer);
  storage_monitor_->RemoveObserver(observer);
}

void QuotaManager::RemoveStorageObserverForFilter(
    StorageObserver* observer, const StorageObserver::Filter& filter) {
  DCHECK(observer);
  storage_monitor_->RemoveObserverForFilter(observer, filter);
}

QuotaManager::~QuotaManager() {
  proxy_->manager_ = NULL;
  for (auto* client : clients_)
    client->OnQuotaManagerDestroyed();
  if (database_)
    db_thread_->DeleteSoon(FROM_HERE, database_.release());
}

QuotaManager::EvictionContext::EvictionContext()
    : evicted_type(kStorageTypeUnknown) {
}

QuotaManager::EvictionContext::~EvictionContext() {
}

void QuotaManager::LazyInitialize() {
  DCHECK(io_thread_->BelongsToCurrentThread());
  if (database_) {
    // Initialization seems to be done already.
    return;
  }

  // Use an empty path to open an in-memory only databse for incognito.
  database_.reset(new QuotaDatabase(is_incognito_ ? base::FilePath() :
      profile_path_.AppendASCII(kDatabaseName)));

  temporary_usage_tracker_.reset(new UsageTracker(
      clients_, kStorageTypeTemporary, special_storage_policy_.get(),
      storage_monitor_.get()));
  persistent_usage_tracker_.reset(new UsageTracker(
      clients_, kStorageTypePersistent, special_storage_policy_.get(),
      storage_monitor_.get()));
  syncable_usage_tracker_.reset(new UsageTracker(
      clients_, kStorageTypeSyncable, special_storage_policy_.get(),
      storage_monitor_.get()));

  int64_t* temporary_quota_override = new int64_t(-1);
  int64_t* desired_available_space = new int64_t(-1);
  PostTaskAndReplyWithResultForDBThread(
      FROM_HERE,
      base::Bind(&InitializeOnDBThread,
                 base::Unretained(temporary_quota_override),
                 base::Unretained(desired_available_space)),
      base::Bind(&QuotaManager::DidInitialize,
                 weak_factory_.GetWeakPtr(),
                 base::Owned(temporary_quota_override),
                 base::Owned(desired_available_space)));
}

void QuotaManager::RegisterClient(QuotaClient* client) {
  DCHECK(!database_.get());
  clients_.push_back(client);
}

UsageTracker* QuotaManager::GetUsageTracker(StorageType type) const {
  switch (type) {
    case kStorageTypeTemporary:
      return temporary_usage_tracker_.get();
    case kStorageTypePersistent:
      return persistent_usage_tracker_.get();
    case kStorageTypeSyncable:
      return syncable_usage_tracker_.get();
    case kStorageTypeQuotaNotManaged:
      return NULL;
    case kStorageTypeUnknown:
      NOTREACHED();
  }
  return NULL;
}

void QuotaManager::GetCachedOrigins(
    StorageType type, std::set<GURL>* origins) {
  DCHECK(origins);
  LazyInitialize();
  DCHECK(GetUsageTracker(type));
  GetUsageTracker(type)->GetCachedOrigins(origins);
}

void QuotaManager::NotifyStorageAccessedInternal(
    QuotaClient::ID client_id,
    const GURL& origin, StorageType type,
    base::Time accessed_time) {
  LazyInitialize();
  if (type == kStorageTypeTemporary && is_getting_eviction_origin_) {
    // Record the accessed origins while GetLRUOrigin task is runing
    // to filter out them from eviction.
    access_notified_origins_.insert(origin);
  }

  if (db_disabled_)
    return;
  PostTaskAndReplyWithResultForDBThread(
      FROM_HERE,
      base::Bind(&UpdateAccessTimeOnDBThread, origin, type, accessed_time),
      base::Bind(&QuotaManager::DidDatabaseWork,
                 weak_factory_.GetWeakPtr()));
}

void QuotaManager::NotifyStorageModifiedInternal(QuotaClient::ID client_id,
                                                 const GURL& origin,
                                                 StorageType type,
                                                 int64_t delta,
                                                 base::Time modified_time) {
  LazyInitialize();
  DCHECK(GetUsageTracker(type));
  GetUsageTracker(type)->UpdateUsageCache(client_id, origin, delta);

  PostTaskAndReplyWithResultForDBThread(
      FROM_HERE,
      base::Bind(&UpdateModifiedTimeOnDBThread, origin, type, modified_time),
      base::Bind(&QuotaManager::DidDatabaseWork,
                 weak_factory_.GetWeakPtr()));
}

void QuotaManager::DumpQuotaTable(const DumpQuotaTableCallback& callback) {
  DumpQuotaTableHelper* helper = new DumpQuotaTableHelper;
  PostTaskAndReplyWithResultForDBThread(
      FROM_HERE,
      base::Bind(&DumpQuotaTableHelper::DumpQuotaTableOnDBThread,
                 base::Unretained(helper)),
      base::Bind(&DumpQuotaTableHelper::DidDumpQuotaTable,
                 base::Owned(helper),
                 weak_factory_.GetWeakPtr(),
                 callback));
}

void QuotaManager::DumpOriginInfoTable(
    const DumpOriginInfoTableCallback& callback) {
  DumpOriginInfoTableHelper* helper = new DumpOriginInfoTableHelper;
  PostTaskAndReplyWithResultForDBThread(
      FROM_HERE,
      base::Bind(&DumpOriginInfoTableHelper::DumpOriginInfoTableOnDBThread,
                 base::Unretained(helper)),
      base::Bind(&DumpOriginInfoTableHelper::DidDumpOriginInfoTable,
                 base::Owned(helper),
                 weak_factory_.GetWeakPtr(),
                 callback));
}

void QuotaManager::StartEviction() {
  DCHECK(!temporary_storage_evictor_.get());
  temporary_storage_evictor_.reset(new QuotaTemporaryStorageEvictor(
      this, kEvictionIntervalInMilliSeconds));
  if (desired_available_space_ >= 0)
    temporary_storage_evictor_->set_min_available_disk_space_to_start_eviction(
        desired_available_space_);
  temporary_storage_evictor_->Start();
}

void QuotaManager::DeleteOriginFromDatabase(const GURL& origin,
                                            StorageType type,
                                            bool is_eviction) {
  LazyInitialize();
  if (db_disabled_)
    return;

  PostTaskAndReplyWithResultForDBThread(
      FROM_HERE,
      base::Bind(&DeleteOriginInfoOnDBThread, origin, type, is_eviction),
      base::Bind(&QuotaManager::DidDatabaseWork, weak_factory_.GetWeakPtr()));
}

void QuotaManager::DidOriginDataEvicted(QuotaStatusCode status) {
  DCHECK(io_thread_->BelongsToCurrentThread());

  // We only try evict origins that are not in use, so basically
  // deletion attempt for eviction should not fail.  Let's record
  // the origin if we get error and exclude it from future eviction
  // if the error happens consistently (> kThresholdOfErrorsToBeBlacklisted).
  if (status != kQuotaStatusOk)
    origins_in_error_[eviction_context_.evicted_origin]++;

  eviction_context_.evict_origin_data_callback.Run(status);
  eviction_context_.evict_origin_data_callback.Reset();
}

void QuotaManager::DeleteOriginDataInternal(const GURL& origin,
                                            StorageType type,
                                            int quota_client_mask,
                                            bool is_eviction,
                                            const StatusCallback& callback) {
  LazyInitialize();

  if (origin.is_empty() || clients_.empty()) {
    callback.Run(kQuotaStatusOk);
    return;
  }

  DCHECK(origin == origin.GetOrigin());
  OriginDataDeleter* deleter = new OriginDataDeleter(
      this, origin, type, quota_client_mask, is_eviction, callback);
  deleter->Start();
}

void QuotaManager::ReportHistogram() {
  DCHECK(!is_incognito_);
  GetGlobalUsage(kStorageTypeTemporary,
                 base::Bind(
                     &QuotaManager::DidGetTemporaryGlobalUsageForHistogram,
                     weak_factory_.GetWeakPtr()));
}

void QuotaManager::DidGetTemporaryGlobalUsageForHistogram(
    int64_t usage,
    int64_t unlimited_usage) {
  UMA_HISTOGRAM_MBYTES("Quota.GlobalUsageOfTemporaryStorage", usage);

  std::set<GURL> origins;
  GetCachedOrigins(kStorageTypeTemporary, &origins);

  size_t num_origins = origins.size();
  size_t protected_origins = 0;
  size_t unlimited_origins = 0;
  CountOriginType(origins,
                  special_storage_policy_.get(),
                  &protected_origins,
                  &unlimited_origins);
  UMA_HISTOGRAM_COUNTS("Quota.NumberOfTemporaryStorageOrigins",
                       num_origins);
  UMA_HISTOGRAM_COUNTS("Quota.NumberOfProtectedTemporaryStorageOrigins",
                       protected_origins);
  UMA_HISTOGRAM_COUNTS("Quota.NumberOfUnlimitedTemporaryStorageOrigins",
                       unlimited_origins);

  GetGlobalUsage(kStorageTypePersistent,
                 base::Bind(
                     &QuotaManager::DidGetPersistentGlobalUsageForHistogram,
                     weak_factory_.GetWeakPtr()));
}

void QuotaManager::DidGetPersistentGlobalUsageForHistogram(
    int64_t usage,
    int64_t unlimited_usage) {
  UMA_HISTOGRAM_MBYTES("Quota.GlobalUsageOfPersistentStorage", usage);

  std::set<GURL> origins;
  GetCachedOrigins(kStorageTypePersistent, &origins);

  size_t num_origins = origins.size();
  size_t protected_origins = 0;
  size_t unlimited_origins = 0;
  CountOriginType(origins,
                  special_storage_policy_.get(),
                  &protected_origins,
                  &unlimited_origins);
  UMA_HISTOGRAM_COUNTS("Quota.NumberOfPersistentStorageOrigins",
                       num_origins);
  UMA_HISTOGRAM_COUNTS("Quota.NumberOfProtectedPersistentStorageOrigins",
                       protected_origins);
  UMA_HISTOGRAM_COUNTS("Quota.NumberOfUnlimitedPersistentStorageOrigins",
                       unlimited_origins);

  // We DumpOriginInfoTable last to ensure the trackers caches are loaded.
  DumpOriginInfoTable(
      base::Bind(&QuotaManager::DidDumpOriginInfoTableForHistogram,
                 weak_factory_.GetWeakPtr()));
}

void QuotaManager::DidDumpOriginInfoTableForHistogram(
    const OriginInfoTableEntries& entries) {
  using UsageMap = std::map<GURL, int64_t>;
  UsageMap usage_map;
  GetUsageTracker(kStorageTypeTemporary)->GetCachedOriginsUsage(&usage_map);
  base::Time now = base::Time::Now();
  for (const auto& info : entries) {
    if (info.type != kStorageTypeTemporary)
      continue;

    // Ignore stale database entries. If there is no map entry, the origin's
    // data has been deleted.
    UsageMap::const_iterator found = usage_map.find(info.origin);
    if (found == usage_map.end() || found->second == 0)
      continue;

    base::TimeDelta age = now - std::max(info.last_access_time,
                                         info.last_modified_time);
    UMA_HISTOGRAM_COUNTS_1000("Quota.AgeOfOriginInDays", age.InDays());

    int64_t kilobytes = std::max(found->second / INT64_C(1024), INT64_C(1));
    base::Histogram::FactoryGet(
        "Quota.AgeOfDataInDays", 1, 1000, 50,
        base::HistogramBase::kUmaTargetedHistogramFlag)->
            AddCount(age.InDays(),
                     base::saturated_cast<int>(kilobytes));
  }
}

std::set<GURL> QuotaManager::GetEvictionOriginExceptions(
    const std::set<GURL>& extra_exceptions) {
  std::set<GURL> exceptions = extra_exceptions;
  for (const auto& p : origins_in_use_) {
    if (p.second > 0)
      exceptions.insert(p.first);
  }

  for (const auto& p : origins_in_error_) {
    if (p.second > QuotaManager::kThresholdOfErrorsToBeBlacklisted)
      exceptions.insert(p.first);
  }

  return exceptions;
}

void QuotaManager::DidGetEvictionOrigin(const GetOriginCallback& callback,
                                        const GURL& origin) {
  // Make sure the returned origin is (still) not in the origin_in_use_ set
  // and has not been accessed since we posted the task.
  if (base::ContainsKey(origins_in_use_, origin) ||
      base::ContainsKey(access_notified_origins_, origin)) {
    callback.Run(GURL());
  } else {
    callback.Run(origin);
  }
  access_notified_origins_.clear();

  is_getting_eviction_origin_ = false;
}

void QuotaManager::GetEvictionOrigin(StorageType type,
                                     const std::set<GURL>& extra_exceptions,
                                     int64_t global_quota,
                                     const GetOriginCallback& callback) {
  LazyInitialize();
  // This must not be called while there's an in-flight task.
  DCHECK(!is_getting_eviction_origin_);
  is_getting_eviction_origin_ = true;

  GetOriginCallback did_get_origin_callback =
      base::Bind(&QuotaManager::DidGetEvictionOrigin,
                 weak_factory_.GetWeakPtr(), callback);

  if (type == kStorageTypeTemporary && temporary_storage_eviction_policy_) {
    std::map<GURL, int64_t> usage_map;
    // The cached origins are populated by the prior call to
    // GetUsageAndQuotaForEviction().
    GetUsageTracker(kStorageTypeTemporary)->GetCachedOriginsUsage(&usage_map);

    temporary_storage_eviction_policy_->GetEvictionOrigin(
        special_storage_policy_, GetEvictionOriginExceptions(extra_exceptions),
        usage_map, global_quota, did_get_origin_callback);

    return;
  }

  // TODO(calamity): convert LRU origin retrieval into a QuotaEvictionPolicy.
  GetLRUOrigin(type, did_get_origin_callback);
}

void QuotaManager::EvictOriginData(const GURL& origin,
                                   StorageType type,
                                   const EvictOriginDataCallback& callback) {
  DCHECK(io_thread_->BelongsToCurrentThread());
  DCHECK_EQ(type, kStorageTypeTemporary);

  eviction_context_.evicted_origin = origin;
  eviction_context_.evicted_type = type;
  eviction_context_.evict_origin_data_callback = callback;

  DeleteOriginDataInternal(origin, type, QuotaClient::kAllClientsMask, true,
                           base::Bind(&QuotaManager::DidOriginDataEvicted,
                                      weak_factory_.GetWeakPtr()));
}

void QuotaManager::GetUsageAndQuotaForEviction(
    const UsageAndQuotaCallback& callback) {
  // crbug.com/349708
  TRACE_EVENT0("io", "QuotaManager::GetUsageAndQuotaForEviction");

  DCHECK(io_thread_->BelongsToCurrentThread());
  LazyInitialize();

  UsageAndQuotaCallbackDispatcher* dispatcher =
      new UsageAndQuotaCallbackDispatcher(this);
  GetUsageTracker(kStorageTypeTemporary)
      ->GetGlobalLimitedUsage(dispatcher->GetGlobalLimitedUsageCallback());
  GetTemporaryGlobalQuota(dispatcher->GetQuotaCallback());
  GetAvailableSpace(dispatcher->GetAvailableSpaceCallback());
  dispatcher->WaitForResults(callback);
}

void QuotaManager::AsyncGetVolumeInfo(
    const VolumeInfoCallback& callback) {
  DCHECK(io_thread_->BelongsToCurrentThread());
  uint64_t* available_space = new uint64_t(0);
  uint64_t* total_space = new uint64_t(0);
  PostTaskAndReplyWithResult(
      db_thread_.get(),
      FROM_HERE,
      base::Bind(get_volume_info_fn_,
                 profile_path_,
                 base::Unretained(available_space),
                 base::Unretained(total_space)),
      base::Bind(&QuotaManager::DidGetVolumeInfo,
                 weak_factory_.GetWeakPtr(),
                 callback,
                 base::Owned(available_space),
                 base::Owned(total_space)));
}

void QuotaManager::DidGetVolumeInfo(
    const VolumeInfoCallback& callback,
    uint64_t* available_space, uint64_t* total_space, bool success) {
  DCHECK(io_thread_->BelongsToCurrentThread());
  callback.Run(success, *available_space, *total_space);
}

void QuotaManager::GetLRUOrigin(StorageType type,
                                const GetOriginCallback& callback) {
  LazyInitialize();
  // This must not be called while there's an in-flight task.
  DCHECK(lru_origin_callback_.is_null());
  lru_origin_callback_ = callback;
  if (db_disabled_) {
    lru_origin_callback_.Run(GURL());
    lru_origin_callback_.Reset();
    return;
  }

  GURL* url = new GURL;
  PostTaskAndReplyWithResultForDBThread(
      FROM_HERE, base::Bind(&GetLRUOriginOnDBThread, type,
                            GetEvictionOriginExceptions(std::set<GURL>()),
                            base::RetainedRef(special_storage_policy_),
                            base::Unretained(url)),
      base::Bind(&QuotaManager::DidGetLRUOrigin, weak_factory_.GetWeakPtr(),
                 base::Owned(url)));
}

void QuotaManager::DidSetTemporaryGlobalOverrideQuota(
    const QuotaCallback& callback,
    const int64_t* new_quota,
    bool success) {
  QuotaStatusCode status = kQuotaErrorInvalidAccess;
  DidDatabaseWork(success);
  if (success) {
    temporary_quota_override_ = *new_quota;
    status = kQuotaStatusOk;
  }

  if (callback.is_null())
    return;

  callback.Run(status, *new_quota);
}

void QuotaManager::DidGetPersistentHostQuota(const std::string& host,
                                             const int64_t* quota,
                                             bool success) {
  DidDatabaseWork(success);
  persistent_host_quota_callbacks_.Run(host, kQuotaStatusOk, *quota);
}

void QuotaManager::DidSetPersistentHostQuota(const std::string& host,
                                             const QuotaCallback& callback,
                                             const int64_t* new_quota,
                                             bool success) {
  DidDatabaseWork(success);
  callback.Run(success ? kQuotaStatusOk : kQuotaErrorInvalidAccess, *new_quota);
}

void QuotaManager::DidInitialize(int64_t* temporary_quota_override,
                                 int64_t* desired_available_space,
                                 bool success) {
  temporary_quota_override_ = *temporary_quota_override;
  desired_available_space_ = *desired_available_space;
  temporary_quota_initialized_ = true;
  DidDatabaseWork(success);

  if (!is_incognito_) {
    histogram_timer_.Start(FROM_HERE,
                           base::TimeDelta::FromMilliseconds(
                               kReportHistogramInterval),
                           this, &QuotaManager::ReportHistogram);
  }

  db_initialization_callbacks_.Run();
  GetTemporaryGlobalQuota(
      base::Bind(&QuotaManager::DidGetInitialTemporaryGlobalQuota,
                 weak_factory_.GetWeakPtr(), base::TimeTicks::Now()));
}

void QuotaManager::DidGetLRUOrigin(const GURL* origin,
                                   bool success) {
  DidDatabaseWork(success);

  lru_origin_callback_.Run(*origin);
  lru_origin_callback_.Reset();
}

void QuotaManager::DidGetInitialTemporaryGlobalQuota(
    base::TimeTicks start_ticks,
    QuotaStatusCode status,
    int64_t quota_unused) {
  UMA_HISTOGRAM_LONG_TIMES(
      "Quota.TimeToInitializeGlobalQuota",
      base::TimeTicks::Now() - start_ticks);

  if (eviction_disabled_)
    return;

  std::set<GURL>* origins = new std::set<GURL>;
  temporary_usage_tracker_->GetCachedOrigins(origins);
  // This will call the StartEviction() when initial origin registration
  // is completed.
  PostTaskAndReplyWithResultForDBThread(
      FROM_HERE,
      base::Bind(&InitializeTemporaryOriginsInfoOnDBThread,
                 base::Owned(origins)),
      base::Bind(&QuotaManager::DidInitializeTemporaryOriginsInfo,
                 weak_factory_.GetWeakPtr()));
}

void QuotaManager::DidInitializeTemporaryOriginsInfo(bool success) {
  DidDatabaseWork(success);
  if (success)
    StartEviction();
}

void QuotaManager::DidGetAvailableSpace(int64_t space) {
  // crbug.com/349708
  TRACE_EVENT1("io", "QuotaManager::DidGetAvailableSpace",
               "n_callbacks", available_space_callbacks_.size());

  available_space_callbacks_.Run(kQuotaStatusOk, space);
}

void QuotaManager::DidDatabaseWork(bool success) {
  db_disabled_ = !success;
}

void QuotaManager::DeleteOnCorrectThread() const {
  if (!io_thread_->BelongsToCurrentThread() &&
      io_thread_->DeleteSoon(FROM_HERE, this)) {
    return;
  }
  delete this;
}

void QuotaManager::PostTaskAndReplyWithResultForDBThread(
    const tracked_objects::Location& from_here,
    const base::Callback<bool(QuotaDatabase*)>& task,
    const base::Callback<void(bool)>& reply) {
  // Deleting manager will post another task to DB thread to delete
  // |database_|, therefore we can be sure that database_ is alive when this
  // task runs.
  base::PostTaskAndReplyWithResult(
      db_thread_.get(),
      from_here,
      base::Bind(task, base::Unretained(database_.get())),
      reply);
}

// static
int64_t QuotaManager::CallGetAmountOfFreeDiskSpace(
    GetVolumeInfoFn get_volume_info_fn,
    const base::FilePath& profile_path) {
  // crbug.com/349708
  TRACE_EVENT0("io", "CallSystemGetAmountOfFreeDiskSpace");
  if (!base::CreateDirectory(profile_path)) {
    LOG(WARNING) << "Create directory failed for path" << profile_path.value();
    return 0;
  }
  uint64_t available, total;
  if (!get_volume_info_fn(profile_path, &available, &total)) {
    return 0;
  }
  UMA_HISTOGRAM_MBYTES("Quota.AvailableDiskSpace", available);
  UMA_HISTOGRAM_MBYTES("Quota.TotalDiskSpace", total);
  return static_cast<int64_t>(available);
}

//static
bool QuotaManager::GetVolumeInfo(const base::FilePath& path,
                                 uint64_t* available_space,
                                 uint64_t* total_size) {
  // Inspired by similar code in the base::SysInfo class.
  base::ThreadRestrictions::AssertIOAllowed();

  int64_t available = base::SysInfo::AmountOfFreeDiskSpace(path);
  if (available < 0)
    return false;
  int64_t total = base::SysInfo::AmountOfTotalDiskSpace(path);
  if (total < 0)
    return false;

  *available_space = static_cast<uint64_t>(available);
  *total_size = static_cast<uint64_t>(total);
  return true;
}

}  // namespace storage
