Files
oak-editor/app/render/diskmanager.cpp
T
2026-01-16 21:22:05 +08:00

423 lines
9.4 KiB
C++

/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "diskmanager.h"
#include <QDataStream>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QMessageBox>
#include <QStandardPaths>
#include "common/filefunctions.h"
#include "config/config.h"
#include "core.h"
#include "dialog/diskcache/diskcachedialog.h"
namespace olive
{
DiskManager *DiskManager::instance_ = nullptr;
DiskManager::DiskManager()
{
// Add default cache location
QFile default_disk_cache_file(GetDefaultDiskCacheConfigFile());
if (default_disk_cache_file.open(QFile::ReadOnly)) {
QString default_dir = default_disk_cache_file.readAll();
if (!default_dir.isEmpty()) {
if (FileFunctions::DirectoryIsValid(default_dir)) {
GetOpenFolder(default_dir);
} else {
QMessageBox::warning(
nullptr, tr("Disk Cache Error"),
tr("Unable to set custom application disk cache. Using default instead."));
}
}
default_disk_cache_file.close();
}
// If no custom default was loaded, load default
if (open_folders_.isEmpty()) {
GetOpenFolder(GetDefaultDiskCachePath());
}
QFile disk_cache_index(QDir(FileFunctions::GetConfigurationLocation())
.filePath(QStringLiteral("diskcache2")));
if (disk_cache_index.open(QFile::ReadOnly)) {
QTextStream stream(&disk_cache_index);
QString line;
while (stream.readLineInto(&line)) {
GetOpenFolder(line);
}
disk_cache_index.close();
}
}
DiskManager::~DiskManager()
{
QFile default_disk_cache_file(GetDefaultDiskCacheConfigFile());
if (default_disk_cache_file.open(QFile::WriteOnly)) {
if (GetDefaultDiskCachePath() != GetDefaultCachePath()) {
default_disk_cache_file.write(GetDefaultCachePath().toUtf8());
}
default_disk_cache_file.close();
}
}
void DiskManager::CreateInstance()
{
instance_ = new DiskManager();
}
void DiskManager::DestroyInstance()
{
delete instance_;
instance_ = nullptr;
}
DiskManager *DiskManager::instance()
{
return instance_;
}
void DiskManager::Accessed(const QString &cache_folder, const QString &filename)
{
DiskCacheFolder *f = GetOpenFolder(cache_folder);
f->Accessed(filename);
}
void DiskManager::CreatedFile(const QString &cache_folder,
const QString &filename)
{
DiskCacheFolder *f = GetOpenFolder(cache_folder);
f->CreatedFile(filename);
}
void DiskManager::DeleteSpecificFile(const QString &filename)
{
foreach (DiskCacheFolder *f, open_folders_) {
f->DeleteSpecificFile(filename);
}
}
bool DiskManager::ClearDiskCache(const QString &cache_folder)
{
DiskCacheFolder *f = GetOpenFolder(cache_folder);
return f->ClearCache();
}
DiskCacheFolder *DiskManager::GetOpenFolder(const QString &path)
{
// If path is empty, this must mean default
if (path.isEmpty()) {
return GetDefaultCacheFolder();
}
// See if we have an existing path with this name
foreach (DiskCacheFolder *f, open_folders_) {
if (f->GetPath() == path) {
return f;
}
}
// We must have to open this folder
DiskCacheFolder *f = new DiskCacheFolder(path, this);
connect(f, &DiskCacheFolder::DeletedFrame, this,
&DiskManager::DeletedFrame);
open_folders_.append(f);
return f;
}
bool DiskManager::ShowDiskCacheChangeConfirmationDialog(QWidget *parent)
{
return (
QMessageBox::question(
parent, tr("Disk Cache"),
tr("You've chosen to change the default disk cache location. This "
"will invalidate your current cache. Would you like to continue?"),
QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok);
}
QString DiskManager::GetDefaultDiskCacheConfigFile()
{
return QDir(FileFunctions::GetConfigurationLocation())
.filePath(QStringLiteral("defaultdiskcache"));
}
QString DiskManager::GetDefaultDiskCachePath()
{
return QDir(QStandardPaths::writableLocation(
QStandardPaths::AppLocalDataLocation))
.filePath("mediacache");
}
void DiskManager::ShowDiskCacheSettingsDialog(DiskCacheFolder *folder,
QWidget *parent)
{
DiskCacheDialog d(folder, parent);
d.exec();
}
void DiskManager::ShowDiskCacheSettingsDialog(const QString &path,
QWidget *parent)
{
if (!FileFunctions::DirectoryIsValid(path)) {
QMessageBox::critical(
parent, tr("Disk Cache Error"),
tr("Failed to open disk cache at \"%1\". Try a different folder.")
.arg(path));
return;
}
DiskCacheFolder *folder = GetOpenFolder(path);
ShowDiskCacheSettingsDialog(folder, parent);
}
DiskCacheFolder::DiskCacheFolder(const QString &path, QObject *parent)
: QObject(parent)
{
SetPath(path);
save_timer_.setInterval(OLIVE_CONFIG("DiskCacheSaveInterval").toInt());
connect(&save_timer_, &QTimer::timeout, this,
&DiskCacheFolder::SaveDiskCacheIndex);
save_timer_.start();
}
DiskCacheFolder::~DiskCacheFolder()
{
CloseCacheFolder();
}
bool DiskCacheFolder::ClearCache()
{
bool deleted_files = true;
auto i = disk_data_.begin();
while (i != disk_data_.end()) {
// We return a false result if any of the files fail to delete, but still try to delete as many as we can
QString filename = i.key();
if (QFile::remove(filename) || !QFileInfo::exists(filename)) {
emit DeletedFrame(path_, filename);
i = disk_data_.erase(i);
} else {
qWarning() << "Failed to delete" << filename;
deleted_files = false;
i++;
}
}
return deleted_files;
}
void DiskCacheFolder::Accessed(const QString &filename)
{
if (!disk_data_.contains(filename)) {
return;
}
disk_data_[filename].access_time = QDateTime::currentMSecsSinceEpoch();
}
void DiskCacheFolder::CreatedFile(const QString &filename)
{
qint64 file_size = QFile(filename).size();
disk_data_.insert(filename,
{ file_size, QDateTime::currentMSecsSinceEpoch() });
consumption_ += file_size;
while (consumption_ > limit_) {
DeleteLeastRecent();
}
}
void DiskCacheFolder::SetPath(const QString &path)
{
// If this is currently set to a folder, close it out now
CloseCacheFolder();
// Signal that disk cache is gone
if (!disk_data_.empty()) {
for (auto it = disk_data_.cbegin(); it != disk_data_.cend(); it++) {
emit DeletedFrame(path_, it.key());
}
disk_data_.clear();
}
// Set defaults
clear_on_close_ = false;
consumption_ = 0;
limit_ = 21474836480; // Default to 20 GB
// Set path
path_ = path;
// Attempt to load existing index file from path
QDir path_dir(path_);
FileFunctions::DirectoryIsValid(path_dir);
index_path_ = path_dir.filePath(QStringLiteral("index"));
// Try to load any current cache index from file
QFile cache_index_file(index_path_);
if (cache_index_file.open(QFile::ReadOnly)) {
QDataStream ds(&cache_index_file);
ds >> limit_;
ds >> clear_on_close_;
while (!cache_index_file.atEnd()) {
QString filename;
HashTime h;
ds >> filename;
ds >> h.file_size;
ds >> h.access_time;
if (QFileInfo::exists(filename)) {
consumption_ += h.file_size;
disk_data_.insert(filename, h);
}
}
cache_index_file.close();
}
}
bool DiskCacheFolder::DeleteFileInternal(
QMap<QString, HashTime>::iterator hash_to_delete)
{
// Cache HashTime object
QString filename = hash_to_delete.key();
HashTime ht = hash_to_delete.value();
// Remove from disk
QFile f(filename);
if (!f.exists() || f.remove()) {
// Remove from internal map
disk_data_.erase(hash_to_delete);
// Reduce consumption
consumption_ -= ht.file_size;
emit DeletedFrame(path_, filename);
return true;
}
return false;
}
bool DiskCacheFolder::DeleteSpecificFile(const QString &f)
{
for (auto it = disk_data_.begin(); it != disk_data_.end(); it++) {
if (it.key() == f) {
// Break out of this loop, assuming we'll only have one instance of each filename
return DeleteFileInternal(it);
}
}
return false;
}
bool DiskCacheFolder::DeleteLeastRecent()
{
auto hash_to_delete = disk_data_.begin();
if (disk_data_.begin() != disk_data_.end()) {
for (auto it = disk_data_.begin() + 1; it != disk_data_.end(); it++) {
if (it->access_time < hash_to_delete->access_time) {
hash_to_delete = it;
}
}
bool e = DeleteFileInternal(hash_to_delete);
if (e) {
Core::instance()->WarnCacheFull();
}
return e;
} else {
return false;
}
}
void DiskCacheFolder::CloseCacheFolder()
{
if (path_.isEmpty()) {
return;
}
if (clear_on_close_) {
// If we're not moving to new and we're set to clear on close, clear now or else it'll never
// get cleared later
ClearCache();
}
// Save current cache index
SaveDiskCacheIndex();
}
void DiskCacheFolder::SaveDiskCacheIndex()
{
QFile cache_index_file(index_path_);
if (cache_index_file.open(QFile::WriteOnly)) {
QDataStream ds(&cache_index_file);
ds << limit_;
ds << clear_on_close_;
for (auto it = disk_data_.cbegin(); it != disk_data_.cend(); it++) {
const HashTime &ht = it.value();
ds << it.key();
ds << ht.file_size;
ds << ht.access_time;
}
cache_index_file.close();
} else {
qWarning() << "Failed to write cache index:" << index_path_;
}
}
}