chromium/ios/chrome/browser/browsing_data/model/cache_counter.cc

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ios/chrome/browser/browsing_data/model/cache_counter.h"

#include "base/functional/bind.h"
#include "components/browsing_data/core/pref_names.h"
#include "ios/chrome/browser/shared/model/profile/profile_ios.h"
#include "ios/web/public/browser_state.h"
#include "ios/web/public/thread/web_task_traits.h"
#include "ios/web/public/thread/web_thread.h"
#include "net/base/completion_repeating_callback.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/disk_cache/disk_cache.h"
#include "net/http/http_cache.h"
#include "net/http/http_transaction_factory.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"

namespace {

class IOThreadCacheCounter {
 public:
  IOThreadCacheCounter(
      const scoped_refptr<net::URLRequestContextGetter>& context_getter,
      const net::Int64CompletionRepeatingCallback& result_callback)
      : next_step_(STEP_GET_BACKEND),
        context_getter_(context_getter),
        result_callback_(result_callback),
        result_(0),
        backend_(nullptr) {}

  void Count() {
    web::GetIOThreadTaskRunner({})->PostTask(
        FROM_HERE, base::BindRepeating(&IOThreadCacheCounter::CountInternal,
                                       base::Unretained(this), net::OK));
  }

 private:
  enum Step {
    STEP_GET_BACKEND,  // Get the disk_cache::Backend instance.
    STEP_COUNT,        // Run CalculateSizeOfAllEntries() on it.
    STEP_CALLBACK,     // Respond on the UI thread.
  };

  void CountInternal(int64_t rv) {
    DCHECK_CURRENTLY_ON(web::WebThread::IO);

    while (rv != net::ERR_IO_PENDING) {
      // In case of an error, skip to the last step.
      if (rv < 0) {
        next_step_ = STEP_CALLBACK;
      }

      // Process the counting in three steps: STEP_GET_BACKEND -> STEP_COUNT ->
      // -> STEP_CALLBACK.
      switch (next_step_) {
        case STEP_GET_BACKEND: {
          next_step_ = STEP_COUNT;

          net::HttpCache* http_cache = context_getter_->GetURLRequestContext()
                                           ->http_transaction_factory()
                                           ->GetCache();

          std::tie(rv, backend_) = http_cache->GetBackend(base::BindOnce(
              [](IOThreadCacheCounter* self,
                 net::HttpCache::GetBackendResult result) {
                self->backend_ = result.second;
                self->CountInternal(static_cast<int64_t>(result.first));
              },
              base::Unretained(this)));
          break;
        }

        case STEP_COUNT: {
          next_step_ = STEP_CALLBACK;

          DCHECK(backend_);
          rv = backend_->CalculateSizeOfAllEntries(base::BindRepeating(
              &IOThreadCacheCounter::CountInternal, base::Unretained(this)));
          break;
        }

        case STEP_CALLBACK: {
          result_ = rv;

          web::GetUIThreadTaskRunner({})->PostTask(
              FROM_HERE,
              base::BindOnce(&IOThreadCacheCounter::OnCountingFinished,
                             base::Unretained(this)));

          // Return instead of break.
          // The task above deletes this object; app would crash if this object
          // is deleted before reentrance of the loop.
          return;
        }
      }
    }
  }

  void OnCountingFinished() {
    DCHECK_CURRENTLY_ON(web::WebThread::UI);
    result_callback_.Run(result_);
    delete this;
  }

  Step next_step_;
  scoped_refptr<net::URLRequestContextGetter> context_getter_;
  net::Int64CompletionRepeatingCallback result_callback_;
  int64_t result_;
  raw_ptr<disk_cache::Backend> backend_;
};

}  // namespace

CacheCounter::CacheCounter(ChromeBrowserState* browser_state)
    : browser_state_(browser_state), weak_ptr_factory_(this) {}

CacheCounter::~CacheCounter() = default;

const char* CacheCounter::GetPrefName() const {
  return browsing_data::prefs::kDeleteCache;
}

void CacheCounter::Count() {
  // Cancel existing requests.
  weak_ptr_factory_.InvalidateWeakPtrs();

  // disk_cache::Backend currently does not implement counting for subsets of
  // cache, only for the entire cache. Thus, ignore the time period setting and
  // always request counting for the unbounded time interval. It is up to the
  // UI to interpret the results for finite time intervals as upper estimates.
  // IOThreadCacheCounter deletes itself when done.
  (new IOThreadCacheCounter(
       browser_state_->GetRequestContext(),
       base::BindRepeating(&CacheCounter::OnCacheSizeCalculated,
                           weak_ptr_factory_.GetWeakPtr())))
      ->Count();
}

void CacheCounter::OnCacheSizeCalculated(int64_t result_bytes) {
  // A value less than 0 means a net error code.
  if (result_bytes < 0) {
    return;
  }

  ReportResult(result_bytes);
}