blob: 69a4ed234b2fd0203317fc637adfa5a06b9a7ee5 [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 "chrome/browser/web_applications/web_app_icon_manager.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/memory/ref_counted_memory.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/stringprintf.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/threading/scoped_blocking_call.h"
#include "chrome/browser/web_applications/components/web_app_utils.h"
#include "chrome/browser/web_applications/file_utils_wrapper.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/common/web_application_info.h"
#include "content/public/browser/browser_thread.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/png_codec.h"
namespace web_app {
namespace {
constexpr base::FilePath::CharType kTempDirectoryName[] =
FILE_PATH_LITERAL("Temp");
constexpr base::FilePath::CharType kIconsDirectoryName[] =
FILE_PATH_LITERAL("Icons");
base::FilePath GetAppDirectory(const base::FilePath& web_apps_directory,
const AppId& app_id) {
return web_apps_directory.AppendASCII(app_id);
}
base::FilePath GetTempDir(FileUtilsWrapper* utils,
const base::FilePath& web_apps_dir) {
// Create the temp directory as a sub-directory of the WebApps directory.
// This guarantees it is on the same file system as the WebApp's eventual
// install target.
base::FilePath temp_path = web_apps_dir.Append(kTempDirectoryName);
if (utils->PathExists(temp_path)) {
if (!utils->DirectoryExists(temp_path)) {
LOG(ERROR) << "Not a directory: " << temp_path.value();
return base::FilePath();
}
if (!utils->PathIsWritable(temp_path)) {
LOG(ERROR) << "Can't write to path: " << temp_path.value();
return base::FilePath();
}
// This is a directory we can write to.
return temp_path;
}
// Directory doesn't exist, so create it.
if (!utils->CreateDirectory(temp_path)) {
LOG(ERROR) << "Could not create directory: " << temp_path.value();
return base::FilePath();
}
return temp_path;
}
bool WriteIcon(FileUtilsWrapper* utils,
const base::FilePath& icons_dir,
const SkBitmap& bitmap) {
DCHECK_NE(bitmap.colorType(), kUnknown_SkColorType);
DCHECK_EQ(bitmap.width(), bitmap.height());
base::FilePath icon_file =
icons_dir.AppendASCII(base::StringPrintf("%i.png", bitmap.width()));
std::vector<unsigned char> image_data;
const bool discard_transparency = false;
if (!gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, discard_transparency,
&image_data)) {
LOG(ERROR) << "Could not encode icon data for file " << icon_file;
return false;
}
const char* image_data_ptr = reinterpret_cast<const char*>(&image_data[0]);
int size = base::checked_cast<int>(image_data.size());
if (utils->WriteFile(icon_file, image_data_ptr, size) != size) {
LOG(ERROR) << "Could not write icon file: " << icon_file;
return false;
}
return true;
}
bool WriteIcons(FileUtilsWrapper* utils,
const base::FilePath& app_dir,
const std::map<SquareSizePx, SkBitmap>& icon_bitmaps) {
const base::FilePath icons_dir = app_dir.Append(kIconsDirectoryName);
if (!utils->CreateDirectory(icons_dir)) {
LOG(ERROR) << "Could not create icons directory.";
return false;
}
for (const std::pair<SquareSizePx, SkBitmap>& icon_bitmap : icon_bitmaps) {
if (!WriteIcon(utils, icons_dir, icon_bitmap.second))
return false;
}
return true;
}
// Performs blocking I/O. May be called on another thread.
// Returns true if no errors occurred.
bool WriteDataBlocking(std::unique_ptr<FileUtilsWrapper> utils,
base::FilePath web_apps_directory,
AppId app_id,
std::map<SquareSizePx, SkBitmap> icons) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
const base::FilePath temp_dir = GetTempDir(utils.get(), web_apps_directory);
if (temp_dir.empty()) {
LOG(ERROR)
<< "Could not get path to WebApps temporary directory in profile.";
return false;
}
base::ScopedTempDir app_temp_dir;
if (!app_temp_dir.CreateUniqueTempDirUnderPath(temp_dir)) {
LOG(ERROR) << "Could not create temporary WebApp directory.";
return false;
}
if (!WriteIcons(utils.get(), app_temp_dir.GetPath(), icons))
return false;
// Commit: move whole app data dir to final destination in one mv operation.
const base::FilePath app_dir = GetAppDirectory(web_apps_directory, app_id);
if (!utils->Move(app_temp_dir.GetPath(), app_dir)) {
LOG(ERROR) << "Could not move temp WebApp directory to final destination.";
return false;
}
app_temp_dir.Take();
return true;
}
// Performs blocking I/O. May be called on another thread.
// Returns true if no errors occurred.
bool DeleteDataBlocking(std::unique_ptr<FileUtilsWrapper> utils,
base::FilePath web_apps_directory,
AppId app_id) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
const base::FilePath app_dir = GetAppDirectory(web_apps_directory, app_id);
return utils->DeleteFileRecursively(app_dir);
}
base::FilePath GetIconFileName(const base::FilePath& web_apps_directory,
const AppId& app_id,
int icon_size_px) {
const base::FilePath app_dir = GetAppDirectory(web_apps_directory, app_id);
const base::FilePath icons_dir = app_dir.Append(kIconsDirectoryName);
return icons_dir.AppendASCII(base::StringPrintf("%i.png", icon_size_px));
}
// Performs blocking I/O. May be called on another thread.
// Returns empty SkBitmap if any errors occurred.
SkBitmap ReadIconBlocking(std::unique_ptr<FileUtilsWrapper> utils,
base::FilePath web_apps_directory,
AppId app_id,
int icon_size_px) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
base::FilePath icon_file =
GetIconFileName(web_apps_directory, app_id, icon_size_px);
auto icon_data = base::MakeRefCounted<base::RefCountedString>();
if (!utils->ReadFileToString(icon_file, &icon_data->data())) {
LOG(ERROR) << "Could not read icon file: " << icon_file;
return SkBitmap();
}
SkBitmap bitmap;
if (!gfx::PNGCodec::Decode(icon_data->front(), icon_data->size(), &bitmap)) {
LOG(ERROR) << "Could not decode icon data for file " << icon_file;
return SkBitmap();
}
return bitmap;
}
// Performs blocking I/O. May be called on another thread.
// Returns empty vector if any errors occurred.
std::vector<uint8_t> ReadCompressedIconBlocking(
std::unique_ptr<FileUtilsWrapper> utils,
base::FilePath web_apps_directory,
AppId app_id,
int icon_size_px) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
base::FilePath icon_file =
GetIconFileName(web_apps_directory, app_id, icon_size_px);
std::string icon_data;
if (!utils->ReadFileToString(icon_file, &icon_data)) {
LOG(ERROR) << "Could not read icon file: " << icon_file;
return std::vector<uint8_t>{};
}
// Copy data: we can't std::move std::string into std::vector.
return std::vector<uint8_t>(icon_data.begin(), icon_data.end());
}
constexpr base::TaskTraits kTaskTraits = {
base::ThreadPool(), base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN};
} // namespace
WebAppIconManager::WebAppIconManager(Profile* profile,
WebAppRegistrar& registrar,
std::unique_ptr<FileUtilsWrapper> utils)
: registrar_(registrar), utils_(std::move(utils)) {
web_apps_directory_ = GetWebAppsDirectory(profile);
}
WebAppIconManager::~WebAppIconManager() = default;
void WebAppIconManager::WriteData(AppId app_id,
std::map<SquareSizePx, SkBitmap> icons,
WriteDataCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::PostTaskAndReplyWithResult(
FROM_HERE, kTaskTraits,
base::BindOnce(WriteDataBlocking, utils_->Clone(), web_apps_directory_,
std::move(app_id), std::move(icons)),
std::move(callback));
}
void WebAppIconManager::DeleteData(AppId app_id, WriteDataCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::PostTaskAndReplyWithResult(
FROM_HERE, kTaskTraits,
base::BindOnce(DeleteDataBlocking, utils_->Clone(), web_apps_directory_,
std::move(app_id)),
std::move(callback));
}
bool WebAppIconManager::HasIcon(const AppId& app_id,
int icon_size_in_px) const {
const WebApp* web_app = registrar_.GetAppById(app_id);
if (!web_app)
return false;
for (SquareSizePx size : web_app->downloaded_icon_sizes()) {
if (size == icon_size_in_px)
return true;
}
return false;
}
bool WebAppIconManager::HasSmallestIcon(const AppId& app_id,
int icon_size_in_px) const {
int best_size_in_px = 0;
return FindBestSizeInPx(app_id, icon_size_in_px, &best_size_in_px);
}
void WebAppIconManager::ReadIcon(const AppId& app_id,
int icon_size_in_px,
ReadIconCallback callback) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(HasIcon(app_id, icon_size_in_px));
ReadIconInternal(app_id, icon_size_in_px, std::move(callback));
}
void WebAppIconManager::ReadSmallestIcon(const AppId& app_id,
int icon_size_in_px,
ReadIconCallback callback) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
int best_size_in_px = 0;
bool has_smallest_icon =
FindBestSizeInPx(app_id, icon_size_in_px, &best_size_in_px);
DCHECK(has_smallest_icon);
ReadIconInternal(app_id, best_size_in_px, std::move(callback));
}
void WebAppIconManager::ReadSmallestCompressedIcon(
const AppId& app_id,
int icon_size_in_px,
ReadCompressedIconCallback callback) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
int best_size_in_px = 0;
bool has_smallest_icon =
FindBestSizeInPx(app_id, icon_size_in_px, &best_size_in_px);
DCHECK(has_smallest_icon);
base::PostTaskAndReplyWithResult(
FROM_HERE, kTaskTraits,
base::BindOnce(ReadCompressedIconBlocking, utils_->Clone(),
web_apps_directory_, app_id, best_size_in_px),
std::move(callback));
}
bool WebAppIconManager::FindBestSizeInPx(const AppId& app_id,
int icon_size_in_px,
int* best_size_in_px) const {
const WebApp* web_app = registrar_.GetAppById(app_id);
if (!web_app)
return false;
*best_size_in_px = std::numeric_limits<int>::max();
for (SquareSizePx size : web_app->downloaded_icon_sizes()) {
if (size >= icon_size_in_px && size < *best_size_in_px) {
*best_size_in_px = size;
}
}
return *best_size_in_px != std::numeric_limits<int>::max();
}
void WebAppIconManager::ReadIconInternal(const AppId& app_id,
int icon_size_in_px,
ReadIconCallback callback) const {
base::PostTaskAndReplyWithResult(
FROM_HERE, kTaskTraits,
base::BindOnce(ReadIconBlocking, utils_->Clone(), web_apps_directory_,
app_id, icon_size_in_px),
std::move(callback));
}
} // namespace web_app