blob: 3a2292dc3e7886a7207a42f0135f7f7161265a85 [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/feed/core/feed_image_manager.h"
#include "base/bind.h"
#include "components/feed/core/time_serialization.h"
#include "components/image_fetcher/core/image_decoder.h"
#include "components/image_fetcher/core/image_fetcher.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image.h"
namespace feed {
namespace {
const int kDefaultGarbageCollectionExpiredDays = 30;
const int kLongGarbageCollectionInterval = 12 * 60 * 60; // 12 hours
const int kShortGarbageCollectionInterval = 5 * 60; // 5 minutes
constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("feed_image_fetcher", R"(
semantics {
sender: "Feed Library Image Fetch"
description:
"Retrieves images for content suggestions, for display on the "
"New Tab page."
trigger:
"Triggered when the user looks at a content suggestion (and its "
"thumbnail isn't cached yet)."
data: "None."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting:
"This can be disabled from the New Tab Page by collapsing the "
"articles section."
chrome_policy {
NTPContentSuggestionsEnabled {
NTPContentSuggestionsEnabled: false
}
}
})");
} // namespace
FeedImageManager::FeedImageManager(
std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher,
std::unique_ptr<FeedImageDatabase> image_database)
: image_garbage_collected_day_(FromDatabaseTime(0)),
image_fetcher_(std::move(image_fetcher)),
image_database_(std::move(image_database)),
weak_ptr_factory_(this) {
DoGarbageCollectionIfNeeded();
}
FeedImageManager::~FeedImageManager() {
StopGarbageCollection();
}
void FeedImageManager::FetchImage(std::vector<std::string> urls,
ImageFetchedCallback callback) {
DCHECK(urls.size() > 0);
DCHECK(image_database_.get());
FetchImagesFromDatabase(0, std::move(urls), std::move(callback));
}
void FeedImageManager::FetchImagesFromDatabase(size_t url_index,
std::vector<std::string> urls,
ImageFetchedCallback callback) {
if (url_index >= urls.size()) {
// Already reached the last entry. Return an empty image.
std::move(callback).Run(gfx::Image());
return;
}
std::string image_id = urls[url_index];
image_database_->LoadImage(
image_id, base::BindOnce(&FeedImageManager::OnImageFetchedFromDatabase,
weak_ptr_factory_.GetWeakPtr(), url_index,
std::move(urls), std::move(callback)));
}
void FeedImageManager::OnImageFetchedFromDatabase(size_t url_index,
std::vector<std::string> urls,
ImageFetchedCallback callback,
std::string image_data) {
if (image_data.empty()) {
// Fetching from the DB failed; start a network fetch.
FetchImageFromNetwork(url_index, std::move(urls), std::move(callback));
return;
}
image_fetcher_->GetImageDecoder()->DecodeImage(
image_data, gfx::Size(),
base::BindRepeating(&FeedImageManager::OnImageDecodedFromDatabase,
weak_ptr_factory_.GetWeakPtr(), url_index,
std::move(urls), base::Passed(std::move(callback))));
}
void FeedImageManager::OnImageDecodedFromDatabase(size_t url_index,
std::vector<std::string> urls,
ImageFetchedCallback callback,
const gfx::Image& image) {
if (image.IsEmpty()) {
// If decoding the image failed, delete the DB entry.
image_database_->DeleteImage(urls[url_index]);
FetchImageFromNetwork(url_index, std::move(urls), std::move(callback));
return;
}
std::move(callback).Run(image);
}
void FeedImageManager::FetchImageFromNetwork(size_t url_index,
std::vector<std::string> urls,
ImageFetchedCallback callback) {
GURL url(urls[url_index]);
if (!url.is_valid()) {
// url is not valid, go to next URL.
FetchImagesFromDatabase(url_index + 1, std::move(urls),
std::move(callback));
return;
}
image_fetcher_->FetchImageData(
url.spec(), url,
base::BindOnce(&FeedImageManager::OnImageFetchedFromNetwork,
weak_ptr_factory_.GetWeakPtr(), url_index, std::move(urls),
std::move(callback)),
kTrafficAnnotation);
}
void FeedImageManager::OnImageFetchedFromNetwork(
size_t url_index,
std::vector<std::string> urls,
ImageFetchedCallback callback,
const std::string& image_data,
const image_fetcher::RequestMetadata& request_metadata) {
if (image_data.empty()) {
// Fetching image failed, let's move to the next url.
FetchImagesFromDatabase(url_index + 1, std::move(urls),
std::move(callback));
return;
}
image_fetcher_->GetImageDecoder()->DecodeImage(
image_data, gfx::Size(),
base::BindRepeating(&FeedImageManager::OnImageDecodedFromNetwork,
weak_ptr_factory_.GetWeakPtr(), url_index,
std::move(urls), base::Passed(std::move(callback)),
image_data));
}
void FeedImageManager::OnImageDecodedFromNetwork(size_t url_index,
std::vector<std::string> urls,
ImageFetchedCallback callback,
const std::string& image_data,
const gfx::Image& image) {
// Decoding urls[url_index] failed, let's move to the next url.
if (image.IsEmpty()) {
FetchImagesFromDatabase(url_index + 1, std::move(urls),
std::move(callback));
return;
}
image_database_->SaveImage(urls[url_index], image_data);
std::move(callback).Run(image);
}
void FeedImageManager::DoGarbageCollectionIfNeeded() {
// For saving resource purpose(ex. cpu, battery), We round up garbage
// collection age to day, so we only run GC once a day.
base::Time to_be_expired =
base::Time::Now().LocalMidnight() -
base::TimeDelta::FromDays(kDefaultGarbageCollectionExpiredDays);
if (image_garbage_collected_day_ != to_be_expired) {
image_database_->GarbageCollectImages(
to_be_expired,
base::BindOnce(&FeedImageManager::OnGarbageCollectionDone,
weak_ptr_factory_.GetWeakPtr(), to_be_expired));
}
}
void FeedImageManager::OnGarbageCollectionDone(base::Time garbage_collected_day,
bool success) {
base::TimeDelta gc_delay =
base::TimeDelta::FromSeconds(kShortGarbageCollectionInterval);
if (success) {
if (image_garbage_collected_day_ < garbage_collected_day)
image_garbage_collected_day_ = garbage_collected_day;
gc_delay = base::TimeDelta::FromSeconds(kLongGarbageCollectionInterval);
}
garbage_collection_timer_.Start(
FROM_HERE, gc_delay, this,
&FeedImageManager::DoGarbageCollectionIfNeeded);
}
void FeedImageManager::StopGarbageCollection() {
garbage_collection_timer_.Stop();
}
} // namespace feed