cache: better implemented changing disk cache folders
Not complete yet, but getting there.
This commit is contained in:
@@ -29,7 +29,9 @@
|
||||
#include "codec/oiio/oiiodecoder.h"
|
||||
#include "codec/waveinput.h"
|
||||
#include "codec/waveoutput.h"
|
||||
#include "common/filefunctions.h"
|
||||
#include "task/taskmanager.h"
|
||||
#include "project/project.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
@@ -175,6 +177,11 @@ QString Decoder::GetConformedFilename(const AudioParams ¶ms)
|
||||
return index_fn;
|
||||
}
|
||||
|
||||
QString Decoder::GetIndexFilename()
|
||||
{
|
||||
return QDir(stream_->footage()->project()->cache_path()).filePath(FileFunctions::GetUniqueFileIdentifier(stream()->footage()->filename()).append(QString::number(stream()->index())));
|
||||
}
|
||||
|
||||
bool Decoder::ConformAudio(const QAtomicInt *, const AudioParams& )
|
||||
{
|
||||
return false;
|
||||
|
||||
+2
-8
@@ -242,19 +242,13 @@ signals:
|
||||
protected:
|
||||
void SignalProcessingProgress(const int64_t& ts);
|
||||
|
||||
/**
|
||||
* @brief Returns the filename for the index
|
||||
*
|
||||
* Retrieves the absolute filename of the index file for this stream. Decoder must be open for
|
||||
* this to work correctly.
|
||||
*/
|
||||
virtual QString GetIndexFilename() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the destination filename of an audio stream conformed to a set of parameters
|
||||
*/
|
||||
QString GetConformedFilename(const AudioParams ¶ms);
|
||||
|
||||
QString GetIndexFilename();
|
||||
|
||||
bool open_;
|
||||
|
||||
QMutex mutex_;
|
||||
|
||||
@@ -611,40 +611,6 @@ void FFmpegDecoder::Error(const QString &s)
|
||||
ClearResources();
|
||||
}
|
||||
|
||||
QMutex scaler_lock;
|
||||
void SaveCacheFrame(FFmpegDecoder* decoder,
|
||||
SwsContext* scaler,
|
||||
AVFrame* frame,
|
||||
VideoParams params,
|
||||
QString dst_fn)
|
||||
{
|
||||
QByteArray converted_buffer(PixelFormat::GetBufferSize(params.format(),
|
||||
params.width(),
|
||||
params.height()),
|
||||
Qt::Uninitialized);
|
||||
|
||||
uint8_t* converted_data = reinterpret_cast<uint8_t*>(converted_buffer.data());
|
||||
int converted_linesize = PixelFormat::GetBufferSize(params.format(),
|
||||
params.width(),
|
||||
1);
|
||||
|
||||
scaler_lock.lock();
|
||||
sws_scale(scaler,
|
||||
frame->data,
|
||||
frame->linesize,
|
||||
0,
|
||||
frame->height,
|
||||
&converted_data,
|
||||
&converted_linesize);
|
||||
scaler_lock.unlock();
|
||||
|
||||
if (!FrameHashCache::SaveCacheFrame(dst_fn, converted_buffer.data(), params, converted_linesize)) {
|
||||
qCritical() <<" Failed to save cache frame" << dst_fn;
|
||||
}
|
||||
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioParams &p)
|
||||
{
|
||||
// Iterate through each audio frame and extract the PCM data
|
||||
@@ -774,17 +740,6 @@ bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioParams
|
||||
return success;
|
||||
}
|
||||
|
||||
QString FFmpegDecoder::GetIndexFilename() const
|
||||
{
|
||||
return FileFunctions::GetMediaIndexFilename(FileFunctions::GetUniqueFileIdentifier(stream()->footage()->filename()))
|
||||
.append(QString::number(stream()->index()));
|
||||
}
|
||||
|
||||
QString FFmpegDecoder::GetProxyFilename(int divider) const
|
||||
{
|
||||
return GetIndexFilename().append('d').append(QString::number(divider));
|
||||
}
|
||||
|
||||
int FFmpegDecoder::GetScaledDimension(int dim, int divider)
|
||||
{
|
||||
return dim / divider;
|
||||
@@ -914,7 +869,6 @@ void FFmpegDecoderInstance::SetWorking(bool working)
|
||||
|
||||
void FFmpegDecoderInstance::Seek(int64_t timestamp)
|
||||
{
|
||||
qDebug() << "Seeking to" << timestamp;
|
||||
avcodec_flush_buffers(codec_ctx_);
|
||||
av_seek_frame(fmt_ctx_, avstream_->index, timestamp, AVSEEK_FLAG_BACKWARD);
|
||||
}
|
||||
@@ -1171,14 +1125,6 @@ void FFmpegDecoder::FreeScaler()
|
||||
}
|
||||
}
|
||||
|
||||
QString FFmpegDecoder::GetProxyFrameFilename(const int64_t ×tamp, const int& divider) const
|
||||
{
|
||||
QString dst_fn = GetProxyFilename(divider);
|
||||
dst_fn.append(QString::number(timestamp));
|
||||
dst_fn.append(FrameHashCache::GetFormatExtension());
|
||||
return dst_fn;
|
||||
}
|
||||
|
||||
int64_t FFmpegDecoderInstance::RangeStart() const
|
||||
{
|
||||
if (cached_frames_.isEmpty()) {
|
||||
|
||||
@@ -172,17 +172,11 @@ private:
|
||||
*/
|
||||
void FFmpegError(int error_code);
|
||||
|
||||
virtual QString GetIndexFilename() const override;
|
||||
|
||||
QString GetProxyFilename(int divider) const;
|
||||
|
||||
void ClearResources();
|
||||
|
||||
void InitScaler(int divider);
|
||||
void FreeScaler();
|
||||
|
||||
QString GetProxyFrameFilename(const int64_t& timestamp, const int ÷r) const;
|
||||
|
||||
static int GetScaledDimension(int dim, int divider);
|
||||
|
||||
static PixelFormat::Format GetNativePixelFormat(AVPixelFormat pix_fmt);
|
||||
|
||||
@@ -220,11 +220,6 @@ bool OIIODecoder::SupportsVideo()
|
||||
return true;
|
||||
}
|
||||
|
||||
QString OIIODecoder::GetIndexFilename() const
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
void OIIODecoder::FrameToBuffer(FramePtr frame, OIIO::ImageBuf *buf)
|
||||
{
|
||||
#if OIIO_VERSION < 20112
|
||||
|
||||
@@ -51,8 +51,6 @@ public:
|
||||
|
||||
virtual bool SupportsVideo() override;
|
||||
|
||||
virtual QString GetIndexFilename() const override;
|
||||
|
||||
static void FrameToBuffer(FramePtr frame, OIIO::ImageBuf* buf);
|
||||
|
||||
static void BufferToFrame(OIIO::ImageBuf* buf, FramePtr frame);
|
||||
|
||||
@@ -42,6 +42,8 @@ const int kProjectIconSizeMaximum = 256;
|
||||
/// The default size an icon in ProjectExplorer can be
|
||||
const int kProjectIconSizeDefault = 64;
|
||||
|
||||
const int kBytesInGigabyte = 1073741824;
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#define MACRO_NAME_AS_STR(s) #s
|
||||
|
||||
@@ -50,35 +50,6 @@ QString FileFunctions::GetUniqueFileIdentifier(const QString &filename)
|
||||
return QString(result.toHex());
|
||||
}
|
||||
|
||||
QString FileFunctions::GetMediaIndexLocation()
|
||||
{
|
||||
QDir local_appdata_dir(Config::Current()["DiskCachePath"].toString());
|
||||
|
||||
QDir media_index_dir = local_appdata_dir.filePath("mediaindex");
|
||||
|
||||
// Attempt to ensure this folder exists
|
||||
media_index_dir.mkpath(".");
|
||||
|
||||
return media_index_dir.absolutePath();
|
||||
}
|
||||
|
||||
QString FileFunctions::GetMediaIndexFilename(const QString &filename)
|
||||
{
|
||||
return QDir(GetMediaIndexLocation()).filePath(filename);
|
||||
}
|
||||
|
||||
QString FileFunctions::GetMediaCacheLocation()
|
||||
{
|
||||
QDir local_appdata_dir(Config::Current()["DiskCachePath"].toString());
|
||||
|
||||
QDir media_cache_dir = local_appdata_dir.filePath("mediacache");
|
||||
|
||||
// Attempt to ensure this folder exists
|
||||
media_cache_dir.mkpath(".");
|
||||
|
||||
return media_cache_dir.absolutePath();
|
||||
}
|
||||
|
||||
QString FileFunctions::GetConfigurationLocation()
|
||||
{
|
||||
if (IsPortable()) {
|
||||
@@ -112,6 +83,30 @@ QString FileFunctions::GetTempFilePath()
|
||||
return temp_path;
|
||||
}
|
||||
|
||||
bool FileFunctions::CanCopyDirectoryWithoutOverwriting(const QString& source, const QString& dest)
|
||||
{
|
||||
QFileInfoList info_list = QDir(source).entryInfoList();
|
||||
|
||||
foreach (const QFileInfo& info, info_list) {
|
||||
// QDir::NoDotAndDotDot continues to not work, so we have to check manually
|
||||
if (info.fileName() == QStringLiteral(".") || info.fileName() == QStringLiteral("..")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString dest_equivalent = QDir(dest).filePath(info.fileName());
|
||||
|
||||
if (info.isDir()) {
|
||||
if (!CanCopyDirectoryWithoutOverwriting(info.absoluteFilePath(), dest_equivalent)) {
|
||||
return false;
|
||||
}
|
||||
} else if (QFileInfo::exists(dest_equivalent)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileFunctions::CopyDirectory(const QString &source, const QString &dest, bool overwrite)
|
||||
{
|
||||
QDir d(source);
|
||||
@@ -152,4 +147,27 @@ void FileFunctions::CopyDirectory(const QString &source, const QString &dest, bo
|
||||
}
|
||||
}
|
||||
|
||||
bool FileFunctions::DirectoryIsValid(const QString &dir, bool try_to_create)
|
||||
{
|
||||
// Empty string is invalid
|
||||
if (dir.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QDir d(dir);
|
||||
|
||||
// If directory already exists, this is valid
|
||||
if (d.exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we can create and creation is successful, this is valid
|
||||
if (try_to_create && d.mkpath(".")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, invalid
|
||||
return false;
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -42,20 +42,18 @@ public:
|
||||
|
||||
static QString GetUniqueFileIdentifier(const QString& filename);
|
||||
|
||||
static QString GetMediaIndexLocation();
|
||||
|
||||
static QString GetMediaIndexFilename(const QString& filename);
|
||||
|
||||
static QString GetMediaCacheLocation();
|
||||
|
||||
static QString GetConfigurationLocation();
|
||||
|
||||
static QString GetApplicationPath();
|
||||
|
||||
static QString GetTempFilePath();
|
||||
|
||||
static bool CanCopyDirectoryWithoutOverwriting(const QString& source, const QString& dest);
|
||||
|
||||
static void CopyDirectory(const QString& source, const QString& dest, bool overwrite = false);
|
||||
|
||||
static bool DirectoryIsValid(const QString& dir, bool try_to_create);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -97,11 +97,8 @@ void Config::SetDefaults()
|
||||
config_map_["AudioOutput"] = QString();
|
||||
config_map_["AudioInput"] = QString();
|
||||
|
||||
config_map_["DiskCachePath"] = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
|
||||
config_map_["DiskCacheSize"] = 20.0;
|
||||
config_map_["DiskCacheBehind"] = QVariant::fromValue(rational(1));
|
||||
config_map_["DiskCacheAhead"] = QVariant::fromValue(rational(5));
|
||||
config_map_["ClearDiskCacheOnClose"] = false;
|
||||
|
||||
config_map_["DefaultSequenceWidth"] = 1920;
|
||||
config_map_["DefaultSequenceHeight"] = 1080;
|
||||
|
||||
@@ -177,10 +177,6 @@ void Core::DeclareTypesForQt()
|
||||
|
||||
void Core::Start()
|
||||
{
|
||||
// Reset config (Config sets to default on construction already, but we do it again here as a workaround that fixes
|
||||
// the fact that some of the config paths set by default rely on the app name having been set (in main())
|
||||
Config::Current().SetDefaults();
|
||||
|
||||
// Load application config
|
||||
Config::Load();
|
||||
|
||||
|
||||
@@ -27,12 +27,15 @@
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "render/diskmanager.h"
|
||||
#include "common/filefunctions.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
PreferencesDiskTab::PreferencesDiskTab()
|
||||
{
|
||||
// Get default disk cache folder
|
||||
default_disk_cache_folder_ = DiskManager::instance()->GetDefaultCacheFolder();
|
||||
|
||||
QVBoxLayout* outer_layout = new QVBoxLayout(this);
|
||||
|
||||
QGroupBox* disk_management_group = new QGroupBox(tr("Disk Management"));
|
||||
@@ -44,15 +47,9 @@ PreferencesDiskTab::PreferencesDiskTab()
|
||||
|
||||
disk_management_layout->addWidget(new QLabel(tr("Disk Cache Location:")), row, 0);
|
||||
|
||||
disk_cache_location_ = new QLineEdit();
|
||||
disk_cache_location_->setText(Config::Current()["DiskCachePath"].toString());
|
||||
connect(disk_cache_location_, &QLineEdit::textChanged, this, &PreferencesDiskTab::DiskCacheLineEditChanged);
|
||||
disk_cache_location_ = new PathWidget(default_disk_cache_folder_->GetPath());
|
||||
disk_management_layout->addWidget(disk_cache_location_, row, 1);
|
||||
|
||||
QPushButton* browse_btn = new QPushButton(tr("Browse"));
|
||||
connect(browse_btn, &QPushButton::clicked, this, &PreferencesDiskTab::BrowseDiskCachePath);
|
||||
disk_management_layout->addWidget(browse_btn, row, 2);
|
||||
|
||||
row++;
|
||||
|
||||
disk_management_layout->addWidget(new QLabel(tr("Maximum Disk Cache:")), row, 0);
|
||||
@@ -60,20 +57,20 @@ PreferencesDiskTab::PreferencesDiskTab()
|
||||
maximum_cache_slider_ = new FloatSlider();
|
||||
maximum_cache_slider_->SetFormat(tr("%1 GB"));
|
||||
maximum_cache_slider_->SetMinimum(1.0);
|
||||
maximum_cache_slider_->SetValue(Config::Current()["DiskCacheSize"].toDouble());
|
||||
disk_management_layout->addWidget(maximum_cache_slider_, row, 1, 1, 2);
|
||||
maximum_cache_slider_->SetValue(static_cast<double>(default_disk_cache_folder_->GetLimit()) / static_cast<double>(kBytesInGigabyte));
|
||||
disk_management_layout->addWidget(maximum_cache_slider_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
clear_cache_btn_ = new QPushButton(tr("Clear Disk Cache"));
|
||||
connect(clear_cache_btn_, &QPushButton::clicked, this, &PreferencesDiskTab::ClearDiskCache);
|
||||
disk_management_layout->addWidget(clear_cache_btn_, row, 1, 1, 2);
|
||||
disk_management_layout->addWidget(clear_cache_btn_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
clear_disk_cache_ = new QCheckBox(tr("Automatically clear disk cache on close"));
|
||||
clear_disk_cache_->setChecked(Config::Current()["ClearDiskCacheOnClose"].toBool());
|
||||
disk_management_layout->addWidget(clear_disk_cache_, row, 1, 1, 2);
|
||||
clear_disk_cache_->setChecked(default_disk_cache_folder_->GetClearOnClose());
|
||||
disk_management_layout->addWidget(clear_disk_cache_, row, 1);
|
||||
|
||||
QGroupBox* cache_behavior = new QGroupBox(tr("Cache Behavior"));
|
||||
outer_layout->addWidget(cache_behavior);
|
||||
@@ -100,35 +97,51 @@ PreferencesDiskTab::PreferencesDiskTab()
|
||||
outer_layout->addStretch();
|
||||
}
|
||||
|
||||
bool PreferencesDiskTab::Validate()
|
||||
{
|
||||
if (disk_cache_location_->text() != default_disk_cache_folder_->GetPath()) {
|
||||
// Disk cache location is changing
|
||||
|
||||
// Check if the user wants to move the cache here
|
||||
if (QMessageBox::question(this,
|
||||
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::Cancel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check validity of the new path
|
||||
if (!FileFunctions::DirectoryIsValid(disk_cache_location_->text(), true)) {
|
||||
QMessageBox::critical(this,
|
||||
tr("Disk Cache"),
|
||||
tr("Failed to set disk cache location. Access was denied."));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PreferencesDiskTab::Accept()
|
||||
{
|
||||
Config::Current()["DiskCachePath"] = disk_cache_location_->text();
|
||||
Config::Current()["DiskCacheSize"] = maximum_cache_slider_->GetValue();
|
||||
Config::Current()["ClearDiskCacheOnClose"] = clear_disk_cache_->isChecked();
|
||||
if (disk_cache_location_->text() != default_disk_cache_folder_->GetPath()) {
|
||||
default_disk_cache_folder_->SetPath(disk_cache_location_->text());
|
||||
}
|
||||
|
||||
qint64 new_disk_cache_limit = qRound64(maximum_cache_slider_->GetValue() * kBytesInGigabyte);
|
||||
if (new_disk_cache_limit != default_disk_cache_folder_->GetLimit()) {
|
||||
default_disk_cache_folder_->SetLimit(new_disk_cache_limit);
|
||||
}
|
||||
|
||||
if (default_disk_cache_folder_->GetClearOnClose() != clear_disk_cache_->isChecked()) {
|
||||
default_disk_cache_folder_->SetClearOnClose(clear_disk_cache_->isChecked());
|
||||
}
|
||||
|
||||
Config::Current()["DiskCacheBehind"] = QVariant::fromValue(rational::fromDouble(cache_behind_slider_->GetValue()));
|
||||
Config::Current()["DiskCacheAhead"] = QVariant::fromValue(rational::fromDouble(cache_ahead_slider_->GetValue()));
|
||||
}
|
||||
|
||||
void PreferencesDiskTab::DiskCacheLineEditChanged()
|
||||
{
|
||||
QString entered_dir = disk_cache_location_->text();
|
||||
|
||||
if (!entered_dir.isEmpty() && !QDir(entered_dir).exists()) {
|
||||
disk_cache_location_->setStyleSheet(QStringLiteral("color: red;"));
|
||||
} else {
|
||||
disk_cache_location_->setStyleSheet(QString());
|
||||
}
|
||||
}
|
||||
|
||||
void PreferencesDiskTab::BrowseDiskCachePath()
|
||||
{
|
||||
QString dir = QFileDialog::getExistingDirectory(this, tr("Browse for disk cache path"), disk_cache_location_->text());
|
||||
|
||||
if (!dir.isEmpty()) {
|
||||
disk_cache_location_->setText(dir);
|
||||
}
|
||||
}
|
||||
|
||||
void PreferencesDiskTab::ClearDiskCache()
|
||||
{
|
||||
if (QMessageBox::question(this,
|
||||
@@ -137,7 +150,7 @@ void PreferencesDiskTab::ClearDiskCache()
|
||||
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
|
||||
clear_cache_btn_->setEnabled(false);
|
||||
|
||||
if (DiskManager::instance()->ClearDiskCache(false)) {
|
||||
if (DiskManager::instance()->ClearDiskCache(default_disk_cache_folder_->GetPath())) {
|
||||
clear_cache_btn_->setText(tr("Disk Cache Cleared"));
|
||||
} else {
|
||||
QMessageBox::information(this,
|
||||
|
||||
@@ -26,7 +26,9 @@
|
||||
#include <QPushButton>
|
||||
|
||||
#include "preferencestab.h"
|
||||
#include "render/diskmanager.h"
|
||||
#include "widget/slider/floatslider.h"
|
||||
#include "widget/path/pathwidget.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
@@ -36,10 +38,12 @@ class PreferencesDiskTab : public PreferencesTab
|
||||
public:
|
||||
PreferencesDiskTab();
|
||||
|
||||
virtual bool Validate() override;
|
||||
|
||||
virtual void Accept() override;
|
||||
|
||||
private:
|
||||
QLineEdit* disk_cache_location_;
|
||||
PathWidget* disk_cache_location_;
|
||||
|
||||
FloatSlider* maximum_cache_slider_;
|
||||
|
||||
@@ -51,11 +55,9 @@ private:
|
||||
|
||||
QPushButton* clear_cache_btn_;
|
||||
|
||||
DiskCacheFolder* default_disk_cache_folder_;
|
||||
|
||||
private slots:
|
||||
void DiskCacheLineEditChanged();
|
||||
|
||||
void BrowseDiskCachePath();
|
||||
|
||||
void ClearDiskCache();
|
||||
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "projectproperties.h"
|
||||
|
||||
#include <QButtonGroup>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFileDialog>
|
||||
#include <QGroupBox>
|
||||
@@ -29,6 +30,7 @@
|
||||
#include <OpenColorIO/OpenColorIO.h>
|
||||
namespace OCIO = OCIO_NAMESPACE::v1;
|
||||
|
||||
#include "common/filefunctions.h"
|
||||
#include "config/config.h"
|
||||
#include "core.h"
|
||||
#include "render/colormanager.h"
|
||||
@@ -51,7 +53,10 @@ ProjectPropertiesDialog::ProjectPropertiesDialog(Project* p, QWidget *parent) :
|
||||
// Color management group
|
||||
QWidget* color_group = new QWidget();
|
||||
|
||||
QGridLayout* color_layout = new QGridLayout(color_group);
|
||||
QVBoxLayout* color_outer_layout = new QVBoxLayout(color_group);
|
||||
|
||||
QGridLayout* color_layout = new QGridLayout();
|
||||
color_outer_layout->addLayout(color_layout);
|
||||
|
||||
int row = 0;
|
||||
|
||||
@@ -80,26 +85,47 @@ ProjectPropertiesDialog::ProjectPropertiesDialog(Project* p, QWidget *parent) :
|
||||
OCIOFilenameUpdated();
|
||||
|
||||
tabs->addTab(color_group, tr("Color Management"));
|
||||
|
||||
color_outer_layout->addStretch();
|
||||
}
|
||||
|
||||
|
||||
|
||||
{
|
||||
// Paths group
|
||||
QWidget* paths_group = new QWidget();
|
||||
// Cache group
|
||||
QWidget* cache_group = new QWidget();
|
||||
|
||||
QGridLayout* paths_layout = new QGridLayout(paths_group);
|
||||
QVBoxLayout* cache_layout = new QVBoxLayout(cache_group);
|
||||
|
||||
cache_path_ = new PathWidget(working_project_->cache_path(), this);
|
||||
QButtonGroup* disk_cache_btn_group = new QButtonGroup();
|
||||
|
||||
int row = 0;
|
||||
disk_cache_use_default_btn_ = new QRadioButton(tr("Use Default Location"));
|
||||
disk_cache_store_alongside_project_btn_ = new QRadioButton(tr("Store Alongside Project"));
|
||||
disk_cache_use_custom_btn_ = new QRadioButton(tr("Use Custom Location:"));
|
||||
|
||||
paths_layout->addWidget(new QLabel(tr("Cache Path:")), row, 0);
|
||||
paths_layout->addWidget(cache_path_->path_edit(), row, 1);
|
||||
paths_layout->addWidget(cache_path_->browse_btn(), row, 2);
|
||||
paths_layout->addWidget(cache_path_->default_box(), row, 3);
|
||||
disk_cache_btn_group->addButton(disk_cache_use_default_btn_);
|
||||
disk_cache_btn_group->addButton(disk_cache_store_alongside_project_btn_);
|
||||
disk_cache_btn_group->addButton(disk_cache_use_custom_btn_);
|
||||
|
||||
tabs->addTab(paths_group, tr("Paths"));
|
||||
cache_layout->addWidget(disk_cache_use_default_btn_);
|
||||
cache_layout->addWidget(disk_cache_store_alongside_project_btn_);
|
||||
cache_layout->addWidget(disk_cache_use_custom_btn_);
|
||||
|
||||
cache_path_ = new PathWidget(working_project_->cache_path(false), this);
|
||||
cache_path_->setEnabled(false);
|
||||
cache_layout->addWidget(cache_path_);
|
||||
|
||||
connect(disk_cache_use_custom_btn_, &QRadioButton::toggled, cache_path_, &PathWidget::setEnabled);
|
||||
|
||||
if (working_project_->cache_path(false).isEmpty()) {
|
||||
disk_cache_use_default_btn_->setChecked(true);
|
||||
} else {
|
||||
disk_cache_use_custom_btn_->setChecked(true);
|
||||
}
|
||||
|
||||
cache_layout->addWidget(cache_path_);
|
||||
|
||||
tabs->addTab(cache_group, tr("Disk Cache"));
|
||||
}
|
||||
|
||||
QDialogButtonBox* dialog_btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
|
||||
@@ -122,7 +148,14 @@ void ProjectPropertiesDialog::accept()
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cache_path_->PathIsValid(true)) {
|
||||
if (disk_cache_use_default_btn_->isChecked()) {
|
||||
// Empty cache path means default
|
||||
working_project_->set_cache_path(QString());
|
||||
} else if (disk_cache_store_alongside_project_btn_->isChecked()) {
|
||||
QMessageBox::information(this, QString(), tr("\"Store alignside project\" functionality not implemented yet"));
|
||||
return;
|
||||
} else {
|
||||
if (!FileFunctions::DirectoryIsValid(cache_path_->text(), true)) {
|
||||
QMessageBox mb(this);
|
||||
mb.setWindowModality(Qt::WindowModal);
|
||||
mb.setIcon(QMessageBox::Critical);
|
||||
@@ -133,7 +166,8 @@ void ProjectPropertiesDialog::accept()
|
||||
return;
|
||||
}
|
||||
|
||||
working_project_->set_cache_path(cache_path_->path_edit()->text());
|
||||
working_project_->set_cache_path(cache_path_->text());
|
||||
}
|
||||
|
||||
// This should ripple changes throughout the program that the color config has changed, therefore must be done last
|
||||
working_project_->color_manager()->SetConfigAndDefaultInput(ocio_filename_->text(),
|
||||
@@ -184,54 +218,4 @@ void ProjectPropertiesDialog::OCIOFilenameUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
PathWidget::PathWidget(const QString &path, QWidget *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
path_edit_ = new QLineEdit();
|
||||
path_edit_->setText(path);
|
||||
connect(path_edit_, &QLineEdit::textChanged, this, &PathWidget::LineEditChanged);
|
||||
|
||||
default_box_ = new QCheckBox(tr("Default"));
|
||||
|
||||
browse_btn_ = new QPushButton(tr("Browse"));
|
||||
|
||||
connect(default_box_, &QCheckBox::toggled, this, &PathWidget::DefaultToggled);
|
||||
|
||||
default_box_->setChecked(path.isEmpty());
|
||||
|
||||
connect(browse_btn_, &QPushButton::clicked, this, &PathWidget::BrowseClicked);
|
||||
}
|
||||
|
||||
bool PathWidget::PathIsValid(bool try_to_create) const
|
||||
{
|
||||
return default_box_->isChecked()
|
||||
|| QDir(path_edit_->text()).exists()
|
||||
|| (try_to_create && QDir(path_edit_->text()).mkpath(QStringLiteral(".")));
|
||||
}
|
||||
|
||||
void PathWidget::DefaultToggled(bool e)
|
||||
{
|
||||
path_edit_->setEnabled(!e);
|
||||
}
|
||||
|
||||
void PathWidget::BrowseClicked()
|
||||
{
|
||||
QString dir = QFileDialog::getExistingDirectory(static_cast<QWidget*>(parent()),
|
||||
tr("Browse for path"),
|
||||
path_edit_->text());
|
||||
|
||||
if (!dir.isEmpty()) {
|
||||
path_edit_->setText(dir);
|
||||
}
|
||||
}
|
||||
|
||||
void PathWidget::LineEditChanged()
|
||||
{
|
||||
if (PathIsValid(false)) {
|
||||
path_edit_->setStyleSheet(QString());
|
||||
} else {
|
||||
path_edit_->setStyleSheet(QStringLiteral("QLineEdit {color: red;}"));
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -26,48 +26,13 @@
|
||||
#include <QDialog>
|
||||
#include <QGridLayout>
|
||||
#include <QLineEdit>
|
||||
#include <QRadioButton>
|
||||
|
||||
#include "project/project.h"
|
||||
#include "widget/path/pathwidget.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class PathWidget : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PathWidget(const QString& path,
|
||||
QWidget* parent = nullptr);
|
||||
|
||||
bool PathIsValid(bool try_to_create) const;
|
||||
|
||||
QLineEdit* path_edit() const {
|
||||
return path_edit_;
|
||||
}
|
||||
|
||||
QCheckBox* default_box() const {
|
||||
return default_box_;
|
||||
}
|
||||
|
||||
QPushButton* browse_btn() const {
|
||||
return browse_btn_;
|
||||
}
|
||||
|
||||
private slots:
|
||||
void DefaultToggled(bool e);
|
||||
|
||||
void BrowseClicked();
|
||||
|
||||
void LineEditChanged();
|
||||
|
||||
private:
|
||||
QLineEdit* path_edit_;
|
||||
|
||||
QCheckBox* default_box_;
|
||||
|
||||
QPushButton* browse_btn_;
|
||||
|
||||
};
|
||||
|
||||
class ProjectPropertiesDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -90,6 +55,12 @@ private:
|
||||
|
||||
PathWidget* cache_path_;
|
||||
|
||||
QRadioButton* disk_cache_use_default_btn_;
|
||||
|
||||
QRadioButton* disk_cache_store_alongside_project_btn_;
|
||||
|
||||
QRadioButton* disk_cache_use_custom_btn_;
|
||||
|
||||
private slots:
|
||||
void BrowseForOCIOConfig();
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <memory>
|
||||
|
||||
#include "render/colormanager.h"
|
||||
#include "render/diskmanager.h"
|
||||
#include "project/item/folder/folder.h"
|
||||
#include "window/mainwindow/mainwindowlayoutinfo.h"
|
||||
|
||||
@@ -71,7 +72,10 @@ public:
|
||||
|
||||
bool is_new() const;
|
||||
|
||||
const QString& cache_path() const {
|
||||
const QString& cache_path(bool default_if_empty = true) const {
|
||||
if (cache_path_.isEmpty() && default_if_empty) {
|
||||
return DiskManager::instance()->GetDefaultCachePath();
|
||||
}
|
||||
return cache_path_;
|
||||
}
|
||||
|
||||
|
||||
@@ -249,7 +249,7 @@ QList<TimeRange> AudioPlaybackCache::NoLockGetValidRanges(const TimeRange& range
|
||||
|
||||
void AudioPlaybackCache::UpdateFilename(const QString &s)
|
||||
{
|
||||
filename_ = QDir(FileFunctions::GetMediaCacheLocation()).filePath(s);
|
||||
filename_ = QDir(GetCacheFilename()).filePath(s);
|
||||
filename_.append(QStringLiteral(".pcm"));
|
||||
}
|
||||
|
||||
|
||||
@@ -396,6 +396,7 @@ void RenderBackend::RunNextJob()
|
||||
worker->SetRenderMode(render_mode_);
|
||||
worker->SetPreviewGenerationEnabled(generate_audio_previews_);
|
||||
worker->SetCopyMap(©_map_);
|
||||
worker->SetViewerNode(viewer_node_);
|
||||
|
||||
// Move ticket from queue to running list
|
||||
RenderTicketPtr ticket = render_queue_.front();
|
||||
@@ -592,7 +593,8 @@ void RenderBackend::AutoCacheVideoRendered()
|
||||
QFutureWatcher<bool>* w = new QFutureWatcher<bool>();
|
||||
autocache_video_download_tasks_.insert(w, hash);
|
||||
connect(w, &QFutureWatcher<bool>::finished, this, &RenderBackend::AutoCacheVideoDownloaded);
|
||||
w->setFuture(QtConcurrent::run(FrameHashCache::SaveCacheFrame,
|
||||
w->setFuture(QtConcurrent::run(viewer_node_->video_frame_cache(),
|
||||
&FrameHashCache::SaveCacheFrame,
|
||||
hash,
|
||||
watcher->Get().value<FramePtr>()));
|
||||
}
|
||||
|
||||
@@ -274,10 +274,7 @@ QVariant RenderWorker::GetCachedFrame(const Node* node, const rational& time)
|
||||
if (node->id() == QStringLiteral("org.olivevideoeditor.Olive.videoinput")) {
|
||||
QByteArray hash = HashNode(node, video_params(), time);
|
||||
|
||||
QString fn = FrameHashCache::CachePathName(hash);
|
||||
|
||||
if (QFileInfo::exists(fn)) {
|
||||
FramePtr f = FrameHashCache::LoadCacheFrame(hash);
|
||||
FramePtr f = viewer_->video_frame_cache()->LoadCacheFrame(hash);
|
||||
|
||||
if (f) {
|
||||
// The cached frame won't load with the correct divider by default, so we enforce it here
|
||||
@@ -290,7 +287,6 @@ QVariant RenderWorker::GetCachedFrame(const Node* node, const rational& time)
|
||||
return CachedFrameToTexture(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
@@ -48,6 +48,11 @@ public:
|
||||
available_ = a;
|
||||
}
|
||||
|
||||
void SetViewerNode(ViewerOutput* viewer)
|
||||
{
|
||||
viewer_ = viewer;
|
||||
}
|
||||
|
||||
void SetVideoParams(const VideoParams& params)
|
||||
{
|
||||
video_params_ = params;
|
||||
@@ -171,6 +176,8 @@ private:
|
||||
|
||||
bool generate_audio_previews_;
|
||||
|
||||
ViewerOutput* viewer_;
|
||||
|
||||
QHash<Node*, Node*>* copy_map_;
|
||||
|
||||
RenderMode::Mode render_mode_;
|
||||
|
||||
+199
-116
@@ -25,62 +25,56 @@
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QMessageBox>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "common/filefunctions.h"
|
||||
#include "config/config.h"
|
||||
#include "core.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
DiskManager* DiskManager::instance_ = nullptr;
|
||||
|
||||
DiskManager::DiskManager() :
|
||||
consumption_(0)
|
||||
DiskManager::DiskManager()
|
||||
{
|
||||
// Try to load any current cache index from file
|
||||
QFile cache_index_file(GetCacheIndexFilename());
|
||||
// Add default cache location
|
||||
QFile default_disk_cache_file(QDir(FileFunctions::GetConfigurationLocation()).filePath(QStringLiteral("defaultdiskcache")));
|
||||
if (default_disk_cache_file.open(QFile::ReadOnly)) {
|
||||
QString default_dir = default_disk_cache_file.readAll();
|
||||
|
||||
if (cache_index_file.open(QFile::ReadOnly)) {
|
||||
QDataStream ds(&cache_index_file);
|
||||
|
||||
while (!cache_index_file.atEnd()) {
|
||||
HashTime h;
|
||||
|
||||
ds >> h.file_name;
|
||||
ds >> h.hash;
|
||||
ds >> h.access_time;
|
||||
ds >> h.file_size;
|
||||
|
||||
if (QFileInfo::exists(h.file_name)) {
|
||||
consumption_ += h.file_size;
|
||||
disk_data_.append(h);
|
||||
if (FileFunctions::DirectoryIsValid(default_dir, true)) {
|
||||
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(QDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("mediacache"));
|
||||
}
|
||||
|
||||
QFile disk_cache_index(QDir(FileFunctions::GetConfigurationLocation()).filePath(QStringLiteral("diskcache")));
|
||||
|
||||
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()
|
||||
{
|
||||
if (Config::Current()["ClearDiskCacheOnClose"].toBool()) {
|
||||
// Clear all cache data
|
||||
ClearDiskCache(true);
|
||||
} else {
|
||||
// Save current cache index
|
||||
QFile cache_index_file(GetCacheIndexFilename());
|
||||
|
||||
if (cache_index_file.open(QFile::WriteOnly)) {
|
||||
QDataStream ds(&cache_index_file);
|
||||
|
||||
foreach (const HashTime& h, disk_data_) {
|
||||
ds << h.file_name;
|
||||
ds << h.hash;
|
||||
ds << h.access_time;
|
||||
ds << h.file_size;
|
||||
}
|
||||
} else {
|
||||
qWarning() << "Failed to write cache index:" << GetCacheIndexFilename();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DiskManager::CreateInstance()
|
||||
@@ -99,107 +93,178 @@ DiskManager *DiskManager::instance()
|
||||
return instance_;
|
||||
}
|
||||
|
||||
void DiskManager::Accessed(const QByteArray &hash)
|
||||
void DiskManager::Accessed(const QString &cache_folder, const QByteArray &hash)
|
||||
{
|
||||
lock_.lock();
|
||||
DiskCacheFolder* f = GetOpenFolder(cache_folder);
|
||||
|
||||
for (int i=disk_data_.size()-1;i>=0;i--) {
|
||||
const HashTime& h = disk_data_.at(i);
|
||||
|
||||
if (h.hash == hash) {
|
||||
HashTime moved_hash = h;
|
||||
|
||||
moved_hash.access_time = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
disk_data_.removeAt(i);
|
||||
disk_data_.append(moved_hash);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lock_.unlock();
|
||||
f->Accessed(hash);
|
||||
}
|
||||
|
||||
void DiskManager::Accessed(const QString &filename)
|
||||
void DiskManager::CreatedFile(const QString &cache_folder, const QString &file_name, const QByteArray &hash)
|
||||
{
|
||||
lock_.lock();
|
||||
DiskCacheFolder* f = GetOpenFolder(cache_folder);
|
||||
|
||||
for (int i=disk_data_.size()-1;i>=0;i--) {
|
||||
const HashTime& h = disk_data_.at(i);
|
||||
|
||||
if (h.file_name == filename) {
|
||||
HashTime moved_hash = h;
|
||||
|
||||
moved_hash.access_time = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
disk_data_.removeAt(i);
|
||||
disk_data_.append(moved_hash);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lock_.unlock();
|
||||
f->CreatedFile(file_name, hash);
|
||||
}
|
||||
|
||||
void DiskManager::CreatedFile(const QString &file_name, const QByteArray &hash)
|
||||
bool DiskManager::ClearDiskCache(const QString &cache_folder)
|
||||
{
|
||||
lock_.lock();
|
||||
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;
|
||||
}
|
||||
|
||||
DiskCacheFolder::DiskCacheFolder(const QString &path, QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
SetPath(path);
|
||||
}
|
||||
|
||||
DiskCacheFolder::~DiskCacheFolder()
|
||||
{
|
||||
CloseCacheFolder();
|
||||
}
|
||||
|
||||
bool DiskCacheFolder::ClearCache()
|
||||
{
|
||||
bool deleted_files = true;
|
||||
|
||||
std::list<HashTime>::iterator 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
|
||||
if (QFile::remove(i->file_name) || !QFileInfo::exists(i->file_name)) {
|
||||
emit DeletedFrame(path_, i->hash);
|
||||
i = disk_data_.erase(i);
|
||||
} else {
|
||||
qWarning() << "Failed to delete" << i->file_name;
|
||||
deleted_files = false;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return deleted_files;
|
||||
}
|
||||
|
||||
void DiskCacheFolder::Accessed(const QByteArray &hash)
|
||||
{
|
||||
std::list<HashTime>::iterator i = disk_data_.begin();
|
||||
|
||||
while (i != disk_data_.end()) {
|
||||
if (i->hash == hash) {
|
||||
// Copy access data and erase from list
|
||||
HashTime accessed_hash = *i;
|
||||
disk_data_.erase(i);
|
||||
|
||||
// Add it to the end
|
||||
disk_data_.push_back(accessed_hash);
|
||||
|
||||
// End loop
|
||||
break;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DiskCacheFolder::CreatedFile(const QString &file_name, const QByteArray &hash)
|
||||
{
|
||||
qint64 file_size = QFile(file_name).size();
|
||||
|
||||
disk_data_.append({file_name, hash, QDateTime::currentMSecsSinceEpoch(), file_size});
|
||||
disk_data_.push_back({file_name, hash, file_size});
|
||||
|
||||
consumption_ += file_size;
|
||||
|
||||
QList<QByteArray> deleted_hashes;
|
||||
|
||||
while (consumption_ > DiskLimit()) {
|
||||
while (consumption_ > limit_) {
|
||||
deleted_hashes.append(DeleteLeastRecent());
|
||||
}
|
||||
|
||||
lock_.unlock();
|
||||
|
||||
foreach (const QByteArray& h, deleted_hashes) {
|
||||
emit DeletedFrame(h);
|
||||
emit DeletedFrame(path_, h);
|
||||
}
|
||||
}
|
||||
|
||||
bool DiskManager::ClearDiskCache(bool quick_delete)
|
||||
void DiskCacheFolder::SetPath(const QString &path)
|
||||
{
|
||||
bool deleted_files;
|
||||
|
||||
lock_.lock();
|
||||
|
||||
if (quick_delete) {
|
||||
deleted_files = QDir(FileFunctions::GetMediaCacheLocation()).removeRecursively();
|
||||
// If this is currently set to a folder, close it out now
|
||||
CloseCacheFolder();
|
||||
|
||||
// Signal that disk cache is gone
|
||||
if (!disk_data_.empty()) {
|
||||
foreach (const HashTime& h, disk_data_) {
|
||||
emit DeletedFrame(path_, h.hash);
|
||||
}
|
||||
disk_data_.clear();
|
||||
} else {
|
||||
deleted_files = true;
|
||||
|
||||
for (int i=0;i<disk_data_.size();i++) {
|
||||
const HashTime& ht = disk_data_.at(i);
|
||||
|
||||
// We return a false result if any of the files fail to delete, but still try to delete as many as we can
|
||||
if (QFile::remove(ht.file_name) || !QFileInfo::exists(ht.file_name)) {
|
||||
emit DeletedFrame(ht.hash);
|
||||
disk_data_.removeAt(i);
|
||||
i--;
|
||||
} else {
|
||||
qWarning() << "Failed to delete" << ht.file_name;
|
||||
deleted_files = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lock_.unlock();
|
||||
// Set defaults
|
||||
clear_on_close_ = false;
|
||||
consumption_ = 0;
|
||||
limit_ = 21474836480; // Default to 20 GB
|
||||
|
||||
return deleted_files;
|
||||
// Set path
|
||||
path_ = path;
|
||||
|
||||
// Attempt to load existing index file from path
|
||||
QDir path_dir(path_);
|
||||
path_dir.mkpath(".");
|
||||
|
||||
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()) {
|
||||
HashTime h;
|
||||
|
||||
ds >> h.file_name;
|
||||
ds >> h.hash;
|
||||
ds >> h.file_size;
|
||||
|
||||
if (QFileInfo::exists(h.file_name)) {
|
||||
consumption_ += h.file_size;
|
||||
disk_data_.push_back(h);
|
||||
}
|
||||
}
|
||||
|
||||
cache_index_file.close();
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray DiskManager::DeleteLeastRecent()
|
||||
QByteArray DiskCacheFolder::DeleteLeastRecent()
|
||||
{
|
||||
HashTime h = disk_data_.takeFirst();
|
||||
HashTime h = disk_data_.front();
|
||||
disk_data_.pop_front();
|
||||
|
||||
QFile::remove(h.file_name);
|
||||
|
||||
@@ -208,19 +273,37 @@ QByteArray DiskManager::DeleteLeastRecent()
|
||||
return h.hash;
|
||||
}
|
||||
|
||||
qint64 DiskManager::DiskLimit()
|
||||
void DiskCacheFolder::CloseCacheFolder()
|
||||
{
|
||||
double gigabytes = Config::Current()["DiskCacheSize"].toDouble();
|
||||
if (path_.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert gigabytes to bytes
|
||||
return qRound64(gigabytes * 1073741824);
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
QString DiskManager::GetCacheIndexFilename()
|
||||
{
|
||||
QDir d(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
|
||||
d.mkpath(".");
|
||||
return d.filePath("diskindex");
|
||||
// Save current cache index
|
||||
QFile cache_index_file(index_path_);
|
||||
|
||||
if (cache_index_file.open(QFile::WriteOnly)) {
|
||||
QDataStream ds(&cache_index_file);
|
||||
|
||||
ds << limit_;
|
||||
ds << clear_on_close_;
|
||||
|
||||
foreach (const HashTime& h, disk_data_) {
|
||||
ds << h.file_name;
|
||||
ds << h.hash;
|
||||
ds << h.file_size;
|
||||
}
|
||||
|
||||
cache_index_file.close();
|
||||
} else {
|
||||
qWarning() << "Failed to write cache index:" << index_path_;
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
+93
-23
@@ -21,6 +21,7 @@
|
||||
#ifndef DISKMANAGER_H
|
||||
#define DISKMANAGER_H
|
||||
|
||||
#include <QMap>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
|
||||
@@ -28,6 +29,75 @@
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class DiskCacheFolder : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
DiskCacheFolder(const QString& path, QObject* parent = nullptr);
|
||||
|
||||
virtual ~DiskCacheFolder() override;
|
||||
|
||||
bool ClearCache();
|
||||
|
||||
void Accessed(const QByteArray& hash);
|
||||
|
||||
void CreatedFile(const QString& file_name, const QByteArray& hash);
|
||||
|
||||
const QString& GetPath() const
|
||||
{
|
||||
return path_;
|
||||
}
|
||||
|
||||
void SetPath(const QString& path);
|
||||
|
||||
qint64 GetLimit() const
|
||||
{
|
||||
return limit_;
|
||||
}
|
||||
|
||||
bool GetClearOnClose() const
|
||||
{
|
||||
return clear_on_close_;
|
||||
}
|
||||
|
||||
void SetLimit(qint64 l)
|
||||
{
|
||||
limit_ = l;
|
||||
}
|
||||
|
||||
void SetClearOnClose(bool e)
|
||||
{
|
||||
clear_on_close_ = e;
|
||||
}
|
||||
|
||||
signals:
|
||||
void DeletedFrame(const QString& path, const QByteArray& hash);
|
||||
|
||||
private:
|
||||
QByteArray DeleteLeastRecent();
|
||||
|
||||
void CloseCacheFolder();
|
||||
|
||||
QString path_;
|
||||
|
||||
QString index_path_;
|
||||
|
||||
struct HashTime {
|
||||
QString file_name;
|
||||
QByteArray hash;
|
||||
qint64 file_size;
|
||||
};
|
||||
|
||||
std::list<HashTime> disk_data_;
|
||||
|
||||
qint64 consumption_;
|
||||
|
||||
qint64 limit_;
|
||||
|
||||
bool clear_on_close_;
|
||||
|
||||
};
|
||||
|
||||
class DiskManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -38,16 +108,33 @@ public:
|
||||
|
||||
static DiskManager* instance();
|
||||
|
||||
void Accessed(const QByteArray& hash);
|
||||
bool ClearDiskCache(const QString& cache_folder);
|
||||
|
||||
void Accessed(const QString& filename);
|
||||
DiskCacheFolder* GetDefaultCacheFolder() const
|
||||
{
|
||||
// The first folder will always be the default
|
||||
return open_folders_.first();
|
||||
}
|
||||
|
||||
void CreatedFile(const QString& file_name, const QByteArray& hash);
|
||||
const QString& GetDefaultCachePath() const
|
||||
{
|
||||
return GetDefaultCacheFolder()->GetPath();
|
||||
}
|
||||
|
||||
bool ClearDiskCache(bool quick_delete);
|
||||
DiskCacheFolder* GetOpenFolder(const QString& path);
|
||||
|
||||
const QVector<DiskCacheFolder*>& GetOpenFolders() const
|
||||
{
|
||||
return open_folders_;
|
||||
}
|
||||
|
||||
public slots:
|
||||
void Accessed(const QString& cache_folder, const QByteArray& hash);
|
||||
|
||||
void CreatedFile(const QString& cache_folder, const QString& file_name, const QByteArray& hash);
|
||||
|
||||
signals:
|
||||
void DeletedFrame(const QByteArray& hash);
|
||||
void DeletedFrame(const QString& path, const QByteArray& hash);
|
||||
|
||||
private:
|
||||
DiskManager();
|
||||
@@ -56,24 +143,7 @@ private:
|
||||
|
||||
static DiskManager* instance_;
|
||||
|
||||
QByteArray DeleteLeastRecent();
|
||||
|
||||
qint64 DiskLimit();
|
||||
|
||||
static QString GetCacheIndexFilename();
|
||||
|
||||
struct HashTime {
|
||||
QString file_name;
|
||||
QByteArray hash;
|
||||
qint64 access_time;
|
||||
qint64 file_size;
|
||||
};
|
||||
|
||||
QList<HashTime> disk_data_;
|
||||
|
||||
qint64 consumption_;
|
||||
|
||||
QMutex lock_;
|
||||
QVector<DiskCacheFolder*> open_folders_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -227,13 +227,18 @@ QVector<rational> FrameHashCache::GetInvalidatedFrames(const TimeRange &intersec
|
||||
bool FrameHashCache::SaveCacheFrame(const QByteArray& hash,
|
||||
char* data,
|
||||
const VideoParams& vparam,
|
||||
int linesize_bytes)
|
||||
int linesize_bytes) const
|
||||
{
|
||||
QString fn = CachePathName(hash);
|
||||
|
||||
if (SaveCacheFrame(fn, data, vparam, linesize_bytes)) {
|
||||
// Register frame with the disk manager
|
||||
DiskManager::instance()->CreatedFile(fn, hash);
|
||||
QMetaObject::invokeMethod(DiskManager::instance(),
|
||||
"CreatedFile",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(QString, GetCacheDirectory()),
|
||||
Q_ARG(QString, fn),
|
||||
Q_ARG(QByteArray, hash));
|
||||
|
||||
return true;
|
||||
} else {
|
||||
@@ -241,7 +246,7 @@ bool FrameHashCache::SaveCacheFrame(const QByteArray& hash,
|
||||
}
|
||||
}
|
||||
|
||||
bool FrameHashCache::SaveCacheFrame(const QByteArray &hash, FramePtr frame)
|
||||
bool FrameHashCache::SaveCacheFrame(const QByteArray &hash, FramePtr frame) const
|
||||
{
|
||||
if (frame) {
|
||||
return SaveCacheFrame(hash, frame->data(), frame->video_params(), frame->linesize_bytes());
|
||||
@@ -251,12 +256,12 @@ bool FrameHashCache::SaveCacheFrame(const QByteArray &hash, FramePtr frame)
|
||||
}
|
||||
}
|
||||
|
||||
FramePtr FrameHashCache::LoadCacheFrame(const QByteArray &hash)
|
||||
FramePtr FrameHashCache::LoadCacheFrame(const QByteArray &hash) const
|
||||
{
|
||||
return LoadCacheFrame(CachePathName(hash));
|
||||
}
|
||||
|
||||
FramePtr FrameHashCache::LoadCacheFrame(const QString &fn)
|
||||
FramePtr FrameHashCache::LoadCacheFrame(const QString &fn) const
|
||||
{
|
||||
FramePtr frame = nullptr;
|
||||
|
||||
@@ -367,8 +372,13 @@ void FrameHashCache::ShiftEvent(const rational &from, const rational &to)
|
||||
}
|
||||
}
|
||||
|
||||
void FrameHashCache::HashDeleted(const QByteArray &hash)
|
||||
void FrameHashCache::HashDeleted(const QString& s, const QByteArray &hash)
|
||||
{
|
||||
QString cache_dir = GetCacheDirectory();
|
||||
if (cache_dir.isEmpty() || s != cache_dir) {
|
||||
return;
|
||||
}
|
||||
|
||||
QMutexLocker locker(lock());
|
||||
|
||||
TimeRangeList invalidated;
|
||||
@@ -390,22 +400,26 @@ void FrameHashCache::HashDeleted(const QByteArray &hash)
|
||||
}
|
||||
}
|
||||
|
||||
QString FrameHashCache::CachePathName(const QByteArray& hash)
|
||||
QString FrameHashCache::CachePathName(const QByteArray& hash) const
|
||||
{
|
||||
QString ext = GetFormatExtension();
|
||||
|
||||
QDir cache_dir(QDir(FileFunctions::GetMediaCacheLocation()).filePath(QString(hash.left(1).toHex())));
|
||||
QDir cache_dir(QDir(GetCacheDirectory()).filePath(QString(hash.left(1).toHex())));
|
||||
cache_dir.mkpath(".");
|
||||
|
||||
QString filename = QStringLiteral("%1%2").arg(QString(hash.mid(1).toHex()), ext);
|
||||
|
||||
// Register that in some way this hash has been accessed
|
||||
DiskManager::instance()->Accessed(hash);
|
||||
QMetaObject::invokeMethod(DiskManager::instance(),
|
||||
"Accessed",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(QString, GetCacheDirectory()),
|
||||
Q_ARG(QByteArray, hash));
|
||||
|
||||
return cache_dir.filePath(filename);
|
||||
}
|
||||
|
||||
bool FrameHashCache::SaveCacheFrame(const QString &filename, char *data, const VideoParams &vparam, int linesize_bytes)
|
||||
bool FrameHashCache::SaveCacheFrame(const QString &filename, char *data, const VideoParams &vparam, int linesize_bytes) const
|
||||
{
|
||||
Q_ASSERT(PixelFormat::FormatIsFloat(vparam.format()));
|
||||
|
||||
|
||||
@@ -58,13 +58,13 @@ public:
|
||||
/**
|
||||
* @brief Return the path of the cached image at this time
|
||||
*/
|
||||
static QString CachePathName(const QByteArray &hash);
|
||||
QString CachePathName(const QByteArray &hash) const;
|
||||
|
||||
static bool SaveCacheFrame(const QString& filename, char *data, const VideoParams &vparam, int linesize_bytes);
|
||||
static bool SaveCacheFrame(const QByteArray& hash, char *data, const VideoParams &vparam, int linesize_bytes);
|
||||
static bool SaveCacheFrame(const QByteArray& hash, FramePtr frame);
|
||||
static FramePtr LoadCacheFrame(const QByteArray& hash);
|
||||
static FramePtr LoadCacheFrame(const QString& fn);
|
||||
bool SaveCacheFrame(const QString& filename, char *data, const VideoParams &vparam, int linesize_bytes) const;
|
||||
bool SaveCacheFrame(const QByteArray& hash, char *data, const VideoParams &vparam, int linesize_bytes) const;
|
||||
bool SaveCacheFrame(const QByteArray& hash, FramePtr frame) const;
|
||||
FramePtr LoadCacheFrame(const QByteArray& hash) const;
|
||||
FramePtr LoadCacheFrame(const QString& fn) const;
|
||||
|
||||
static QString GetFormatExtension();
|
||||
|
||||
@@ -87,7 +87,7 @@ private:
|
||||
rational timebase_;
|
||||
|
||||
private slots:
|
||||
void HashDeleted(const QByteArray& hash);
|
||||
void HashDeleted(const QString &s, const QByteArray& hash);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
#include "node/output/viewer/viewer.h"
|
||||
#include "project/item/sequence/sequence.h"
|
||||
#include "project/project.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
void PlaybackCache::Invalidate(const TimeRange &r)
|
||||
@@ -190,4 +194,25 @@ void PlaybackCache::RemoveRangeFromJobs(const TimeRange &remove)
|
||||
}
|
||||
}
|
||||
|
||||
QString PlaybackCache::GetCacheDirectory() const
|
||||
{
|
||||
// NOTE: A lot of assumptions in this behavior
|
||||
ViewerOutput* viewer = static_cast<ViewerOutput*>(parent());
|
||||
if (!viewer) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
Sequence* sequence = static_cast<Sequence*>(viewer->parent());
|
||||
if (!sequence) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
Project* project = sequence->project();
|
||||
if (!project) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
return project->cache_path();
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -103,6 +103,8 @@ protected:
|
||||
|
||||
virtual void ShiftEvent(const rational& from, const rational& to);
|
||||
|
||||
QString GetCacheDirectory() const;
|
||||
|
||||
QMutex* lock()
|
||||
{
|
||||
return &lock_;
|
||||
|
||||
@@ -75,7 +75,7 @@ bool PreCacheTask::Run()
|
||||
|
||||
QFuture<void> PreCacheTask::DownloadFrame(FramePtr frame, const QByteArray &hash)
|
||||
{
|
||||
return QtConcurrent::run(&download_threads_, FrameHashCache::SaveCacheFrame, hash, frame);
|
||||
return QtConcurrent::run(&download_threads_, viewer()->video_frame_cache(), &FrameHashCache::SaveCacheFrame, hash, frame);
|
||||
}
|
||||
|
||||
void PreCacheTask::FrameDownloaded(const QByteArray &hash, const std::list<rational> ×, qint64 job_time)
|
||||
|
||||
@@ -33,6 +33,7 @@ add_subdirectory(nodeview)
|
||||
add_subdirectory(nodeparamview)
|
||||
add_subdirectory(nodetableview)
|
||||
add_subdirectory(panel)
|
||||
add_subdirectory(path)
|
||||
add_subdirectory(pixelsampler)
|
||||
add_subdirectory(playbackcontrols)
|
||||
add_subdirectory(projectexplorer)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 Olive Team
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
widget/path/pathwidget.h
|
||||
widget/path/pathwidget.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,68 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
|
||||
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 "pathwidget.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileDialog>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
#include "common/filefunctions.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
PathWidget::PathWidget(const QString &path, QWidget *parent) :
|
||||
QWidget(parent)
|
||||
{
|
||||
QHBoxLayout* layout = new QHBoxLayout(this);
|
||||
layout->setMargin(0);
|
||||
|
||||
path_edit_ = new QLineEdit();
|
||||
path_edit_->setText(path);
|
||||
layout->addWidget(path_edit_);
|
||||
connect(path_edit_, &QLineEdit::textChanged, this, &PathWidget::LineEditChanged);
|
||||
|
||||
browse_btn_ = new QPushButton(tr("Browse"));
|
||||
layout->addWidget(browse_btn_);
|
||||
|
||||
connect(browse_btn_, &QPushButton::clicked, this, &PathWidget::BrowseClicked);
|
||||
}
|
||||
|
||||
void PathWidget::BrowseClicked()
|
||||
{
|
||||
QString dir = QFileDialog::getExistingDirectory(static_cast<QWidget*>(parent()),
|
||||
tr("Browse for path"),
|
||||
path_edit_->text());
|
||||
|
||||
if (!dir.isEmpty()) {
|
||||
path_edit_->setText(dir);
|
||||
}
|
||||
}
|
||||
|
||||
void PathWidget::LineEditChanged()
|
||||
{
|
||||
if (FileFunctions::DirectoryIsValid(text(), false)) {
|
||||
path_edit_->setStyleSheet(QString());
|
||||
} else {
|
||||
path_edit_->setStyleSheet(QStringLiteral("QLineEdit {color: red;}"));
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
@@ -0,0 +1,57 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef PATHWIDGET_H
|
||||
#define PATHWIDGET_H
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "common/define.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class PathWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PathWidget(const QString& path,
|
||||
QWidget* parent = nullptr);
|
||||
|
||||
QString text() const
|
||||
{
|
||||
return path_edit_->text();
|
||||
}
|
||||
|
||||
private slots:
|
||||
void BrowseClicked();
|
||||
|
||||
void LineEditChanged();
|
||||
|
||||
private:
|
||||
QLineEdit* path_edit_;
|
||||
|
||||
QPushButton* browse_btn_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // PATHWIDGET_H
|
||||
@@ -361,9 +361,9 @@ void ViewerWidget::SetGizmos(Node *node)
|
||||
display_widget_->SetGizmos(node);
|
||||
}
|
||||
|
||||
FramePtr DecodeCachedImage(const QString &fn, const rational& time)
|
||||
FramePtr ViewerWidget::DecodeCachedImage(const QString &fn, const rational& time) const
|
||||
{
|
||||
FramePtr frame = FrameHashCache::LoadCacheFrame(fn);
|
||||
FramePtr frame = GetConnectedNode()->video_frame_cache()->LoadCacheFrame(fn);
|
||||
|
||||
if (frame) {
|
||||
frame->set_timestamp(time);
|
||||
@@ -374,7 +374,7 @@ FramePtr DecodeCachedImage(const QString &fn, const rational& time)
|
||||
return frame;
|
||||
}
|
||||
|
||||
void DecodeCachedImage(RenderTicketPtr ticket, const QString &fn, const rational& time)
|
||||
void ViewerWidget::DecodeCachedImage(RenderTicketPtr ticket, const QString &fn, const rational& time) const
|
||||
{
|
||||
ticket->Finish(QVariant::fromValue(DecodeCachedImage(fn, time)));
|
||||
}
|
||||
@@ -606,7 +606,7 @@ RenderTicketPtr ViewerWidget::GetFrame(const rational &t, bool clear_render_queu
|
||||
// Frame has been cached, grab the frame
|
||||
RenderTicketPtr ticket = std::make_shared<RenderTicket>(RenderTicket::kTypeVideo,
|
||||
QVariant::fromValue(t));
|
||||
QtConcurrent::run(DecodeCachedImage, ticket, cache_fn, t);
|
||||
QtConcurrent::run(this, &ViewerWidget::DecodeCachedImage, ticket, cache_fn, t);
|
||||
|
||||
return ticket;
|
||||
}
|
||||
|
||||
@@ -204,6 +204,10 @@ private:
|
||||
|
||||
void PopOldestFrameFromPlaybackQueue();
|
||||
|
||||
FramePtr DecodeCachedImage(const QString &fn, const rational& time) const;
|
||||
|
||||
void DecodeCachedImage(RenderTicketPtr ticket, const QString &fn, const rational& time) const;
|
||||
|
||||
QStackedWidget* stack_;
|
||||
|
||||
ViewerSizer* sizer_;
|
||||
|
||||
Reference in New Issue
Block a user