Add folder condition

It allows you to watch for chagnes in a given folder
This commit is contained in:
WarmUpTill 2024-03-17 12:53:59 +01:00 committed by WarmUpTill
parent a01af6cfc3
commit defbdf8b7a
4 changed files with 516 additions and 0 deletions

View File

@ -683,6 +683,17 @@ AdvSceneSwitcher.condition.clipboard.condition.isImage="Clipboard contains an im
AdvSceneSwitcher.condition.clipboard.condition.isURL="Clipboard contains an URL"
AdvSceneSwitcher.condition.clipboard.condition.matches="Clipboard content matches"
AdvSceneSwitcher.condition.clipboard.condition.entry="{{conditions}}{{regex}}{{urlInfo}}"
AdvSceneSwitcher.condition.folder="Folder watch"
AdvSceneSwitcher.condition.folder.tooltip="This condition type will allow you to monitor the contents of a folder.\nNote that the monitoring will *not* recursively scan for changes in sub directories within directories of the selected folder!"
AdvSceneSwitcher.condition.folder.condition.any="Any change happened"
AdvSceneSwitcher.condition.folder.condition.fileAdd="A file was added"
AdvSceneSwitcher.condition.folder.condition.fileChange="A file was modified"
AdvSceneSwitcher.condition.folder.condition.fileRemove="A file was removed"
AdvSceneSwitcher.condition.folder.condition.folderAdd="A directory was added"
AdvSceneSwitcher.condition.folder.condition.folderRemove="A directory was removed"
AdvSceneSwitcher.condition.folder.entry="{{conditions}}in{{folder}}{{tooltip}}"
AdvSceneSwitcher.condition.folder.enableFilter="Only evaluate to true, if the changed path matches a patern"
AdvSceneSwitcher.condition.folder.entry.filter="{{filter}}{{regex}}"
; Macro Actions
AdvSceneSwitcher.action.scene="Switch scene"
@ -1734,6 +1745,12 @@ AdvSceneSwitcher.tempVar.clipboard.text.description="The text contained in the c
AdvSceneSwitcher.tempVar.file.content="File content"
AdvSceneSwitcher.tempVar.file.date="File modification date"
AdvSceneSwitcher.tempVar.folder.newFiles="New files"
AdvSceneSwitcher.tempVar.folder.changedFiles="Changed files"
AdvSceneSwitcher.tempVar.folder.removedFiles="Removed files"
AdvSceneSwitcher.tempVar.folder.newDirs="New directories"
AdvSceneSwitcher.tempVar.folder.removedDirs="Removed directories"
AdvSceneSwitcher.selectScene="--select scene--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.selectCurrentScene="Current Scene"

View File

