blob: 206ca4fda4e2444f3e00d4a97e064869113e5a91 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/ambient/managed/screensaver_image_downloader.h"
#include "base/files/file_util.h"
#include "base/hash/sha1.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/thread_pool.h"
#include "net/http/http_request_headers.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
namespace ash {
namespace {
constexpr net::NetworkTrafficAnnotationTag
kScreensaverImageDownloaderNetworkTag =
net::DefineNetworkTrafficAnnotation("screensaver_image_downloader",
R"(
semantics {
sender: "Managed Screensaver"
description:
"Fetch external image files that will be cached and displayed "
"in the policy-controlled screensaver."
trigger:
"An update to the ScreensaverLockScreenImages policy that includes "
"new references to external image files."
data:
"This request does not send any data from the device. It fetches"
"images from URLs provided by the policy."
destination: OTHER
user_data {
type: NONE
}
internal {
contacts {
email: "mpetrisor@google.com"
}
contacts {
email: "eariassoto@google.com"
}
}
last_reviewed: "2023-03-30"
}
policy {
cookies_allowed: NO
setting:
"This feature is controlled by enterprise policies, and cannot"
"be overridden by users. It is disabled by default."
chrome_policy {
ScreensaverLockScreenImages {
ScreensaverLockScreenImages {
entries: ""
}
}
}
})");
constexpr char kCacheFileExt[] = ".cache";
constexpr int64_t kMaxFileSizeInBytes = 8 * 1024 * 1024; // 8 MB
constexpr int kMaxUrlFetchRetries = 3;
std::unique_ptr<network::SimpleURLLoader> CreateSimpleURLLoader(
const std::string& url) {
auto request = std::make_unique<network::ResourceRequest>();
request->url = GURL(url);
request->method = net::HttpRequestHeaders::kGetMethod;
request->credentials_mode = network::mojom::CredentialsMode::kOmit;
CHECK(request->url.SchemeIs(url::kHttpsScheme));
auto loader = network::SimpleURLLoader::Create(
std::move(request), kScreensaverImageDownloaderNetworkTag);
const int retry_mode = network::SimpleURLLoader::RETRY_ON_5XX |
network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE;
loader->SetRetryOptions(kMaxUrlFetchRetries, retry_mode);
return loader;
}
// Helper function to extract response code from `SimpleURLLoader`.
int GetResponseCode(network::SimpleURLLoader* simple_loader) {
if (!simple_loader->ResponseInfo() ||
!simple_loader->ResponseInfo()->headers) {
return -1;
}
return simple_loader->ResponseInfo()->headers->response_code();
}
bool VerifyOrCreateDownloadDirectory(const base::FilePath& download_directory) {
if (!base::DirectoryExists(download_directory) &&
!base::CreateDirectory(download_directory)) {
LOG(ERROR) << "Cannot create download directory";
// TODO(b/276208772): Track result with metrics
return false;
}
if (!base::PathIsWritable(download_directory)) {
LOG(ERROR) << "Cannot write to download directory";
// TODO(b/276208772): Track result with metrics
return false;
}
return true;
}
} // namespace
ScreensaverImageDownloader::Job::Job(const std::string& image_url)
: image_url(image_url) {}
ScreensaverImageDownloader::Job::~Job() = default;
std::string ScreensaverImageDownloader::Job::file_name() const {
const std::string hash = base::SHA1HashString(image_url);
const std::string encoded_hash = base::HexEncode(hash.data(), hash.size());
return encoded_hash + kCacheFileExt;
}
ScreensaverImageDownloader::ScreensaverImageDownloader(
scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory,
const base::FilePath& download_directory,
ImageListUpdatedCallback image_list_updated_callback)
: task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})),
shared_url_loader_factory_(shared_url_loader_factory),
download_directory_(download_directory),
image_list_updated_callback_(image_list_updated_callback) {}
ScreensaverImageDownloader::~ScreensaverImageDownloader() = default;
void ScreensaverImageDownloader::QueueDownloadJob(
std::unique_ptr<Job> download_job) {
CHECK(download_job);
// TODO(b/276208772): Track queue usage with metrics
if (queue_state_ == QueueState::kWaiting) {
CHECK(downloading_queue_.empty());
StartDownloadJob(std::move(download_job));
} else {
downloading_queue_.emplace(std::move(download_job));
}
}
void ScreensaverImageDownloader::ClearRequestQueue() {
base::queue<std::unique_ptr<Job>> buffer_queue;
buffer_queue.swap(downloading_queue_);
queue_state_ = QueueState::kWaiting;
while (!buffer_queue.empty()) {
FinishDownloadJob(std::move(buffer_queue.front()),
ScreensaverImageDownloadResult::kCancelled,
absl::nullopt);
buffer_queue.pop();
}
}
void ScreensaverImageDownloader::DeleteDownloadedImages() {
// TODO(b/278548884): Do not ignore callback result and track its result.
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(base::IgnoreResult(&base::DeletePathRecursively),
download_directory_));
}
std::vector<base::FilePath> ScreensaverImageDownloader::GetScreensaverImages() {
return std::vector<base::FilePath>(downloaded_images_.begin(),
downloaded_images_.end());
}
void ScreensaverImageDownloader::SetImagesForTesting(
const std::vector<base::FilePath>& images_file_paths) {
downloaded_images_ = base::flat_set<base::FilePath>(images_file_paths);
}
base::FilePath ScreensaverImageDownloader::GetDowloadDirForTesting() {
return download_directory_;
}
void ScreensaverImageDownloader::StartDownloadJob(
std::unique_ptr<Job> download_job) {
queue_state_ = QueueState::kDownloading;
task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&VerifyOrCreateDownloadDirectory, download_directory_),
base::BindOnce(
&ScreensaverImageDownloader::OnVerifyDownloadDirectoryCompleted,
weak_ptr_factory_.GetWeakPtr(), std::move(download_job)));
}
void ScreensaverImageDownloader::OnVerifyDownloadDirectoryCompleted(
std::unique_ptr<Job> download_job,
bool can_download_file) {
if (!can_download_file) {
FinishDownloadJob(std::move(download_job),
ScreensaverImageDownloadResult::kFileSystemWriteError,
absl::nullopt);
return;
}
// The download folder exists, check if the file is already in cache before
// attempting to download it.
const base::FilePath file_path =
download_directory_.AppendASCII(download_job->file_name());
task_runner_->PostTaskAndReplyWithResult(
FROM_HERE, base::BindOnce(&base::PathExists, file_path),
base::BindOnce(&ScreensaverImageDownloader::OnCheckIsFileIsInCache,
weak_ptr_factory_.GetWeakPtr(), file_path,
std::move(download_job)));
}
void ScreensaverImageDownloader::OnCheckIsFileIsInCache(
const base::FilePath& file_path,
std::unique_ptr<Job> download_job,
bool is_file_present) {
if (is_file_present) {
FinishDownloadJob(std::move(download_job),
ScreensaverImageDownloadResult::kSuccess, file_path);
return;
}
CHECK(shared_url_loader_factory_);
std::unique_ptr<network::SimpleURLLoader> simple_loader =
CreateSimpleURLLoader(download_job->image_url);
auto* loader = simple_loader.get();
// Download to temp file first to guarantee entire image is written without
// errors before attempting to read it.
loader->DownloadToTempFile(
shared_url_loader_factory_.get(),
base::BindOnce(&ScreensaverImageDownloader::OnUrlDownloadedToTempFile,
weak_ptr_factory_.GetWeakPtr(), std::move(simple_loader),
std::move(download_job)),
kMaxFileSizeInBytes);
}
void ScreensaverImageDownloader::OnUrlDownloadedToTempFile(
std::unique_ptr<network::SimpleURLLoader> simple_loader,
std::unique_ptr<Job> download_job,
base::FilePath temp_path) {
const base::FilePath desired_path =
download_directory_.AppendASCII(download_job->file_name());
if (simple_loader->NetError() != net::OK || temp_path.empty()) {
LOG(ERROR) << "Downloading to file failed with error code: "
<< GetResponseCode(simple_loader.get()) << " with network error "
<< simple_loader->NetError();
if (!temp_path.empty()) {
// Clean up temporary file.
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(base::IgnoreResult(&base::DeleteFile), temp_path));
}
FinishDownloadJob(std::move(download_job),
ScreensaverImageDownloadResult::kNetworkError,
absl::nullopt);
return;
}
// Swap the temporary file to the desired path, and then run the callback.
task_runner_->PostTaskAndReplyWithResult(
FROM_HERE, base::BindOnce(&base::Move, temp_path, desired_path),
base::BindOnce(&ScreensaverImageDownloader::OnUrlDownloadToFileComplete,
weak_ptr_factory_.GetWeakPtr(), desired_path,
std::move(download_job)));
}
void ScreensaverImageDownloader::OnUrlDownloadToFileComplete(
const base::FilePath& path,
std::unique_ptr<Job> download_job,
bool file_is_present) {
if (!file_is_present) {
DLOG(WARNING) << "Could not save the downloaded file to " << path;
FinishDownloadJob(std::move(download_job),
ScreensaverImageDownloadResult::kFileSaveError,
absl::nullopt);
return;
}
FinishDownloadJob(std::move(download_job),
ScreensaverImageDownloadResult::kSuccess, path);
}
void ScreensaverImageDownloader::FinishDownloadJob(
std::unique_ptr<Job> download_job,
ScreensaverImageDownloadResult result,
absl::optional<base::FilePath> path) {
// TODO(b/276208772): Track result with metrics
if (result == ScreensaverImageDownloadResult::kSuccess) {
downloaded_images_.insert(*path);
image_list_updated_callback_.Run(std::vector<base::FilePath>(
downloaded_images_.begin(), downloaded_images_.end()));
}
if (downloading_queue_.empty()) {
queue_state_ = QueueState::kWaiting;
} else {
StartDownloadJob(std::move(downloading_queue_.front()));
downloading_queue_.pop();
}
}
} // namespace ash