blob: d38e9ed58d5a52eeadcc230df51675f0c60a327a [file] [log] [blame]
// 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/callback_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/completion_repeating_callback.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_ = nullptr; }
void Callback(int return_value) {
if (appcache_diskcache_)
appcache_diskcache_->OnCreateBackendComplete(return_value);
}
std::unique_ptr<disk_cache::Backend> backend_ptr_; // Accessed directly.
private:
friend class base::RefCounted<CreateBackendCallbackShim>;
~CreateBackendCallbackShim() {
}
AppCacheDiskCache* appcache_diskcache_; // Unowned pointer.
};
AppCacheDiskCacheEntry::AppCacheDiskCacheEntry(
disk_cache::Entry* disk_cache_entry,
AppCacheDiskCache* cache)
: disk_cache_entry_(disk_cache_entry), cache_(cache) {
DCHECK(disk_cache_entry);
DCHECK(cache);
cache_->AddOpenEntry(this);
}
AppCacheDiskCacheEntry::~AppCacheDiskCacheEntry() {
if (cache_)
cache_->RemoveOpenEntry(this);
}
int AppCacheDiskCacheEntry::Read(int index,
int64_t offset,
net::IOBuffer* buf,
int buf_len,
net::CompletionOnceCallback callback) {
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, std::move(callback));
}
int AppCacheDiskCacheEntry::Write(int index,
int64_t offset,
net::IOBuffer* buf,
int buf_len,
net::CompletionOnceCallback callback) {
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, std::move(callback), kTruncate);
}
int64_t AppCacheDiskCacheEntry::GetSize(int index) {
return disk_cache_entry_ ? disk_cache_entry_->GetDataSize(index) : 0L;
}
void AppCacheDiskCacheEntry::Close() {
if (disk_cache_entry_)
disk_cache_entry_->Close();
delete this;
}
void AppCacheDiskCacheEntry::Abandon() {
cache_ = nullptr;
disk_cache_entry_->Close();
disk_cache_entry_ = nullptr;
}
// 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 net::Error CreateEntry(const base::WeakPtr<AppCacheDiskCache>& owner,
int64_t key,
AppCacheDiskCacheEntry** entry,
net::CompletionOnceCallback callback) {
scoped_refptr<ActiveCall> active_call(
new ActiveCall(owner, entry, std::move(callback)));
net::Error return_value = owner->disk_cache()->CreateEntry(
base::NumberToString(key), net::HIGHEST, &active_call->entry_ptr_,
base::BindOnce(&ActiveCall::OnAsyncCompletion, active_call));
return active_call->HandleImmediateReturnValue(return_value);
}
static net::Error OpenEntry(const base::WeakPtr<AppCacheDiskCache>& owner,
int64_t key,
AppCacheDiskCacheEntry** entry,
net::CompletionOnceCallback callback) {
scoped_refptr<ActiveCall> active_call(
new ActiveCall(owner, entry, std::move(callback)));
net::Error return_value = owner->disk_cache()->OpenEntry(
base::NumberToString(key), net::HIGHEST, &active_call->entry_ptr_,
base::BindOnce(&ActiveCall::OnAsyncCompletion, active_call));
return active_call->HandleImmediateReturnValue(return_value);
}
static net::Error DoomEntry(const base::WeakPtr<AppCacheDiskCache>& owner,
int64_t key,
net::CompletionOnceCallback callback) {
scoped_refptr<ActiveCall> active_call(
new ActiveCall(owner, nullptr, std::move(callback)));
net::Error return_value = owner->disk_cache()->DoomEntry(
base::NumberToString(key), net::HIGHEST,
base::BindOnce(&ActiveCall::OnAsyncCompletion, active_call));
return active_call->HandleImmediateReturnValue(return_value);
}
private:
friend class base::RefCounted<AppCacheDiskCache::ActiveCall>;
ActiveCall(const base::WeakPtr<AppCacheDiskCache>& owner,
AppCacheDiskCacheEntry** entry,
net::CompletionOnceCallback callback)
: owner_(owner),
entry_(entry),
callback_(std::move(callback)),
entry_ptr_(nullptr) {
DCHECK(owner_);
}
~ActiveCall() {}
net::Error HandleImmediateReturnValue(net::Error rv) {
if (rv == net::ERR_IO_PENDING) {
// OnAsyncCompletion will be called later.
return rv;
}
if (rv == net::OK && entry_) {
DCHECK(entry_ptr_);
*entry_ = new AppCacheDiskCacheEntry(entry_ptr_, owner_.get());
}
return rv;
}
void OnAsyncCompletion(int rv) {
if (rv == net::OK && entry_) {
DCHECK(entry_ptr_);
if (owner_) {
*entry_ = new AppCacheDiskCacheEntry(entry_ptr_, owner_.get());
} else {
entry_ptr_->Close();
rv = net::ERR_ABORTED;
}
}
std::move(callback_).Run(rv);
}
base::WeakPtr<AppCacheDiskCache> owner_;
AppCacheDiskCacheEntry** entry_;
net::CompletionOnceCallback callback_;
disk_cache::Entry* entry_ptr_;
};
AppCacheDiskCache::AppCacheDiskCache()
#if defined(APPCACHE_USE_SIMPLE_CACHE)
: AppCacheDiskCache("DiskCache.AppCache", true)
#else
: AppCacheDiskCache("DiskCache.AppCache", false)
#endif
{
}
AppCacheDiskCache::~AppCacheDiskCache() {
Disable();
}
net::Error AppCacheDiskCache::InitWithDiskBackend(
const base::FilePath& disk_cache_directory,
int disk_cache_size,
bool force,
base::OnceClosure post_cleanup_callback,
net::CompletionOnceCallback callback) {
return Init(net::APP_CACHE, disk_cache_directory, disk_cache_size, force,
std::move(post_cleanup_callback), std::move(callback));
}
net::Error AppCacheDiskCache::InitWithMemBackend(
int mem_cache_size,
net::CompletionOnceCallback callback) {
return Init(net::MEMORY_CACHE, base::FilePath(), mem_cache_size, false,
base::OnceClosure(), std::move(callback));
}
void AppCacheDiskCache::Disable() {
if (is_disabled_)
return;
is_disabled_ = true;
if (create_backend_callback_.get()) {
create_backend_callback_->Cancel();
create_backend_callback_ = nullptr;
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 (AppCacheDiskCacheEntry* entry : open_entries_) {
entry->Abandon();
}
open_entries_.clear();
disk_cache_.reset();
}
net::Error AppCacheDiskCache::CreateEntry(
int64_t key,
AppCacheDiskCacheEntry** entry,
net::CompletionOnceCallback 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, std::move(callback)));
return net::ERR_IO_PENDING;
}
if (!disk_cache_)
return net::ERR_FAILED;
return ActiveCall::CreateEntry(weak_factory_.GetWeakPtr(), key, entry,
std::move(callback));
}
net::Error AppCacheDiskCache::OpenEntry(int64_t key,
AppCacheDiskCacheEntry** entry,
net::CompletionOnceCallback 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, std::move(callback)));
return net::ERR_IO_PENDING;
}
if (!disk_cache_)
return net::ERR_FAILED;
return ActiveCall::OpenEntry(weak_factory_.GetWeakPtr(), key, entry,
std::move(callback));
}
net::Error AppCacheDiskCache::DoomEntry(int64_t key,
net::CompletionOnceCallback 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, nullptr, std::move(callback)));
return net::ERR_IO_PENDING;
}
if (!disk_cache_)
return net::ERR_FAILED;
return ActiveCall::DoomEntry(weak_factory_.GetWeakPtr(), key,
std::move(callback));
}
base::WeakPtr<AppCacheDiskCache> AppCacheDiskCache::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
AppCacheDiskCache::AppCacheDiskCache(const char* uma_name,
bool use_simple_cache)
: use_simple_cache_(use_simple_cache),
is_disabled_(false),
is_waiting_to_initialize_(false),
uma_name_(uma_name),
weak_factory_(this) {}
AppCacheDiskCache::PendingCall::PendingCall()
: call_type(CREATE), key(0), entry(nullptr) {}
AppCacheDiskCache::PendingCall::PendingCall(
PendingCallType call_type,
int64_t key,
AppCacheDiskCacheEntry** entry,
net::CompletionOnceCallback callback)
: call_type(call_type),
key(key),
entry(entry),
callback(std::move(callback)) {}
AppCacheDiskCache::PendingCall::PendingCall(PendingCall&& other) = default;
AppCacheDiskCache::PendingCall::~PendingCall() = default;
net::Error AppCacheDiskCache::Init(net::CacheType cache_type,
const base::FilePath& cache_directory,
int cache_size,
bool force,
base::OnceClosure post_cleanup_callback,
net::CompletionOnceCallback callback) {
DCHECK(!is_initializing_or_waiting_to_initialize() && !disk_cache_.get());
is_disabled_ = false;
create_backend_callback_ = new CreateBackendCallbackShim(this);
net::Error return_value = disk_cache::CreateCacheBackend(
cache_type,
use_simple_cache_ ? net::CACHE_BACKEND_SIMPLE
: net::CACHE_BACKEND_DEFAULT,
cache_directory, cache_size, force, nullptr,
&(create_backend_callback_->backend_ptr_),
std::move(post_cleanup_callback),
base::BindOnce(&CreateBackendCallbackShim::Callback,
create_backend_callback_));
if (return_value == net::ERR_IO_PENDING)
init_callback_ = std::move(callback);
else
OnCreateBackendComplete(return_value);
return return_value;
}
void AppCacheDiskCache::OnCreateBackendComplete(int return_value) {
if (return_value == net::OK) {
disk_cache_ = std::move(create_backend_callback_->backend_ptr_);
}
create_backend_callback_ = nullptr;
// Invoke our clients callback function.
if (!init_callback_.is_null()) {
std::move(init_callback_).Run(return_value);
}
// Service pending calls that were queued up while we were initializing.
for (auto& call : pending_calls_) {
// This is safe, because the callback will only be called once.
net::CompletionRepeatingCallback copyable_callback =
base::AdaptCallbackForRepeating(std::move(call.callback));
return_value = net::ERR_FAILED;
switch (call.call_type) {
case CREATE:
return_value = CreateEntry(call.key, call.entry, copyable_callback);
break;
case OPEN:
return_value = OpenEntry(call.key, call.entry, copyable_callback);
break;
case DOOM:
return_value = DoomEntry(call.key, copyable_callback);
break;
}
if (return_value != net::ERR_IO_PENDING)
copyable_callback.Run(return_value);
}
pending_calls_.clear();
}
} // namespace content