@ -87,6 +87,8 @@ target_sources(
macro-condition-file.hpp
macro-condition-filter.cpp
macro-condition-filter.hpp
macro-condition-folder.cpp
macro-condition-folder.hpp
macro-condition-hotkey.cpp
macro-condition-hotkey.hpp
macro-condition-idle.cpp

View File

@ -0,0 +1,389 @@
#include "macro-condition-folder.hpp"
#include "macro-helpers.hpp"
#include "layout-helpers.hpp"
#include "ui-helpers.hpp"
#include <QDir>
#include <QFileInfo>
namespace advss {
const std::string MacroConditionFolder::id = "folder";
bool MacroConditionFolder::_registered = MacroConditionFactory::Register(
MacroConditionFolder::id,
{MacroConditionFolder::Create, MacroConditionFolderEdit::Create,
"AdvSceneSwitcher.condition.folder"});
static const std::map<MacroConditionFolder::Condition, std::string> conditions =
{
{MacroConditionFolder::Condition::ANY,
"AdvSceneSwitcher.condition.folder.condition.any"},
{MacroConditionFolder::Condition::FILE_ADD,
"AdvSceneSwitcher.condition.folder.condition.fileAdd"},
{MacroConditionFolder::Condition::FILE_CHANGE,
"AdvSceneSwitcher.condition.folder.condition.fileChange"},
{MacroConditionFolder::Condition::FILE_REMOVE,
"AdvSceneSwitcher.condition.folder.condition.fileRemove"},
{MacroConditionFolder::Condition::FOLDER_ADD,
"AdvSceneSwitcher.condition.folder.condition.folderAdd"},
{MacroConditionFolder::Condition::FOLDER_REMOVE,
"AdvSceneSwitcher.condition.folder.condition.folderRemove"},
};
MacroConditionFolder::MacroConditionFolder(Macro *m) : MacroCondition(m, true)
{
}
bool MacroConditionFolder::CheckCondition()
{
bool ret = _matched;
if (_lastWatchedValue != _folder.UnresolvedValue()) {
SetupWatcher();
}
SetTempVarValues();
_newFiles.clear();
_changedFiles.clear();
_removedFiles.clear();
_newDirs.clear();
_removedDirs.clear();
_matched = false;
return ret;
}
bool MacroConditionFolder::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
_folder.Save(obj, "file");
obs_data_set_bool(obj, "enableFilter", _enableFilter);
_regex.Save(obj);
_filter.Save(obj, "filter");
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
return true;
}
bool MacroConditionFolder::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_folder.Load(obj, "file");
_enableFilter = obs_data_get_bool(obj, "enableFilter");
_regex.Load(obj);
_regex.SetEnabled(true); // Already controlled via _enableFilter
_filter.Save(obj, "filter");
_condition = static_cast<Condition>(obs_data_get_int(obj, "condition"));
SetupWatcher();
return true;
}
std::string MacroConditionFolder::GetShortDesc() const
{
return _folder.UnresolvedValue();
}
void MacroConditionFolder::SetFolder(const std::string &folder)
{
_folder = folder;
SetupWatcher();
}
static QSet<QString> getFilesInDir(const QString &path)
{
QSet<QString> result;
for (const auto &file : QDir(path).entryList(QDir::Files)) {
result << file;
}
return result;
}
static QSet<QString> getDirsInDir(const QString &path)
{
QSet<QString> result;
for (const auto &dir : QDir(path).entryList(QDir::AllDirs)) {
result << dir;
}
return result;
}
void MacroConditionFolder::DirectoryChanged(const QString &path)
{
if (MacroIsPaused(GetMacro())) {
return;
}
if (_enableFilter && !_regex.Matches(path.toStdString(), _filter)) {
return;
}
auto currentFiles = getFilesInDir(path);
auto currentDirs = getDirsInDir(path);
if (currentFiles.count() > _currentFiles.count()) {
_newFiles += currentFiles - _currentFiles;
} else {
_removedFiles += _currentFiles - currentFiles;
}
if (currentDirs.count() > _currentDirs.count()) {
_newDirs += currentDirs - _currentDirs;
} else {
_removedDirs += _currentDirs - currentDirs;
}
switch (_condition) {
case Condition::ANY:
_matched = true;
break;
case Condition::FILE_ADD:
_matched = _newFiles.count() > 0;
break;
case Condition::FILE_REMOVE:
_matched = _removedFiles.count() > 0;
break;
case Condition::FOLDER_ADD:
_matched = _newDirs.count() > 0;
break;
case Condition::FOLDER_REMOVE:
_matched = _removedDirs.count() > 0;
break;
default:
break;
}
for (const auto &newFile : _newFiles) {
_watcher->addPath(path + "/" + newFile);
}
_currentFiles = currentFiles;
_currentDirs = currentDirs;
}
void MacroConditionFolder::FileChanged(const QString &path)
{
QFileInfo fileInfo(path);
if (!fileInfo.exists()) {
return;
}
_changedFiles << fileInfo.fileName();
if (_condition == Condition::FILE_CHANGE) {
_matched = true;
}
}
static QStringList getFileWatcherList(const QSet<QString> &files,
const QString &dirName)
{
QStringList list;
for (const auto &value : files) {
list << (dirName + "/" + value);
}
return list;
}
void MacroConditionFolder::SetupWatcher()
{
_watcher = std::make_unique<QFileSystemWatcher>();
const auto path = QString::fromStdString(_folder);
_currentFiles = getFilesInDir(path);
_currentDirs = getDirsInDir(path);
_lastWatchedValue = _folder.UnresolvedValue();
connect(_watcher.get(), SIGNAL(directoryChanged(const QString &)), this,
SLOT(DirectoryChanged(const QString &)));
connect(_watcher.get(), SIGNAL(fileChanged(const QString &)), this,
SLOT(FileChanged(const QString &)));
_watcher.get()->addPaths(getFileWatcherList(_currentFiles, path));
_watcher->addPath(path);
}
void MacroConditionFolder::SetTempVarValues()
{
auto setVarHelper = [this](const QSet<QString> &set,
const std::string &id) {
std::string result;
for (const auto &value : set) {
result += value.toStdString() + "\n";
}
if (result.size() > 0) {
result.pop_back();
}
SetTempVarValue(id, result);
};
setVarHelper(_newFiles, "newFiles");
setVarHelper(_changedFiles, "changedFiles");
setVarHelper(_removedFiles, "removedFiles");
setVarHelper(_newDirs, "newDirs");
setVarHelper(_removedDirs, "removedDirs");
}
void MacroConditionFolder::SetupTempVars()
{
MacroCondition::SetupTempVars();
AddTempvar("newFiles",
obs_module_text("AdvSceneSwitcher.tempVar.folder.newFiles"));
AddTempvar("changedFiles",
obs_module_text(
"AdvSceneSwitcher.tempVar.folder.changedFiles"));
AddTempvar("removedFiles",
obs_module_text(
"AdvSceneSwitcher.tempVar.folder.removedFiles"));
AddTempvar("newDirs",
obs_module_text("AdvSceneSwitcher.tempVar.folder.newDirs"));
AddTempvar(
"removedDirs",
obs_module_text("AdvSceneSwitcher.tempVar.folder.removedDirs"));
}
static void populateConditions(QComboBox *list)
{
for (const auto &[value, name] : conditions) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(value));
}
}
MacroConditionFolderEdit::MacroConditionFolderEdit(
QWidget *parent, std::shared_ptr<MacroConditionFolder> entryData)
: QWidget(parent),
_conditions(new QComboBox()),
_folder(new FileSelection(FileSelection::Type::FOLDER)),
_enableFilter(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.folder.enableFilter"))),
_filterLayout(new QHBoxLayout()),
_regex(new RegexConfigWidget(this, false)),
_filter(new VariableLineEdit(this))
{
QString path = GetThemeTypeName() == "Light"
? ":/res/images/help.svg"
: ":/res/images/help_light.svg";
QIcon icon(path);
QPixmap pixmap = icon.pixmap(QSize(16, 16));
auto tooltipLabel = new QLabel(this);
tooltipLabel->setPixmap(pixmap);
tooltipLabel->setToolTip(
obs_module_text("AdvSceneSwitcher.condition.folder.tooltip"));
populateConditions(_conditions);
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_folder, SIGNAL(PathChanged(const QString &)), this,
SLOT(PathChanged(const QString &)));
QWidget::connect(_enableFilter, SIGNAL(stateChanged(int)), this,
SLOT(EnableFilterChanged(int)));
QWidget::connect(_regex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(RegexChanged(const RegexConfig &)));
QWidget::connect(_filter, SIGNAL(editingFinished()), this,
SLOT(FilterChanged()));
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{conditions}}", _conditions}, {"{{folder}}", _folder},
{"{{tooltip}}", tooltipLabel}, {"{{regex}}", _regex},
{"{{filter}}", _filter},
};
auto entryLayout = new QHBoxLayout();
entryLayout->setContentsMargins(0, 0, 0, 0);
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.folder.entry"),
entryLayout, widgetPlaceholders, false);
_filterLayout->setContentsMargins(0, 0, 0, 0);
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.condition.folder.entry.filter"),
_filterLayout, widgetPlaceholders, false);
auto layout = new QVBoxLayout();
layout->addLayout(entryLayout);
layout->addWidget(_enableFilter);
layout->addLayout(_filterLayout);
setLayout(layout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionFolderEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_conditions->setCurrentIndex(_conditions->findData(
static_cast<int>(_entryData->_condition)));
_folder->SetPath(_entryData->GetFolder());
_enableFilter->setChecked(_entryData->_enableFilter);
_regex->SetRegexConfig(_entryData->_regex);
_filter->setText(_entryData->_filter);
SetWidgetVisibility();
}
void MacroConditionFolderEdit::ConditionChanged(int index)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_condition = static_cast<MacroConditionFolder::Condition>(
_conditions->itemData(index).toInt());
}
void MacroConditionFolderEdit::PathChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->SetFolder(text.toStdString());
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroConditionFolderEdit::RegexChanged(const RegexConfig &regex)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_regex = regex;
_entryData->_regex.SetEnabled(true);
}
void MacroConditionFolderEdit::FilterChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_filter = _filter->text().toStdString();
}
void MacroConditionFolderEdit::EnableFilterChanged(int value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_enableFilter = value;
SetWidgetVisibility();
}
void MacroConditionFolderEdit::SetWidgetVisibility()
{
SetLayoutVisible(_filterLayout, _entryData->_enableFilter);
adjustSize();
updateGeometry();
}
} // namespace advss

