|  | // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "content/browser/appcache/appcache_disk_cache.h" | 
|  |  | 
|  | #include <limits> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/bind_helpers.h" | 
|  | #include "base/files/file_path.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/memory/ref_counted.h" | 
|  | #include "base/single_thread_task_runner.h" | 
|  | #include "base/stl_util.h" | 
|  | #include "base/strings/string_number_conversions.h" | 
|  | #include "net/base/cache_type.h" | 
|  | #include "net/base/net_errors.h" | 
|  |  | 
|  | namespace content { | 
|  |  | 
|  | // A callback shim that provides storage for the 'backend_ptr' value | 
|  | // and will delete a resulting ptr if completion occurs after its | 
|  | // been canceled. | 
|  | class AppCacheDiskCache::CreateBackendCallbackShim | 
|  | : public base::RefCounted<CreateBackendCallbackShim> { | 
|  | public: | 
|  | explicit CreateBackendCallbackShim(AppCacheDiskCache* object) | 
|  | : appcache_diskcache_(object) { | 
|  | } | 
|  |  | 
|  | void Cancel() { | 
|  | appcache_diskcache_ = NULL; | 
|  | } | 
|  |  | 
|  | void Callback(int rv) { | 
|  | if (appcache_diskcache_) | 
|  | appcache_diskcache_->OnCreateBackendComplete(rv); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<disk_cache::Backend> backend_ptr_;  // Accessed directly. | 
|  |  | 
|  | private: | 
|  | friend class base::RefCounted<CreateBackendCallbackShim>; | 
|  |  | 
|  | ~CreateBackendCallbackShim() { | 
|  | } | 
|  |  | 
|  | AppCacheDiskCache* appcache_diskcache_;  // Unowned pointer. | 
|  | }; | 
|  |  | 
|  | // An implementation of AppCacheDiskCacheInterface::Entry that's a thin | 
|  | // wrapper around disk_cache::Entry. | 
|  | class AppCacheDiskCache::EntryImpl : public Entry { | 
|  | public: | 
|  | EntryImpl(disk_cache::Entry* disk_cache_entry, | 
|  | AppCacheDiskCache* owner) | 
|  | : disk_cache_entry_(disk_cache_entry), owner_(owner) { | 
|  | DCHECK(disk_cache_entry); | 
|  | DCHECK(owner); | 
|  | owner_->AddOpenEntry(this); | 
|  | } | 
|  |  | 
|  | // Entry implementation. | 
|  | int Read(int index, | 
|  | int64_t offset, | 
|  | net::IOBuffer* buf, | 
|  | int buf_len, | 
|  | const net::CompletionCallback& callback) override { | 
|  | if (offset < 0 || offset > std::numeric_limits<int32_t>::max()) | 
|  | return net::ERR_INVALID_ARGUMENT; | 
|  | if (!disk_cache_entry_) | 
|  | return net::ERR_ABORTED; | 
|  | return disk_cache_entry_->ReadData( | 
|  | index, static_cast<int>(offset), buf, buf_len, callback); | 
|  | } | 
|  |  | 
|  | int Write(int index, | 
|  | int64_t offset, | 
|  | net::IOBuffer* buf, | 
|  | int buf_len, | 
|  | const net::CompletionCallback& callback) override { | 
|  | if (offset < 0 || offset > std::numeric_limits<int32_t>::max()) | 
|  | return net::ERR_INVALID_ARGUMENT; | 
|  | if (!disk_cache_entry_) | 
|  | return net::ERR_ABORTED; | 
|  | const bool kTruncate = true; | 
|  | return disk_cache_entry_->WriteData( | 
|  | index, static_cast<int>(offset), buf, buf_len, callback, kTruncate); | 
|  | } | 
|  |  | 
|  | int64_t GetSize(int index) override { | 
|  | return disk_cache_entry_ ? disk_cache_entry_->GetDataSize(index) : 0L; | 
|  | } | 
|  |  | 
|  | void Close() override { | 
|  | if (disk_cache_entry_) | 
|  | disk_cache_entry_->Close(); | 
|  | delete this; | 
|  | } | 
|  |  | 
|  | void Abandon() { | 
|  | owner_ = NULL; | 
|  | disk_cache_entry_->Close(); | 
|  | disk_cache_entry_ = NULL; | 
|  | } | 
|  |  | 
|  | private: | 
|  | ~EntryImpl() override { | 
|  | if (owner_) | 
|  | owner_->RemoveOpenEntry(this); | 
|  | } | 
|  |  | 
|  | disk_cache::Entry* disk_cache_entry_; | 
|  | AppCacheDiskCache* owner_; | 
|  | }; | 
|  |  | 
|  | // Separate object to hold state for each Create, Delete, or Doom call | 
|  | // while the call is in-flight and to produce an EntryImpl upon completion. | 
|  | class AppCacheDiskCache::ActiveCall | 
|  | : public base::RefCounted<AppCacheDiskCache::ActiveCall> { | 
|  | public: | 
|  | static int CreateEntry(const base::WeakPtr<AppCacheDiskCache>& owner, | 
|  | int64_t key, | 
|  | Entry** entry, | 
|  | const net::CompletionCallback& callback) { | 
|  | scoped_refptr<ActiveCall> active_call( | 
|  | new ActiveCall(owner, entry, callback)); | 
|  | int rv = owner->disk_cache()->CreateEntry( | 
|  | base::Int64ToString(key), &active_call->entry_ptr_, | 
|  | base::Bind(&ActiveCall::OnAsyncCompletion, active_call)); | 
|  | return active_call->HandleImmediateReturnValue(rv); | 
|  | } | 
|  |  | 
|  | static int OpenEntry(const base::WeakPtr<AppCacheDiskCache>& owner, | 
|  | int64_t key, | 
|  | Entry** entry, | 
|  | const net::CompletionCallback& callback) { | 
|  | scoped_refptr<ActiveCall> active_call( | 
|  | new ActiveCall(owner, entry, callback)); | 
|  | int rv = owner->disk_cache()->OpenEntry( | 
|  | base::Int64ToString(key), &active_call->entry_ptr_, | 
|  | base::Bind(&ActiveCall::OnAsyncCompletion, active_call)); | 
|  | return active_call->HandleImmediateReturnValue(rv); | 
|  | } | 
|  |  | 
|  | static int DoomEntry(const base::WeakPtr<AppCacheDiskCache>& owner, | 
|  | int64_t key, | 
|  | const net::CompletionCallback& callback) { | 
|  | scoped_refptr<ActiveCall> active_call( | 
|  | new ActiveCall(owner, nullptr, callback)); | 
|  | int rv = owner->disk_cache()->DoomEntry( | 
|  | base::Int64ToString(key), | 
|  | base::Bind(&ActiveCall::OnAsyncCompletion, active_call)); | 
|  | return active_call->HandleImmediateReturnValue(rv); | 
|  | } | 
|  |  | 
|  | private: | 
|  | friend class base::RefCounted<AppCacheDiskCache::ActiveCall>; | 
|  |  | 
|  | ActiveCall(const base::WeakPtr<AppCacheDiskCache>& owner, | 
|  | Entry** entry, | 
|  | const net::CompletionCallback& callback) | 
|  | : owner_(owner), | 
|  | entry_(entry), | 
|  | callback_(callback), | 
|  | entry_ptr_(nullptr) { | 
|  | DCHECK(owner_); | 
|  | } | 
|  |  | 
|  | ~ActiveCall() {} | 
|  |  | 
|  | int HandleImmediateReturnValue(int rv) { | 
|  | if (rv == net::ERR_IO_PENDING) { | 
|  | // OnAsyncCompletion will be called later. | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | if (rv == net::OK && entry_) { | 
|  | DCHECK(entry_ptr_); | 
|  | *entry_ = new EntryImpl(entry_ptr_, owner_.get()); | 
|  | } | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | void OnAsyncCompletion(int rv) { | 
|  | if (rv == net::OK && entry_) { | 
|  | DCHECK(entry_ptr_); | 
|  | if (owner_) { | 
|  | *entry_ = new EntryImpl(entry_ptr_, owner_.get()); | 
|  | } else { | 
|  | entry_ptr_->Close(); | 
|  | rv = net::ERR_ABORTED; | 
|  | } | 
|  | } | 
|  | callback_.Run(rv); | 
|  | } | 
|  |  | 
|  | base::WeakPtr<AppCacheDiskCache> owner_; | 
|  | Entry** entry_; | 
|  | net::CompletionCallback callback_; | 
|  | disk_cache::Entry* entry_ptr_; | 
|  | }; | 
|  |  | 
|  | AppCacheDiskCache::AppCacheDiskCache() | 
|  | #if defined(APPCACHE_USE_SIMPLE_CACHE) | 
|  | : AppCacheDiskCache(true) | 
|  | #else | 
|  | : AppCacheDiskCache(false) | 
|  | #endif | 
|  | { | 
|  | } | 
|  |  | 
|  | AppCacheDiskCache::~AppCacheDiskCache() { | 
|  | Disable(); | 
|  | } | 
|  |  | 
|  | int AppCacheDiskCache::InitWithDiskBackend( | 
|  | const base::FilePath& disk_cache_directory, | 
|  | int disk_cache_size, | 
|  | bool force, | 
|  | const scoped_refptr<base::SingleThreadTaskRunner>& cache_thread, | 
|  | const net::CompletionCallback& callback) { | 
|  | return Init(net::APP_CACHE, | 
|  | disk_cache_directory, | 
|  | disk_cache_size, | 
|  | force, | 
|  | cache_thread, | 
|  | callback); | 
|  | } | 
|  |  | 
|  | int AppCacheDiskCache::InitWithMemBackend( | 
|  | int mem_cache_size, const net::CompletionCallback& callback) { | 
|  | return Init(net::MEMORY_CACHE, base::FilePath(), mem_cache_size, false, NULL, | 
|  | callback); | 
|  | } | 
|  |  | 
|  | void AppCacheDiskCache::Disable() { | 
|  | if (is_disabled_) | 
|  | return; | 
|  |  | 
|  | is_disabled_ = true; | 
|  |  | 
|  | if (create_backend_callback_.get()) { | 
|  | create_backend_callback_->Cancel(); | 
|  | create_backend_callback_ = NULL; | 
|  | OnCreateBackendComplete(net::ERR_ABORTED); | 
|  | } | 
|  |  | 
|  | // We need to close open file handles in order to reinitalize the | 
|  | // appcache system on the fly. File handles held in both entries and in | 
|  | // the main disk_cache::Backend class need to be released. | 
|  | for (OpenEntries::const_iterator iter = open_entries_.begin(); | 
|  | iter != open_entries_.end(); ++iter) { | 
|  | (*iter)->Abandon(); | 
|  | } | 
|  | open_entries_.clear(); | 
|  | disk_cache_.reset(); | 
|  | } | 
|  |  | 
|  | int AppCacheDiskCache::CreateEntry(int64_t key, | 
|  | Entry** entry, | 
|  | const net::CompletionCallback& callback) { | 
|  | DCHECK(entry); | 
|  | DCHECK(!callback.is_null()); | 
|  | if (is_disabled_) | 
|  | return net::ERR_ABORTED; | 
|  |  | 
|  | if (is_initializing_or_waiting_to_initialize()) { | 
|  | pending_calls_.push_back(PendingCall(CREATE, key, entry, callback)); | 
|  | return net::ERR_IO_PENDING; | 
|  | } | 
|  |  | 
|  | if (!disk_cache_) | 
|  | return net::ERR_FAILED; | 
|  |  | 
|  | return ActiveCall::CreateEntry( | 
|  | weak_factory_.GetWeakPtr(), key, entry, callback); | 
|  | } | 
|  |  | 
|  | int AppCacheDiskCache::OpenEntry(int64_t key, | 
|  | Entry** entry, | 
|  | const net::CompletionCallback& callback) { | 
|  | DCHECK(entry); | 
|  | DCHECK(!callback.is_null()); | 
|  | if (is_disabled_) | 
|  | return net::ERR_ABORTED; | 
|  |  | 
|  | if (is_initializing_or_waiting_to_initialize()) { | 
|  | pending_calls_.push_back(PendingCall(OPEN, key, entry, callback)); | 
|  | return net::ERR_IO_PENDING; | 
|  | } | 
|  |  | 
|  | if (!disk_cache_) | 
|  | return net::ERR_FAILED; | 
|  |  | 
|  | return ActiveCall::OpenEntry( | 
|  | weak_factory_.GetWeakPtr(), key, entry, callback); | 
|  | } | 
|  |  | 
|  | int AppCacheDiskCache::DoomEntry(int64_t key, | 
|  | const net::CompletionCallback& callback) { | 
|  | DCHECK(!callback.is_null()); | 
|  | if (is_disabled_) | 
|  | return net::ERR_ABORTED; | 
|  |  | 
|  | if (is_initializing_or_waiting_to_initialize()) { | 
|  | pending_calls_.push_back(PendingCall(DOOM, key, NULL, callback)); | 
|  | return net::ERR_IO_PENDING; | 
|  | } | 
|  |  | 
|  | if (!disk_cache_) | 
|  | return net::ERR_FAILED; | 
|  |  | 
|  | return ActiveCall::DoomEntry(weak_factory_.GetWeakPtr(), key, callback); | 
|  | } | 
|  |  | 
|  | AppCacheDiskCache::AppCacheDiskCache(bool use_simple_cache) | 
|  | : use_simple_cache_(use_simple_cache), | 
|  | is_disabled_(false), | 
|  | is_waiting_to_initialize_(false), | 
|  | weak_factory_(this) { | 
|  | } | 
|  |  | 
|  | AppCacheDiskCache::PendingCall::PendingCall() | 
|  | : call_type(CREATE), | 
|  | key(0), | 
|  | entry(NULL) { | 
|  | } | 
|  |  | 
|  | AppCacheDiskCache::PendingCall::PendingCall( | 
|  | PendingCallType call_type, | 
|  | int64_t key, | 
|  | Entry** entry, | 
|  | const net::CompletionCallback& callback) | 
|  | : call_type(call_type), key(key), entry(entry), callback(callback) {} | 
|  |  | 
|  | AppCacheDiskCache::PendingCall::PendingCall(const PendingCall& other) = default; | 
|  |  | 
|  | AppCacheDiskCache::PendingCall::~PendingCall() {} | 
|  |  | 
|  | int AppCacheDiskCache::Init( | 
|  | net::CacheType cache_type, | 
|  | const base::FilePath& cache_directory, | 
|  | int cache_size, | 
|  | bool force, | 
|  | const scoped_refptr<base::SingleThreadTaskRunner>& cache_thread, | 
|  | const net::CompletionCallback& callback) { | 
|  | DCHECK(!is_initializing_or_waiting_to_initialize() && !disk_cache_.get()); | 
|  | is_disabled_ = false; | 
|  | create_backend_callback_ = new CreateBackendCallbackShim(this); | 
|  |  | 
|  | int rv = disk_cache::CreateCacheBackend( | 
|  | cache_type, | 
|  | use_simple_cache_ ? net::CACHE_BACKEND_SIMPLE | 
|  | : net::CACHE_BACKEND_DEFAULT, | 
|  | cache_directory, | 
|  | cache_size, | 
|  | force, | 
|  | cache_thread, | 
|  | NULL, | 
|  | &(create_backend_callback_->backend_ptr_), | 
|  | base::Bind(&CreateBackendCallbackShim::Callback, | 
|  | create_backend_callback_)); | 
|  | if (rv == net::ERR_IO_PENDING) | 
|  | init_callback_ = callback; | 
|  | else | 
|  | OnCreateBackendComplete(rv); | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | void AppCacheDiskCache::OnCreateBackendComplete(int rv) { | 
|  | if (rv == net::OK) { | 
|  | disk_cache_ = std::move(create_backend_callback_->backend_ptr_); | 
|  | } | 
|  | create_backend_callback_ = NULL; | 
|  |  | 
|  | // Invoke our clients callback function. | 
|  | if (!init_callback_.is_null()) { | 
|  | init_callback_.Run(rv); | 
|  | init_callback_.Reset(); | 
|  | } | 
|  |  | 
|  | // Service pending calls that were queued up while we were initializing. | 
|  | for (PendingCalls::const_iterator iter = pending_calls_.begin(); | 
|  | iter < pending_calls_.end(); ++iter) { | 
|  | int rv = net::ERR_FAILED; | 
|  | switch (iter->call_type) { | 
|  | case CREATE: | 
|  | rv = CreateEntry(iter->key, iter->entry, iter->callback); | 
|  | break; | 
|  | case OPEN: | 
|  | rv = OpenEntry(iter->key, iter->entry, iter->callback); | 
|  | break; | 
|  | case DOOM: | 
|  | rv = DoomEntry(iter->key, iter->callback); | 
|  | break; | 
|  | default: | 
|  | NOTREACHED(); | 
|  | break; | 
|  | } | 
|  | if (rv != net::ERR_IO_PENDING) | 
|  | iter->callback.Run(rv); | 
|  | } | 
|  | pending_calls_.clear(); | 
|  | } | 
|  |  | 
|  | }  // namespace content |