text: // Copyright 2014 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 "components/services/storage/service_worker/service_worker_disk_cache.h"
  
  #include <limits>
  #include <utility>
  
  #include "base/bind.h"
  #include "base/callback.h"
  #include "base/callback_helpers.h"
  #include "base/check.h"
  #include "base/containers/contains.h"
  #include "base/files/file_path.h"
  #include "base/memory/ref_counted.h"
  #include "base/strings/string_number_conversions.h"
  #include "base/task/single_thread_task_runner.h"
  #include "net/base/cache_type.h"
  #include "net/base/completion_once_callback.h"
  #include "net/base/completion_repeating_callback.h"
  #include "net/base/net_errors.h"
  
  namespace storage {
  
  // A callback shim that provides storage for the 'backend_ptr' value
  // and will delete a resulting ptr if completion occurs after the
  // callback has been canceled.
  class ServiceWorkerDiskCache::CreateBackendCallbackShim
      : public base::RefCounted<CreateBackendCallbackShim> {
   public:
    explicit CreateBackendCallbackShim(ServiceWorkerDiskCache* object)
        : service_worker_disk_cache_(object) {}
  
    void Cancel() { service_worker_disk_cache_ = nullptr; }
  
    void Callback(int return_value) {
      if (service_worker_disk_cache_)
        service_worker_disk_cache_->OnCreateBackendComplete(return_value);
    }
  
    std::unique_ptr<disk_cache::Backend> backend_ptr_;  // Accessed directly.
  
   private:
    friend class base::RefCounted<CreateBackendCallbackShim>;
  
    ~CreateBackendCallbackShim() = default;
  
    ServiceWorkerDiskCache* service_worker_disk_cache_;  // Unowned pointer.
  };
  
  ServiceWorkerDiskCacheEntry::ServiceWorkerDiskCacheEntry(
      disk_cache::Entry* disk_cache_entry,
      ServiceWorkerDiskCache* cache)
      : disk_cache_entry_(disk_cache_entry), cache_(cache) {
    DCHECK(disk_cache_entry);
    DCHECK(cache);
    cache_->AddOpenEntry(this);
  }
  
  ServiceWorkerDiskCacheEntry::~ServiceWorkerDiskCacheEntry() {
    if (disk_cache_entry_) {
      disk_cache_entry_->Close();
      cache_->RemoveOpenEntry(this);
    }
  }
  
  int ServiceWorkerDiskCacheEntry::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 ServiceWorkerDiskCacheEntry::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 ServiceWorkerDiskCacheEntry::GetSize(int index) {
    return disk_cache_entry_ ? disk_cache_entry_->GetDataSize(index) : 0L;
  }
  
  void ServiceWorkerDiskCacheEntry::Abandon() {
    disk_cache_entry_->Close();
    disk_cache_entry_ = nullptr;
  }
  
  ServiceWorkerDiskCache::ServiceWorkerDiskCache() = default;
  
  ServiceWorkerDiskCache::~ServiceWorkerDiskCache() {
    Disable();
  }
  
  net::Error ServiceWorkerDiskCache::InitWithDiskBackend(
      const base::FilePath& disk_cache_directory,
      base::OnceClosure post_cleanup_callback,
      net::CompletionOnceCallback callback) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return Init(net::APP_CACHE, disk_cache_directory,
                std::numeric_limits<int64_t>::max(),
                std::move(post_cleanup_callback), std::move(callback));
  }
  
  net::Error ServiceWorkerDiskCache::InitWithMemBackend(
      int64_t mem_cache_size,
      net::CompletionOnceCallback callback) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return Init(net::MEMORY_CACHE, base::FilePath(), mem_cache_size,
                base::OnceClosure(), std::move(callback));
  }
  
  void ServiceWorkerDiskCache::Disable() {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    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 reinitialize the
    // service worker system on the fly. File handles held in both entries and in
    // the main disk_cache::Backend class need to be released.
    for (ServiceWorkerDiskCacheEntry* entry : open_entries_) {
      entry->Abandon();
    }
    open_entries_.clear();
    disk_cache_.reset();
  }
  
  void ServiceWorkerDiskCache::CreateEntry(int64_t key, EntryCallback callback) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    DCHECK(!callback.is_null());
    if (is_disabled_) {
      std::move(callback).Run(net::ERR_ABORTED, nullptr);
      return;
    }
  
    if (is_initializing_or_waiting_to_initialize()) {
      // Unretained use is safe here because the callback is stored in
      // `pending_calls_`, which is owned by this instance.
      pending_calls_.emplace_back(
          base::BindOnce(&ServiceWorkerDiskCache::CreateEntry,
                         base::Unretained(this), key, std::move(callback)));
      return;
    }
  
    if (!disk_cache_) {
      std::move(callback).Run(net::ERR_FAILED, nullptr);
      return;
    }
  
    uint64_t call_id = GetNextCallId();
    DCHECK(!base::Contains(active_entry_calls_, call_id));
    active_entry_calls_.emplace(call_id, std::move(callback));
  
    disk_cache::EntryResult result = disk_cache_->CreateEntry(
        base::NumberToString(key), net::HIGHEST,
        base::BindOnce(&ServiceWorkerDiskCache::DidGetEntryResult,
                       weak_factory_.GetWeakPtr(), call_id));
    if (result.net_error() != net::ERR_IO_PENDING) {
      DidGetEntryResult(call_id, std::move(result));
    }
  }
  
  void ServiceWorkerDiskCache::OpenEntry(int64_t key, EntryCallback callback) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    DCHECK(!callback.is_null());
    if (is_disabled_) {
      std::move(callback).Run(net::ERR_ABORTED, nullptr);
      return;
    }
  
    if (is_initializing_or_waiting_to_initialize()) {
      // Unretained use is safe here because the callback is stored in
      // `pending_calls_`, which is owned by this instance.
      pending_calls_.emplace_back(
          base::BindOnce(&ServiceWorkerDiskCache::OpenEntry,
                         base::Unretained(this), key, std::move(callback)));
      return;
    }
  
    if (!disk_cache_) {
      std::move(callback).Run(net::ERR_FAILED, nullptr);
      return;
    }
  
    uint64_t call_id = GetNextCallId();
    DCHECK(!base::Contains(active_entry_calls_, call_id));
    active_entry_calls_.emplace(call_id, std::move(callback));
  
    disk_cache::EntryResult result = disk_cache_->OpenEntry(
        base::NumberToString(key), net::HIGHEST,
        base::BindOnce(&ServiceWorkerDiskCache::DidGetEntryResult,
                       weak_factory_.GetWeakPtr(), call_id));
    if (result.net_error() != net::ERR_IO_PENDING) {
      DidGetEntryResult(call_id, std::move(result));
    }
  }
  
  void ServiceWorkerDiskCache::DoomEntry(int64_t key,
                                         net::CompletionOnceCallback callback) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    DCHECK(!callback.is_null());
    if (is_disabled_) {
      std::move(callback).Run(net::ERR_ABORTED);
      return;
    }
  
    if (is_initializing_or_waiting_to_initialize()) {
      // Unretained use is safe here because the callback is stored in
      // `pending_calls_`, which is owned by this instance.
      pending_calls_.emplace_back(
          base::BindOnce(&ServiceWorkerDiskCache::DoomEntry,
                         base::Unretained(this), key, std::move(callback)));
      return;
    }
  
    if (!disk_cache_) {
      std::move(callback).Run(net::ERR_FAILED);
      return;
    }
  
    uint64_t call_id = GetNextCallId();
    DCHECK(!base::Contains(active_doom_calls_, call_id));
    active_doom_calls_.emplace(call_id, std::move(callback));
  
    net::Error net_error = disk_cache_->DoomEntry(
        base::NumberToString(key), net::HIGHEST,
        base::BindOnce(&ServiceWorkerDiskCache::DidDoomEntry,
                       weak_factory_.GetWeakPtr(), call_id));
    if (net_error != net::ERR_IO_PENDING) {
      DidDoomEntry(call_id, net_error);
    }
  }
  
  base::WeakPtr<ServiceWorkerDiskCache> ServiceWorkerDiskCache::GetWeakPtr() {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return weak_factory_.GetWeakPtr();
  }
  
  net::Error ServiceWorkerDiskCache::Init(net::CacheType cache_type,
                                          const base::FilePath& cache_directory,
                                          int64_t cache_size,
                                          base::OnceClosure post_cleanup_callback,
                                          net::CompletionOnceCallback callback) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    DCHECK(!is_initializing_or_waiting_to_initialize() && !disk_cache_.get());
    is_disabled_ = false;
    create_backend_callback_ =
        base::MakeRefCounted<CreateBackendCallbackShim>(this);
  
    net::Error return_value = disk_cache::CreateCacheBackend(
        cache_type, net::CACHE_BACKEND_SIMPLE, cache_directory, cache_size,
        disk_cache::ResetHandling::kNeverReset, 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 ServiceWorkerDiskCache::OnCreateBackendComplete(int return_value) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    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_)
      std::move(call).Run();
    pending_calls_.clear();
  }
  
  uint64_t ServiceWorkerDiskCache::GetNextCallId() {
    return next_call_id_++;
  }
  
  void ServiceWorkerDiskCache::DidGetEntryResult(uint64_t call_id,
                                                 disk_cache::EntryResult result) {
    auto it = active_entry_calls_.find(call_id);
    DCHECK(it != active_entry_calls_.end());
    EntryCallback callback = std::move(it->second);
    active_entry_calls_.erase(it);
  
    net::Error net_error = result.net_error();
    std::unique_ptr<ServiceWorkerDiskCacheEntry> entry;
    if (net_error == net::OK) {
      entry = std::make_unique<ServiceWorkerDiskCacheEntry>(result.ReleaseEntry(),
                                                            this);
    }
  
    std::move(callback).Run(net_error, std::move(entry));
  }
  
  void ServiceWorkerDiskCache::DidDoomEntry(uint64_t call_id, int net_error) {
    auto it = active_doom_calls_.find(call_id);
    DCHECK(it != active_doom_calls_.end());
    net::CompletionOnceCallback callback = std::move(it->second);
    active_doom_calls_.erase(it);
  
    std::move(callback).Run(net_error);
  }
  
  void ServiceWorkerDiskCache::AddOpenEntry(ServiceWorkerDiskCacheEntry* entry) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    open_entries_.insert(entry);
  }
  
  void ServiceWorkerDiskCache::RemoveOpenEntry(
      ServiceWorkerDiskCacheEntry* entry) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    open_entries_.erase(entry);
  }
  
  }  // namespace storage
  