blob: 6467baed21aca876e5f6fb85c786e05292829b64 [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/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 "chrome/browser/web_applications/file_utils_wrapper.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_utils.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 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 WebApplicationInfo::IconInfo& icon_info) {
base::FilePath icon_file =
icons_dir.AppendASCII(base::StringPrintf("%i.png", icon_info.width));
std::vector<unsigned char> image_data;
const bool discard_transparency = false;
if (!gfx::PNGCodec::EncodeBGRASkBitmap(icon_info.data, 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 WebApplicationInfo& web_app_info) {
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 WebApplicationInfo::IconInfo& icon_info : web_app_info.icons) {
// Skip unfetched bitmaps.
if (icon_info.data.colorType() == kUnknown_SkColorType)
continue;
if (!WriteIcon(utils, icons_dir, icon_info))
return false;
}
return true;
}
// Performs blocking I/O. May be called on another thread.
// Returns true if no errors occured.
bool WriteDataBlocking(std::unique_ptr<FileUtilsWrapper> utils,
base::FilePath web_apps_directory,
AppId app_id,
std::unique_ptr<WebApplicationInfo> web_app_info) {
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(), *web_app_info))
return false;
// Commit: move whole app data dir to final destination in one mv operation.
const base::FilePath app_id_dir = web_apps_directory.AppendASCII(app_id);
if (!utils->Move(app_temp_dir.GetPath(), app_id_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 empty SkBitmap if any errors occured.
SkBitmap ReadIconBlocking(std::unique_ptr<FileUtilsWrapper> utils,
base::FilePath web_apps_directory,
AppId app_id,
int icon_size_px) {
const base::FilePath app_dir = web_apps_directory.AppendASCII(app_id);
const base::FilePath icons_dir = app_dir.Append(kIconsDirectoryName);
base::FilePath icon_file =
icons_dir.AppendASCII(base::StringPrintf("%i.png", 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;
}
constexpr base::TaskTraits kTaskTraits = {
base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN};
} // namespace
WebAppIconManager::WebAppIconManager(Profile* profile,
std::unique_ptr<FileUtilsWrapper> utils)
: utils_(std::move(utils)) {
web_apps_directory_ = GetWebAppsDirectory(profile);
}
WebAppIconManager::~WebAppIconManager() = default;
void WebAppIconManager::WriteData(
AppId app_id,
std::unique_ptr<WebApplicationInfo> web_app_info,
WriteDataCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, kTaskTraits,
base::BindOnce(WriteDataBlocking, utils_->Clone(), web_apps_directory_,
std::move(app_id), std::move(web_app_info)),
std::move(callback));
}
bool WebAppIconManager::ReadIcon(const WebApp& web_app,
int icon_size_in_px,
ReadIconCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
for (const WebApp::IconInfo& icon_info : web_app.icons()) {
if (icon_info.size_in_px == icon_size_in_px) {
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, kTaskTraits,
base::BindOnce(ReadIconBlocking, utils_->Clone(), web_apps_directory_,
web_app.app_id(), icon_size_in_px),
std::move(callback));
return true;
}
}
return false;
}
} // namespace web_app