View File

@ -0,0 +1,108 @@
#pragma once
#include "macro-condition-edit.hpp"
#include "file-selection.hpp"
#include "regex-config.hpp"
#include "variable-line-edit.hpp"
#include <QFileSystemWatcher>
namespace advss {
class MacroConditionFolder : public QObject, public MacroCondition {
Q_OBJECT
public:
MacroConditionFolder(Macro *m);
bool CheckCondition();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetShortDesc() const;
std::string GetId() const { return id; };
static std::shared_ptr<MacroCondition> Create(Macro *m)
{
return std::make_shared<MacroConditionFolder>(m);
}
void SetFolder(const std::string &);
StringVariable GetFolder() const { return _folder; }
enum class Condition {
ANY,
FILE_ADD,
FILE_CHANGE,
FILE_REMOVE,
FOLDER_ADD,
FOLDER_REMOVE,
};
Condition _condition = Condition::ANY;
bool _enableFilter = false;
RegexConfig _regex = RegexConfig(true);
StringVariable _filter = ".*";
private slots:
void DirectoryChanged(const QString &);
void FileChanged(const QString &);
private:
void SetupWatcher();
void SetTempVarValues();
void SetupTempVars();
StringVariable _folder = obs_module_text("AdvSceneSwitcher.enterPath");
std::unique_ptr<QFileSystemWatcher> _watcher;
std::string _lastWatchedValue = "";
bool _matched = false;
QSet<QString> _newFiles;
QSet<QString> _changedFiles;
QSet<QString> _removedFiles;
QSet<QString> _newDirs;
QSet<QString> _removedDirs;
QSet<QString> _currentFiles;
QSet<QString> _currentDirs;
static bool _registered;
static const std::string id;
};
class MacroConditionFolderEdit : public QWidget {
Q_OBJECT
public:
MacroConditionFolderEdit(
QWidget *parent,
std::shared_ptr<MacroConditionFolder> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionFolderEdit(
parent,
std::dynamic_pointer_cast<MacroConditionFolder>(cond));
}
private slots:
void ConditionChanged(int index);
void PathChanged(const QString &text);
void EnableFilterChanged(int value);
void RegexChanged(const RegexConfig &);
void FilterChanged();
signals:
void HeaderInfoChanged(const QString &);
private:
void SetWidgetVisibility();
QComboBox *_conditions;
FileSelection *_folder;
QCheckBox *_enableFilter;
QHBoxLayout *_filterLayout;
RegexConfigWidget *_regex;
VariableLineEdit *_filter;
std::shared_ptr<MacroConditionFolder> _entryData;
bool _loading = true;
};
} // namespace advss