Commit 150df93f authored by Brandon Wylie's avatar Brandon Wylie Committed by Commit Bot

[IC] Cached image fetcher implementation

Implementing a cached_image_fetcher to interact with the existing
cache/network classes.

Bug: 882970
Change-Id: Ibf95b561a069b94785c5a10a66b39399af8acf68
Reviewed-on: https://chromium-review.googlesource.com/1239251Reviewed-by: default avatarDominic Battré <battre@chromium.org>
Reviewed-by: default avatarMathieu Perreault <mathp@chromium.org>
Reviewed-by: default avatarSky Malice <skym@chromium.org>
Reviewed-by: default avatarFilip Gorski <fgorski@chromium.org>
Commit-Queue: Brandon Wylie <wylieb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#594391}
parent 1ff55638
......@@ -4,6 +4,8 @@
static_library("core") {
sources = [
"cached_image_fetcher.cc",
"cached_image_fetcher.h",
"image_data_fetcher.cc",
"image_data_fetcher.h",
"image_decoder.h",
......@@ -50,6 +52,7 @@ static_library("test_support") {
source_set("unit_tests") {
testonly = true
sources = [
"cached_image_fetcher_unittest.cc",
"image_data_fetcher_unittest.cc",
"image_fetcher_impl_unittest.cc",
"request_metadata_unittest.cc",
......@@ -57,7 +60,11 @@ source_set("unit_tests") {
deps = [
":core",
":test_support",
"storage",
"storage:storage_unit_tests",
"storage/proto",
"//components/leveldb_proto:test_support",
"//components/prefs:test_support",
"//net",
"//net:test_support",
"//testing/gmock",
......
include_rules = [
"+components/leveldb_proto",
"+components/prefs",
]
// 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/cached_image_fetcher.h"
#include <utility>
#include "base/bind.h"
#include "components/image_fetcher/core/image_decoder.h"
#include "components/image_fetcher/core/request_metadata.h"
#include "components/image_fetcher/core/storage/image_cache.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image.h"
namespace image_fetcher {
namespace {
// Wrapper to check if callbacks can be called.
void CallbackIfPresent(ImageDataFetcherCallback data_callback,
ImageFetcherCallback image_callback,
const std::string& id,
const std::string& image_data,
const gfx::Image& image,
const image_fetcher::RequestMetadata& metadata) {
if (!data_callback.is_null())
std::move(data_callback).Run(image_data, metadata);
if (!image_callback.is_null())
std::move(image_callback).Run(id, image, metadata);
}
} // namespace
CachedImageFetcher::CachedImageFetcher(
std::unique_ptr<ImageFetcher> image_fetcher,
std::unique_ptr<ImageCache> image_cache)
: image_fetcher_(std::move(image_fetcher)),
image_cache_(std::move(image_cache)),
weak_ptr_factory_(this) {
DCHECK(image_fetcher_);
DCHECK(image_cache_);
}
CachedImageFetcher::~CachedImageFetcher() = default;
void CachedImageFetcher::SetDataUseServiceName(
DataUseServiceName data_use_service_name) {
image_fetcher_->SetDataUseServiceName(data_use_service_name);
}
void CachedImageFetcher::SetDesiredImageFrameSize(const gfx::Size& size) {
image_fetcher_->SetDesiredImageFrameSize(size);
}
void CachedImageFetcher::SetImageDownloadLimit(
base::Optional<int64_t> max_download_bytes) {
image_fetcher_->SetImageDownloadLimit(max_download_bytes);
}
ImageDecoder* CachedImageFetcher::GetImageDecoder() {
return image_fetcher_->GetImageDecoder();
}
void CachedImageFetcher::FetchImageAndData(
const std::string& id,
const GURL& image_url,
ImageDataFetcherCallback data_callback,
ImageFetcherCallback image_callback,
const net::NetworkTrafficAnnotationTag& traffic_annotation) {
// First, try to load the image from the cache, then try the network.
image_cache_->LoadImage(
image_url.spec(),
base::BindOnce(&CachedImageFetcher::OnImageFetchedFromCache,
weak_ptr_factory_.GetWeakPtr(), id, image_url,
std::move(data_callback), std::move(image_callback),
traffic_annotation));
}
void CachedImageFetcher::OnImageFetchedFromCache(
const std::string& id,
const GURL& image_url,
ImageDataFetcherCallback data_callback,
ImageFetcherCallback image_callback,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
std::string image_data) {
if (image_data.empty()) {
// Fetching from the DB failed, start a network fetch.
FetchImageFromNetwork(id, image_url, std::move(data_callback),
std::move(image_callback), traffic_annotation);
} else {
GetImageDecoder()->DecodeImage(
image_data, gfx::Size(),
base::BindRepeating(&CachedImageFetcher::OnImageDecodedFromCache,
weak_ptr_factory_.GetWeakPtr(), id, image_url,
base::Passed(std::move(data_callback)),
base::Passed(std::move(image_callback)),
traffic_annotation, image_data));
}
}
void CachedImageFetcher::OnImageDecodedFromCache(
const std::string& id,
const GURL& image_url,
ImageDataFetcherCallback data_callback,
ImageFetcherCallback image_callback,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
const std::string& image_data,
const gfx::Image& image) {
if (image.IsEmpty()) {
FetchImageFromNetwork(id, image_url, std::move(data_callback),
std::move(image_callback), traffic_annotation);
image_cache_->DeleteImage(image_url.spec());
} else {
CallbackIfPresent(std::move(data_callback), std::move(image_callback), id,
image_data, image, RequestMetadata());
}
}
void CachedImageFetcher::FetchImageFromNetwork(
const std::string& id,
const GURL& image_url,
ImageDataFetcherCallback data_callback,
ImageFetcherCallback image_callback,
const net::NetworkTrafficAnnotationTag& traffic_annotation) {
if (!image_url.is_valid()) {
// URL is invalid, return empty image/data.
CallbackIfPresent(std::move(data_callback), std::move(image_callback), id,
std::string(), gfx::Image(), RequestMetadata());
return;
}
image_fetcher_->FetchImageData(
id, image_url,
base::BindOnce(&CachedImageFetcher::OnImageFetchedFromNetwork,
weak_ptr_factory_.GetWeakPtr(), id, image_url,
std::move(data_callback), std::move(image_callback)),
traffic_annotation);
}
void CachedImageFetcher::OnImageFetchedFromNetwork(
const std::string& id,
const GURL& image_url,
ImageDataFetcherCallback data_callback,
ImageFetcherCallback image_callback,
const std::string& image_data,
const RequestMetadata& request_metadata) {
if (image_data.empty()) {
// Fetching image failed, return empty image/data.
CallbackIfPresent(std::move(data_callback), std::move(image_callback), id,
image_data, gfx::Image(), request_metadata);
return;
}
image_fetcher_->GetImageDecoder()->DecodeImage(
image_data, gfx::Size(),
base::BindRepeating(&CachedImageFetcher::OnImageDecodedFromNetwork,
weak_ptr_factory_.GetWeakPtr(), id, image_url,
base::Passed(std::move(data_callback)),
base::Passed(std::move(image_callback)), image_data,
request_metadata));
}
void CachedImageFetcher::OnImageDecodedFromNetwork(
const std::string& id,
const GURL& image_url,
ImageDataFetcherCallback data_callback,
ImageFetcherCallback image_callback,
const std::string& image_data,
const RequestMetadata& request_metadata,
const gfx::Image& image) {
CallbackIfPresent(std::move(data_callback), std::move(image_callback), id,
image_data, image, request_metadata);
if (!image.IsEmpty()) {
image_cache_->SaveImage(image_url.spec(), image_data);
}
}
} // namespace image_fetcher
// 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.
#ifndef COMPONENTS_IMAGE_FETCHER_CORE_CACHED_IMAGE_FETCHER_H_
#define COMPONENTS_IMAGE_FETCHER_CORE_CACHED_IMAGE_FETCHER_H_
#include <memory>
#include <string>
#include <vector>
#include "base/memory/weak_ptr.h"
#include "base/timer/timer.h"
#include "components/image_fetcher/core/image_decoder.h"
#include "components/image_fetcher/core/image_fetcher.h"
#include "components/image_fetcher/core/image_fetcher_types.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "ui/gfx/geometry/size.h"
#include "url/gurl.h"
namespace gfx {
class Image;
} // namespace gfx
namespace image_fetcher {
class ImageCache;
class ImageFetcher;
struct RequestMetadata;
// TODO(wylieb): Transcode the image once it's downloaded.
// TODO(wylieb): Consider creating a struct to encapsulate the request.
// CachedImageFetcher takes care of fetching images from the network and caching
// them.
class CachedImageFetcher : public ImageFetcher {
public:
CachedImageFetcher(std::unique_ptr<ImageFetcher> image_fetcher,
std::unique_ptr<ImageCache> image_cache);
~CachedImageFetcher() override;
// ImageFetcher:
void SetDataUseServiceName(DataUseServiceName data_use_service_name) override;
void SetDesiredImageFrameSize(const gfx::Size& size) override;
void SetImageDownloadLimit(
base::Optional<int64_t> max_download_bytes) override;
void FetchImageAndData(
const std::string& id,
const GURL& image_url,
ImageDataFetcherCallback image_data_callback,
ImageFetcherCallback image_callback,
const net::NetworkTrafficAnnotationTag& traffic_annotation) override;
ImageDecoder* GetImageDecoder() override;
private:
// Cache
void OnImageFetchedFromCache(
const std::string& id,
const GURL& image_url,
ImageDataFetcherCallback image_data_callback,
ImageFetcherCallback image_callback,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
std::string image_data);
void OnImageDecodedFromCache(
const std::string& id,
const GURL& image_url,
ImageDataFetcherCallback image_data_callback,
ImageFetcherCallback image_callback,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
const std::string& image_data,
const gfx::Image& image);
// Network
void FetchImageFromNetwork(
const std::string& id,
const GURL& image_url,
ImageDataFetcherCallback image_data_callback,
ImageFetcherCallback image_callback,
const net::NetworkTrafficAnnotationTag& traffic_annotation);
void OnImageFetchedFromNetwork(const std::string& id,
const GURL& image_url,
ImageDataFetcherCallback image_data_callback,
ImageFetcherCallback image_callback,
const std::string& image_data,
const RequestMetadata& request_metadata);
void OnImageDecodedFromNetwork(const std::string& id,
const GURL& image_url,
ImageDataFetcherCallback image_data_callback,
ImageFetcherCallback image_callback,
const std::string& image_data,
const RequestMetadata& request_metadata,
const gfx::Image& image);
std::unique_ptr<ImageFetcher> image_fetcher_;
std::unique_ptr<ImageCache> image_cache_;
base::WeakPtrFactory<CachedImageFetcher> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(CachedImageFetcher);
};
} // namespace image_fetcher
#endif // COMPONENTS_IMAGE_FETCHER_CORE_CACHED_IMAGE_FETCHER_H_
// 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/cached_image_fetcher.h"
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/files/scoped_temp_dir.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_task_environment.h"
#include "base/test/simple_test_clock.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "components/image_fetcher/core/fake_image_decoder.h"
#include "components/image_fetcher/core/image_fetcher_impl.h"
#include "components/image_fetcher/core/image_fetcher_types.h"
#include "components/image_fetcher/core/storage/image_cache.h"
#include "components/image_fetcher/core/storage/image_data_store_disk.h"
#include "components/image_fetcher/core/storage/image_metadata_store_leveldb.h"
#include "components/image_fetcher/core/storage/proto/cached_image_metadata.pb.h"
#include "components/leveldb_proto/testing/fake_db.h"
#include "components/prefs/testing_pref_service.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_unittest_util.h"
using testing::_;
using leveldb_proto::test::FakeDB;
namespace image_fetcher {
class FakeImageDecoder;
namespace {
const GURL kImageUrl = GURL("http://gstatic.img.com/foo.jpg");
constexpr char kImageData[] = "data";
} // namespace
class ComponentizedCachedImageFetcherTest : public testing::Test {
public:
ComponentizedCachedImageFetcherTest() {}
~ComponentizedCachedImageFetcherTest() override {
cached_image_fetcher_.reset();
// We need to run until idle after deleting the database, because
// ProtoDatabaseImpl deletes the actual LevelDB asynchronously.
RunUntilIdle();
}
void SetUp() override {
ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
auto db =
std::make_unique<FakeDB<CachedImageMetadataProto>>(&metadata_store_);
db_ = db.get();
auto metadata_store = std::make_unique<ImageMetadataStoreLevelDB>(
base::FilePath(), std::move(db), &clock_);
auto data_store = std::make_unique<ImageDataStoreDisk>(
data_dir_.GetPath(), base::SequencedTaskRunnerHandle::Get());
ImageCache::RegisterProfilePrefs(test_prefs_.registry());
auto image_cache = std::make_unique<ImageCache>(
std::move(data_store), std::move(metadata_store), &test_prefs_, &clock_,
base::SequencedTaskRunnerHandle::Get());
image_cache_ = image_cache.get();
image_cache_->SaveImage(kImageUrl.spec(), kImageData);
RunUntilIdle();
db_->InitCallback(true);
image_cache_->DeleteImage(kImageUrl.spec());
RunUntilIdle();
shared_factory_ =
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory_);
auto decoder = std::make_unique<FakeImageDecoder>();
fake_image_decoder_ = decoder.get();
cached_image_fetcher_ = std::make_unique<CachedImageFetcher>(
std::make_unique<image_fetcher::ImageFetcherImpl>(std::move(decoder),
shared_factory_),
std::move(image_cache));
RunUntilIdle();
}
void RunUntilIdle() { scoped_task_environment_.RunUntilIdle(); }
CachedImageFetcher* cached_image_fetcher() {
return cached_image_fetcher_.get();
}
ImageCache* image_cache() { return image_cache_; }
FakeImageDecoder* image_decoder() { return fake_image_decoder_; }
network::TestURLLoaderFactory* test_url_loader_factory() {
return &test_url_loader_factory_;
}
MOCK_METHOD1(OnImageLoaded, void(std::string));
private:
std::unique_ptr<CachedImageFetcher> cached_image_fetcher_;
network::TestURLLoaderFactory test_url_loader_factory_;
scoped_refptr<network::SharedURLLoaderFactory> shared_factory_;
FakeImageDecoder* fake_image_decoder_;
ImageCache* image_cache_;
base::SimpleTestClock clock_;
TestingPrefServiceSimple test_prefs_;
base::ScopedTempDir data_dir_;
FakeDB<CachedImageMetadataProto>* db_;
std::map<std::string, CachedImageMetadataProto> metadata_store_;
base::test::ScopedTaskEnvironment scoped_task_environment_;
DISALLOW_COPY_AND_ASSIGN(ComponentizedCachedImageFetcherTest);
};
MATCHER(EmptyImage, "") {
return arg.IsEmpty();
}
MATCHER(NonEmptyImage, "") {
return !arg.IsEmpty();
}
// TODO(wylieb): Rename these tests CachedImageFetcherTest* when ntp_snippets/-
// remote/cached_image_fetcher has been migrated.
TEST_F(ComponentizedCachedImageFetcherTest, FetchEmptyUrl) {
base::MockCallback<ImageDataFetcherCallback> data_callback;
base::MockCallback<ImageFetcherCallback> image_callback;
GURL empty_url = GURL(std::string());
// Make sure an empty image passed to callback.
EXPECT_CALL(data_callback, Run(std::string(), _));
EXPECT_CALL(image_callback, Run(std::string(), EmptyImage(), _));
cached_image_fetcher()->FetchImageAndData(
empty_url.spec(), empty_url, data_callback.Get(), image_callback.Get(),
TRAFFIC_ANNOTATION_FOR_TESTS);
RunUntilIdle();
}
TEST_F(ComponentizedCachedImageFetcherTest, FetchImageFromCache) {
// Save the image in the database.
image_cache()->SaveImage(kImageUrl.spec(), kImageData);
RunUntilIdle();
base::MockCallback<ImageDataFetcherCallback> data_callback;
base::MockCallback<ImageFetcherCallback> image_callback;
EXPECT_CALL(data_callback, Run(kImageData, _));
EXPECT_CALL(image_callback, Run(kImageUrl.spec(), NonEmptyImage(), _));
cached_image_fetcher()->FetchImageAndData(
kImageUrl.spec(), kImageUrl, data_callback.Get(), image_callback.Get(),
TRAFFIC_ANNOTATION_FOR_TESTS);
RunUntilIdle();
}
TEST_F(ComponentizedCachedImageFetcherTest, FetchImagePopulatesCache) {
// Expect the image to be fetched by URL.
{
test_url_loader_factory()->AddResponse(kImageUrl.spec(), kImageData);
base::MockCallback<ImageDataFetcherCallback> data_callback;
base::MockCallback<ImageFetcherCallback> image_callback;
EXPECT_CALL(data_callback, Run(kImageData, _));
EXPECT_CALL(image_callback, Run(kImageUrl.spec(), NonEmptyImage(), _));
cached_image_fetcher()->FetchImageAndData(
kImageUrl.spec(), kImageUrl, data_callback.Get(), image_callback.Get(),
TRAFFIC_ANNOTATION_FOR_TESTS);
RunUntilIdle();
}
// Make sure the image data is in the database.
{
EXPECT_CALL(*this, OnImageLoaded(kImageData));
image_cache()->LoadImage(
kImageUrl.spec(),
base::BindOnce(&ComponentizedCachedImageFetcherTest::OnImageLoaded,
base::Unretained(this)));
RunUntilIdle();
}
// Fetch again. The cache should be populated, no network request is needed.
{
test_url_loader_factory()->ClearResponses();
base::MockCallback<ImageDataFetcherCallback> data_callback;
base::MockCallback<ImageFetcherCallback> image_callback;
EXPECT_CALL(data_callback, Run(kImageData, _));
EXPECT_CALL(image_callback, Run(kImageUrl.spec(), NonEmptyImage(), _));
cached_image_fetcher()->FetchImageAndData(
kImageUrl.spec(), kImageUrl, data_callback.Get(), image_callback.Get(),
TRAFFIC_ANNOTATION_FOR_TESTS);
RunUntilIdle();
}
}
TEST_F(ComponentizedCachedImageFetcherTest, DecodingErrorWillDeleteCache) {
// Save the image in the database.
image_cache()->SaveImage(kImageUrl.spec(), kImageData);
RunUntilIdle();
{
// Set decoding always error.
image_decoder()->SetDecodingValid(false);
base::MockCallback<ImageFetcherCallback> image_callback;
cached_image_fetcher()->FetchImage(kImageUrl.spec(), kImageUrl,
image_callback.Get(),
TRAFFIC_ANNOTATION_FOR_TESTS);
RunUntilIdle();
}
// Make sure the image data was deleted from database.
{
EXPECT_CALL(*this, OnImageLoaded(std::string()));
image_cache()->LoadImage(
kImageUrl.spec(),
base::BindOnce(&ComponentizedCachedImageFetcherTest::OnImageLoaded,
base::Unretained(this)));
RunUntilIdle();
}
}
} // namespace image_fetcher
......@@ -11,7 +11,7 @@
namespace image_fetcher {
FakeImageDecoder::FakeImageDecoder() : enabled_(true) {}
FakeImageDecoder::FakeImageDecoder() : enabled_(true), valid_(true) {}
FakeImageDecoder::~FakeImageDecoder() = default;
void FakeImageDecoder::DecodeImage(
......@@ -20,7 +20,7 @@ void FakeImageDecoder::DecodeImage(
const image_fetcher::ImageDecodedCallback& callback) {
ASSERT_TRUE(enabled_);
gfx::Image image;
if (!image_data.empty()) {
if (valid_ && !image_data.empty()) {
image = gfx::test::CreateImage(2, 3);
}
......@@ -37,9 +37,13 @@ void FakeImageDecoder::SetEnabled(bool enabled) {
enabled_ = enabled;
}
void FakeImageDecoder::SetDecodingValid(bool valid) {
valid_ = valid;
}
void FakeImageDecoder::SetBeforeImageDecoded(
const base::RepeatingClosure& callback) {
before_image_decoded_ = callback;
}
} // namespace image_fetcher
\ No newline at end of file
} // namespace image_fetcher
......@@ -5,6 +5,8 @@
#ifndef COMPONENTS_IMAGE_FETCHER_CORE_FAKE_IMAGE_DECODER_H_
#define COMPONENTS_IMAGE_FETCHER_CORE_FAKE_IMAGE_DECODER_H_
#include <string>
#include "base/bind.h"
#include "components/image_fetcher/core/image_decoder.h"
#include "components/image_fetcher/core/image_fetcher_types.h"
......@@ -26,9 +28,11 @@ class FakeImageDecoder : public image_fetcher::ImageDecoder {
const image_fetcher::ImageDecodedCallback& callback) override;
void SetEnabled(bool enabled);
void SetBeforeImageDecoded(const base::RepeatingClosure& callback);
void SetDecodingValid(bool valid);
private:
bool enabled_;
bool valid_;
base::RepeatingClosure before_image_decoded_;
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment