// 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 "webkit/appcache/appcache_disk_cache.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/file_path.h" #include "base/logging.h" #include "base/stl_util.h" #include "base/string_number_conversions.h" #include "net/base/net_errors.h" namespace appcache { // TODO(ajwong): Change disk_cache's API to return results via Callback. struct AppCacheDiskCache::CreateBackendDataShim { CreateBackendDataShim() : backend(NULL) {} disk_cache::Backend* backend; }; // An implementation of AppCacheDiskCacheInterface::Entry that's a thin // wrapper around disk_cache::Entry. class AppCacheDiskCache::EntryImpl : public Entry { public: explicit EntryImpl(disk_cache::Entry* disk_cache_entry) : disk_cache_entry_(disk_cache_entry) { DCHECK(disk_cache_entry); } // Entry implementation. virtual int Read(int index, int64 offset, net::IOBuffer* buf, int buf_len, const net::CompletionCallback& callback) { if (offset < 0 || offset > kint32max) return net::ERR_INVALID_ARGUMENT; return disk_cache_entry_->ReadData( index, static_cast(offset), buf, buf_len, callback); } virtual int Write(int index, int64 offset, net::IOBuffer* buf, int buf_len, const net::CompletionCallback& callback) { if (offset < 0 || offset > kint32max) return net::ERR_INVALID_ARGUMENT; const bool kTruncate = true; return disk_cache_entry_->WriteData( index, static_cast(offset), buf, buf_len, callback, kTruncate); } virtual int64 GetSize(int index) { return disk_cache_entry_->GetDataSize(index); } virtual void Close() { disk_cache_entry_->Close(); delete this; } private: disk_cache::Entry* disk_cache_entry_; }; // 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: explicit ActiveCall(AppCacheDiskCache* owner) : entry_(NULL), owner_(owner), entry_ptr_(NULL) { } int CreateEntry(int64 key, Entry** entry, const net::CompletionCallback& callback) { int rv = owner_->disk_cache()->CreateEntry( base::Int64ToString(key), &entry_ptr_, base::Bind(&ActiveCall::OnAsyncCompletion, base::Unretained(this))); return HandleImmediateReturnValue(rv, entry, callback); } int OpenEntry(int64 key, Entry** entry, const net::CompletionCallback& callback) { int rv = owner_->disk_cache()->OpenEntry( base::Int64ToString(key), &entry_ptr_, base::Bind(&ActiveCall::OnAsyncCompletion, base::Unretained(this))); return HandleImmediateReturnValue(rv, entry, callback); } int DoomEntry(int64 key, const net::CompletionCallback& callback) { int rv = owner_->disk_cache()->DoomEntry( base::Int64ToString(key), base::Bind(&ActiveCall::OnAsyncCompletion, base::Unretained(this))); return HandleImmediateReturnValue(rv, NULL, callback); } private: int HandleImmediateReturnValue(int rv, Entry** entry, const net::CompletionCallback& callback) { if (rv == net::ERR_IO_PENDING) { // OnAsyncCompletion will be called later. callback_ = callback; entry_ = entry; owner_->AddActiveCall(this); return net::ERR_IO_PENDING; } if (rv == net::OK && entry) *entry = new EntryImpl(entry_ptr_); delete this; return rv; } void OnAsyncCompletion(int rv) { owner_->RemoveActiveCall(this); if (rv == net::OK && entry_) *entry_ = new EntryImpl(entry_ptr_); callback_.Run(rv); callback_.Reset(); delete this; } Entry** entry_; net::CompletionCallback callback_; AppCacheDiskCache* owner_; disk_cache::Entry* entry_ptr_; }; AppCacheDiskCache::AppCacheDiskCache() : is_disabled_(false) { } AppCacheDiskCache::~AppCacheDiskCache() { if (!create_backend_callback_.IsCancelled()) { create_backend_callback_.Cancel(); OnCreateBackendComplete(NULL, net::ERR_ABORTED); } disk_cache_.reset(); STLDeleteElements(&active_calls_); } int AppCacheDiskCache::InitWithDiskBackend( const FilePath& disk_cache_directory, int disk_cache_size, bool force, base::MessageLoopProxy* 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, FilePath(), mem_cache_size, false, NULL, callback); } void AppCacheDiskCache::Disable() { if (is_disabled_) return; is_disabled_ = true; if (!create_backend_callback_.IsCancelled()) { create_backend_callback_.Cancel(); OnCreateBackendComplete(NULL, net::ERR_ABORTED); } } int AppCacheDiskCache::CreateEntry(int64 key, Entry** entry, const net::CompletionCallback& callback) { DCHECK(entry); DCHECK(!callback.is_null()); if (is_disabled_) return net::ERR_ABORTED; if (is_initializing()) { pending_calls_.push_back(PendingCall(CREATE, key, entry, callback)); return net::ERR_IO_PENDING; } if (!disk_cache_.get()) return net::ERR_FAILED; return (new ActiveCall(this))->CreateEntry(key, entry, callback); } int AppCacheDiskCache::OpenEntry(int64 key, Entry** entry, const net::CompletionCallback& callback) { DCHECK(entry); DCHECK(!callback.is_null()); if (is_disabled_) return net::ERR_ABORTED; if (is_initializing()) { pending_calls_.push_back(PendingCall(OPEN, key, entry, callback)); return net::ERR_IO_PENDING; } if (!disk_cache_.get()) return net::ERR_FAILED; return (new ActiveCall(this))->OpenEntry(key, entry, callback); } int AppCacheDiskCache::DoomEntry(int64 key, const net::CompletionCallback& callback) { DCHECK(!callback.is_null()); if (is_disabled_) return net::ERR_ABORTED; if (is_initializing()) { pending_calls_.push_back(PendingCall(DOOM, key, NULL, callback)); return net::ERR_IO_PENDING; } if (!disk_cache_.get()) return net::ERR_FAILED; return (new ActiveCall(this))->DoomEntry(key, callback); } AppCacheDiskCache::PendingCall::~PendingCall() {} int AppCacheDiskCache::Init(net::CacheType cache_type, const FilePath& cache_directory, int cache_size, bool force, base::MessageLoopProxy* cache_thread, const net::CompletionCallback& callback) { DCHECK(!is_initializing() && !disk_cache_.get()); is_disabled_ = false; // TODO(ajwong): Change disk_cache's API to return results via Callback. disk_cache::Backend** backend_ptr = new(disk_cache::Backend*); create_backend_callback_.Reset( base::Bind(&AppCacheDiskCache::OnCreateBackendComplete, base::Unretained(this), base::Owned(backend_ptr))); int rv = disk_cache::CreateCacheBackend( cache_type, cache_directory, cache_size, force, cache_thread, NULL, backend_ptr, create_backend_callback_.callback()); if (rv == net::ERR_IO_PENDING) init_callback_ = callback; else OnCreateBackendComplete(backend_ptr, rv); return rv; } void AppCacheDiskCache::OnCreateBackendComplete( disk_cache::Backend** backend, int rv) { if (rv == net::OK) { DCHECK(backend); disk_cache_.reset(*backend); } create_backend_callback_.Cancel(); // 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 appcache