// 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 "content/browser/quota_dispatcher_host.h"
#include "base/bind.h"
#include "base/memory/weak_ptr.h"
#include "content/common/quota_messages.h"
#include "content/public/browser/quota_permission_context.h"
#include "net/base/net_util.h"
#include "url/gurl.h"
#include "webkit/browser/quota/quota_manager.h"
using quota::QuotaClient;
using quota::QuotaManager;
using quota::QuotaStatusCode;
using quota::StorageType;
namespace content {
// Created one per request to carry the request's request_id around.
// Dispatches requests from renderer/worker to the QuotaManager and
// sends back the response to the renderer/worker.
class QuotaDispatcherHost::RequestDispatcher {
public:
RequestDispatcher(base::WeakPtr<QuotaDispatcherHost> dispatcher_host,
int request_id)
: dispatcher_host_(dispatcher_host),
render_process_id_(dispatcher_host->process_id_),
request_id_(request_id) {
dispatcher_host_->outstanding_requests_.AddWithID(this, request_id_);
}
virtual ~RequestDispatcher() {}
protected:
// Subclass must call this when it's done with the request.
void Completed() {
if (dispatcher_host_)
dispatcher_host_->outstanding_requests_.Remove(request_id_);
}
QuotaDispatcherHost* dispatcher_host() const {
return dispatcher_host_.get();
}
quota::QuotaManager* quota_manager() const {
return dispatcher_host_ ? dispatcher_host_->quota_manager_ : NULL;
}
QuotaPermissionContext* permission_context() const {
return dispatcher_host_ ?
dispatcher_host_->permission_context_.get() : NULL;
}
int render_process_id() const { return render_process_id_; }
int request_id() const { return request_id_; }
private:
base::WeakPtr<QuotaDispatcherHost> dispatcher_host_;
int render_process_id_;
int request_id_;
};
class QuotaDispatcherHost::QueryUsageAndQuotaDispatcher
: public RequestDispatcher {
public:
QueryUsageAndQuotaDispatcher(
base::WeakPtr<QuotaDispatcherHost> dispatcher_host,
int request_id)
: RequestDispatcher(dispatcher_host, request_id),
weak_factory_(this) {}
virtual ~QueryUsageAndQuotaDispatcher() {}
void QueryStorageUsageAndQuota(const GURL& origin, StorageType type) {
quota_manager()->GetUsageAndQuotaForWebApps(
origin, type,
base::Bind(&QueryUsageAndQuotaDispatcher::DidQueryStorageUsageAndQuota,
weak_factory_.GetWeakPtr()));
}
private:
void DidQueryStorageUsageAndQuota(
QuotaStatusCode status, int64 usage, int64 quota) {
if (!dispatcher_host())
return;
if (status != quota::kQuotaStatusOk) {
dispatcher_host()->Send(new QuotaMsg_DidFail(request_id(), status));
} else {
dispatcher_host()->Send(new QuotaMsg_DidQueryStorageUsageAndQuota(
request_id(), usage, quota));
}
Completed();
}
base::WeakPtrFactory<QueryUsageAndQuotaDispatcher> weak_factory_;
};
class QuotaDispatcherHost::RequestQuotaDispatcher
: public RequestDispatcher {
public:
typedef RequestQuotaDispatcher self_type;
RequestQuotaDispatcher(base::WeakPtr<QuotaDispatcherHost> dispatcher_host,
int request_id,
const GURL& origin,
StorageType type,
int64 requested_quota,
int render_view_id)
: RequestDispatcher(dispatcher_host, request_id),
origin_(origin),
host_(net::GetHostOrSpecFromURL(origin)),
type_(type),
current_quota_(0),
requested_quota_(requested_quota),
render_view_id_(render_view_id),
weak_factory_(this) {}
virtual ~RequestQuotaDispatcher() {}
void Start() {
DCHECK(dispatcher_host());
DCHECK(type_ == quota::kStorageTypeTemporary ||
type_ == quota::kStorageTypePersistent ||
type_ == quota::kStorageTypeSyncable);
if (type_ == quota::kStorageTypePersistent) {
quota_manager()->GetPersistentHostQuota(
host_,
base::Bind(&self_type::DidGetHostQuota,
weak_factory_.GetWeakPtr(), host_, type_));
} else {
quota_manager()->GetUsageAndQuotaForWebApps(
origin_, type_,
base::Bind(&self_type::DidGetTemporaryUsageAndQuota,
weak_factory_.GetWeakPtr()));
}
}
private:
void DidGetHostQuota(const std::string& host,
StorageType type,
QuotaStatusCode status,
int64 quota) {
if (!dispatcher_host())
return;
DCHECK_EQ(type_, type);
DCHECK_EQ(host_, host);
if (status != quota::kQuotaStatusOk) {
DidFinish(status, 0);
return;
}
if (requested_quota_ < 0) {
DidFinish(quota::kQuotaErrorInvalidModification, 0);
return;
}
if (requested_quota_ <= quota) {
// Seems like we can just let it go.
DidFinish(quota::kQuotaStatusOk, requested_quota_);
return;
}
current_quota_ = quota;
// Otherwise we need to consult with the permission context and
// possibly show an infobar.
DCHECK(permission_context());
permission_context()->RequestQuotaPermission(
origin_, type_, requested_quota_, render_process_id(), render_view_id_,
base::Bind(&self_type::DidGetPermissionResponse,
weak_factory_.GetWeakPtr()));
}
void DidGetTemporaryUsageAndQuota(QuotaStatusCode status,
int64 usage_unused,
int64 quota) {
DidFinish(status, std::min(requested_quota_, quota));
}
void DidGetPermissionResponse(
QuotaPermissionContext::QuotaPermissionResponse response) {
if (!dispatcher_host())
return;
if (response != QuotaPermissionContext::QUOTA_PERMISSION_RESPONSE_ALLOW) {
// User didn't allow the new quota. Just returning the current quota.
DidFinish(quota::kQuotaStatusOk, current_quota_);
return;
}
// Now we're allowed to set the new quota.
quota_manager()->SetPersistentHostQuota(
host_, requested_quota_,
base::Bind(&self_type::DidSetHostQuota, weak_factory_.GetWeakPtr()));
}
void DidSetHostQuota(QuotaStatusCode status, int64 new_quota) {
DidFinish(status, new_quota);
}
void DidFinish(QuotaStatusCode status, int64 granted_quota) {
if (!dispatcher_host())
return;
DCHECK(dispatcher_host());
if (status != quota::kQuotaStatusOk) {
dispatcher_host()->Send(new QuotaMsg_DidFail(request_id(), status));
} else {
dispatcher_host()->Send(new QuotaMsg_DidGrantStorageQuota(
request_id(), granted_quota));
}
Completed();
}
const GURL origin_;
const std::string host_;
const StorageType type_;
int64 current_quota_;
const int64 requested_quota_;
const int render_view_id_;
base::WeakPtrFactory<self_type> weak_factory_;
};
QuotaDispatcherHost::QuotaDispatcherHost(
int process_id,
QuotaManager* quota_manager,
QuotaPermissionContext* permission_context)
: process_id_(process_id),
quota_manager_(quota_manager),
permission_context_(permission_context),
weak_factory_(this) {
}
bool QuotaDispatcherHost::OnMessageReceived(
const IPC::Message& message, bool* message_was_ok) {
*message_was_ok = true;
bool handled = true;
IPC_BEGIN_MESSAGE_MAP_EX(QuotaDispatcherHost, message, *message_was_ok)
IPC_MESSAGE_HANDLER(QuotaHostMsg_QueryStorageUsageAndQuota,
OnQueryStorageUsageAndQuota)
IPC_MESSAGE_HANDLER(QuotaHostMsg_RequestStorageQuota,
OnRequestStorageQuota)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP_EX()
return handled;
}
QuotaDispatcherHost::~QuotaDispatcherHost() {}
void QuotaDispatcherHost::OnQueryStorageUsageAndQuota(
int request_id,
const GURL& origin,
StorageType type) {
QueryUsageAndQuotaDispatcher* dispatcher = new QueryUsageAndQuotaDispatcher(
weak_factory_.GetWeakPtr(), request_id);
dispatcher->QueryStorageUsageAndQuota(origin, type);
}
void QuotaDispatcherHost::OnRequestStorageQuota(
int render_view_id,
int request_id,
const GURL& origin,
StorageType type,
int64 requested_size) {
if (quota_manager_->IsStorageUnlimited(origin, type)) {
// If the origin is marked 'unlimited' we always just return ok.
Send(new QuotaMsg_DidGrantStorageQuota(request_id, requested_size));
return;
}
if (type != quota::kStorageTypeTemporary &&
type != quota::kStorageTypePersistent) {
// Unsupported storage types.
Send(new QuotaMsg_DidFail(request_id, quota::kQuotaErrorNotSupported));
return;
}
RequestQuotaDispatcher* dispatcher = new RequestQuotaDispatcher(
weak_factory_.GetWeakPtr(), request_id, origin, type,
requested_size, render_view_id);
dispatcher->Start();
}
} // namespace content