summaryrefslogtreecommitdiffstats
path: root/webkit
diff options
context:
space:
mode:
authorkinuko@chromium.org <kinuko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-27 13:20:14 +0000
committerkinuko@chromium.org <kinuko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-27 13:20:14 +0000
commit595765cf3c42fda899d308e6f875dd5627837989 (patch)
treeca06cb28f9a65f472011b0dc5f94d9b0ee9f653c /webkit
parent216f6923c677b509de56a4735f91eafdd21d6fc2 (diff)
downloadchromium_src-595765cf3c42fda899d308e6f875dd5627837989.zip
chromium_src-595765cf3c42fda899d308e6f875dd5627837989.tar.gz
chromium_src-595765cf3c42fda899d308e6f875dd5627837989.tar.bz2
Add 1st cut of QuotaManager code
No persistent storage support yet. Some notes: - There are a lot of TODOs especially for persistent type storage handling. - QuotaTask base class is for now only subclassed by QuotaInitializeTask, but it is planned to add more subclasses. BUG=61676,79639 TEST=QuotaManagerTest.* Review URL: http://codereview.chromium.org/6826052 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@83145 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit')
-rw-r--r--webkit/quota/mock_storage_client.cc140
-rw-r--r--webkit/quota/mock_storage_client.h74
-rw-r--r--webkit/quota/quota_client.h63
-rw-r--r--webkit/quota/quota_database.h8
-rw-r--r--webkit/quota/quota_manager.cc516
-rw-r--r--webkit/quota/quota_manager.h149
-rw-r--r--webkit/quota/quota_manager_unittest.cc295
-rw-r--r--webkit/quota/quota_task.cc87
-rw-r--r--webkit/quota/quota_task.h97
-rw-r--r--webkit/quota/quota_types.h141
-rw-r--r--webkit/quota/usage_tracker.cc400
-rw-r--r--webkit/quota/usage_tracker.h107
-rw-r--r--webkit/quota/webkit_quota.gypi8
-rw-r--r--webkit/tools/test_shell/test_shell.gypi3
14 files changed, 2081 insertions, 7 deletions
diff --git a/webkit/quota/mock_storage_client.cc b/webkit/quota/mock_storage_client.cc
new file mode 100644
index 0000000..860ff92
--- /dev/null
+++ b/webkit/quota/mock_storage_client.cc
@@ -0,0 +1,140 @@
+// Copyright (c) 2011 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/quota/mock_storage_client.h"
+
+#include "base/atomic_sequence_num.h"
+#include "base/basictypes.h"
+#include "base/memory/singleton.h"
+#include "base/message_loop_proxy.h"
+#include "base/scoped_ptr.h"
+#include "base/stl_util-inl.h"
+#include "net/base/net_util.h"
+#include "webkit/quota/quota_manager.h"
+
+using base::AtomicSequenceNumber;
+
+namespace quota {
+
+namespace {
+
+class MockStorageClientIDSequencer {
+ public:
+ static MockStorageClientIDSequencer* GetInstance() {
+ return Singleton<MockStorageClientIDSequencer>::get();
+ }
+
+ QuotaClient::ID NextMockID() {
+ return static_cast<QuotaClient::ID>(
+ QuotaClient::kMockStart + seq_.GetNext());
+ }
+
+ private:
+ MockStorageClientIDSequencer() { }
+ friend struct DefaultSingletonTraits<MockStorageClientIDSequencer>;
+ AtomicSequenceNumber seq_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockStorageClientIDSequencer);
+};
+
+} // anonymous namespace
+
+MockStorageClient::MockStorageClient(QuotaManager* qm)
+ : quota_manager_(qm),
+ id_(MockStorageClientIDSequencer::GetInstance()->NextMockID()),
+ runnable_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
+}
+
+MockStorageClient::~MockStorageClient() {
+ STLDeleteContainerPointers(usage_callbacks_.begin(), usage_callbacks_.end());
+ STLDeleteContainerPointers(
+ origins_callbacks_.begin(), origins_callbacks_.end());
+}
+
+void MockStorageClient::AddMockOriginData(
+ const GURL& origin_url, StorageType type, int64 size) {
+ origin_data_.insert(std::make_pair(origin_url, MockOriginData(type, size)));
+}
+
+void MockStorageClient::ModifyMockOriginDataSize(
+ const GURL& origin_url, StorageType type, int64 delta) {
+ std::map<GURL, MockOriginData>::iterator find = origin_data_.find(origin_url);
+ if (find == origin_data_.end() || find->second.type != type) {
+ DCHECK(delta >= 0);
+ AddMockOriginData(origin_url, type, delta);
+ return;
+ }
+ quota_manager_->NotifyStorageModified(id(), origin_url, type, delta);
+}
+
+void MockStorageClient::GetOriginUsage(const GURL& origin_url,
+ StorageType type,
+ GetUsageCallback* callback) {
+ usage_callbacks_.insert(callback);
+ base::MessageLoopProxy::CreateForCurrentThread()->PostTask(
+ FROM_HERE, runnable_factory_.NewRunnableMethod(
+ &MockStorageClient::RunGetOriginUsage,
+ origin_url, type, callback));
+}
+
+void MockStorageClient::GetOriginsForType(
+ StorageType type, GetOriginsCallback* callback) {
+ origins_callbacks_.insert(callback);
+ base::MessageLoopProxy::CreateForCurrentThread()->PostTask(
+ FROM_HERE, runnable_factory_.NewRunnableMethod(
+ &MockStorageClient::RunGetOriginsForType,
+ type, callback));
+}
+
+void MockStorageClient::GetOriginsForHost(
+ StorageType type, const std::string& host,
+ GetOriginsCallback* callback) {
+ origins_callbacks_.insert(callback);
+ base::MessageLoopProxy::CreateForCurrentThread()->PostTask(
+ FROM_HERE, runnable_factory_.NewRunnableMethod(
+ &MockStorageClient::RunGetOriginsForHost,
+ type, host, callback));
+}
+
+void MockStorageClient::RunGetOriginUsage(
+ const GURL& origin_url, StorageType type, GetUsageCallback* callback_ptr) {
+ usage_callbacks_.erase(callback_ptr);
+ scoped_ptr<GetUsageCallback> callback(callback_ptr);
+ std::map<GURL, MockOriginData>::iterator find = origin_data_.find(origin_url);
+ if (find == origin_data_.end()) {
+ callback->Run(0);
+ } else {
+ callback->Run(find->second.usage);
+ }
+}
+
+void MockStorageClient::RunGetOriginsForType(
+ StorageType type, GetOriginsCallback* callback_ptr) {
+ scoped_ptr<GetOriginsCallback> callback(callback_ptr);
+ origins_callbacks_.erase(callback_ptr);
+ std::set<GURL> origins;
+ for (std::map<GURL, MockOriginData>::iterator iter = origin_data_.begin();
+ iter != origin_data_.end(); ++iter) {
+ if (type == iter->second.type)
+ origins.insert(iter->first);
+ }
+ callback->Run(origins);
+}
+
+void MockStorageClient::RunGetOriginsForHost(
+ StorageType type, const std::string& host,
+ GetOriginsCallback* callback_ptr) {
+ scoped_ptr<GetOriginsCallback> callback(callback_ptr);
+ origins_callbacks_.erase(callback_ptr);
+ std::set<GURL> origins;
+ for (std::map<GURL, MockOriginData>::iterator iter = origin_data_.begin();
+ iter != origin_data_.end(); ++iter) {
+ std::string host_or_spec = net::GetHostOrSpecFromURL(iter->first);
+ if (type == iter->second.type && host == host_or_spec)
+ origins.insert(iter->first);
+ }
+ callback->Run(origins);
+}
+
+} // namespace quota
diff --git a/webkit/quota/mock_storage_client.h b/webkit/quota/mock_storage_client.h
new file mode 100644
index 0000000..d7a27cd
--- /dev/null
+++ b/webkit/quota/mock_storage_client.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2011 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.
+
+#ifndef WEBKIT_QUOTA_MOCK_STORAGE_CLIENT_H_
+#define WEBKIT_QUOTA_MOCK_STORAGE_CLIENT_H_
+
+#include <map>
+
+#include "base/compiler_specific.h"
+#include "base/task.h"
+#include "googleurl/src/gurl.h"
+#include "webkit/quota/quota_client.h"
+
+namespace quota {
+
+class QuotaManager;
+
+// Mock storage class for testing.
+class MockStorageClient : public QuotaClient {
+ public:
+ MockStorageClient(QuotaManager* qm);
+ virtual ~MockStorageClient();
+
+ virtual ID id() const OVERRIDE { return id_; }
+
+ // To add or modify mock data in this client.
+ void AddMockOriginData(const GURL& origin_url, StorageType type, int64 size);
+ void ModifyMockOriginDataSize(
+ const GURL& origin_url, StorageType type, int64 delta);
+
+ typedef QuotaClient::GetUsageCallback GetUsageCallback;
+ typedef QuotaClient::GetOriginsCallback GetOriginsCallback;
+
+ // QuotaClient methods.
+ virtual void GetOriginUsage(const GURL& origin_url,
+ StorageType type,
+ GetUsageCallback* callback) OVERRIDE;
+ virtual void GetOriginsForType(StorageType type,
+ GetOriginsCallback* callback) OVERRIDE;
+ virtual void GetOriginsForHost(StorageType type, const std::string& host,
+ GetOriginsCallback* callback) OVERRIDE;
+
+ private:
+ void RunGetOriginUsage(const GURL& origin_url,
+ StorageType type,
+ GetUsageCallback* callback);
+ void RunGetOriginsForType(StorageType type,
+ GetOriginsCallback* callback);
+ void RunGetOriginsForHost(StorageType type,
+ const std::string& host,
+ GetOriginsCallback* callback);
+
+ QuotaManager* quota_manager_;
+ const ID id_;
+
+ struct MockOriginData {
+ MockOriginData(StorageType type, int64 usage) : type(type), usage(usage) { }
+ StorageType type;
+ int64 usage;
+ };
+ std::map<GURL, MockOriginData> origin_data_;
+
+ std::set<GetUsageCallback*> usage_callbacks_;
+ std::set<GetOriginsCallback*> origins_callbacks_;
+
+ ScopedRunnableMethodFactory<MockStorageClient> runnable_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockStorageClient);
+};
+
+} // namespace quota
+
+#endif // WEBKIT_QUOTA_MOCK_STORAGE_H_
diff --git a/webkit/quota/quota_client.h b/webkit/quota/quota_client.h
new file mode 100644
index 0000000..1852e27
--- /dev/null
+++ b/webkit/quota/quota_client.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2011 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.
+
+#ifndef WEBKIT_QUOTA_QUOTA_CLIENT_H_
+#define WEBKIT_QUOTA_QUOTA_CLIENT_H_
+
+#include <set>
+#include <list>
+
+#include "base/callback.h"
+#include "base/time.h"
+#include "googleurl/src/gurl.h"
+#include "webkit/quota/quota_types.h"
+
+namespace quota {
+
+// An abstract interface for quota manager clients.
+// Each storage API must provide an implementation of this interface and
+// register it to the quota manager.
+// All the methods are assumed to be called on the IO thread in the browser.
+class QuotaClient {
+ public:
+ typedef Callback1<int64>::Type GetUsageCallback;
+ typedef Callback1<const std::set<GURL>&>::Type GetOriginsCallback;
+
+ virtual ~QuotaClient() {}
+
+ enum ID {
+ kUnknown,
+ kFileSystem,
+ kDatabase,
+ kAppcache,
+ kIndexedDatabase,
+ kMockStart, // This needs to be the end of the enum.
+ };
+
+ virtual ID id() const = 0;
+
+ // Called by the QuotaManager.
+ // Gets the amount of data stored in the storage specified by
+ // |origin_url| and |type|.
+ virtual void GetOriginUsage(const GURL& origin_url,
+ StorageType type,
+ GetUsageCallback* callback) = 0;
+
+ // Called by the QuotaManager.
+ // Returns a list of origins that has data in the |type| storage.
+ virtual void GetOriginsForType(StorageType type,
+ GetOriginsCallback* callback) = 0;
+
+ // Called by the QuotaManager.
+ // Returns a list of origins that match the |host|.
+ virtual void GetOriginsForHost(StorageType type,
+ const std::string& host,
+ GetOriginsCallback* callback) = 0;
+};
+
+typedef std::list<QuotaClient*> QuotaClientList;
+
+} // namespace quota
+
+#endif // WEBKIT_QUOTA_QUOTA_CLIENT_H_
diff --git a/webkit/quota/quota_database.h b/webkit/quota/quota_database.h
index aabb9ac..98aced9 100644
--- a/webkit/quota/quota_database.h
+++ b/webkit/quota/quota_database.h
@@ -12,6 +12,7 @@
#include "base/gtest_prod_util.h"
#include "base/memory/scoped_ptr.h"
#include "base/time.h"
+#include "webkit/quota/quota_types.h"
namespace sql {
class Connection;
@@ -23,13 +24,6 @@ class GURL;
namespace quota {
-// TODO(kinuko): put this in a separated header file when we have more modules.
-enum StorageType {
- kStorageTypeUnknown,
- kStorageTypeTemporary,
- kStorageTypePersistent,
-};
-
// All the methods of this class must run on the DB thread.
class QuotaDatabase {
public:
diff --git a/webkit/quota/quota_manager.cc b/webkit/quota/quota_manager.cc
new file mode 100644
index 0000000..647131c
--- /dev/null
+++ b/webkit/quota/quota_manager.cc
@@ -0,0 +1,516 @@
+// Copyright (c) 2011 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/quota/quota_manager.h"
+
+#include <deque>
+#include <algorithm>
+
+#include "base/callback.h"
+#include "base/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop_proxy.h"
+#include "base/stl_util-inl.h"
+#include "base/sys_info.h"
+#include "net/base/net_util.h"
+#include "webkit/quota/quota_database.h"
+#include "webkit/quota/quota_types.h"
+#include "webkit/quota/usage_tracker.h"
+
+using base::ScopedCallbackFactory;
+
+namespace quota {
+
+namespace {
+// Returns the initial size of the temporary storage quota.
+// (This just gives a default initial size; once its initial size is determined
+// it won't automatically be adjusted.)
+int64 GetInitialTemporaryStorageQuotaSize(const FilePath& path) {
+ int64 free_space = base::SysInfo::AmountOfFreeDiskSpace(path);
+ // Returns 0 (disables the temporary storage) if the available space is
+ // less than the twice of the default quota size.
+ if (free_space < QuotaManager::kTemporaryStorageQuotaDefaultSize * 2) {
+ return 0;
+ }
+ // Returns the default quota size while it is more than 5% of the
+ // available space.
+ if (free_space < QuotaManager::kTemporaryStorageQuotaDefaultSize * 20) {
+ return QuotaManager::kTemporaryStorageQuotaDefaultSize;
+ }
+ // Returns the 5% of the available space while it does not exceed the
+ // maximum quota size (1GB).
+ if (free_space < QuotaManager::kTemporaryStorageQuotaMaxSize * 20) {
+ return free_space / 20;
+ }
+ return QuotaManager::kTemporaryStorageQuotaMaxSize;
+}
+
+const int64 MBytes = 1024 * 1024;
+} // anonymous namespace
+
+// TODO(kinuko): We will need to have different sizes for different platforms
+// (e.g. larger for desktop etc) and may want to have them in preferences.
+const int64 QuotaManager::kTemporaryStorageQuotaDefaultSize = 50 * MBytes;
+const int64 QuotaManager::kTemporaryStorageQuotaMaxSize = 1 * 1024 * MBytes;
+const char QuotaManager::kDatabaseName[] = "QuotaManager";
+
+const int64 QuotaManager::kIncognitoDefaultTemporaryQuota = 5 * MBytes;
+
+// This class is for posting GetUsage/GetQuota tasks, gathering
+// results and dispatching GetAndQuota callbacks.
+// This class is self-destructed.
+class QuotaManager::UsageAndQuotaDispatcherTask : public QuotaTask {
+ public:
+ typedef std::deque<GetUsageAndQuotaCallback*> CallbackList;
+
+ static UsageAndQuotaDispatcherTask* Create(
+ QuotaManager* manager, const std::string& host, StorageType type);
+
+ // Returns true if it is the first call for this task; which means
+ // the caller needs to call Start().
+ bool AddCallback(GetUsageAndQuotaCallback* callback) {
+ callbacks_.push_back(callback);
+ return (callbacks_.size() == 1);
+ }
+
+ void DidGetGlobalUsage(int64 usage) {
+ global_usage_ = usage;
+ CheckCompleted();
+ }
+
+ void DidGetHostUsage(const std::string& host_unused, int64 usage) {
+ host_usage_ = usage;
+ CheckCompleted();
+ }
+
+ void DidGetGlobalQuota(int64 quota) {
+ quota_ = quota;
+ CheckCompleted();
+ }
+
+ void DidGetHostQuota(const std::string& host_unused, int64 quota) {
+ quota_ = quota;
+ CheckCompleted();
+ }
+
+ protected:
+ UsageAndQuotaDispatcherTask(
+ QuotaManager* manager,
+ const std::string& host,
+ StorageType type)
+ : QuotaTask(manager),
+ host_(host),
+ type_(type),
+ quota_(-1),
+ global_usage_(-1),
+ host_usage_(-1),
+ callback_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {}
+
+ virtual ~UsageAndQuotaDispatcherTask() {
+ STLDeleteContainerPointers(callbacks_.begin(), callbacks_.end());
+ }
+
+ virtual bool IsCompleted() const = 0;
+
+ virtual void DispatchCallback(GetUsageAndQuotaCallback* callback) = 0;
+
+ virtual void Aborted() OVERRIDE {
+ for (CallbackList::iterator iter = callbacks_.begin();
+ iter != callbacks_.end();
+ ++iter) {
+ (*iter)->Run(kQuotaErrorAbort, 0, 0);
+ delete *iter;
+ }
+ callbacks_.clear();
+ delete this;
+ }
+
+ virtual void Completed() OVERRIDE { delete this; }
+ QuotaManager* manager() const {
+ return static_cast<QuotaManager*>(observer());
+ }
+
+ std::string host() const { return host_; }
+ StorageType type() const { return type_; }
+ int64 quota() const { return quota_; }
+ int64 global_usage() const { return global_usage_; }
+ int64 host_usage() const { return host_usage_; }
+
+ UsageCallback* NewGlobalUsageCallback() {
+ return callback_factory_.NewCallback(
+ &UsageAndQuotaDispatcherTask::DidGetGlobalUsage);
+ }
+
+ HostUsageCallback* NewHostUsageCallback() {
+ return callback_factory_.NewCallback(
+ &UsageAndQuotaDispatcherTask::DidGetHostUsage);
+ }
+
+ QuotaCallback* NewGlobalQuotaCallback() {
+ return callback_factory_.NewCallback(
+ &UsageAndQuotaDispatcherTask::DidGetGlobalQuota);
+ }
+
+ HostQuotaCallback* NewHostQuotaCallback() {
+ return callback_factory_.NewCallback(
+ &UsageAndQuotaDispatcherTask::DidGetHostQuota);
+ }
+
+ private:
+ void CheckCompleted() {
+ if (IsCompleted()) {
+ // Dispatches callbacks.
+ for (CallbackList::iterator iter = callbacks_.begin();
+ iter != callbacks_.end();
+ ++iter) {
+ DispatchCallback(*iter);
+ delete *iter;
+ }
+ callbacks_.clear();
+ UsageAndQuotaDispatcherTaskMap& dispatcher_map =
+ manager()->usage_and_quota_dispatchers_;
+ DCHECK(dispatcher_map.find(std::make_pair(host_, type_)) !=
+ dispatcher_map.end());
+ dispatcher_map.erase(std::make_pair(host_, type_));
+ CallCompleted();
+ }
+ }
+
+ const std::string host_;
+ const StorageType type_;
+ int64 quota_;
+ int64 global_usage_;
+ int64 host_usage_;
+ CallbackList callbacks_;
+ ScopedCallbackFactory<UsageAndQuotaDispatcherTask> callback_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(UsageAndQuotaDispatcherTask);
+};
+
+class QuotaManager::UsageAndQuotaDispatcherTaskForTemporary
+ : public QuotaManager::UsageAndQuotaDispatcherTask {
+ public:
+ UsageAndQuotaDispatcherTaskForTemporary(
+ QuotaManager* manager, const std::string host)
+ : UsageAndQuotaDispatcherTask(manager, host, kStorageTypeTemporary) {}
+
+ protected:
+ virtual void Run() OVERRIDE {
+ manager()->temporary_usage_tracker_->GetGlobalUsage(
+ NewGlobalUsageCallback());
+ manager()->temporary_usage_tracker_->GetHostUsage(
+ host(), NewHostUsageCallback());
+ manager()->GetTemporaryGlobalQuota(NewGlobalQuotaCallback());
+ }
+
+ virtual bool IsCompleted() const OVERRIDE {
+ return (quota() >= 0 && global_usage() >= 0 && host_usage() >= 0);
+ }
+
+ virtual void DispatchCallback(GetUsageAndQuotaCallback* callback) OVERRIDE {
+ // TODO(kinuko): For now it returns pessimistic quota. Change this
+ // to return {usage, quota - nonevictable_usage} once eviction is
+ // supported.
+ int64 other_usage = global_usage() - host_usage();
+ callback->Run(kQuotaStatusOk, host_usage(), quota() - other_usage);
+ }
+};
+
+class QuotaManager::UsageAndQuotaDispatcherTaskForPersistent
+ : public QuotaManager::UsageAndQuotaDispatcherTask {
+ public:
+ UsageAndQuotaDispatcherTaskForPersistent(
+ QuotaManager* manager, const std::string host)
+ : UsageAndQuotaDispatcherTask(manager, host, kStorageTypePersistent) {}
+
+ protected:
+ virtual void Run() OVERRIDE {
+ manager()->persistent_usage_tracker_->GetHostUsage(
+ host(), NewHostUsageCallback());
+ manager()->GetPersistentHostQuota(
+ host(), NewHostQuotaCallback());
+ }
+
+ virtual bool IsCompleted() const OVERRIDE {
+ return (quota() >= 0 && host_usage() >= 0);
+ }
+
+ virtual void DispatchCallback(GetUsageAndQuotaCallback* callback) OVERRIDE {
+ callback->Run(kQuotaStatusOk, host_usage(), quota());
+ }
+};
+
+// static
+QuotaManager::UsageAndQuotaDispatcherTask*
+QuotaManager::UsageAndQuotaDispatcherTask::Create(
+ QuotaManager* manager, const std::string& host, StorageType type) {
+ switch (type) {
+ case kStorageTypeTemporary:
+ return new UsageAndQuotaDispatcherTaskForTemporary(
+ manager, host);
+ case kStorageTypePersistent:
+ return new UsageAndQuotaDispatcherTaskForPersistent(
+ manager, host);
+ default:
+ NOTREACHED();
+ }
+ return NULL;
+}
+
+class QuotaManager::InitializeTask : public QuotaThreadTask {
+ public:
+ InitializeTask(
+ QuotaManager* manager,
+ QuotaDatabase* database,
+ scoped_refptr<base::MessageLoopProxy> db_message_loop,
+ const FilePath& profile_path)
+ : QuotaThreadTask(manager, db_message_loop),
+ manager_(manager),
+ database_(database),
+ profile_path_(profile_path),
+ temporary_storage_quota_(-1),
+ db_disabled_(false) {
+ DCHECK(database_);
+ }
+
+ protected:
+ virtual void RunOnTargetThread() OVERRIDE {
+ // Initializes the global temporary quota.
+ if (!database_->GetGlobalQuota(
+ kStorageTypeTemporary, &temporary_storage_quota_)) {
+ // If the temporary storage quota size has not been initialized,
+ // make up one and store it in the database.
+ temporary_storage_quota_ = GetInitialTemporaryStorageQuotaSize(
+ profile_path_);
+ if (!database_->SetGlobalQuota(
+ kStorageTypeTemporary, temporary_storage_quota_)) {
+ db_disabled_ = true;
+ }
+ }
+ }
+
+ virtual void Completed() OVERRIDE {
+ DCHECK(manager_);
+ if (manager_->temporary_global_quota_ < 0)
+ manager_->DidGetTemporaryGlobalQuota(temporary_storage_quota_);
+ manager_->db_initialized_ = !db_disabled_;
+ manager_->db_disabled_ = db_disabled_;
+ }
+
+ private:
+ QuotaManager* manager_;
+ QuotaDatabase* database_;
+ FilePath profile_path_;
+ int64 temporary_storage_quota_;
+ bool db_disabled_;
+};
+
+class QuotaManager::TemporaryGlobalQuotaUpdateTask : public QuotaThreadTask {
+ public:
+ TemporaryGlobalQuotaUpdateTask(
+ QuotaManager* manager,
+ QuotaDatabase* database,
+ scoped_refptr<base::MessageLoopProxy> db_message_loop,
+ int64 new_quota)
+ : QuotaThreadTask(manager, db_message_loop),
+ manager_(manager),
+ database_(database),
+ new_quota_(new_quota),
+ db_disabled_(false) {
+ DCHECK(database_);
+ }
+
+ protected:
+ virtual void RunOnTargetThread() OVERRIDE {
+ if (!database_->SetGlobalQuota(kStorageTypeTemporary, new_quota_))
+ db_disabled_ = true;
+ }
+
+ virtual void Completed() OVERRIDE {
+ DCHECK(manager_);
+ manager_->db_disabled_ = db_disabled_;
+ }
+
+ private:
+ QuotaManager* manager_;
+ QuotaDatabase* database_;
+ int64 new_quota_;
+ bool db_disabled_;
+};
+
+QuotaManager::QuotaManager(bool is_incognito,
+ const FilePath& profile_path,
+ scoped_refptr<base::MessageLoopProxy> io_thread,
+ scoped_refptr<base::MessageLoopProxy> db_thread)
+ : is_incognito_(is_incognito),
+ profile_path_(profile_path),
+ db_initialized_(false),
+ db_disabled_(false),
+ io_thread_(io_thread),
+ db_thread_(db_thread),
+ temporary_global_quota_(-1),
+ callback_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
+}
+
+QuotaManager::~QuotaManager() {
+ STLDeleteContainerPointers(clients_.begin(), clients_.end());
+}
+
+void QuotaManager::RegisterClient(QuotaClient* client) {
+ DCHECK(!database_.get());
+ clients_.push_back(client);
+}
+
+void QuotaManager::GetUsageAndQuota(
+ const GURL& origin, StorageType type,
+ GetUsageAndQuotaCallback* callback_ptr) {
+ scoped_ptr<GetUsageAndQuotaCallback> callback(callback_ptr);
+ LazyInitialize();
+ if (is_incognito_) {
+ int64 quota = 0;
+ if (type == kStorageTypeTemporary)
+ quota = clients_.size() * kIncognitoDefaultTemporaryQuota;
+ // TODO(kinuko): This does not return useful usage value for now.
+ callback->Run(kQuotaStatusOk, 0, quota);
+ return;
+ }
+
+ std::string host = net::GetHostOrSpecFromURL(origin);
+ UsageAndQuotaDispatcherTaskMap::iterator found =
+ usage_and_quota_dispatchers_.find(std::make_pair(host, type));
+ if (found == usage_and_quota_dispatchers_.end()) {
+ UsageAndQuotaDispatcherTask* dispatcher =
+ UsageAndQuotaDispatcherTask::Create(this, host, type);
+ found = usage_and_quota_dispatchers_.insert(
+ std::make_pair(std::make_pair(host, type), dispatcher)).first;
+ }
+ if (found->second->AddCallback(callback.release()))
+ found->second->Start();
+}
+
+void QuotaManager::RequestQuota(
+ const GURL& origin, StorageType type,
+ int64 requested_size,
+ RequestQuotaCallback* callback) {
+ LazyInitialize();
+ // TODO(kinuko): implement me.
+ callback->Run(kQuotaErrorNotSupported, 0);
+ delete callback;
+}
+
+void QuotaManager::NotifyStorageModified(
+ QuotaClient::ID client_id,
+ const GURL& origin, StorageType type, int64 delta) {
+ LazyInitialize();
+ UsageTracker* tracker = GetUsageTracker(type);
+ DCHECK(tracker);
+ tracker->UpdateUsageCache(client_id, origin, delta);
+}
+
+void QuotaManager::GetTemporaryGlobalQuota(QuotaCallback* callback) {
+ LazyInitialize();
+ if (temporary_global_quota_ >= 0) {
+ // TODO(kinuko): The in-memory quota value should be periodically
+ // updated not to exceed the current available space in the hard drive.
+ callback->Run(temporary_global_quota_);
+ delete callback;
+ return;
+ }
+ // They are called upon completion of InitializeTask.
+ temporary_global_quota_callbacks_.Add(callback);
+}
+
+void QuotaManager::SetTemporaryGlobalQuota(int64 new_quota) {
+ LazyInitialize();
+ DCHECK(new_quota >= 0);
+ DidGetTemporaryGlobalQuota(new_quota);
+ if (!db_disabled_) {
+ scoped_refptr<TemporaryGlobalQuotaUpdateTask> task(
+ new TemporaryGlobalQuotaUpdateTask(
+ this, database_.get(), db_thread_, new_quota));
+ task->Start();
+ }
+}
+
+void QuotaManager::GetPersistentHostQuota(const std::string& host,
+ HostQuotaCallback* callback) {
+ LazyInitialize();
+ std::map<std::string, int64>::iterator found =
+ persistent_host_quota_.find(host);
+ if (found != persistent_host_quota_.end()) {
+ callback->Run(host, found->second);
+ delete callback;
+ return;
+ }
+ if (persistent_host_quota_callbacks_.Add(host, callback)) {
+ // This is the first call for this host.
+ // TODO(kinuko): Dispatch a task to get the host quota for the host
+ // once QuotaDatabase is updated to accept hosts instead of origins.
+ }
+}
+
+void QuotaManager::SetPersistentHostQuota(const std::string& host,
+ int64 new_quota) {
+ LazyInitialize();
+ // TODO(kinuko): Implement once QuotaDatabase is updated.
+}
+
+void QuotaManager::LazyInitialize() {
+ DCHECK(io_thread_->BelongsToCurrentThread());
+ if (database_.get()) {
+ // Initialization seems to be done already.
+ return;
+ }
+
+ if (is_incognito_)
+ return;
+
+ database_.reset(new QuotaDatabase(profile_path_.AppendASCII(kDatabaseName)));
+
+ temporary_usage_tracker_.reset(
+ new UsageTracker(clients_, kStorageTypeTemporary));
+ persistent_usage_tracker_.reset(
+ new UsageTracker(clients_, kStorageTypePersistent));
+
+ scoped_refptr<InitializeTask> task(
+ new InitializeTask(this, database_.get(), db_thread_, profile_path_));
+ task->Start();
+}
+
+UsageTracker* QuotaManager::GetUsageTracker(StorageType type) const {
+ switch (type) {
+ case kStorageTypeTemporary:
+ return temporary_usage_tracker_.get();
+ case kStorageTypePersistent:
+ return persistent_usage_tracker_.get();
+ default:
+ NOTREACHED();
+ }
+ return NULL;
+}
+
+void QuotaManager::DidGetTemporaryGlobalQuota(int64 quota) {
+ temporary_global_quota_ = quota;
+ temporary_global_quota_callbacks_.Run(quota);
+}
+
+void QuotaManager::DidGetPersistentHostQuota(const std::string& host,
+ int64 quota) {
+ DCHECK(persistent_host_quota_.find(host) == persistent_host_quota_.end());
+ persistent_host_quota_[host] = quota;
+ persistent_host_quota_callbacks_.Run(host, quota);
+}
+
+void QuotaManager::DeleteOnCorrectThread() const {
+ if (database_.get()) {
+ db_thread_->DeleteSoon(FROM_HERE, database_.release());
+ }
+ if (!io_thread_->BelongsToCurrentThread()) {
+ io_thread_->DeleteSoon(FROM_HERE, this);
+ return;
+ }
+ delete this;
+}
+
+} // namespace quota
diff --git a/webkit/quota/quota_manager.h b/webkit/quota/quota_manager.h
new file mode 100644
index 0000000..a9a458c
--- /dev/null
+++ b/webkit/quota/quota_manager.h
@@ -0,0 +1,149 @@
+// Copyright (c) 2011 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.
+
+#ifndef WEBKIT_QUOTA_QUOTA_MANAGER_H_
+#define WEBKIT_QUOTA_QUOTA_MANAGER_H_
+#pragma once
+
+#include <deque>
+#include <list>
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/file_path.h"
+#include "base/memory/scoped_callback_factory.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "webkit/quota/quota_client.h"
+#include "webkit/quota/quota_task.h"
+#include "webkit/quota/quota_types.h"
+
+class FilePath;
+
+namespace quota {
+
+class QuotaDatabase;
+class UsageTracker;
+
+struct QuotaManagerDeleter;
+
+// The quota manager class. This class is instantiated per profile and
+// held by the profile.
+class QuotaManager : public QuotaTaskObserver,
+ public base::RefCountedThreadSafe<
+ QuotaManager, QuotaManagerDeleter> {
+ public:
+ typedef Callback3<QuotaStatusCode,
+ int64 /* usage */,
+ int64 /* quota */>::Type GetUsageAndQuotaCallback;
+ typedef Callback2<QuotaStatusCode,
+ int64 /* granted_quota */>::Type RequestQuotaCallback;
+
+ QuotaManager(bool is_incognito,
+ const FilePath& profile_path,
+ scoped_refptr<base::MessageLoopProxy> io_thread,
+ scoped_refptr<base::MessageLoopProxy> db_thread);
+
+ virtual ~QuotaManager();
+
+ // The client's ownership is transferred to the manager.
+ void RegisterClient(QuotaClient* client);
+
+ // Called by clients or webapps on the IO thread.
+ void GetUsageAndQuota(const GURL& origin, StorageType type,
+ GetUsageAndQuotaCallback* callback);
+
+ // Called by webapps on the IO thread.
+ void RequestQuota(const GURL& origin, StorageType type,
+ int64 requested_size,
+ RequestQuotaCallback* callback);
+
+ // Called by clients on the IO thread.
+ // QuotaClients must call this method whenever they have made any
+ // modifications that change the amount of data stored in their storage.
+ void NotifyStorageModified(QuotaClient::ID client_id,
+ const GURL& origin,
+ StorageType type,
+ int64 delta);
+
+ // Called by UI and internal modules.
+ void GetTemporaryGlobalQuota(QuotaCallback* callback);
+ void SetTemporaryGlobalQuota(int64 new_quota);
+ void GetPersistentHostQuota(const std::string& host,
+ HostQuotaCallback* callback);
+ void SetPersistentHostQuota(const std::string& host, int64 new_quota);
+
+ // TODO(kinuko): Add more APIs for UI:
+ // - Get temporary global/per-host usage
+ // - Get persistent global/per-host usage
+
+ const static int64 kTemporaryStorageQuotaDefaultSize;
+ const static int64 kTemporaryStorageQuotaMaxSize;
+ const static char kDatabaseName[];
+
+ const static int64 kIncognitoDefaultTemporaryQuota;
+
+ private:
+ class InitializeTask;
+ class TemporaryGlobalQuotaUpdateTask;
+
+ class UsageAndQuotaDispatcherTask;
+ class UsageAndQuotaDispatcherTaskForTemporary;
+ class UsageAndQuotaDispatcherTaskForPersistent;
+
+ typedef std::pair<std::string, StorageType> HostAndType;
+ typedef std::map<HostAndType, UsageAndQuotaDispatcherTask*>
+ UsageAndQuotaDispatcherTaskMap;
+
+ // This initialization method is lazily called on the IO thread
+ // when the first quota manager API is called.
+ // Initialize must be called after all quota clients are added to the
+ // manager by RegisterStorage.
+ void LazyInitialize();
+
+ UsageTracker* GetUsageTracker(StorageType type) const;
+
+ void DidGetTemporaryGlobalQuota(int64 quota);
+ void DidGetPersistentHostQuota(const std::string& host, int64 quota);
+
+ friend struct QuotaManagerDeleter;
+ void DeleteOnCorrectThread() const;
+
+ const bool is_incognito_;
+ const FilePath profile_path_;
+
+ bool db_initialized_;
+ bool db_disabled_;
+ scoped_refptr<base::MessageLoopProxy> io_thread_;
+ scoped_refptr<base::MessageLoopProxy> db_thread_;
+ mutable scoped_ptr<QuotaDatabase> database_;
+
+ QuotaClientList clients_;
+
+ scoped_ptr<UsageTracker> temporary_usage_tracker_;
+ scoped_ptr<UsageTracker> persistent_usage_tracker_;
+
+ UsageAndQuotaDispatcherTaskMap usage_and_quota_dispatchers_;
+
+ int64 temporary_global_quota_;
+ QuotaCallbackQueue temporary_global_quota_callbacks_;
+
+ std::map<std::string, int64> persistent_host_quota_;
+ HostQuotaCallbackMap persistent_host_quota_callbacks_;
+
+ base::ScopedCallbackFactory<QuotaManager> callback_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuotaManager);
+};
+
+struct QuotaManagerDeleter {
+ static void Destruct(const QuotaManager* manager) {
+ manager->DeleteOnCorrectThread();
+ }
+};
+
+} // namespace quota
+
+#endif // WEBKIT_QUOTA_QUOTA_MANAGER_H_
diff --git a/webkit/quota/quota_manager_unittest.cc b/webkit/quota/quota_manager_unittest.cc
new file mode 100644
index 0000000..47a4c27
--- /dev/null
+++ b/webkit/quota/quota_manager_unittest.cc
@@ -0,0 +1,295 @@
+// Copyright (c) 2011 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 "testing/gtest/include/gtest/gtest.h"
+
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/memory/scoped_callback_factory.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_temp_dir.h"
+#include "base/message_loop.h"
+#include "base/message_loop_proxy.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebStorageQuotaError.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebStorageQuotaType.h"
+#include "webkit/quota/mock_storage_client.h"
+#include "webkit/quota/quota_database.h"
+#include "webkit/quota/quota_manager.h"
+
+using base::MessageLoopProxy;
+using WebKit::WebStorageQuotaError;
+using WebKit::WebStorageQuotaType;
+
+namespace quota {
+
+namespace {
+struct MockOriginData {
+ const char* origin;
+ StorageType type;
+ int64 usage;
+};
+} // anonymous namespace
+
+class QuotaManagerTest : public testing::Test {
+ public:
+ QuotaManagerTest()
+ : callback_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
+ }
+
+ void SetUp() {
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+ quota_manager_ = new QuotaManager(
+ false /* is_incognito */,
+ data_dir_.path(),
+ MessageLoopProxy::CreateForCurrentThread(),
+ MessageLoopProxy::CreateForCurrentThread());
+ additional_callback_count_ = 0;
+ }
+
+ void TearDown() {
+ // Make sure the quota manager cleans up correctly.
+ quota_manager_ = NULL;
+ MessageLoop::current()->RunAllPending();
+ }
+
+ protected:
+ MockStorageClient* CreateClient(
+ const MockOriginData* mock_data, size_t mock_data_size) {
+ MockStorageClient* client(
+ new MockStorageClient(quota_manager_.get()));
+ for (size_t i = 0; i < mock_data_size; ++i) {
+ client->AddMockOriginData(GURL(mock_data[i].origin),
+ mock_data[i].type,
+ mock_data[i].usage);
+ }
+ return client;
+ }
+
+ void RegisterClient(MockStorageClient* client) {
+ quota_manager_->RegisterClient(client);
+ }
+
+ void GetUsageAndQuota(const GURL& origin, StorageType type) {
+ status_ = kQuotaStatusUnknown;
+ usage_ = -1;
+ quota_ = -1;
+ quota_manager_->GetUsageAndQuota(origin, type,
+ callback_factory_.NewCallback(
+ &QuotaManagerTest::DidGetUsageAndQuota));
+ }
+
+ void RunAdditionalUsageAndQuotaTask(const GURL& origin, StorageType type) {
+ quota_manager_->GetUsageAndQuota(origin, type,
+ callback_factory_.NewCallback(
+ &QuotaManagerTest::DidGetUsageAndQuotaAdditional));
+ }
+
+ void DidGetUsageAndQuota(QuotaStatusCode status, int64 usage, int64 quota) {
+ status_ = status;
+ usage_ = usage;
+ quota_ = quota;
+ }
+
+ void set_additional_callback_count(int c) { additional_callback_count_ = c; }
+ int additional_callback_count() const { return additional_callback_count_; }
+ void DidGetUsageAndQuotaAdditional(
+ QuotaStatusCode status, int64 usage, int64 quota) {
+ ++additional_callback_count_;
+ }
+
+ QuotaManager* quota_manager() const { return quota_manager_.get(); }
+ void set_quota_manager(QuotaManager* quota_manager) {
+ quota_manager_ = quota_manager;
+ }
+
+ QuotaStatusCode status() const { return status_; }
+ int64 usage() const { return usage_; }
+ int64 quota() const { return quota_; }
+
+ private:
+ ScopedTempDir data_dir_;
+ base::ScopedCallbackFactory<QuotaManagerTest> callback_factory_;
+
+ scoped_refptr<QuotaManager> quota_manager_;
+
+ QuotaStatusCode status_;
+ int64 usage_;
+ int64 quota_;
+
+ int additional_callback_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuotaManagerTest);
+};
+
+TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_Simple) {
+ const static MockOriginData kData[] = {
+ { "http://foo.com/", kStorageTypeTemporary, 10 },
+ { "http://foo.com/", kStorageTypePersistent, 80 },
+ };
+ RegisterClient(CreateClient(kData, ARRAYSIZE_UNSAFE(kData)));
+
+ GetUsageAndQuota(GURL("http://foo.com/"), kStorageTypeTemporary);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10, usage());
+ EXPECT_GT(quota(), 0);
+ EXPECT_LE(quota(), QuotaManager::kTemporaryStorageQuotaMaxSize);
+ int64 quota_returned_for_foo = quota();
+
+ GetUsageAndQuota(GURL("http://bar.com/"), kStorageTypeTemporary);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(0, usage());
+ EXPECT_EQ(quota_returned_for_foo - 10, quota());
+}
+
+TEST_F(QuotaManagerTest, GetTemporaryUsage_NoClient) {
+ GetUsageAndQuota(GURL("http://foo.com/"), kStorageTypeTemporary);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(0, usage());
+}
+
+TEST_F(QuotaManagerTest, GetTemporaryUsage_EmptyClient) {
+ RegisterClient(CreateClient(NULL, 0));
+ GetUsageAndQuota(GURL("http://foo.com/"), kStorageTypeTemporary);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(0, usage());
+}
+
+TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_MultiOrigins) {
+ const static MockOriginData kData[] = {
+ { "http://foo.com/", kStorageTypeTemporary, 10 },
+ { "http://foo.com:8080/", kStorageTypeTemporary, 20 },
+ { "http://bar.com/", kStorageTypeTemporary, 5 },
+ { "https://bar.com/", kStorageTypeTemporary, 7 },
+ { "http://baz.com/", kStorageTypeTemporary, 30 },
+ { "http://foo.com/", kStorageTypePersistent, 40 },
+ };
+ RegisterClient(CreateClient(kData, ARRAYSIZE_UNSAFE(kData)));
+
+ // This time explicitly sets a temporary global quota.
+ quota_manager()->SetTemporaryGlobalQuota(100);
+
+ GetUsageAndQuota(GURL("http://foo.com/"), kStorageTypeTemporary);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10 + 20, usage());
+
+ // The returned quota must be equal to (global_quota - other_origins_usage).
+ EXPECT_EQ(100 - (5 + 7 + 30), quota());
+
+ GetUsageAndQuota(GURL("http://bar.com/"), kStorageTypeTemporary);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(5 + 7, usage());
+ EXPECT_EQ(100 - (10 + 20 + 30), quota());
+}
+
+TEST_F(QuotaManagerTest, GetTemporaryUsage_MultipleClients) {
+ const static MockOriginData kData1[] = {
+ { "http://foo.com/", kStorageTypeTemporary, 10 },
+ { "http://bar.com/", kStorageTypeTemporary, 20 },
+ };
+ const static MockOriginData kData2[] = {
+ { "https://foo.com/", kStorageTypeTemporary, 30 },
+ { "http://example.com/", kStorageTypePersistent, 40 },
+ };
+ RegisterClient(CreateClient(kData1, ARRAYSIZE_UNSAFE(kData1)));
+ RegisterClient(CreateClient(kData2, ARRAYSIZE_UNSAFE(kData2)));
+
+ GetUsageAndQuota(GURL("http://foo.com/"), kStorageTypeTemporary);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10 + 30, usage());
+}
+
+TEST_F(QuotaManagerTest, GetTemporaryUsage_WithModify) {
+ const static MockOriginData kData[] = {
+ { "http://foo.com/", kStorageTypeTemporary, 10 },
+ { "http://foo.com:1/", kStorageTypeTemporary, 20 },
+ };
+ MockStorageClient* client = CreateClient(kData, ARRAYSIZE_UNSAFE(kData));
+ RegisterClient(client);
+
+ GetUsageAndQuota(GURL("http://foo.com/"), kStorageTypeTemporary);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10 + 20, usage());
+
+ client->ModifyMockOriginDataSize(
+ GURL("http://foo.com/"), kStorageTypeTemporary, 20);
+ client->ModifyMockOriginDataSize(
+ GURL("http://foo.com:1/"), kStorageTypeTemporary, -5);
+ client->ModifyMockOriginDataSize(
+ GURL("http://bar.com/"), kStorageTypeTemporary, 33);
+
+ GetUsageAndQuota(GURL("http://foo.com/"), kStorageTypeTemporary);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10 + 20 + 20 - 5, usage());
+
+ GetUsageAndQuota(GURL("http://bar.com/"), kStorageTypeTemporary);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(33, usage());
+}
+
+TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_WithAdditionalTasks) {
+ const static MockOriginData kData[] = {
+ { "http://foo.com/", kStorageTypeTemporary, 10 },
+ { "http://foo.com:8080/", kStorageTypeTemporary, 20 },
+ { "http://bar.com/", kStorageTypeTemporary, 13 },
+ { "http://foo.com/", kStorageTypePersistent, 40 },
+ };
+ RegisterClient(CreateClient(kData, ARRAYSIZE_UNSAFE(kData)));
+ quota_manager()->SetTemporaryGlobalQuota(100);
+
+ GetUsageAndQuota(GURL("http://foo.com/"), kStorageTypeTemporary);
+ GetUsageAndQuota(GURL("http://foo.com/"), kStorageTypeTemporary);
+ GetUsageAndQuota(GURL("http://foo.com/"), kStorageTypeTemporary);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10 + 20, usage());
+ EXPECT_EQ(100 - 13, quota());
+
+ set_additional_callback_count(0);
+ RunAdditionalUsageAndQuotaTask(GURL("http://foo.com/"),
+ kStorageTypeTemporary);
+ GetUsageAndQuota(GURL("http://foo.com/"), kStorageTypeTemporary);
+ RunAdditionalUsageAndQuotaTask(GURL("http://bar.com/"),
+ kStorageTypeTemporary);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(kQuotaStatusOk, status());
+ EXPECT_EQ(10 + 20, usage());
+ EXPECT_EQ(100 - 13, quota());
+ EXPECT_EQ(2, additional_callback_count());
+}
+
+TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_NukeManager) {
+ const static MockOriginData kData[] = {
+ { "http://foo.com/", kStorageTypeTemporary, 10 },
+ { "http://foo.com:8080/", kStorageTypeTemporary, 20 },
+ { "http://bar.com/", kStorageTypeTemporary, 13 },
+ { "http://foo.com/", kStorageTypePersistent, 40 },
+ };
+ RegisterClient(CreateClient(kData, ARRAYSIZE_UNSAFE(kData)));
+ quota_manager()->SetTemporaryGlobalQuota(100);
+
+ set_additional_callback_count(0);
+ GetUsageAndQuota(GURL("http://foo.com/"), kStorageTypeTemporary);
+ RunAdditionalUsageAndQuotaTask(GURL("http://foo.com/"),
+ kStorageTypeTemporary);
+ RunAdditionalUsageAndQuotaTask(GURL("http://bar.com/"),
+ kStorageTypeTemporary);
+
+ // Nuke before waiting for callbacks.
+ set_quota_manager(NULL);
+ MessageLoop::current()->RunAllPending();
+ EXPECT_EQ(kQuotaErrorAbort, status());
+}
+
+} // namespace quota
diff --git a/webkit/quota/quota_task.cc b/webkit/quota/quota_task.cc
new file mode 100644
index 0000000..974e9b4
--- /dev/null
+++ b/webkit/quota/quota_task.cc
@@ -0,0 +1,87 @@
+// Copyright (c) 2011 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/quota/quota_task.h"
+
+#include <algorithm>
+#include <functional>
+
+#include "base/message_loop_proxy.h"
+
+using base::MessageLoopProxy;
+
+namespace quota {
+
+// QuotaTask ---------------------------------------------------------------
+
+QuotaTask::QuotaTask(QuotaTaskObserver* observer)
+ : observer_(observer),
+ original_message_loop_(MessageLoopProxy::CreateForCurrentThread()) {
+}
+
+QuotaTask::~QuotaTask() {
+}
+
+void QuotaTask::Start() {
+ DCHECK(observer_);
+ observer()->RegisterTask(this);
+ Run();
+}
+
+void QuotaTask::CallCompleted() {
+ DCHECK(original_message_loop_->BelongsToCurrentThread());
+ if (observer_) {
+ observer_->UnregisterTask(this);
+ Completed();
+ }
+}
+
+void QuotaTask::Abort() {
+ DCHECK(original_message_loop_->BelongsToCurrentThread());
+ observer_ = NULL;
+ Aborted();
+}
+
+// QuotaThreadTask ---------------------------------------------------------
+
+QuotaThreadTask::QuotaThreadTask(
+ QuotaTaskObserver* observer,
+ scoped_refptr<MessageLoopProxy> target_message_loop)
+ : QuotaTask(observer),
+ target_message_loop_(target_message_loop) {
+}
+
+QuotaThreadTask::~QuotaThreadTask() {
+}
+
+void QuotaThreadTask::Run() {
+ target_message_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &QuotaThreadTask::CallRunOnTargetThread));
+}
+
+void QuotaThreadTask::CallRunOnTargetThread() {
+ DCHECK(target_message_loop_->BelongsToCurrentThread());
+ RunOnTargetThread();
+ original_message_loop()->PostTask(
+ FROM_HERE, NewRunnableMethod(this, &QuotaThreadTask::CallCompleted));
+}
+
+// QuotaTaskObserver -------------------------------------------------------
+
+QuotaTaskObserver::~QuotaTaskObserver() {
+ std::for_each(running_quota_tasks_.begin(),
+ running_quota_tasks_.end(),
+ std::mem_fun(&QuotaTask::Abort));
+}
+
+void QuotaTaskObserver::RegisterTask(QuotaTask* task) {
+ running_quota_tasks_.insert(task);
+}
+
+void QuotaTaskObserver::UnregisterTask(QuotaTask* task) {
+ DCHECK(running_quota_tasks_.find(task) != running_quota_tasks_.end());
+ running_quota_tasks_.erase(task);
+}
+
+} // namespace quota
diff --git a/webkit/quota/quota_task.h b/webkit/quota/quota_task.h
new file mode 100644
index 0000000..23d7225
--- /dev/null
+++ b/webkit/quota/quota_task.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2011 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.
+
+#ifndef WEBKIT_QUOTA_QUOTA_TASK_H_
+#define WEBKIT_QUOTA_QUOTA_TASK_H_
+#pragma once
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+
+namespace base {
+class MessageLoopProxy;
+}
+
+namespace quota {
+
+class QuotaTaskObserver;
+
+// A base class for quota tasks.
+class QuotaTask {
+ public:
+ void Start();
+
+ protected:
+ QuotaTask(QuotaTaskObserver* observer);
+ virtual ~QuotaTask();
+
+ // The task body.
+ virtual void Run() = 0;
+
+ // Called upon completion, on the original message loop.
+ virtual void Completed() = 0;
+
+ // Called when the task is aborted.
+ virtual void Aborted() {}
+
+ void CallCompleted();
+ QuotaTaskObserver* observer() const { return observer_; }
+ scoped_refptr<base::MessageLoopProxy> original_message_loop() const {
+ return original_message_loop_;
+ }
+
+ private:
+ friend class QuotaTaskObserver;
+ void Abort();
+ QuotaTaskObserver* observer_;
+ scoped_refptr<base::MessageLoopProxy> original_message_loop_;
+};
+
+// For tasks that post tasks to the other thread.
+class QuotaThreadTask : public QuotaTask,
+ public base::RefCountedThreadSafe<QuotaThreadTask> {
+ public:
+ QuotaThreadTask(QuotaTaskObserver* observer,
+ scoped_refptr<base::MessageLoopProxy> target_message_loop);
+
+ protected:
+ virtual ~QuotaThreadTask();
+
+ // Called on the target message loop.
+ virtual void RunOnTargetThread() = 0;
+
+ virtual void Run() OVERRIDE;
+ scoped_refptr<base::MessageLoopProxy> target_message_loop() const {
+ return target_message_loop_;
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<QuotaThreadTask>;
+ friend class QuotaTaskObserver;
+ void CallRunOnTargetThread();
+
+ scoped_refptr<base::MessageLoopProxy> target_message_loop_;
+};
+
+class QuotaTaskObserver {
+ public:
+ virtual ~QuotaTaskObserver();
+
+ protected:
+ friend class QuotaTask;
+ friend class QuotaThreadTask;
+
+ void RegisterTask(QuotaTask* task);
+ void UnregisterTask(QuotaTask* task);
+
+ typedef std::set<QuotaTask*> TaskSet;
+ TaskSet running_quota_tasks_;
+};
+
+}
+
+#endif // WEBKIT_QUOTA_QUOTA_TASK_H_
diff --git a/webkit/quota/quota_types.h b/webkit/quota/quota_types.h
new file mode 100644
index 0000000..9c67b34
--- /dev/null
+++ b/webkit/quota/quota_types.h
@@ -0,0 +1,141 @@
+// Copyright (c) 2011 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.
+
+#ifndef WEBKIT_QUOTA_QUOTA_TYPES_H_
+#define WEBKIT_QUOTA_QUOTA_TYPES_H_
+#pragma once
+
+#include <deque>
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/stl_util-inl.h"
+
+namespace quota {
+
+enum StorageType {
+ kStorageTypeTemporary,
+ kStorageTypePersistent,
+ kStorageTypeUnknown,
+};
+
+enum QuotaStatusCode {
+ kQuotaStatusOk = 0,
+ kQuotaErrorNotSupported = 9,
+ kQuotaErrorAbort = 20,
+ kQuotaStatusUnknown = -1,
+};
+
+// Common callback types that are used throughout in the quota module.
+typedef Callback1<int64>::Type UsageCallback;
+typedef Callback1<int64>::Type QuotaCallback;
+typedef Callback2<const std::string& /* host */,
+ int64>::Type HostUsageCallback;
+typedef Callback2<const std::string& /* host */,
+ int64>::Type HostQuotaCallback;
+
+// Simple template wrapper for a callback queue.
+template <typename CallbackType>
+class CallbackQueueBase {
+ public:
+ typedef typename std::deque<CallbackType> Queue;
+ typedef typename Queue::iterator iterator;
+
+ virtual ~CallbackQueueBase() {
+ STLDeleteContainerPointers(callbacks_.begin(), callbacks_.end());
+ }
+
+ // Returns true if the given |callback| is the first one added to the queue.
+ bool Add(CallbackType callback) {
+ callbacks_.push_back(callback);
+ return (callbacks_.size() == 1);
+ }
+
+ bool HasCallbacks() const {
+ return !callbacks_.empty();
+ }
+
+ protected:
+ std::deque<CallbackType> callbacks_;
+};
+
+template <typename CallbackType1, typename A1>
+class CallbackQueue1 : public CallbackQueueBase<CallbackType1> {
+ public:
+ typedef typename CallbackQueueBase<CallbackType1>::Queue Queue;
+ // Runs the callbacks added to the queue and clears the queue.
+ void Run(A1 arg) {
+ for (typename Queue::iterator iter = this->callbacks_.begin();
+ iter != this->callbacks_.end(); ++iter) {
+ (*iter)->Run(arg);
+ delete *iter;
+ }
+ this->callbacks_.clear();
+ }
+};
+
+template <typename CallbackType2, typename A1, typename A2>
+class CallbackQueue2 : public CallbackQueueBase<CallbackType2> {
+ public:
+ typedef typename CallbackQueueBase<CallbackType2>::Queue Queue;
+ // Runs the callbacks added to the queue and clears the queue.
+ void Run(A1 arg1, A2 arg2) {
+ for (typename Queue::iterator iter = this->callbacks_.begin();
+ iter != this->callbacks_.end(); ++iter) {
+ (*iter)->Run(arg1, arg2);
+ delete *iter;
+ }
+ this->callbacks_.clear();
+ }
+};
+
+typedef CallbackQueue1<UsageCallback*, int64> UsageCallbackQueue;
+typedef CallbackQueue1<QuotaCallback*, int64> QuotaCallbackQueue;
+
+template <typename CallbackType2, typename KEY = std::string,
+ typename ARG = int64>
+class CallbackQueueMap {
+ public:
+ typedef CallbackQueue2<CallbackType2, const KEY&, ARG> Queue;
+ typedef std::map<KEY, Queue> CallbackMap;
+ typedef typename CallbackMap::iterator iterator;
+
+ bool Add(const KEY& key, CallbackType2 callback) {
+ return callback_map_[key].Add(callback);
+ }
+
+ bool HasCallbacks(const KEY& key) const {
+ return (callback_map_.find(key) != callback_map_.end());
+ }
+
+ // Runs the callbacks added for the given |key| and clears the key
+ // from the map.
+ void Run(const KEY& key, ARG arg) {
+ if (!HasCallbacks(key))
+ return;
+ Queue& queue = callback_map_[key];
+ queue.Run(key, arg);
+ callback_map_.erase(key);
+ }
+
+ iterator Begin() { return callback_map_.begin(); }
+ iterator End() { return callback_map_.end(); }
+ static void RunAt(iterator iter, ARG arg) {
+ iter->second.Run(iter->first, arg);
+ }
+
+ void Clear() { callback_map_.clear(); }
+
+ private:
+ CallbackMap callback_map_;
+};
+
+typedef CallbackQueueMap<HostUsageCallback*> HostUsageCallbackMap;
+typedef CallbackQueueMap<HostQuotaCallback*> HostQuotaCallbackMap;
+
+} // namespace quota
+
+#endif // WEBKIT_QUOTA_QUOTA_TYPES_H_
diff --git a/webkit/quota/usage_tracker.cc b/webkit/quota/usage_tracker.cc
new file mode 100644
index 0000000..043373e
--- /dev/null
+++ b/webkit/quota/usage_tracker.cc
@@ -0,0 +1,400 @@
+// Copyright (c) 2011 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/quota/usage_tracker.h"
+
+#include <deque>
+
+#include "base/message_loop_proxy.h"
+#include "base/stl_util-inl.h"
+#include "net/base/net_util.h"
+
+namespace quota {
+
+// A task class for getting the total amount of data used for a collection of
+// origins. This class is self-destructed.
+class ClientUsageTracker::GatherUsageTaskBase : public QuotaTask {
+ public:
+ GatherUsageTaskBase(
+ UsageTracker* tracker,
+ QuotaClient* client)
+ : QuotaTask(tracker),
+ client_(client),
+ tracker_(tracker),
+ callback_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
+ DCHECK(tracker_);
+ DCHECK(client_);
+ client_tracker_ = tracker_->GetClientTracker(client_->id());
+ DCHECK(client_tracker_);
+ }
+ virtual ~GatherUsageTaskBase() {}
+
+ // Get total usage for the given |origins|.
+ void GetUsageForOrigins(const std::set<GURL>& origins) {
+ DCHECK(original_message_loop()->BelongsToCurrentThread());
+ std::set<GURL> origins_to_process;
+ // We do not get usage for origins for which we have valid usage cache.
+ client_tracker()->DetermineOriginsToGetUsage(origins, &origins_to_process);
+ if (origins_to_process.empty()) {
+ // Nothing to be done.
+ CallCompleted();
+ delete this;
+ }
+ for (std::set<GURL>::const_iterator iter = origins_to_process.begin();
+ iter != origins_to_process.end();
+ iter++) {
+ pending_origins_.push_back(*iter);
+ client_->GetOriginUsage(
+ *iter,
+ tracker_->type(),
+ callback_factory_.NewCallback(&GatherUsageTaskBase::DidGetUsage));
+ }
+ }
+
+ bool IsOriginDone(const GURL& origin) const {
+ DCHECK(original_message_loop()->BelongsToCurrentThread());
+ return origin_usage_map_.find(origin) != origin_usage_map_.end();
+ }
+
+ protected:
+ virtual void Aborted() OVERRIDE {
+ delete this;
+ }
+
+ UsageTracker* tracker() const { return tracker_; }
+ ClientUsageTracker* client_tracker() const { return client_tracker_; }
+ const std::map<GURL, int64>& origin_usage_map() const {
+ return origin_usage_map_;
+ }
+
+ private:
+ void DidGetUsage(int64 usage) {
+ // This code assumes DidGetUsage callbacks are called in the same
+ // order as we dispatched GetOriginUsage calls.
+ DCHECK(original_message_loop()->BelongsToCurrentThread());
+ DCHECK(!pending_origins_.empty());
+ origin_usage_map_.insert(std::make_pair(
+ pending_origins_.front(), usage));
+ pending_origins_.pop_front();
+ if (pending_origins_.empty()) {
+ // We're done.
+ CallCompleted();
+ delete this;
+ }
+ }
+
+ QuotaClient* client_;
+ UsageTracker* tracker_;
+ ClientUsageTracker* client_tracker_;
+ std::deque<GURL> pending_origins_;
+ std::map<GURL, int64> origin_usage_map_;
+ base::ScopedCallbackFactory<GatherUsageTaskBase> callback_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(GatherUsageTaskBase);
+};
+
+// A task class for getting the total amount of data used for a given storage
+// type. This class is self-destructed.
+class ClientUsageTracker::GatherGlobalUsageTask
+ : public GatherUsageTaskBase {
+ public:
+ GatherGlobalUsageTask(
+ UsageTracker* tracker,
+ QuotaClient* client)
+ : GatherUsageTaskBase(tracker, client),
+ client_(client),
+ callback_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
+ DCHECK(tracker);
+ DCHECK(client);
+ }
+ virtual ~GatherGlobalUsageTask() {}
+
+ protected:
+ virtual void Run() OVERRIDE {
+ client_->GetOriginsForType(tracker()->type(),
+ callback_factory_.NewCallback(
+ &GatherUsageTaskBase::GetUsageForOrigins));
+ }
+
+ virtual void Completed() OVERRIDE {
+ client_tracker()->DidGetGlobalUsage(origin_usage_map());
+ }
+
+private:
+ QuotaClient* client_;
+ base::ScopedCallbackFactory<GatherUsageTaskBase> callback_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(GatherGlobalUsageTask);
+};
+
+// A task class for getting the total amount of data used for a given host.
+// This class is self-destructed.
+class ClientUsageTracker::GatherHostUsageTask
+ : public GatherUsageTaskBase {
+ public:
+ GatherHostUsageTask(
+ UsageTracker* tracker,
+ QuotaClient* client,
+ const std::string& host)
+ : GatherUsageTaskBase(tracker, client),
+ client_(client),
+ host_(host),
+ callback_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
+ DCHECK(client_);
+ }
+ virtual ~GatherHostUsageTask() {}
+
+ protected:
+ virtual void Run() OVERRIDE {
+ client_->GetOriginsForHost(tracker()->type(), host_,
+ callback_factory_.NewCallback(
+ &GatherUsageTaskBase::GetUsageForOrigins));
+ }
+
+ virtual void Completed() OVERRIDE {
+ client_tracker()->DidGetHostUsage(host_, origin_usage_map());
+ }
+
+ private:
+ QuotaClient* client_;
+ std::string host_;
+ base::ScopedCallbackFactory<GatherUsageTaskBase> callback_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(GatherHostUsageTask);
+};
+
+// UsageTracker ----------------------------------------------------------
+
+UsageTracker::UsageTracker(const QuotaClientList& clients, StorageType type)
+ : type_(type),
+ callback_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
+ for (QuotaClientList::const_iterator iter = clients.begin();
+ iter != clients.end();
+ ++iter) {
+ client_tracker_map_.insert(std::make_pair(
+ (*iter)->id(), new ClientUsageTracker(this, *iter)));
+ }
+}
+
+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::GetGlobalUsage(UsageCallback* callback) {
+ if (client_tracker_map_.size() == 0) {
+ // No clients registered.
+ callback->Run(0);
+ delete callback;
+ return;
+ }
+ if (global_usage_callbacks_.Add(callback)) {
+ // This is the first call. Asks each ClientUsageTracker to collect
+ // usage information.
+ global_usage_.pending_clients = client_tracker_map_.size();
+ global_usage_.usage = 0;
+ for (ClientTrackerMap::iterator iter = client_tracker_map_.begin();
+ iter != client_tracker_map_.end();
+ ++iter) {
+ iter->second->GetGlobalUsage(callback_factory_.NewCallback(
+ &UsageTracker::DidGetClientGlobalUsage));
+ }
+ }
+}
+
+void UsageTracker::GetHostUsage(
+ const std::string& host, HostUsageCallback* callback) {
+ if (client_tracker_map_.size() == 0) {
+ // No clients registered.
+ callback->Run(host, 0);
+ delete callback;
+ return;
+ }
+ if (host_usage_callbacks_.Add(host, callback)) {
+ // This is the first call for the given host.
+ DCHECK(outstanding_host_usage_.find(host) == outstanding_host_usage_.end());
+ outstanding_host_usage_[host].pending_clients = client_tracker_map_.size();
+ for (ClientTrackerMap::iterator iter = client_tracker_map_.begin();
+ iter != client_tracker_map_.end();
+ ++iter) {
+ iter->second->GetHostUsage(host, callback_factory_.NewCallback(
+ &UsageTracker::DidGetClientHostUsage));
+ }
+ }
+}
+
+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::DidGetClientGlobalUsage(int64 usage) {
+ global_usage_.usage += usage;
+ if (--global_usage_.pending_clients == 0) {
+ // All the clients have returned their usage data. Dispatches the
+ // pending callbacks.
+ global_usage_callbacks_.Run(global_usage_.usage);
+ }
+}
+
+void UsageTracker::DidGetClientHostUsage(const std::string& host, int64 usage) {
+ TrackingInfo& info = outstanding_host_usage_[host];
+ info.usage += usage;
+ if (--info.pending_clients == 0) {
+ // All the clients have returned their usage data. Dispatches the
+ // pending callbacks.
+ host_usage_callbacks_.Run(host, info.usage);
+ outstanding_host_usage_.erase(host);
+ }
+}
+
+// ClientUsageTracker ----------------------------------------------------
+
+ClientUsageTracker::ClientUsageTracker(
+ UsageTracker* tracker, QuotaClient* client)
+ : tracker_(tracker),
+ client_(client),
+ global_usage_(0),
+ global_usage_retrieved_(false),
+ global_usage_task_(NULL) {
+ DCHECK(tracker_);
+ DCHECK(client_);
+}
+
+ClientUsageTracker::~ClientUsageTracker() {
+}
+
+void ClientUsageTracker::GetGlobalUsage(UsageCallback* callback) {
+ if (global_usage_retrieved_) {
+ callback->Run(global_usage_);
+ delete callback;
+ return;
+ }
+ DCHECK(!global_usage_callback_.HasCallbacks());
+ global_usage_callback_.Add(callback);
+ global_usage_task_ = new GatherGlobalUsageTask(tracker_, client_);
+ global_usage_task_->Start();
+}
+
+void ClientUsageTracker::GetHostUsage(
+ const std::string& host, HostUsageCallback* callback) {
+ std::map<std::string, int64>::iterator found = host_usage_map_.find(host);
+ if (found != host_usage_map_.end()) {
+ // TODO(kinuko): Drop host_usage_map_ cache periodically.
+ callback->Run(host, found->second);
+ delete callback;
+ return;
+ }
+ DCHECK(!host_usage_callbacks_.HasCallbacks(host));
+ DCHECK(host_usage_tasks_.find(host) == host_usage_tasks_.end());
+ host_usage_callbacks_.Add(host, callback);
+ if (global_usage_task_)
+ return;
+ GatherHostUsageTask* task = new GatherHostUsageTask(tracker_, client_, host);
+ task->Start();
+ host_usage_tasks_[host] = task;
+}
+
+void ClientUsageTracker::DetermineOriginsToGetUsage(
+ const std::set<GURL>& origins, std::set<GURL>* origins_to_process) {
+ DCHECK(origins_to_process);
+ for (std::set<GURL>::const_iterator iter = origins.begin();
+ iter != origins.end(); ++iter) {
+ if (cached_origins_.find(*iter) == cached_origins_.end())
+ origins_to_process->insert(*iter);
+ }
+}
+
+void ClientUsageTracker::UpdateUsageCache(
+ const GURL& origin, int64 delta) {
+ std::string host = net::GetHostOrSpecFromURL(origin);
+ if (cached_origins_.find(origin) != cached_origins_.end()) {
+ host_usage_map_[host] += delta;
+ global_usage_ += delta;
+ return;
+ }
+ if (global_usage_retrieved_ ||
+ host_usage_map_.find(host) != host_usage_map_.end()) {
+ // This might be for a new origin.
+ cached_origins_.insert(origin);
+ host_usage_map_[host] = delta;
+ global_usage_ += delta;
+ return;
+ }
+ // See if the origin has been processed in outstanding gather tasks
+ // and add up the delta if it has.
+ if (global_usage_task_ && global_usage_task_->IsOriginDone(origin)) {
+ host_usage_map_[host] += delta;
+ global_usage_ += delta;
+ return;
+ }
+ if (host_usage_tasks_.find(host) != host_usage_tasks_.end() &&
+ host_usage_tasks_[host]->IsOriginDone(origin)) {
+ host_usage_map_[host] += delta;
+ }
+ // Otherwise we have not cached usage info for the origin yet.
+ // Succeeding GetUsage tasks would eventually catch the change.
+}
+
+void ClientUsageTracker::DidGetGlobalUsage(
+ const std::map<GURL, int64>& origin_usage_map) {
+ DCHECK(global_usage_task_ != NULL);
+ global_usage_task_ = NULL;
+ // TODO(kinuko): Record when it has retrieved the global usage.
+ global_usage_retrieved_ = true;
+ for (std::map<GURL, int64>::const_iterator iter = origin_usage_map.begin();
+ iter != origin_usage_map.end();
+ ++iter) {
+ if (cached_origins_.insert(iter->first).second) {
+ global_usage_ += iter->second;
+ std::string host = net::GetHostOrSpecFromURL(iter->first);
+ host_usage_map_[host] += iter->second;
+ }
+ }
+
+ // Dispatches the global usage callback.
+ DCHECK(global_usage_callback_.HasCallbacks());
+ global_usage_callback_.Run(global_usage_);
+
+ // Dispatches host usage callbacks.
+ for (HostUsageCallbackMap::iterator iter = host_usage_callbacks_.Begin();
+ iter != host_usage_callbacks_.End();
+ ++iter) {
+ std::map<std::string, int64>::iterator found =
+ host_usage_map_.find(iter->first);
+ if (found == host_usage_map_.end())
+ HostUsageCallbackMap::RunAt(iter, 0);
+ else
+ HostUsageCallbackMap::RunAt(iter, found->second);
+ }
+ host_usage_callbacks_.Clear();
+}
+
+void ClientUsageTracker::DidGetHostUsage(
+ const std::string& host,
+ const std::map<GURL, int64>& origin_usage_map) {
+ DCHECK(host_usage_tasks_.find(host) != host_usage_tasks_.end());
+ host_usage_tasks_.erase(host);
+ for (std::map<GURL, int64>::const_iterator iter = origin_usage_map.begin();
+ iter != origin_usage_map.end();
+ ++iter) {
+ if (cached_origins_.insert(iter->first).second) {
+ global_usage_ += iter->second;
+ host_usage_map_[host] += iter->second;
+ }
+ }
+
+ // Dispatches the host usage callback.
+ host_usage_callbacks_.Run(host, host_usage_map_[host]);
+}
+
+} // namespace quota
diff --git a/webkit/quota/usage_tracker.h b/webkit/quota/usage_tracker.h
new file mode 100644
index 0000000..c2856ca
--- /dev/null
+++ b/webkit/quota/usage_tracker.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2011 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.
+
+#ifndef WEBKIT_QUOTA_USAGE_TRACKER_H_
+#define WEBKIT_QUOTA_USAGE_TRACKER_H_
+#pragma once
+
+#include <list>
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/scoped_callback_factory.h"
+#include "base/memory/scoped_ptr.h"
+#include "googleurl/src/gurl.h"
+#include "webkit/quota/quota_client.h"
+#include "webkit/quota/quota_task.h"
+#include "webkit/quota/quota_types.h"
+
+namespace quota {
+
+class ClientUsageTracker;
+
+// A helper class that gathers and tracks the amount of data stored in
+// all quota clients.
+// An instance of this class is created per storage type.
+class UsageTracker : public QuotaTaskObserver {
+ public:
+ UsageTracker(const QuotaClientList& clients, StorageType type);
+ ~UsageTracker();
+
+ StorageType type() const { return type_; }
+ ClientUsageTracker* GetClientTracker(QuotaClient::ID client_id);
+
+ void GetGlobalUsage(UsageCallback* callback);
+ void GetHostUsage(const std::string& host, HostUsageCallback* callback);
+ void UpdateUsageCache(QuotaClient::ID client_id,
+ const GURL& origin,
+ int64 delta);
+
+ private:
+ struct TrackingInfo {
+ TrackingInfo() : pending_clients(0), usage(0) {}
+ int pending_clients;
+ int64 usage;
+ };
+
+ typedef std::map<QuotaClient::ID, ClientUsageTracker*> ClientTrackerMap;
+
+ friend class ClientUsageTracker;
+ void DidGetClientGlobalUsage(int64 usage);
+ void DidGetClientHostUsage(const std::string& host, int64 usage);
+
+ StorageType type_;
+ ClientTrackerMap client_tracker_map_;
+ TrackingInfo global_usage_;
+ std::map<std::string, TrackingInfo> outstanding_host_usage_;
+
+ UsageCallbackQueue global_usage_callbacks_;
+ HostUsageCallbackMap host_usage_callbacks_;
+
+ base::ScopedCallbackFactory<UsageTracker> callback_factory_;
+ DISALLOW_COPY_AND_ASSIGN(UsageTracker);
+};
+
+// This class holds per-client usage tracking information and caches per-host
+// usage data. An instance of this class is created per client.
+class ClientUsageTracker {
+ public:
+ ClientUsageTracker(UsageTracker* tracking_info, QuotaClient* client);
+ ~ClientUsageTracker();
+
+ void GetGlobalUsage(UsageCallback* callback);
+ void GetHostUsage(const std::string& host, HostUsageCallback* callback);
+ void DetermineOriginsToGetUsage(const std::set<GURL>& origins,
+ std::set<GURL>* origins_to_process);
+ void UpdateUsageCache(const GURL& origin, int64 delta);
+
+ private:
+ class GatherUsageTaskBase;
+ class GatherGlobalUsageTask;
+ class GatherHostUsageTask;
+
+ void DidGetGlobalUsage(const std::map<GURL, int64>& origin_usage_map);
+ void DidGetHostUsage(const std::string& host,
+ const std::map<GURL, int64>& origin_usage_map);
+
+ UsageTracker* tracker_;
+ QuotaClient* client_;
+ std::set<GURL> cached_origins_;
+
+ int64 global_usage_;
+ bool global_usage_retrieved_;
+ GatherGlobalUsageTask* global_usage_task_;
+ UsageCallbackQueue global_usage_callback_;
+
+ std::map<std::string, int64> host_usage_map_;
+ std::map<std::string, GatherHostUsageTask*> host_usage_tasks_;
+ HostUsageCallbackMap host_usage_callbacks_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClientUsageTracker);
+};
+
+} // namespace quota
+
+#endif // WEBKIT_QUOTA_USAGE_TRACKER_H_
diff --git a/webkit/quota/webkit_quota.gypi b/webkit/quota/webkit_quota.gypi
index 491c494..101daf7 100644
--- a/webkit/quota/webkit_quota.gypi
+++ b/webkit/quota/webkit_quota.gypi
@@ -14,10 +14,18 @@
'<(DEPTH)/net/net.gyp:net',
],
'sources': [
+ 'quota_client.h',
'quota_database.cc',
'quota_database.h',
+ 'quota_manager.cc',
+ 'quota_manager.h',
+ 'quota_task.cc',
+ 'quota_task.h',
+ 'quota_types.h',
'special_storage_policy.cc',
'special_storage_policy.h',
+ 'usage_tracker.cc',
+ 'usage_tracker.h',
],
'conditions': [
['inside_chromium_build==0', {
diff --git a/webkit/tools/test_shell/test_shell.gypi b/webkit/tools/test_shell/test_shell.gypi
index bdbc356..3f90d44 100644
--- a/webkit/tools/test_shell/test_shell.gypi
+++ b/webkit/tools/test_shell/test_shell.gypi
@@ -422,7 +422,10 @@
'../../plugins/ppapi/ppapi_unittest.h',
'../../plugins/ppapi/resource_tracker_unittest.cc',
'../../plugins/ppapi/url_request_info_unittest.cc',
+ '../../quota/mock_storage_client.cc',
+ '../../quota/mock_storage_client.h',
'../../quota/quota_database_unittest.cc',
+ '../../quota/quota_manager_unittest.cc',
'../webcore_unit_tests/BMPImageDecoder_unittest.cpp',
'../webcore_unit_tests/ICOImageDecoder_unittest.cpp',
'event_listener_unittest.cc',