blob: 6a6078853914942de68799dd8ce2fc62e7e851aa [file] [log] [blame]
// Copyright 2018 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/image_fetcher/core/cache/image_cache.h"
#include <algorithm>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/hash/sha1.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/task_runner_util.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "components/base32/base32.h"
#include "components/image_fetcher/core/cache/image_data_store.h"
#include "components/image_fetcher/core/cache/image_metadata_store.h"
#include "components/image_fetcher/core/image_fetcher_metrics_reporter.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
namespace {
constexpr char kPrefLastStartupEviction[] =
"cached_image_fetcher_last_startup_eviction_time";
constexpr char kPrefLastLRUEviction[] =
"cached_image_fetcher_last_lru_eviction_time";
// TODO(wylieb): Control these parameters server-side.
constexpr int kCacheMaxSize = 64 * 1024 * 1024; // 64mb.
constexpr int kCacheResizeWhenFull = 48 * 1024 * 1024; // 48mb.
// Cache items are allowed to live for the given amount of days.
constexpr int kCacheItemsTimeToLiveDays = 7;
constexpr int kImageCacheEvictionIntervalHours = 24;
void OnStartupEvictionQueued() {}
} // namespace
namespace image_fetcher {
// static
std::string ImageCache::HashUrlToKey(const std::string& input) {
return base32::Base32Encode(base::SHA1HashString(input));
}
// static
void ImageCache::RegisterProfilePrefs(PrefRegistrySimple* registry) {
registry->RegisterTimePref(kPrefLastStartupEviction, base::Time());
registry->RegisterTimePref(kPrefLastLRUEviction, base::Time());
}
ImageCache::ImageCache(std::unique_ptr<ImageDataStore> data_store,
std::unique_ptr<ImageMetadataStore> metadata_store,
PrefService* pref_service,
base::Clock* clock,
scoped_refptr<base::SequencedTaskRunner> task_runner)
: initialization_attempted_(false),
data_store_(std::move(data_store)),
metadata_store_(std::move(metadata_store)),
pref_service_(pref_service),
clock_(clock),
task_runner_(task_runner),
weak_ptr_factory_(this) {}
ImageCache::~ImageCache() = default;
void ImageCache::SaveImage(std::string url, std::string image_data) {
// If the image data is larger than the cache's max size, bail out.
if (image_data.length() > kCacheMaxSize) {
return;
}
base::OnceClosure request =
base::BindOnce(&ImageCache::SaveImageImpl, weak_ptr_factory_.GetWeakPtr(),
url, std::move(image_data));
QueueOrStartRequest(std::move(request));
}
void ImageCache::LoadImage(bool read_only,
std::string url,
ImageDataCallback callback) {
base::OnceClosure request =
base::BindOnce(&ImageCache::LoadImageImpl, weak_ptr_factory_.GetWeakPtr(),
read_only, url, std::move(callback));
QueueOrStartRequest(std::move(request));
}
void ImageCache::DeleteImage(std::string url) {
base::OnceClosure request = base::BindOnce(
&ImageCache::DeleteImageImpl, weak_ptr_factory_.GetWeakPtr(), url);
QueueOrStartRequest(std::move(request));
}
void ImageCache::QueueOrStartRequest(base::OnceClosure request) {
if (!AreAllDependenciesInitialized()) {
queued_requests_.push_back(std::move(request));
MaybeStartInitialization();
return;
}
std::move(request).Run();
}
void ImageCache::MaybeStartInitialization() {
if (initialization_attempted_) {
return;
}
initialization_attempted_ = true;
data_store_->Initialize(base::BindOnce(&ImageCache::OnDependencyInitialized,
weak_ptr_factory_.GetWeakPtr()));
metadata_store_->Initialize(base::BindOnce(
&ImageCache::OnDependencyInitialized, weak_ptr_factory_.GetWeakPtr()));
}
bool ImageCache::AreAllDependenciesInitialized() const {
return data_store_->IsInitialized() && metadata_store_->IsInitialized();
}
void ImageCache::OnDependencyInitialized() {
if (!AreAllDependenciesInitialized()) {
return;
}
// Everything is initialized, take care of the queued requests.
for (base::OnceClosure& request : queued_requests_) {
std::move(request).Run();
}
queued_requests_.clear();
// TODO(wylieb): Consider delaying eviction as new requests come in via
// separate weak pointers.
ImageFetcherMetricsReporter::ReportEvent(
ImageFetcherMetricsReporter::kCachedImageFetcherInternalUmaClientName,
ImageFetcherEvent::kCacheStartupEvictionStarted);
// Once all the queued requests are taken care of, run eviction.
base::PostTaskWithTraitsAndReply(
FROM_HERE, {base::TaskPriority::BEST_EFFORT},
base::BindOnce(OnStartupEvictionQueued),
base::BindOnce(&ImageCache::RunEvictionOnStartup,
weak_ptr_factory_.GetWeakPtr()));
}
void ImageCache::SaveImageImpl(const std::string& url, std::string image_data) {
std::string key = ImageCache::HashUrlToKey(url);
// If the cache is full, evict some stuff.
RunEvictionWhenFull();
size_t length = image_data.length();
data_store_->SaveImage(key, std::move(image_data));
metadata_store_->SaveImageMetadata(key, length);
}
void ImageCache::LoadImageImpl(bool read_only,
const std::string& url,
ImageDataCallback callback) {
std::string key = ImageCache::HashUrlToKey(url);
data_store_->LoadImage(key, std::move(callback));
if (!read_only) {
metadata_store_->UpdateImageMetadata(key);
}
}
void ImageCache::DeleteImageImpl(const std::string& url) {
std::string key = ImageCache::HashUrlToKey(url);
data_store_->DeleteImage(key);
metadata_store_->DeleteImageMetadata(key);
}
void ImageCache::RunEvictionOnStartup() {
base::Time last_eviction_time =
pref_service_->GetTime(kPrefLastStartupEviction);
// If we've already garbage collected in the past interval, bail out.
if (last_eviction_time >
clock_->Now() -
base::TimeDelta::FromHours(kImageCacheEvictionIntervalHours)) {
return;
}
// Update the time we did startup eviction so it can used for reporting.
pref_service_->SetTime(kPrefLastStartupEviction, clock_->Now());
RunEviction(kCacheMaxSize, base::BindOnce(&ImageCache::RunReconciliation,
weak_ptr_factory_.GetWeakPtr()));
}
void ImageCache::RunEvictionWhenFull() {
// Storage is within limits, bail out.
if (metadata_store_->GetEstimatedSize() < kCacheMaxSize) {
return;
}
// Report the time since the last LRU eviction. This allows us to gauge if the
// cache is properly sized.
base::Time last_eviction_time = pref_service_->GetTime(kPrefLastLRUEviction);
// Only report for non-null times.
if (last_eviction_time != base::Time()) {
ImageFetcherMetricsReporter::ReportTimeSinceLastCacheLRUEviction(
last_eviction_time);
}
// Update the time we did LRU eviction so it can used for reporting.
pref_service_->SetTime(kPrefLastLRUEviction, clock_->Now());
RunEviction(kCacheResizeWhenFull, base::DoNothing());
}
void ImageCache::RunEviction(size_t bytes_left,
base::OnceClosure on_completion) {
metadata_store_->EvictImageMetadata(
clock_->Now() - base::TimeDelta::FromDays(kCacheItemsTimeToLiveDays),
bytes_left,
base::BindOnce(&ImageCache::OnKeysEvicted, weak_ptr_factory_.GetWeakPtr(),
std::move(on_completion)));
}
void ImageCache::OnKeysEvicted(base::OnceClosure on_completion,
std::vector<std::string> keys) {
for (const std::string& key : keys) {
data_store_->DeleteImage(key);
}
std::move(on_completion).Run();
}
void ImageCache::RunReconciliation() {
metadata_store_->GetAllKeys(base::BindOnce(&ImageCache::ReconcileMetadataKeys,
weak_ptr_factory_.GetWeakPtr()));
}
void ImageCache::ReconcileMetadataKeys(std::vector<std::string> metadata_keys) {
data_store_->GetAllKeys(base::BindOnce(&ImageCache::ReconcileDataKeys,
weak_ptr_factory_.GetWeakPtr(),
std::move(metadata_keys)));
}
void ImageCache::ReconcileDataKeys(std::vector<std::string> metadata_keys,
std::vector<std::string> data_keys) {
std::sort(metadata_keys.begin(), metadata_keys.end());
std::sort(data_keys.begin(), data_keys.end());
std::vector<std::string> diff;
// Get the keys that should be removed from metadata.
std::set_difference(metadata_keys.begin(), metadata_keys.end(),
data_keys.begin(), data_keys.end(),
std::inserter(diff, diff.begin()));
for (const std::string& key : diff) {
metadata_store_->DeleteImageMetadata(key);
}
diff.clear();
// Get the keys that should be removed from data.
std::set_difference(data_keys.begin(), data_keys.end(), metadata_keys.begin(),
metadata_keys.end(), std::inserter(diff, diff.begin()));
for (const std::string& key : diff) {
data_store_->DeleteImage(key);
}
ImageFetcherMetricsReporter::ReportEvent(
ImageFetcherMetricsReporter::kCachedImageFetcherInternalUmaClientName,
ImageFetcherEvent::kCacheStartupEvictionFinished);
}
} // namespace image_fetcher