blob: 57b5e0d24eacd11c9eb7670928deedfe6d5f6f2e [file] [log] [blame]
// 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 "webkit/browser/quota/usage_tracker.h"
#include <algorithm>
#include <deque>
#include <set>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/stl_util.h"
#include "net/base/net_util.h"
namespace quota {
namespace {
typedef ClientUsageTracker::OriginUsageAccumulator OriginUsageAccumulator;
typedef ClientUsageTracker::OriginSetByHost OriginSetByHost;
void DidGetOriginUsage(const OriginUsageAccumulator& accumulator,
const GURL& origin,
int64 usage) {
accumulator.Run(origin, usage);
}
void DidGetHostUsage(const UsageCallback& callback,
int64 limited_usage,
int64 unlimited_usage) {
DCHECK_GE(limited_usage, 0);
DCHECK_GE(unlimited_usage, 0);
callback.Run(limited_usage + unlimited_usage);
}
void NoopHostUsageCallback(int64 usage) {}
bool EraseOriginFromOriginSet(OriginSetByHost* origins_by_host,
const std::string& host,
const GURL& origin) {
OriginSetByHost::iterator found = origins_by_host->find(host);
if (found == origins_by_host->end())
return false;
if (!found->second.erase(origin))
return false;
if (found->second.empty())
origins_by_host->erase(host);
return true;
}
bool OriginSetContainsOrigin(const OriginSetByHost& origins,
const std::string& host,
const GURL& origin) {
OriginSetByHost::const_iterator itr = origins.find(host);
return itr != origins.end() && ContainsKey(itr->second, origin);
}
void DidGetGlobalUsageForLimitedGlobalUsage(const UsageCallback& callback,
int64 total_global_usage,
int64 global_unlimited_usage) {
callback.Run(total_global_usage - global_unlimited_usage);
}
} // namespace
// UsageTracker ----------------------------------------------------------
UsageTracker::UsageTracker(const QuotaClientList& clients,
StorageType type,
SpecialStoragePolicy* special_storage_policy)
: type_(type),
weak_factory_(this) {
for (QuotaClientList::const_iterator iter = clients.begin();
iter != clients.end();
++iter) {
if ((*iter)->DoesSupport(type)) {
client_tracker_map_[(*iter)->id()] =
new ClientUsageTracker(this, *iter, type, special_storage_policy);
}
}
}
UsageTracker::~UsageTracker() {
STLDeleteValues(&client_tracker_map_);
}
ClientUsageTracker* UsageTracker::GetClientTracker(QuotaClient::ID client_id) {
ClientTrackerMap::iterator found = client_tracker_map_.find(client_id);
if (found != client_tracker_map_.end())
return found->second;
return NULL;
}
void UsageTracker::GetGlobalLimitedUsage(const UsageCallback& callback) {
if (global_usage_callbacks_.HasCallbacks()) {
global_usage_callbacks_.Add(base::Bind(
&DidGetGlobalUsageForLimitedGlobalUsage, callback));
return;
}
if (!global_limited_usage_callbacks_.Add(callback))
return;
AccumulateInfo* info = new AccumulateInfo;
// Calling GetGlobalLimitedUsage(accumulator) may synchronously
// return if the usage is cached, which may in turn dispatch
// the completion callback before we finish looping over
// all clients (because info->pending_clients may reach 0
// during the loop).
// To avoid this, we add one more pending client as a sentinel
// and fire the sentinel callback at the end.
info->pending_clients = client_tracker_map_.size() + 1;
UsageCallback accumulator = base::Bind(
&UsageTracker::AccumulateClientGlobalLimitedUsage,
weak_factory_.GetWeakPtr(), base::Owned(info));
for (ClientTrackerMap::iterator iter = client_tracker_map_.begin();
iter != client_tracker_map_.end();
++iter)
iter->second->GetGlobalLimitedUsage(accumulator);
// Fire the sentinel as we've now called GetGlobalUsage for all clients.
accumulator.Run(0);
}
void UsageTracker::GetGlobalUsage(const GlobalUsageCallback& callback) {
if (!global_usage_callbacks_.Add(callback))
return;
AccumulateInfo* info = new AccumulateInfo;
// Calling GetGlobalUsage(accumulator) may synchronously
// return if the usage is cached, which may in turn dispatch
// the completion callback before we finish looping over
// all clients (because info->pending_clients may reach 0
// during the loop).
// To avoid this, we add one more pending client as a sentinel
// and fire the sentinel callback at the end.
info->pending_clients = client_tracker_map_.size() + 1;
GlobalUsageCallback accumulator = base::Bind(
&UsageTracker::AccumulateClientGlobalUsage, weak_factory_.GetWeakPtr(),
base::Owned(info));
for (ClientTrackerMap::iterator iter = client_tracker_map_.begin();
iter != client_tracker_map_.end();
++iter)
iter->second->GetGlobalUsage(accumulator);
// Fire the sentinel as we've now called GetGlobalUsage for all clients.
accumulator.Run(0, 0);
}
void UsageTracker::GetHostUsage(const std::string& host,
const UsageCallback& callback) {
if (!host_usage_callbacks_.Add(host, callback))
return;
AccumulateInfo* info = new AccumulateInfo;
// Calling GetHostUsage(accumulator) may synchronously
// return if the usage is cached, which may in turn dispatch
// the completion callback before we finish looping over
// all clients (because info->pending_clients may reach 0
// during the loop).
// To avoid this, we add one more pending client as a sentinel
// and fire the sentinel callback at the end.
info->pending_clients = client_tracker_map_.size() + 1;
UsageCallback accumulator = base::Bind(
&UsageTracker::AccumulateClientHostUsage, weak_factory_.GetWeakPtr(),
base::Owned(info), host);
for (ClientTrackerMap::iterator iter = client_tracker_map_.begin();
iter != client_tracker_map_.end();
++iter)
iter->second->GetHostUsage(host, accumulator);
// Fire the sentinel as we've now called GetHostUsage for all clients.
accumulator.Run(0);
}
void UsageTracker::UpdateUsageCache(
QuotaClient::ID client_id, const GURL& origin, int64 delta) {
ClientUsageTracker* client_tracker = GetClientTracker(client_id);
DCHECK(client_tracker);
client_tracker->UpdateUsageCache(origin, delta);
}
void UsageTracker::GetCachedHostsUsage(
std::map<std::string, int64>* host_usage) const {
DCHECK(host_usage);
host_usage->clear();
for (ClientTrackerMap::const_iterator iter = client_tracker_map_.begin();
iter != client_tracker_map_.end(); ++iter) {
iter->second->GetCachedHostsUsage(host_usage);
}
}
void UsageTracker::GetCachedOrigins(std::set<GURL>* origins) const {
DCHECK(origins);
origins->clear();
for (ClientTrackerMap::const_iterator iter = client_tracker_map_.begin();
iter != client_tracker_map_.end(); ++iter) {
iter->second->GetCachedOrigins(origins);
}
}
void UsageTracker::SetUsageCacheEnabled(QuotaClient::ID client_id,
const GURL& origin,
bool enabled) {
ClientUsageTracker* client_tracker = GetClientTracker(client_id);
DCHECK(client_tracker);
client_tracker->SetUsageCacheEnabled(origin, enabled);
}
void UsageTracker::AccumulateClientGlobalLimitedUsage(AccumulateInfo* info,
int64 limited_usage) {
info->usage += limited_usage;
if (--info->pending_clients)
return;
// All the clients have returned their usage data. Dispatch the
// pending callbacks.
global_limited_usage_callbacks_.Run(MakeTuple(info->usage));
}
void UsageTracker::AccumulateClientGlobalUsage(AccumulateInfo* info,
int64 usage,
int64 unlimited_usage) {
info->usage += usage;
info->unlimited_usage += unlimited_usage;
if (--info->pending_clients)
return;
// Defend against confusing inputs from clients.
if (info->usage < 0)
info->usage = 0;
// TODO(michaeln): The unlimited number is not trustworthy, it
// can get out of whack when apps are installed or uninstalled.
if (info->unlimited_usage > info->usage)
info->unlimited_usage = info->usage;
else if (info->unlimited_usage < 0)
info->unlimited_usage = 0;
// All the clients have returned their usage data. Dispatch the
// pending callbacks.
global_usage_callbacks_.Run(MakeTuple(info->usage, info->unlimited_usage));
}
void UsageTracker::AccumulateClientHostUsage(AccumulateInfo* info,
const std::string& host,
int64 usage) {
info->usage += usage;
if (--info->pending_clients)
return;
// Defend against confusing inputs from clients.
if (info->usage < 0)
info->usage = 0;
// All the clients have returned their usage data. Dispatch the
// pending callbacks.
host_usage_callbacks_.Run(host, MakeTuple(info->usage));
}
// ClientUsageTracker ----------------------------------------------------
ClientUsageTracker::ClientUsageTracker(
UsageTracker* tracker, QuotaClient* client, StorageType type,
SpecialStoragePolicy* special_storage_policy)
: tracker_(tracker),
client_(client),
type_(type),
global_limited_usage_(0),
global_unlimited_usage_(0),
global_usage_retrieved_(false),
special_storage_policy_(special_storage_policy) {
DCHECK(tracker_);
DCHECK(client_);
if (special_storage_policy_.get())
special_storage_policy_->AddObserver(this);
}
ClientUsageTracker::~ClientUsageTracker() {
if (special_storage_policy_.get())
special_storage_policy_->RemoveObserver(this);
}
void ClientUsageTracker::GetGlobalLimitedUsage(const UsageCallback& callback) {
if (!global_usage_retrieved_) {
GetGlobalUsage(base::Bind(&DidGetGlobalUsageForLimitedGlobalUsage,
callback));
return;
}
if (non_cached_limited_origins_by_host_.empty()) {
callback.Run(global_limited_usage_);
return;
}
AccumulateInfo* info = new AccumulateInfo;
info->pending_jobs = non_cached_limited_origins_by_host_.size() + 1;
UsageCallback accumulator = base::Bind(
&ClientUsageTracker::AccumulateLimitedOriginUsage, AsWeakPtr(),
base::Owned(info), callback);
for (OriginSetByHost::iterator host_itr =
non_cached_limited_origins_by_host_.begin();
host_itr != non_cached_limited_origins_by_host_.end(); ++host_itr) {
for (std::set<GURL>::iterator origin_itr = host_itr->second.begin();
origin_itr != host_itr->second.end(); ++origin_itr)
client_->GetOriginUsage(*origin_itr, type_, accumulator);
}
accumulator.Run(global_limited_usage_);
}
void ClientUsageTracker::GetGlobalUsage(const GlobalUsageCallback& callback) {
if (global_usage_retrieved_ &&
non_cached_limited_origins_by_host_.empty() &&
non_cached_unlimited_origins_by_host_.empty()) {
callback.Run(global_limited_usage_ + global_unlimited_usage_,
global_unlimited_usage_);
return;
}
client_->GetOriginsForType(type_, base::Bind(
&ClientUsageTracker::DidGetOriginsForGlobalUsage, AsWeakPtr(),
callback));
}
void ClientUsageTracker::GetHostUsage(
const std::string& host, const UsageCallback& callback) {
if (ContainsKey(cached_hosts_, host) &&
!ContainsKey(non_cached_limited_origins_by_host_, host) &&
!ContainsKey(non_cached_unlimited_origins_by_host_, host)) {
// TODO(kinuko): Drop host_usage_map_ cache periodically.
callback.Run(GetCachedHostUsage(host));
return;
}
if (!host_usage_accumulators_.Add(
host, base::Bind(&DidGetHostUsage, callback)))
return;
client_->GetOriginsForHost(type_, host, base::Bind(
&ClientUsageTracker::DidGetOriginsForHostUsage, AsWeakPtr(), host));
}
void ClientUsageTracker::UpdateUsageCache(
const GURL& origin, int64 delta) {
std::string host = net::GetHostOrSpecFromURL(origin);
if (cached_hosts_.find(host) != cached_hosts_.end()) {
if (!IsUsageCacheEnabledForOrigin(origin))
return;
cached_usage_by_host_[host][origin] += delta;
if (IsStorageUnlimited(origin))
global_unlimited_usage_ += delta;
else
global_limited_usage_ += delta;
DCHECK_GE(cached_usage_by_host_[host][origin], 0);
DCHECK_GE(global_limited_usage_, 0);
return;
}
// We don't know about this host yet, so populate our cache for it.
GetHostUsage(host, base::Bind(&NoopHostUsageCallback));
}
void ClientUsageTracker::GetCachedHostsUsage(
std::map<std::string, int64>* host_usage) const {
DCHECK(host_usage);
for (HostUsageMap::const_iterator host_iter = cached_usage_by_host_.begin();
host_iter != cached_usage_by_host_.end(); host_iter++) {
const std::string& host = host_iter->first;
(*host_usage)[host] += GetCachedHostUsage(host);
}
}
void ClientUsageTracker::GetCachedOrigins(std::set<GURL>* origins) const {
DCHECK(origins);
for (HostUsageMap::const_iterator host_iter = cached_usage_by_host_.begin();
host_iter != cached_usage_by_host_.end(); host_iter++) {
const UsageMap& origin_map = host_iter->second;
for (UsageMap::const_iterator origin_iter = origin_map.begin();
origin_iter != origin_map.end(); origin_iter++) {
origins->insert(origin_iter->first);
}
}
}
void ClientUsageTracker::SetUsageCacheEnabled(const GURL& origin,
bool enabled) {
std::string host = net::GetHostOrSpecFromURL(origin);
if (!enabled) {
// Erase |origin| from cache and subtract its usage.
HostUsageMap::iterator found_host = cached_usage_by_host_.find(host);
if (found_host != cached_usage_by_host_.end()) {
UsageMap& cached_usage_for_host = found_host->second;
UsageMap::iterator found = cached_usage_for_host.find(origin);
if (found != cached_usage_for_host.end()) {
int64 usage = found->second;
UpdateUsageCache(origin, -usage);
cached_usage_for_host.erase(found);
if (cached_usage_for_host.empty()) {
cached_usage_by_host_.erase(found_host);
cached_hosts_.erase(host);
}
}
}
if (IsStorageUnlimited(origin))
non_cached_unlimited_origins_by_host_[host].insert(origin);
else
non_cached_limited_origins_by_host_[host].insert(origin);
} else {
// Erase |origin| from |non_cached_origins_| and invalidate the usage cache
// for the host.
if (EraseOriginFromOriginSet(&non_cached_limited_origins_by_host_,
host, origin) ||
EraseOriginFromOriginSet(&non_cached_unlimited_origins_by_host_,
host, origin)) {
cached_hosts_.erase(host);
global_usage_retrieved_ = false;
}
}
}
void ClientUsageTracker::AccumulateLimitedOriginUsage(
AccumulateInfo* info,
const UsageCallback& callback,
int64 usage) {
info->limited_usage += usage;
if (--info->pending_jobs)
return;
callback.Run(info->limited_usage);
}
void ClientUsageTracker::DidGetOriginsForGlobalUsage(
const GlobalUsageCallback& callback,
const std::set<GURL>& origins) {
OriginSetByHost origins_by_host;
for (std::set<GURL>::const_iterator itr = origins.begin();
itr != origins.end(); ++itr)
origins_by_host[net::GetHostOrSpecFromURL(*itr)].insert(*itr);
AccumulateInfo* info = new AccumulateInfo;
// Getting host usage may synchronously return the result if the usage is
// cached, which may in turn dispatch the completion callback before we finish
// looping over all hosts (because info->pending_jobs may reach 0 during the
// loop). To avoid this, we add one more pending host as a sentinel and
// fire the sentinel callback at the end.
info->pending_jobs = origins_by_host.size() + 1;
HostUsageAccumulator accumulator =
base::Bind(&ClientUsageTracker::AccumulateHostUsage, AsWeakPtr(),
base::Owned(info), callback);
for (OriginSetByHost::iterator itr = origins_by_host.begin();
itr != origins_by_host.end(); ++itr) {
if (host_usage_accumulators_.Add(itr->first, accumulator))
GetUsageForOrigins(itr->first, itr->second);
}
// Fire the sentinel as we've now called GetUsageForOrigins for all clients.
accumulator.Run(0, 0);
}
void ClientUsageTracker::AccumulateHostUsage(
AccumulateInfo* info,
const GlobalUsageCallback& callback,
int64 limited_usage,
int64 unlimited_usage) {
info->limited_usage += limited_usage;
info->unlimited_usage += unlimited_usage;
if (--info->pending_jobs)
return;
DCHECK_GE(info->limited_usage, 0);
DCHECK_GE(info->unlimited_usage, 0);
global_usage_retrieved_ = true;
callback.Run(info->limited_usage + info->unlimited_usage,
info->unlimited_usage);
}
void ClientUsageTracker::DidGetOriginsForHostUsage(
const std::string& host,
const std::set<GURL>& origins) {
GetUsageForOrigins(host, origins);
}
void ClientUsageTracker::GetUsageForOrigins(
const std::string& host,
const std::set<GURL>& origins) {
AccumulateInfo* info = new AccumulateInfo;
// Getting origin usage may synchronously return the result if the usage is
// cached, which may in turn dispatch the completion callback before we finish
// looping over all origins (because info->pending_jobs may reach 0 during the
// loop). To avoid this, we add one more pending origin as a sentinel and
// fire the sentinel callback at the end.
info->pending_jobs = origins.size() + 1;
OriginUsageAccumulator accumulator =
base::Bind(&ClientUsageTracker::AccumulateOriginUsage, AsWeakPtr(),
base::Owned(info), host);
for (std::set<GURL>::const_iterator itr = origins.begin();
itr != origins.end(); ++itr) {
DCHECK_EQ(host, net::GetHostOrSpecFromURL(*itr));
int64 origin_usage = 0;
if (GetCachedOriginUsage(*itr, &origin_usage)) {
accumulator.Run(*itr, origin_usage);
} else {
client_->GetOriginUsage(*itr, type_, base::Bind(
&DidGetOriginUsage, accumulator, *itr));
}
}
// Fire the sentinel as we've now called GetOriginUsage for all clients.
accumulator.Run(GURL(), 0);
}
void ClientUsageTracker::AccumulateOriginUsage(AccumulateInfo* info,
const std::string& host,
const GURL& origin,
int64 usage) {
if (!origin.is_empty()) {
if (usage < 0)
usage = 0;
if (IsStorageUnlimited(origin))
info->unlimited_usage += usage;
else
info->limited_usage += usage;
if (IsUsageCacheEnabledForOrigin(origin))
AddCachedOrigin(origin, usage);
}
if (--info->pending_jobs)
return;
AddCachedHost(host);
host_usage_accumulators_.Run(
host, MakeTuple(info->limited_usage, info->unlimited_usage));
}
void ClientUsageTracker::AddCachedOrigin(
const GURL& origin, int64 new_usage) {
DCHECK(IsUsageCacheEnabledForOrigin(origin));
std::string host = net::GetHostOrSpecFromURL(origin);
int64* usage = &cached_usage_by_host_[host][origin];
int64 delta = new_usage - *usage;
*usage = new_usage;
if (delta) {
if (IsStorageUnlimited(origin))
global_unlimited_usage_ += delta;
else
global_limited_usage_ += delta;
}
DCHECK_GE(*usage, 0);
DCHECK_GE(global_limited_usage_, 0);
}
void ClientUsageTracker::AddCachedHost(const std::string& host) {
cached_hosts_.insert(host);
}
int64 ClientUsageTracker::GetCachedHostUsage(const std::string& host) const {
HostUsageMap::const_iterator found = cached_usage_by_host_.find(host);
if (found == cached_usage_by_host_.end())
return 0;
int64 usage = 0;
const UsageMap& map = found->second;
for (UsageMap::const_iterator iter = map.begin();
iter != map.end(); ++iter) {
usage += iter->second;
}
return usage;
}
bool ClientUsageTracker::GetCachedOriginUsage(
const GURL& origin,
int64* usage) const {
std::string host = net::GetHostOrSpecFromURL(origin);
HostUsageMap::const_iterator found_host = cached_usage_by_host_.find(host);
if (found_host == cached_usage_by_host_.end())
return false;
UsageMap::const_iterator found = found_host->second.find(origin);
if (found == found_host->second.end())
return false;
DCHECK(IsUsageCacheEnabledForOrigin(origin));
*usage = found->second;
return true;
}
bool ClientUsageTracker::IsUsageCacheEnabledForOrigin(
const GURL& origin) const {
std::string host = net::GetHostOrSpecFromURL(origin);
return !OriginSetContainsOrigin(non_cached_limited_origins_by_host_,
host, origin) &&
!OriginSetContainsOrigin(non_cached_unlimited_origins_by_host_,
host, origin);
}
void ClientUsageTracker::OnGranted(const GURL& origin,
int change_flags) {
DCHECK(CalledOnValidThread());
if (change_flags & SpecialStoragePolicy::STORAGE_UNLIMITED) {
int64 usage = 0;
if (GetCachedOriginUsage(origin, &usage)) {
global_unlimited_usage_ += usage;
global_limited_usage_ -= usage;
}
std::string host = net::GetHostOrSpecFromURL(origin);
if (EraseOriginFromOriginSet(&non_cached_limited_origins_by_host_,
host, origin))
non_cached_unlimited_origins_by_host_[host].insert(origin);
}
}
void ClientUsageTracker::OnRevoked(const GURL& origin,
int change_flags) {
DCHECK(CalledOnValidThread());
if (change_flags & SpecialStoragePolicy::STORAGE_UNLIMITED) {
int64 usage = 0;
if (GetCachedOriginUsage(origin, &usage)) {
global_unlimited_usage_ -= usage;
global_limited_usage_ += usage;
}
std::string host = net::GetHostOrSpecFromURL(origin);
if (EraseOriginFromOriginSet(&non_cached_unlimited_origins_by_host_,
host, origin))
non_cached_limited_origins_by_host_[host].insert(origin);
}
}
void ClientUsageTracker::OnCleared() {
DCHECK(CalledOnValidThread());
global_limited_usage_ += global_unlimited_usage_;
global_unlimited_usage_ = 0;
for (OriginSetByHost::const_iterator host_itr =
non_cached_unlimited_origins_by_host_.begin();
host_itr != non_cached_unlimited_origins_by_host_.end();
++host_itr) {
for (std::set<GURL>::const_iterator origin_itr = host_itr->second.begin();
origin_itr != host_itr->second.end();
++origin_itr)
non_cached_limited_origins_by_host_[host_itr->first].insert(*origin_itr);
}
non_cached_unlimited_origins_by_host_.clear();
}
bool ClientUsageTracker::IsStorageUnlimited(const GURL& origin) const {
if (type_ == kStorageTypeSyncable)
return false;
return special_storage_policy_.get() &&
special_storage_policy_->IsStorageUnlimited(origin);
}
} // namespace quota