|  | // 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/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(ProfileIOS* profile) | 
|  | : profile_(profile), 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( | 
|  | profile_->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); | 
|  | } |