#include "chrome/common/notifications/notification_image_retainer.h"
#include <algorithm>
#include <set>
#include "base/bind.h"
#include "base/containers/contains.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/memory/ref_counted_memory.h"
#include "base/numerics/safe_conversions.h"
#include "base/path_service.h"
#include "base/task/post_task.h"
#include "base/task/thread_pool.h"
#include "base/time/default_tick_clock.h"
#include "chrome/common/chrome_paths.h"
#include "ui/gfx/image/image.h"
namespace {
constexpr base::FilePath::CharType kImageRoot[] =
FILE_PATH_LITERAL("Notification Resources");
constexpr base::TimeDelta kDeletionDelay = base::Seconds(12);
base::FilePath DetermineImageDirectory() {
base::FilePath data_dir;
bool success = base::PathService::Get(chrome::DIR_USER_DATA, &data_dir);
DCHECK(success);
return data_dir.Append(kImageRoot);
}
std::vector<base::FilePath> GetFilesFromPrevSessions(
const base::FilePath& dir,
const std::set<base::FilePath>& registered_names) {
base::FileEnumerator file_enumerator(
dir, false,
base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES,
FILE_PATH_LITERAL("*"));
std::vector<base::FilePath> files;
for (base::FilePath current = file_enumerator.Next(); !current.empty();
current = file_enumerator.Next()) {
if (!base::Contains(registered_names, current.BaseName()))
files.push_back(std::move(current));
}
return files;
}
void DeleteFiles(std::vector<base::FilePath> paths) {
for (const auto& file_path : paths)
base::DeletePathRecursively(file_path);
}
}
NotificationImageRetainer::NotificationImageRetainer(
scoped_refptr<base::SequencedTaskRunner> deletion_task_runner,
const base::TickClock* tick_clock)
: deletion_task_runner_(std::move(deletion_task_runner)),
image_dir_(DetermineImageDirectory()),
tick_clock_(tick_clock),
deletion_timer_(tick_clock) {
DCHECK(deletion_task_runner_);
DCHECK(tick_clock);
DETACH_FROM_SEQUENCE(sequence_checker_);
}
NotificationImageRetainer::NotificationImageRetainer()
: NotificationImageRetainer(
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}),
base::DefaultTickClock::GetInstance()) {}
NotificationImageRetainer::~NotificationImageRetainer() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void NotificationImageRetainer::CleanupFilesFromPrevSessions() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::set<base::FilePath> registered_names;
for (const auto& pair : registered_images_)
registered_names.insert(pair.first);
std::vector<base::FilePath> files =
GetFilesFromPrevSessions(image_dir_, registered_names);
if (!files.empty()) {
deletion_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&DeleteFiles, std::move(files)));
}
}
base::FilePath NotificationImageRetainer::RegisterTemporaryImage(
const gfx::Image& image) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
scoped_refptr<base::RefCountedMemory> data = image.As1xPNGBytes();
if (data->size() == 0)
return base::FilePath();
if (!base::CreateDirectory(image_dir_))
return base::FilePath();
base::FilePath temp_file;
if (!base::CreateTemporaryFileInDir(image_dir_, &temp_file))
return base::FilePath();
const base::TimeTicks now = tick_clock_->NowTicks();
DCHECK(registered_images_.empty() || now >= registered_images_.back().second);
registered_images_.emplace_back(temp_file.BaseName(), now);
int data_len = base::checked_cast<int>(data->size());
bool data_write_success = (base::WriteFile(temp_file, data->front_as<char>(),
data_len) == data_len);
if (!deletion_timer_.IsRunning()) {
deletion_timer_.Start(
FROM_HERE, kDeletionDelay,
base::BindRepeating(&NotificationImageRetainer::DeleteExpiredFiles,
weak_ptr_factory_.GetWeakPtr()));
}
return data_write_success ? temp_file : base::FilePath();
}
base::OnceClosure NotificationImageRetainer::GetCleanupTask() {
return base::BindOnce(
&NotificationImageRetainer::CleanupFilesFromPrevSessions,
weak_ptr_factory_.GetWeakPtr());
}
void NotificationImageRetainer::DeleteExpiredFiles() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!registered_images_.empty());
const base::TimeTicks then = tick_clock_->NowTicks() - kDeletionDelay;
const auto end =
std::upper_bound(registered_images_.begin(), registered_images_.end(),
std::make_pair(base::FilePath(), then),
[](const NameAndTime& a, const NameAndTime& b) {
return a.second < b.second;
});
if (end == registered_images_.begin())
return;
std::vector<base::FilePath> files_to_delete;
files_to_delete.reserve(end - registered_images_.begin());
for (auto iter = registered_images_.begin(); iter < end; ++iter)
files_to_delete.push_back(image_dir_.Append(iter->first));
deletion_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&DeleteFiles, std::move(files_to_delete)));
registered_images_.erase(registered_images_.begin(), end);
if (registered_images_.empty())
deletion_timer_.Stop();
}