blob: ab5934b331c567e914a8976e43105868c44fcf1d [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_database.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/sequenced_task_runner.h"
#include "base/sys_info.h"
#include "base/task_scheduler/post_task.h"
#include "base/task_scheduler/task_traits.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "components/feed/core/proto/cached_image.pb.h"
#include "components/feed/core/time_serialization.h"
#include "components/leveldb_proto/proto_database_impl.h"
namespace feed {
namespace {
// Statistics are logged to UMA with this string as part of histogram name. They
// can all be found under LevelDB.*.FeedImageDatabase. Changing this needs to
// synchronize with histograms.xml, AND will also become incompatible with older
// browsers still reporting the previous values.
const char kImageDatabaseUMAClientName[] = "FeedImageDatabase";
const char kImageDatabaseFolder[] = "images";
const size_t kDatabaseWriteBufferSizeBytes = 512 * 1024;
const size_t kDatabaseWriteBufferSizeBytesForLowEndDevice = 128 * 1024;
} // namespace
FeedImageDatabase::FeedImageDatabase(const base::FilePath& database_dir)
: FeedImageDatabase(
database_dir,
std::make_unique<leveldb_proto::ProtoDatabaseImpl<CachedImageProto>>(
base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::BACKGROUND,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}))) {}
FeedImageDatabase::FeedImageDatabase(
const base::FilePath& database_dir,
std::unique_ptr<leveldb_proto::ProtoDatabase<CachedImageProto>>
image_database)
: database_status_(UNINITIALIZED),
image_database_(std::move(image_database)),
weak_ptr_factory_(this) {
leveldb_env::Options options = leveldb_proto::CreateSimpleOptions();
if (base::SysInfo::IsLowEndDevice()) {
options.write_buffer_size = kDatabaseWriteBufferSizeBytesForLowEndDevice;
} else {
options.write_buffer_size = kDatabaseWriteBufferSizeBytes;
}
base::FilePath image_dir = database_dir.AppendASCII(kImageDatabaseFolder);
image_database_->Init(
kImageDatabaseUMAClientName, image_dir, options,
base::BindOnce(&FeedImageDatabase::OnDatabaseInitialized,
weak_ptr_factory_.GetWeakPtr()));
}
FeedImageDatabase::~FeedImageDatabase() = default;
bool FeedImageDatabase::IsInitialized() {
return INITIALIZED == database_status_;
}
void FeedImageDatabase::SaveImage(const std::string& url,
const std::string& image_data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// If database is not ready, ignore the request.
if (!IsInitialized())
return;
CachedImageProto image_proto;
image_proto.set_url(url);
image_proto.set_data(image_data);
image_proto.set_last_used_time(ToDatabaseTime(base::Time::Now()));
SaveImageImpl(url, std::move(image_proto));
}
void FeedImageDatabase::LoadImage(const std::string& url,
FeedImageDatabaseCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
switch (database_status_) {
case INITIALIZED:
case INIT_FAILURE:
LoadImageImpl(url, std::move(callback));
break;
case UNINITIALIZED:
pending_image_callbacks_.emplace_back(url, std::move(callback));
break;
default:
NOTREACHED();
}
}
void FeedImageDatabase::DeleteImage(const std::string& url) {
DeleteImageImpl(url, base::BindOnce(&FeedImageDatabase::OnImageUpdated,
weak_ptr_factory_.GetWeakPtr()));
}
void FeedImageDatabase::GarbageCollectImages(
base::Time expired_time,
FeedImageDatabaseOperationCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// If database is not initialized yet, ignore the request.
if (!IsInitialized()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), false));
return;
}
image_database_->LoadEntries(base::BindOnce(
&FeedImageDatabase::GarbageCollectImagesImpl,
weak_ptr_factory_.GetWeakPtr(), expired_time, std::move(callback)));
}
void FeedImageDatabase::OnDatabaseInitialized(bool success) {
DCHECK_EQ(database_status_, UNINITIALIZED);
if (success) {
database_status_ = INITIALIZED;
} else {
database_status_ = INIT_FAILURE;
DVLOG(1) << "FeedImageDatabase init failed.";
}
ProcessPendingImageLoads();
}
void FeedImageDatabase::ProcessPendingImageLoads() {
DCHECK_NE(database_status_, UNINITIALIZED);
for (auto& image_callback : pending_image_callbacks_)
LoadImageImpl(image_callback.first, std::move(image_callback.second));
pending_image_callbacks_.clear();
}
void FeedImageDatabase::SaveImageImpl(const std::string& url,
CachedImageProto image_proto) {
auto entries_to_save = std::make_unique<ImageKeyEntryVector>();
entries_to_save->emplace_back(url, std::move(image_proto));
image_database_->UpdateEntries(
std::move(entries_to_save), std::make_unique<std::vector<std::string>>(),
base::BindOnce(&FeedImageDatabase::OnImageUpdated,
weak_ptr_factory_.GetWeakPtr()));
}
void FeedImageDatabase::OnImageLoaded(std::string url,
FeedImageDatabaseCallback callback,
bool success,
std::unique_ptr<CachedImageProto> entry) {
if (!success || !entry) {
DVLOG_IF(1, !success) << "FeedImageDatabase load failed.";
std::move(callback).Run(std::string());
return;
}
DCHECK_EQ(url, entry->url());
std::move(callback).Run(entry->data());
// Update timestamp for image.
entry->set_last_used_time(ToDatabaseTime(base::Time::Now()));
SaveImageImpl(url, std::move(*entry));
}
void FeedImageDatabase::LoadImageImpl(const std::string& url,
FeedImageDatabaseCallback callback) {
DCHECK_NE(database_status_, UNINITIALIZED);
if (IsInitialized()) {
image_database_->GetEntry(
url, base::BindOnce(&FeedImageDatabase::OnImageLoaded,
weak_ptr_factory_.GetWeakPtr(), url,
std::move(callback)));
} else {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::string()));
}
}
void FeedImageDatabase::OnImageUpdated(bool success) {
DVLOG_IF(1, !success) << "FeedImageDatabase update failed.";
}
void FeedImageDatabase::DeleteImageImpl(
const std::string& url,
FeedImageDatabaseOperationCallback callback) {
image_database_->UpdateEntries(
std::make_unique<ImageKeyEntryVector>(),
std::make_unique<std::vector<std::string>>(1, url), std::move(callback));
}
void FeedImageDatabase::GarbageCollectImagesImpl(
base::Time expired_time,
FeedImageDatabaseOperationCallback callback,
bool load_entries_success,
std::unique_ptr<std::vector<CachedImageProto>> image_entries) {
if (!load_entries_success) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&FeedImageDatabase::OnGarbageCollectionDone,
weak_ptr_factory_.GetWeakPtr(),
std::move(callback), false));
return;
}
int64_t expired_database_time = ToDatabaseTime(expired_time);
auto keys_to_remove = std::make_unique<std::vector<std::string>>();
for (const CachedImageProto& image : *image_entries) {
if (image.last_used_time() < expired_database_time) {
keys_to_remove->emplace_back(image.url());
}
}
if (keys_to_remove->empty()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), true));
return;
}
image_database_->UpdateEntries(
std::make_unique<ImageKeyEntryVector>(), std::move(keys_to_remove),
base::BindOnce(&FeedImageDatabase::OnGarbageCollectionDone,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void FeedImageDatabase::OnGarbageCollectionDone(
FeedImageDatabaseOperationCallback callback,
bool success) {
DVLOG_IF(1, !success) << "FeedImageDatabase garbage collection failed.";
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), success));
}
} // namespace feed