diff options
author | tzik@chromium.org <tzik@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-06 02:49:11 +0000 |
---|---|---|
committer | tzik@chromium.org <tzik@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-06 02:49:11 +0000 |
commit | b2a091dcf3534128b01ddbf09d5b6e96a6339445 (patch) | |
tree | 2ab0de95b9c314934f338536212f5c0b91d11045 /webkit/browser | |
parent | 4db04605ae6215c1314e63daf56d77914866ab1f (diff) | |
download | chromium_src-b2a091dcf3534128b01ddbf09d5b6e96a6339445.zip chromium_src-b2a091dcf3534128b01ddbf09d5b6e96a6339445.tar.gz chromium_src-b2a091dcf3534128b01ddbf09d5b6e96a6339445.tar.bz2 |
[FileAPI] Add QuotaReservationManager and related classes.
* Adding QuotaReservationManager.
* Adding Reservation, ReservationPool, OpenFile, ActiveFile as its inner class.
TEST=content_unittests:QuotaReservationManagerTest.*
BUG=303443
TBR=avi
NOTRY=true
Review URL: https://codereview.chromium.org/43323002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@233199 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/browser')
11 files changed, 1179 insertions, 0 deletions
diff --git a/webkit/browser/fileapi/quota/open_file_handle.cc b/webkit/browser/fileapi/quota/open_file_handle.cc new file mode 100644 index 0000000..f0f69c6 --- /dev/null +++ b/webkit/browser/fileapi/quota/open_file_handle.cc @@ -0,0 +1,40 @@ +// 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/fileapi/quota/open_file_handle.h" + +#include "webkit/browser/fileapi/quota/open_file_handle_context.h" +#include "webkit/browser/fileapi/quota/quota_reservation.h" + +namespace fileapi { + +OpenFileHandle::~OpenFileHandle() { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); +} + +int64 OpenFileHandle::UpdateMaxWrittenOffset(int64 offset) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + + int64 new_file_size = 0; + int64 growth = 0; + context_->UpdateMaxWrittenOffset(offset, &new_file_size, &growth); + + if (growth > 0) + reservation_->ConsumeReservation(growth); + + return new_file_size; +} + +int64 OpenFileHandle::base_file_size() const { + return context_->base_file_size(); +} + +OpenFileHandle::OpenFileHandle(QuotaReservation* reservation, + OpenFileHandleContext* context) + : reservation_(reservation), + context_(context) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); +} + +} // namespace fileapi diff --git a/webkit/browser/fileapi/quota/open_file_handle.h b/webkit/browser/fileapi/quota/open_file_handle.h new file mode 100644 index 0000000..79f1380 --- /dev/null +++ b/webkit/browser/fileapi/quota/open_file_handle.h @@ -0,0 +1,51 @@ +// 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. + +#ifndef WEBKIT_BROWSER_FILEAPI_QUOTA_OPEN_FILE_HANDLE_H_ +#define WEBKIT_BROWSER_FILEAPI_QUOTA_OPEN_FILE_HANDLE_H_ + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace fileapi { + +class QuotaReservation; +class OpenFileHandleContext; +class QuotaReservationBuffer; + +// Represents an open file like a file descriptor. +// This should be alive while a consumer keeps a file opened and should be +// deleted when the plugin closes the file. +class WEBKIT_STORAGE_BROWSER_EXPORT OpenFileHandle { + public: + ~OpenFileHandle(); + + // Updates cached file size and consumes quota for that. + // This should be called for each modified file before calling RefreshQuota + // and file close. + // Returns updated base file size that should be used to measure quota + // consumption by difference to this. + int64 UpdateMaxWrittenOffset(int64 offset); + + int64 base_file_size() const; + + private: + friend class QuotaReservationBuffer; + + OpenFileHandle(QuotaReservation* reservation, + OpenFileHandleContext* context); + + scoped_refptr<QuotaReservation> reservation_; + scoped_refptr<OpenFileHandleContext> context_; + + base::SequenceChecker sequence_checker_; + + DISALLOW_COPY_AND_ASSIGN(OpenFileHandle); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_QUOTA_OPEN_FILE_HANDLE_H_ diff --git a/webkit/browser/fileapi/quota/open_file_handle_context.cc b/webkit/browser/fileapi/quota/open_file_handle_context.cc new file mode 100644 index 0000000..a730f06 --- /dev/null +++ b/webkit/browser/fileapi/quota/open_file_handle_context.cc @@ -0,0 +1,60 @@ +// 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/fileapi/quota/open_file_handle_context.h" + +#include "base/file_util.h" +#include "webkit/browser/fileapi/quota/quota_reservation_buffer.h" + +namespace fileapi { + +OpenFileHandleContext::OpenFileHandleContext( + const base::FilePath& platform_path, + QuotaReservationBuffer* reservation_buffer) + : initial_file_size_(0), + maximum_written_offset_(0), + platform_path_(platform_path), + reservation_buffer_(reservation_buffer) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + + file_util::GetFileSize(platform_path, &initial_file_size_); + maximum_written_offset_ = initial_file_size_; +} + +void OpenFileHandleContext::UpdateMaxWrittenOffset( + int64 offset, + int64* new_file_size, + int64* growth) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + if (offset > maximum_written_offset_) { + *growth = offset - maximum_written_offset_; + maximum_written_offset_ = offset; + } else { + *growth = 0; + } + + *new_file_size = maximum_written_offset_; +} + +OpenFileHandleContext::~OpenFileHandleContext() { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + + // TODO(tzik): Optimize this for single operation. + + int64 file_size = 0; + file_util::GetFileSize(platform_path_, &file_size); + int64 usage_delta = file_size - initial_file_size_; + + // |quota_consumption| may be greater than the recorded file growth when a + // plugin crashed before reporting its consumption. + // In this case, the reserved quota for the plugin should be handled as + // consumed quota. + int64 quota_consumption = + std::max(maximum_written_offset_, file_size) - initial_file_size_; + + reservation_buffer_->CommitFileGrowth(quota_consumption, usage_delta); + reservation_buffer_->DetachOpenFileHandleContext(this); +} + +} // namespace fileapi diff --git a/webkit/browser/fileapi/quota/open_file_handle_context.h b/webkit/browser/fileapi/quota/open_file_handle_context.h new file mode 100644 index 0000000..4b5d3cc --- /dev/null +++ b/webkit/browser/fileapi/quota/open_file_handle_context.h @@ -0,0 +1,59 @@ +// 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. + +#ifndef WEBKIT_BROWSER_FILEAPI_OPEN_FILE_HANDLE_CONTEXT_H_ +#define WEBKIT_BROWSER_FILEAPI_OPEN_FILE_HANDLE_CONTEXT_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/platform_file.h" +#include "url/gurl.h" +#include "webkit/browser/webkit_storage_browser_export.h" +#include "webkit/common/fileapi/file_system_types.h" + +namespace fileapi { + +class QuotaReservationBuffer; + +// This class represents a underlying file of a managed FileSystem file. +// The instance keeps alive while at least one consumer keeps an open file +// handle. +// This class is usually manipulated only via OpenFileHandle. +class OpenFileHandleContext : public base::RefCounted<OpenFileHandleContext> { + public: + OpenFileHandleContext(const base::FilePath& platform_path, + QuotaReservationBuffer* reservation_buffer); + + void UpdateMaxWrittenOffset(int64 offset, + int64* new_file_size, + int64* growth); + + const base::FilePath& platform_path() const { + return platform_path_; + } + + int64 base_file_size() const { return maximum_written_offset_; } + + private: + friend class base::RefCounted<OpenFileHandleContext>; + virtual ~OpenFileHandleContext(); + + int64 initial_file_size_; + int64 maximum_written_offset_; + base::FilePath platform_path_; + + scoped_refptr<QuotaReservationBuffer> reservation_buffer_; + + base::SequenceChecker sequence_checker_; + + DISALLOW_COPY_AND_ASSIGN(OpenFileHandleContext); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_OPEN_FILE_HANDLE_CONTEXT_H_ diff --git a/webkit/browser/fileapi/quota/quota_reservation.cc b/webkit/browser/fileapi/quota/quota_reservation.cc new file mode 100644 index 0000000..c5a22ab --- /dev/null +++ b/webkit/browser/fileapi/quota/quota_reservation.cc @@ -0,0 +1,113 @@ +// 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/fileapi/quota/quota_reservation.h" + +#include "base/bind.h" +#include "webkit/browser/fileapi/quota/open_file_handle.h" +#include "webkit/browser/fileapi/quota/quota_reservation_buffer.h" + +namespace fileapi { + +void QuotaReservation::RefreshReservation( + int64 size, + const StatusCallback& callback) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + DCHECK(!running_refresh_request_); + if (!reservation_manager()) + return; + + running_refresh_request_ = true; + + reservation_manager()->ReserveQuota( + origin(), type(), size - remaining_quota_, + base::Bind(&QuotaReservation::AdaptDidUpdateReservedQuota, + weak_ptr_factory_.GetWeakPtr(), + size, callback)); + + if (running_refresh_request_) + remaining_quota_ = 0; +} + +scoped_ptr<OpenFileHandle> QuotaReservation::GetOpenFileHandle( + const base::FilePath& platform_path) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + return reservation_buffer_->GetOpenFileHandle(this, platform_path); +} + +void QuotaReservation::OnClientCrash() { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + + if (remaining_quota_) { + reservation_buffer_->PutReservationToBuffer(remaining_quota_); + remaining_quota_ = 0; + } +} + +void QuotaReservation::ConsumeReservation(int64 size) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + DCHECK_LT(0, size); + DCHECK_LT(size, remaining_quota_); + + remaining_quota_ -= size; + reservation_buffer_->PutReservationToBuffer(size); +} + +QuotaReservationManager* QuotaReservation::reservation_manager() { + return reservation_buffer_->reservation_manager(); +} + +const GURL& QuotaReservation::origin() const { + return reservation_buffer_->origin(); +} + +FileSystemType QuotaReservation::type() const { + return reservation_buffer_->type(); +} + +QuotaReservation::QuotaReservation( + QuotaReservationBuffer* reservation_buffer) + : running_refresh_request_(false), + remaining_quota_(0), + reservation_buffer_(reservation_buffer), + weak_ptr_factory_(this) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); +} + +QuotaReservation::~QuotaReservation() { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + + if (remaining_quota_ && reservation_manager()) { + reservation_manager()->ReleaseReservedQuota( + origin(), type(), remaining_quota_); + } +} + +// static +bool QuotaReservation::AdaptDidUpdateReservedQuota( + const base::WeakPtr<QuotaReservation>& reservation, + int64 new_reserved_size, + const StatusCallback& callback, + base::PlatformFileError error) { + if (!reservation) + return false; + + reservation->DidUpdateReservedQuota(new_reserved_size, callback, error); + return true; +} + +void QuotaReservation::DidUpdateReservedQuota( + int64 new_reserved_size, + const StatusCallback& callback, + base::PlatformFileError error) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + DCHECK(running_refresh_request_); + running_refresh_request_ = false; + + if (error == base::PLATFORM_FILE_OK) + remaining_quota_ = new_reserved_size; + callback.Run(error); +} + +} // namespace fileapi diff --git a/webkit/browser/fileapi/quota/quota_reservation.h b/webkit/browser/fileapi/quota/quota_reservation.h new file mode 100644 index 0000000..d534a0d --- /dev/null +++ b/webkit/browser/fileapi/quota/quota_reservation.h @@ -0,0 +1,89 @@ +// 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. + +#ifndef WEBKIT_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_H_ +#define WEBKIT_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_H_ + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "webkit/browser/fileapi/quota/quota_reservation_manager.h" +#include "webkit/browser/webkit_storage_browser_export.h" +#include "webkit/common/fileapi/file_system_types.h" + +class GURL; + +namespace fileapi { + +class QuotaReservationBuffer; +class OpenFileHandle; + +// Represents a unit of quota reservation. +class WEBKIT_STORAGE_BROWSER_EXPORT QuotaReservation + : public base::RefCounted<QuotaReservation> { + public: + typedef base::Callback<void(base::PlatformFileError error)> StatusCallback; + + // Reclaims unused quota and reserves another |size| of quota. So that the + // resulting new |remaining_quota| will be same as |size|. + // Invokes |callback| upon completion. + void RefreshReservation(int64 size, const StatusCallback& callback); + + // Associates |platform_path| to the QuotaReservation instance. + // Returns an OpenFileHandle instance that represents a quota managed file. + scoped_ptr<OpenFileHandle> GetOpenFileHandle( + const base::FilePath& platform_path); + + // Should be called when the associated client is crashed. + // This implies the client can no longer report its consumption of the + // reserved quota. + // QuotaReservation puts all remaining quota to the QuotaReservationBuffer, so + // that the remaining quota will be reclaimed after all open files associated + // to the origin and type. + void OnClientCrash(); + + // Consumes |size| of reserved quota for a associated file. + // Consumed quota is sent to associated QuotaReservationBuffer for staging. + void ConsumeReservation(int64 size); + + // Returns amount of unused reserved quota. + int64 remaining_quota() const { return remaining_quota_; } + + QuotaReservationManager* reservation_manager(); + const GURL& origin() const; + FileSystemType type() const; + + private: + friend class QuotaReservationBuffer; + + // Use QuotaReservationManager as the entry point. + explicit QuotaReservation(QuotaReservationBuffer* reservation_buffer); + + friend class base::RefCounted<QuotaReservation>; + virtual ~QuotaReservation(); + + static bool AdaptDidUpdateReservedQuota( + const base::WeakPtr<QuotaReservation>& reservation, + int64 new_reserved_size, + const StatusCallback& callback, + base::PlatformFileError error); + void DidUpdateReservedQuota(int64 new_reserved_size, + const StatusCallback& callback, + base::PlatformFileError error); + + bool running_refresh_request_; + int64 remaining_quota_; + + scoped_refptr<QuotaReservationBuffer> reservation_buffer_; + + base::SequenceChecker sequence_checker_; + base::WeakPtrFactory<QuotaReservation> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(QuotaReservation); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_H_ diff --git a/webkit/browser/fileapi/quota/quota_reservation_buffer.cc b/webkit/browser/fileapi/quota/quota_reservation_buffer.cc new file mode 100644 index 0000000..c83073e --- /dev/null +++ b/webkit/browser/fileapi/quota/quota_reservation_buffer.cc @@ -0,0 +1,107 @@ +// 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/fileapi/quota/quota_reservation_buffer.h" + +#include "base/bind.h" +#include "webkit/browser/fileapi/quota/open_file_handle.h" +#include "webkit/browser/fileapi/quota/open_file_handle_context.h" +#include "webkit/browser/fileapi/quota/quota_reservation.h" + +namespace fileapi { + +namespace { + +void IgnoreResult(base::PlatformFileError error) {} + +} // namespace + +QuotaReservationBuffer::QuotaReservationBuffer( + base::WeakPtr<QuotaReservationManager> reservation_manager, + const GURL& origin, + FileSystemType type) + : reservation_manager_(reservation_manager), + origin_(origin), + type_(type), + reserved_quota_(0) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + reservation_manager_->IncreaseDirtyCount(origin, type); +} + +scoped_refptr<QuotaReservation> QuotaReservationBuffer::CreateReservation() { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + return make_scoped_refptr(new QuotaReservation(this)); +} + +scoped_ptr<OpenFileHandle> QuotaReservationBuffer::GetOpenFileHandle( + QuotaReservation* reservation, + const base::FilePath& platform_path) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + OpenFileHandleContext** open_file = &open_files_[platform_path]; + if (!*open_file) + *open_file = new OpenFileHandleContext(platform_path, this); + return make_scoped_ptr(new OpenFileHandle(reservation, *open_file)); +} + +void QuotaReservationBuffer::CommitFileGrowth(int64 quota_consumption, + int64 usage_delta) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + if (!reservation_manager_) + return; + + reservation_manager_->CommitQuotaUsage( + origin_, type_, usage_delta, + base::Bind(&IgnoreResult)); + + DCHECK_LE(quota_consumption, reserved_quota_); + if (quota_consumption > 0) { + reserved_quota_ -= quota_consumption; + reservation_manager_->ReleaseReservedQuota( + origin_, type_, quota_consumption); + } +} + +void QuotaReservationBuffer::DetachOpenFileHandleContext( + OpenFileHandleContext* open_file) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + DCHECK_EQ(open_file, open_files_[open_file->platform_path()]); + open_files_.erase(open_file->platform_path()); +} + +void QuotaReservationBuffer::PutReservationToBuffer(int64 reservation) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + DCHECK_LE(0, reservation); + reserved_quota_ += reservation; +} + +QuotaReservationBuffer::~QuotaReservationBuffer() { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + if (!reservation_manager_) + return; + + DCHECK_LE(0, reserved_quota_); + if (reserved_quota_ && reservation_manager_) { + reservation_manager_->ReserveQuota( + origin_, type_, -reserved_quota_, + base::Bind(&QuotaReservationBuffer::DecreaseDirtyCount, + reservation_manager_, origin_, type_)); + } + reservation_manager_->ReleaseReservationBuffer(this); +} + +// static +bool QuotaReservationBuffer::DecreaseDirtyCount( + base::WeakPtr<QuotaReservationManager> reservation_manager, + const GURL& origin, + FileSystemType type, + base::PlatformFileError error) { + if (error == base::PLATFORM_FILE_OK && reservation_manager) { + reservation_manager->DecreaseDirtyCount(origin, type); + return true; + } + return false; +} + + +} // namespace fileapi diff --git a/webkit/browser/fileapi/quota/quota_reservation_buffer.h b/webkit/browser/fileapi/quota/quota_reservation_buffer.h new file mode 100644 index 0000000..1b27529 --- /dev/null +++ b/webkit/browser/fileapi/quota/quota_reservation_buffer.h @@ -0,0 +1,86 @@ +// 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. + +#ifndef WEBKIT_BROWSER_FILEAPI_QUOTA_RESERVATION_BUFFER_H_ +#define WEBKIT_BROWSER_FILEAPI_QUOTA_RESERVATION_BUFFER_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/platform_file.h" +#include "url/gurl.h" +#include "webkit/browser/webkit_storage_browser_export.h" +#include "webkit/common/fileapi/file_system_types.h" + +namespace fileapi { + +class QuotaReservation; +class OpenFileHandle; +class OpenFileHandleContext; +class QuotaReservationManager; + +// QuotaReservationBuffer manages QuotaReservation instances. All consumed +// quota and leaked quota by associated QuotaReservation will be staged in +// QuotaReservationBuffer, and will be committed on a modified file is closed. +// The instance keeps alive while any associated QuotaReservation or +// OpenFileHandle alive. +// This class is usually manipulated only via OpenFileHandle and +// QuotaReservation. +class QuotaReservationBuffer : public base::RefCounted<QuotaReservationBuffer> { + public: + QuotaReservationBuffer( + base::WeakPtr<QuotaReservationManager> reservation_manager, + const GURL& origin, + FileSystemType type); + + scoped_refptr<QuotaReservation> CreateReservation(); + scoped_ptr<OpenFileHandle> GetOpenFileHandle( + QuotaReservation* reservation, + const base::FilePath& platform_path); + void CommitFileGrowth(int64 quota_consumption, int64 usage_delta); + void DetachOpenFileHandleContext(OpenFileHandleContext* context); + void PutReservationToBuffer(int64 size); + + QuotaReservationManager* reservation_manager() { + return reservation_manager_.get(); + } + + const GURL& origin() const { return origin_; } + FileSystemType type() const { return type_; } + + private: + friend class base::RefCounted<QuotaReservationBuffer>; + virtual ~QuotaReservationBuffer(); + + static bool DecreaseDirtyCount( + base::WeakPtr<QuotaReservationManager> reservation_manager, + const GURL& origin, + FileSystemType type, + base::PlatformFileError error); + + typedef std::map<base::FilePath, OpenFileHandleContext*> + OpenFileHandleContextByPath; + + // Not owned. The destructor of OpenFileHandler should erase itself from + // |open_files_|. + OpenFileHandleContextByPath open_files_; + + base::WeakPtr<QuotaReservationManager> reservation_manager_; + + GURL origin_; + fileapi::FileSystemType type_; + + int64 reserved_quota_; + + base::SequenceChecker sequence_checker_; + + DISALLOW_COPY_AND_ASSIGN(QuotaReservationBuffer); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_QUOTA_RESERVATION_BUFFER_H_ diff --git a/webkit/browser/fileapi/quota/quota_reservation_manager.cc b/webkit/browser/fileapi/quota/quota_reservation_manager.cc new file mode 100644 index 0000000..0a02940 --- /dev/null +++ b/webkit/browser/fileapi/quota/quota_reservation_manager.cc @@ -0,0 +1,84 @@ +// 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/fileapi/quota/quota_reservation_manager.h" + +#include "webkit/browser/fileapi/quota/quota_reservation.h" +#include "webkit/browser/fileapi/quota/quota_reservation_buffer.h" + +namespace fileapi { + +QuotaReservationManager::QuotaReservationManager(QuotaBackend* backend) + : backend_(backend), + weak_ptr_factory_(this) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); +} + +QuotaReservationManager::~QuotaReservationManager() { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); +} + +void QuotaReservationManager::ReserveQuota( + const GURL& origin, + FileSystemType type, + int64 size, + const ReserveQuotaCallback& callback) { + backend_->ReserveQuota(origin, type, size, callback); +} + +void QuotaReservationManager::ReleaseReservedQuota( + const GURL& origin, + FileSystemType type, + int64 size) { + backend_->ReleaseReservedQuota(origin, type, size); +} + +void QuotaReservationManager::CommitQuotaUsage( + const GURL& origin, + FileSystemType type, + int64 delta, + const StatusCallback& callback) { + backend_->CommitQuotaUsage(origin, type, delta, callback); +} + +void QuotaReservationManager::IncreaseDirtyCount(const GURL& origin, + FileSystemType type) { + backend_->IncreaseDirtyCount(origin, type); +} + +void QuotaReservationManager::DecreaseDirtyCount(const GURL& origin, + FileSystemType type) { + backend_->DecreaseDirtyCount(origin, type); +} + +scoped_refptr<QuotaReservationBuffer> +QuotaReservationManager::GetReservationBuffer( + const GURL& origin, + FileSystemType type) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + QuotaReservationBuffer** buffer = + &reservation_buffers_[std::make_pair(origin, type)]; + if (!*buffer) { + *buffer = new QuotaReservationBuffer( + weak_ptr_factory_.GetWeakPtr(), origin, type); + } + return make_scoped_refptr(*buffer); +} + +void QuotaReservationManager::ReleaseReservationBuffer( + QuotaReservationBuffer* reservation_buffer) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + std::pair<GURL, FileSystemType> key(reservation_buffer->origin(), + reservation_buffer->type()); + DCHECK_EQ(reservation_buffers_[key], reservation_buffer); + reservation_buffers_.erase(key); +} + +scoped_refptr<QuotaReservation> QuotaReservationManager::CreateReservation( + const GURL& origin, + FileSystemType type) { + return GetReservationBuffer(origin, type)->CreateReservation();; +} + +} // namespace fileapi diff --git a/webkit/browser/fileapi/quota/quota_reservation_manager.h b/webkit/browser/fileapi/quota/quota_reservation_manager.h new file mode 100644 index 0000000..2e3ae93 --- /dev/null +++ b/webkit/browser/fileapi/quota/quota_reservation_manager.h @@ -0,0 +1,125 @@ +// 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. + +#ifndef WEBKIT_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_MANAGER_H_ +#define WEBKIT_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_MANAGER_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/platform_file.h" +#include "url/gurl.h" +#include "webkit/browser/webkit_storage_browser_export.h" +#include "webkit/common/fileapi/file_system_types.h" + +namespace fileapi { + +class QuotaReservation; +class QuotaReservationBuffer; +class OpenFileHandle; +class OpenFileHandleContext; + +class WEBKIT_STORAGE_BROWSER_EXPORT QuotaReservationManager { + public: + typedef base::Callback<void(base::PlatformFileError error)> StatusCallback; + typedef base::Callback<bool(base::PlatformFileError error)> + ReserveQuotaCallback; + + // An abstraction of backing quota system. + class QuotaBackend { + public: + // Reserves or reclaims |delta| of quota for |origin| and |type| pair. + // Reserved quota should be counted as usage, but it should be on-memory + // and be cleared by a browser restart. + // Invokes |callback| upon completion with an error code. + // |callback| should return false if it can't accept the reservation, in + // that case, the backend should roll back the reservation. + virtual void ReserveQuota(const GURL& origin, + FileSystemType type, + int64 delta, + const ReserveQuotaCallback& callback) = 0; + + // Reclaims |size| of quota for |origin| and |type|. + virtual void ReleaseReservedQuota(const GURL& origin, + FileSystemType type, + int64 size) = 0; + + // Updates disk usage of |origin| and |type|. + // Invokes |callback| upon completion with an error code. + virtual void CommitQuotaUsage(const GURL& origin, + FileSystemType type, + int64 delta, + const StatusCallback& callback) = 0; + + virtual void IncreaseDirtyCount(const GURL& origin, + FileSystemType type) = 0; + virtual void DecreaseDirtyCount(const GURL& origin, + FileSystemType type) = 0; + + protected: + QuotaBackend() {} + virtual ~QuotaBackend() {} + + private: + DISALLOW_COPY_AND_ASSIGN(QuotaBackend); + }; + + explicit QuotaReservationManager(QuotaBackend* backend); + ~QuotaReservationManager(); + + // The entry point of the quota reservation. Creates new reservation object + // for |origin| and |type|. + scoped_refptr<QuotaReservation> CreateReservation( + const GURL& origin, + FileSystemType type); + + private: + typedef std::map<std::pair<GURL, FileSystemType>, QuotaReservationBuffer*> + ReservationBufferByOriginAndType; + + friend class QuotaReservation; + friend class QuotaReservationBuffer; + + void ReserveQuota(const GURL& origin, + FileSystemType type, + int64 delta, + const ReserveQuotaCallback& callback); + + void ReleaseReservedQuota(const GURL& origin, + FileSystemType type, + int64 size); + + void CommitQuotaUsage(const GURL& origin, + FileSystemType type, + int64 delta, + const StatusCallback& callback); + + void IncreaseDirtyCount(const GURL& origin, FileSystemType type); + void DecreaseDirtyCount(const GURL& origin, FileSystemType type); + + scoped_refptr<QuotaReservationBuffer> GetReservationBuffer( + const GURL& origin, + FileSystemType type); + void ReleaseReservationBuffer(QuotaReservationBuffer* reservation_pool); + + + // Not owned. |backend_| should outlive QuotaReservationManager + QuotaBackend* backend_; + + // Not owned. The destructor of ReservationBuffer should erase itself from + // |reservation_buffers_| by calling ReleaseReservationBuffer. + ReservationBufferByOriginAndType reservation_buffers_; + + base::SequenceChecker sequence_checker_; + base::WeakPtrFactory<QuotaReservationManager> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(QuotaReservationManager); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_MANAGER_H_ diff --git a/webkit/browser/fileapi/quota/quota_reservation_manager_unittest.cc b/webkit/browser/fileapi/quota/quota_reservation_manager_unittest.cc new file mode 100644 index 0000000..86590c7a --- /dev/null +++ b/webkit/browser/fileapi/quota/quota_reservation_manager_unittest.cc @@ -0,0 +1,365 @@ +// 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/fileapi/quota/quota_reservation_manager.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/quota/open_file_handle.h" +#include "webkit/browser/fileapi/quota/quota_reservation.h" + +namespace fileapi { + +namespace { + +const char kOrigin[] = "http://example.com"; +const FileSystemType kType = kFileSystemTypeTemporary; +const int64 kInitialFileSize = 30; + +typedef QuotaReservationManager::ReserveQuotaCallback ReserveQuotaCallback; +typedef QuotaReservationManager::StatusCallback StatusCallback; + +class FakeBackend : public QuotaReservationManager::QuotaBackend { + public: + FakeBackend() + : on_memory_usage_(0), + on_disk_usage_(0) {} + virtual ~FakeBackend() {} + + virtual void ReserveQuota(const GURL& origin, + FileSystemType type, + int64 delta, + const ReserveQuotaCallback& callback) OVERRIDE { + EXPECT_EQ(GURL(kOrigin), origin); + EXPECT_EQ(kType, type); + on_memory_usage_ += delta; + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(base::IgnoreResult(callback), base::PLATFORM_FILE_OK)); + } + + virtual void ReleaseReservedQuota(const GURL& origin, + FileSystemType type, + int64 size) OVERRIDE { + EXPECT_LE(0, size); + EXPECT_EQ(GURL(kOrigin), origin); + EXPECT_EQ(kType, type); + on_memory_usage_ -= size; + } + + virtual void CommitQuotaUsage(const GURL& origin, + FileSystemType type, + int64 delta, + const StatusCallback& callback) OVERRIDE { + EXPECT_EQ(GURL(kOrigin), origin); + EXPECT_EQ(kType, type); + on_disk_usage_ += delta; + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, base::Bind(callback, base::PLATFORM_FILE_OK)); + } + + virtual void IncreaseDirtyCount(const GURL& origin, + FileSystemType type) OVERRIDE {} + virtual void DecreaseDirtyCount(const GURL& origin, + FileSystemType type) OVERRIDE {} + + int64 on_memory_usage() { return on_memory_usage_; } + int64 on_disk_usage() { return on_disk_usage_; } + + private: + int64 on_memory_usage_; + int64 on_disk_usage_; + + DISALLOW_COPY_AND_ASSIGN(FakeBackend); +}; + +void ExpectSuccess(bool* done, base::PlatformFileError error) { + EXPECT_FALSE(*done); + *done = true; + EXPECT_EQ(base::PLATFORM_FILE_OK, error); +} + +void RefreshQuota(QuotaReservation* reservation, int64 size) { + DCHECK(reservation); + + bool done = false; + reservation->RefreshReservation(size, base::Bind(&ExpectSuccess, &done)); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(done); +} + +} // namespace + +class QuotaReservationManagerTest : public testing::Test { + public: + QuotaReservationManagerTest() {} + virtual ~QuotaReservationManagerTest() {} + + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(work_dir_.CreateUniqueTempDir()); + file_path_ = work_dir_.path().Append(FILE_PATH_LITERAL("hoge")); + SetFileSize(kInitialFileSize); + + fake_backend_.reset(new FakeBackend); + reservation_manager_.reset(new QuotaReservationManager( + fake_backend_.get())); + } + + virtual void TearDown() OVERRIDE { + reservation_manager_.reset(); + fake_backend_.reset(); + } + + int64 GetFileSize() { + int64 size = 0; + file_util::GetFileSize(file_path_, &size); + return size; + } + + void SetFileSize(int64 size) { + bool created = false; + base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED; + base::PlatformFile file = CreatePlatformFile( + file_path_, + base::PLATFORM_FILE_OPEN_ALWAYS | base::PLATFORM_FILE_WRITE, + &created, &error); + ASSERT_EQ(base::PLATFORM_FILE_OK, error); + ASSERT_TRUE(base::TruncatePlatformFile(file, size)); + ASSERT_TRUE(base::ClosePlatformFile(file)); + } + + void ExtendFileTo(int64 size) { + if (GetFileSize() < size) + SetFileSize(size); + } + + FakeBackend* fake_backend() { + return fake_backend_.get(); + } + + QuotaReservationManager* reservation_manager() { + return reservation_manager_.get(); + } + + const base::FilePath& file_path() const { + return file_path_; + } + + private: + base::MessageLoop message_loop_; + base::ScopedTempDir work_dir_; + base::FilePath file_path_; + scoped_ptr<FakeBackend> fake_backend_; + scoped_ptr<QuotaReservationManager> reservation_manager_; + + DISALLOW_COPY_AND_ASSIGN(QuotaReservationManagerTest); +}; + +TEST_F(QuotaReservationManagerTest, BasicTest) { + GURL origin(kOrigin); + FileSystemType type = kType; + + // Create Reservation channel for the origin and type. + // Reservation holds remaining quota reservation and provides a method to + // refresh it. + scoped_refptr<QuotaReservation> reservation = + reservation_manager()->CreateReservation(origin, type); + EXPECT_EQ(0, reservation->remaining_quota()); + + RefreshQuota(reservation, 100); + EXPECT_EQ(100, reservation->remaining_quota()); + + { + // For each open file for write, the client should create OpenFileHandle + // object. + // It's OK to create multiple OpenFileHandle for single file. + scoped_ptr<OpenFileHandle> open_file = + reservation->GetOpenFileHandle(file_path()); + + // Before reserved quota ran out, the client can perform any number of + // operation for the file. + // The client should calculate how much quota is consumed by itself. + int64 remaining_quota = reservation->remaining_quota(); + int64 base_file_size = open_file->base_file_size(); + int64 max_written_offset = base_file_size; + ExtendFileTo(90); + max_written_offset = 90; + remaining_quota -= max_written_offset - base_file_size; + + // When the reserved quota ran out, the client can request quota refresh + // through Reservation. Before requesting another portion of quota, the + // client should report maximum written offset for each modified files. + open_file->UpdateMaxWrittenOffset(max_written_offset); + EXPECT_EQ(remaining_quota, reservation->remaining_quota()); + + RefreshQuota(reservation, 100); + EXPECT_EQ(100, reservation->remaining_quota()); + } + + EXPECT_EQ(90, GetFileSize()); + EXPECT_EQ(100, fake_backend()->on_memory_usage()); + EXPECT_EQ(90 - kInitialFileSize, fake_backend()->on_disk_usage()); + + reservation = NULL; + + EXPECT_EQ(90, GetFileSize()); + EXPECT_EQ(0, fake_backend()->on_memory_usage()); + EXPECT_EQ(90 - kInitialFileSize, fake_backend()->on_disk_usage()); +} + +TEST_F(QuotaReservationManagerTest, MultipleWriter) { + GURL origin(kOrigin); + FileSystemType type = kType; + + scoped_refptr<QuotaReservation> reservation = + reservation_manager()->CreateReservation(origin, type); + EXPECT_EQ(0, reservation->remaining_quota()); + + RefreshQuota(reservation, 100); + EXPECT_EQ(100, reservation->remaining_quota()); + + { + scoped_ptr<OpenFileHandle> open_file1 = + reservation->GetOpenFileHandle(file_path()); + scoped_ptr<OpenFileHandle> open_file2 = + reservation->GetOpenFileHandle(file_path()); + + int64 remaining_quota = reservation->remaining_quota(); + + int64 base_file_size_for_file1 = open_file1->base_file_size(); + int64 max_written_offset_for_file1 = base_file_size_for_file1; + + int64 base_file_size_for_file2 = open_file2->base_file_size(); + int64 max_written_offset_for_file2 = base_file_size_for_file2; + + // Each writer should maintain max_written_offset and base_file_size + // independently even if there are multiple writers for the same file. + max_written_offset_for_file1 = 50; + ExtendFileTo(max_written_offset_for_file1); + remaining_quota -= max_written_offset_for_file1 - base_file_size_for_file1; + base_file_size_for_file1 = max_written_offset_for_file1; + + max_written_offset_for_file2 = 90; + ExtendFileTo(max_written_offset_for_file2); + remaining_quota -= max_written_offset_for_file2 - base_file_size_for_file2; + base_file_size_for_file2 = max_written_offset_for_file2; + + // Before requesting quota refresh, each writer should report their + // maximum_written_offset. UpdateMaxWrittenOffset returns updated + // base_file_size that the writer should calculate quota consumption based + // on that. + base_file_size_for_file1 = + open_file1->UpdateMaxWrittenOffset(max_written_offset_for_file1); + max_written_offset_for_file1 = base_file_size_for_file1; + EXPECT_EQ(100 - (50 - kInitialFileSize), reservation->remaining_quota()); + + base_file_size_for_file2 = + open_file2->UpdateMaxWrittenOffset(max_written_offset_for_file2); + max_written_offset_for_file2 = base_file_size_for_file2; + EXPECT_EQ(100 - (50 - kInitialFileSize) - (90 - 50), + reservation->remaining_quota()); + + RefreshQuota(reservation, 100); + EXPECT_EQ(100, reservation->remaining_quota()); + } + + EXPECT_EQ(90, GetFileSize()); + EXPECT_EQ(100, fake_backend()->on_memory_usage()); + EXPECT_EQ(90 - kInitialFileSize, fake_backend()->on_disk_usage()); + + reservation = NULL; + + EXPECT_EQ(90, GetFileSize()); + EXPECT_EQ(0, fake_backend()->on_memory_usage()); + EXPECT_EQ(90 - kInitialFileSize, fake_backend()->on_disk_usage()); +} + +TEST_F(QuotaReservationManagerTest, MultipleClient) { + GURL origin(kOrigin); + FileSystemType type = kType; + + scoped_refptr<QuotaReservation> reservation1 = + reservation_manager()->CreateReservation(origin, type); + EXPECT_EQ(0, reservation1->remaining_quota()); + RefreshQuota(reservation1, 100); + EXPECT_EQ(100, reservation1->remaining_quota()); + + scoped_refptr<QuotaReservation> reservation2 = + reservation_manager()->CreateReservation(origin, type); + EXPECT_EQ(0, reservation2->remaining_quota()); + RefreshQuota(reservation2, 500); + EXPECT_EQ(500, reservation2->remaining_quota()); + + // Attach a file to both of two reservations. + scoped_ptr<OpenFileHandle> open_file1 = + reservation1->GetOpenFileHandle(file_path()); + scoped_ptr<OpenFileHandle> open_file2 = + reservation2->GetOpenFileHandle(file_path()); + + // Each client should manage reserved quota and its consumption separately. + int64 remaining_quota1 = reservation1->remaining_quota(); + int64 base_file_size1 = open_file1->base_file_size(); + int64 max_written_offset1 = base_file_size1; + + int64 remaining_quota2 = reservation2->remaining_quota(); + int64 base_file_size2 = open_file2->base_file_size(); + int64 max_written_offset2 = base_file_size2; + + max_written_offset1 = 50; + remaining_quota1 -= max_written_offset1 - base_file_size1; + base_file_size1 = max_written_offset1; + ExtendFileTo(max_written_offset1); + + max_written_offset2 = 400; + remaining_quota2 -= max_written_offset2 - base_file_size2; + base_file_size2 = max_written_offset2; + ExtendFileTo(max_written_offset2); + + // For multiple Reservation case, RefreshQuota needs usage report only from + // associated OpenFile's. + base_file_size1 = + open_file1->UpdateMaxWrittenOffset(max_written_offset1); + max_written_offset1 = base_file_size1; + EXPECT_EQ(100 - (50 - kInitialFileSize), reservation1->remaining_quota()); + + RefreshQuota(reservation1, 200); + EXPECT_EQ(200, reservation1->remaining_quota()); + + base_file_size2 = + open_file2->UpdateMaxWrittenOffset(max_written_offset2); + max_written_offset2 = base_file_size2; + EXPECT_EQ(500 - (400 - 50), reservation2->remaining_quota()); + + RefreshQuota(reservation2, 150); + EXPECT_EQ(150, reservation2->remaining_quota()); + + open_file1.reset(); + open_file2.reset(); + + EXPECT_EQ(400, GetFileSize()); + EXPECT_EQ(200 + 150, fake_backend()->on_memory_usage()); + EXPECT_EQ(400 - kInitialFileSize, fake_backend()->on_disk_usage()); + + reservation1 = NULL; + + EXPECT_EQ(400, GetFileSize()); + EXPECT_EQ(150, fake_backend()->on_memory_usage()); + EXPECT_EQ(400 - kInitialFileSize, fake_backend()->on_disk_usage()); + + reservation2 = NULL; + + EXPECT_EQ(400, GetFileSize()); + EXPECT_EQ(0, fake_backend()->on_memory_usage()); + EXPECT_EQ(400 - kInitialFileSize, fake_backend()->on_disk_usage()); +} + +// TODO(tzik): Add Truncate test. +// TODO(tzik): Add PluginCrash test and DropReservationManager test. + +} // namespace fileapi |