blob: 1d8e570eb3d4801f15bbe351d903cf78d4de22ff [file] [log] [blame]
// Copyright 2014 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/storage_monitor.h"
#include <stdint.h>
#include <algorithm>
#include <memory>
#include "base/bind.h"
#include "net/base/url_util.h"
#include "storage/browser/quota/quota_manager.h"
namespace storage {
// StorageObserverList:
StorageObserverList::ObserverState::ObserverState()
: requires_update(false) {
}
StorageObserverList::StorageObserverList() = default;
StorageObserverList::~StorageObserverList() = default;
void StorageObserverList::AddObserver(
StorageObserver* observer, const StorageObserver::MonitorParams& params) {
ObserverState& observer_state = observer_state_map_[observer];
observer_state.origin = params.filter.origin;
observer_state.rate = params.rate;
}
void StorageObserverList::RemoveObserver(StorageObserver* observer) {
observer_state_map_.erase(observer);
}
size_t StorageObserverList::ObserverCount() const {
return observer_state_map_.size();
}
void StorageObserverList::OnStorageChange(const StorageObserver::Event& event) {
for (auto& observer_state_pair : observer_state_map_)
observer_state_pair.second.requires_update = true;
MaybeDispatchEvent(event);
}
void StorageObserverList::MaybeDispatchEvent(
const StorageObserver::Event& event) {
notification_timer_.Stop();
base::TimeDelta min_delay = base::TimeDelta::Max();
bool all_observers_notified = true;
for (auto& observer_state_pair : observer_state_map_) {
StorageObserver* observer = observer_state_pair.first;
ObserverState& state = observer_state_pair.second;
if (!state.requires_update)
continue;
base::TimeTicks current_time = base::TimeTicks::Now();
base::TimeDelta delta = current_time - state.last_notification_time;
if (state.last_notification_time.is_null() || delta >= state.rate) {
state.requires_update = false;
state.last_notification_time = current_time;
if (state.origin == event.filter.origin) {
observer->OnStorageEvent(event);
} else {
// When the quota and usage of an origin is requested, QuotaManager
// returns the quota and usage of the host. Multiple origins can map to
// to the same host, so ensure the |origin| field in the dispatched
// event matches the |origin| specified by the observer when it was
// registered.
StorageObserver::Event dispatch_event(event);
dispatch_event.filter.origin = state.origin;
observer->OnStorageEvent(dispatch_event);
}
} else {
all_observers_notified = false;
base::TimeDelta delay = state.rate - delta;
if (delay < min_delay)
min_delay = delay;
}
}
// We need to respect the notification rate specified by observers. So if it
// is too soon to dispatch an event to an observer, save the event and
// dispatch it after a delay. If we simply drop the event, another one may
// not arrive anytime soon and the observer will miss the most recent event.
if (!all_observers_notified) {
pending_event_ = event;
notification_timer_.Start(
FROM_HERE,
min_delay,
this,
&StorageObserverList::DispatchPendingEvent);
}
}
void StorageObserverList::ScheduleUpdateForObserver(StorageObserver* observer) {
DCHECK(base::Contains(observer_state_map_, observer));
observer_state_map_[observer].requires_update = true;
}
void StorageObserverList::DispatchPendingEvent() {
MaybeDispatchEvent(pending_event_);
}
// HostStorageObservers:
HostStorageObservers::HostStorageObservers(QuotaManager* quota_manager)
: quota_manager_(quota_manager),
initialized_(false),
initializing_(false),
event_occurred_before_init_(false),
usage_deltas_during_init_(0),
cached_usage_(0),
cached_quota_(0),
weak_factory_(this) {
}
HostStorageObservers::~HostStorageObservers() = default;
void HostStorageObservers::AddObserver(
StorageObserver* observer,
const StorageObserver::MonitorParams& params) {
observers_.AddObserver(observer, params);
if (!params.dispatch_initial_state)
return;
if (initialized_) {
StorageObserver::Event event(params.filter,
std::max<int64_t>(cached_usage_, 0),
std::max<int64_t>(cached_quota_, 0));
observer->OnStorageEvent(event);
return;
}
// Ensure the observer receives the initial storage state once initialization
// is complete.
observers_.ScheduleUpdateForObserver(observer);
StartInitialization(params.filter);
}
void HostStorageObservers::RemoveObserver(StorageObserver* observer) {
observers_.RemoveObserver(observer);
}
bool HostStorageObservers::ContainsObservers() const {
return observers_.ObserverCount() > 0;
}
void HostStorageObservers::NotifyUsageChange(
const StorageObserver::Filter& filter,
int64_t delta) {
if (initialized_) {
cached_usage_ += delta;
DispatchEvent(filter, true);
return;
}
// If a storage change occurs before initialization, ensure all observers will
// receive an event once initialization is complete.
event_occurred_before_init_ = true;
// During QuotaManager::GetUsageAndQuotaForWebApps(), cached data is read
// synchronously, but other data may be retrieved asynchronously. A usage
// change may occur between the function call and callback. These deltas need
// to be added to the usage received by GotHostUsageAndQuota() to ensure
// |cached_usage_| is correctly initialized.
if (initializing_) {
usage_deltas_during_init_ += delta;
return;
}
StartInitialization(filter);
}
void HostStorageObservers::StartInitialization(
const StorageObserver::Filter& filter) {
if (initialized_ || initializing_)
return;
initializing_ = true;
quota_manager_->GetUsageAndQuotaForWebApps(
filter.origin, filter.storage_type,
base::BindOnce(&HostStorageObservers::GotHostUsageAndQuota,
weak_factory_.GetWeakPtr(), filter));
}
void HostStorageObservers::GotHostUsageAndQuota(
const StorageObserver::Filter& filter,
blink::mojom::QuotaStatusCode status,
int64_t usage,
int64_t quota) {
initializing_ = false;
if (status != blink::mojom::QuotaStatusCode::kOk)
return;
initialized_ = true;
cached_quota_ = quota;
cached_usage_ = usage + usage_deltas_during_init_;
DispatchEvent(filter, event_occurred_before_init_);
}
void HostStorageObservers::DispatchEvent(
const StorageObserver::Filter& filter, bool is_update) {
StorageObserver::Event event(filter, std::max<int64_t>(cached_usage_, 0),
std::max<int64_t>(cached_quota_, 0));
if (is_update)
observers_.OnStorageChange(event);
else
observers_.MaybeDispatchEvent(event);
}
// StorageTypeObservers:
StorageTypeObservers::StorageTypeObservers(QuotaManager* quota_manager)
: quota_manager_(quota_manager) {
}
StorageTypeObservers::~StorageTypeObservers() = default;
void StorageTypeObservers::AddObserver(
StorageObserver* observer, const StorageObserver::MonitorParams& params) {
std::string host = net::GetHostOrSpecFromURL(params.filter.origin.GetURL());
if (host.empty())
return;
auto& host_observers = host_observers_map_[host];
if (!host_observers) {
// Because there are no null entries in host_observers_map_, the [] inserted
// a blank pointer, so let's populate it.
host_observers = std::make_unique<HostStorageObservers>(quota_manager_);
}
host_observers->AddObserver(observer, params);
}
void StorageTypeObservers::RemoveObserver(StorageObserver* observer) {
for (auto it = host_observers_map_.begin();
it != host_observers_map_.end();) {
it->second->RemoveObserver(observer);
if (!it->second->ContainsObservers()) {
it = host_observers_map_.erase(it);
} else {
++it;
}
}
}
const HostStorageObservers* StorageTypeObservers::GetHostObservers(
const std::string& host) const {
auto it = host_observers_map_.find(host);
if (it != host_observers_map_.end())
return it->second.get();
return nullptr;
}
void StorageTypeObservers::NotifyUsageChange(
const StorageObserver::Filter& filter,
int64_t delta) {
std::string host = net::GetHostOrSpecFromURL(filter.origin.GetURL());
auto it = host_observers_map_.find(host);
if (it == host_observers_map_.end())
return;
it->second->NotifyUsageChange(filter, delta);
}
// StorageMonitor:
StorageMonitor::StorageMonitor(QuotaManager* quota_manager)
: quota_manager_(quota_manager) {
}
StorageMonitor::~StorageMonitor() = default;
void StorageMonitor::AddObserver(
StorageObserver* observer, const StorageObserver::MonitorParams& params) {
DCHECK(observer);
// Check preconditions.
if (params.filter.storage_type == blink::mojom::StorageType::kUnknown ||
params.filter.storage_type ==
blink::mojom::StorageType::kQuotaNotManaged) {
NOTREACHED();
return;
}
auto& type_observers =
storage_type_observers_map_[params.filter.storage_type];
if (!type_observers)
type_observers = std::make_unique<StorageTypeObservers>(quota_manager_);
type_observers->AddObserver(observer, params);
}
void StorageMonitor::RemoveObserver(StorageObserver* observer) {
for (const auto& type_observers_pair : storage_type_observers_map_)
type_observers_pair.second->RemoveObserver(observer);
}
const StorageTypeObservers* StorageMonitor::GetStorageTypeObservers(
blink::mojom::StorageType storage_type) const {
auto it = storage_type_observers_map_.find(storage_type);
if (it != storage_type_observers_map_.end())
return it->second.get();
return nullptr;
}
void StorageMonitor::NotifyUsageChange(const StorageObserver::Filter& filter,
int64_t delta) {
// Check preconditions.
if (filter.storage_type == blink::mojom::StorageType::kUnknown ||
filter.storage_type == blink::mojom::StorageType::kQuotaNotManaged) {
NOTREACHED();
return;
}
auto it = storage_type_observers_map_.find(filter.storage_type);
if (it == storage_type_observers_map_.end())
return;
it->second->NotifyUsageChange(filter, delta);
}
} // namespace storage