Add option to match changed content

This commit is contained in:
WarmUpTill 2023-12-13 17:04:26 +01:00
parent 51d121bda2
commit 7ada858046
3 changed files with 236 additions and 90 deletions

View File

@ -195,12 +195,16 @@ AdvSceneSwitcher.condition.window.entry.text="{{checkText}}Window contains text{
AdvSceneSwitcher.condition.window.entry.text.note="This option might not work on every text being displayed in a window.\nIf that is the case, you can consider using the Video conditions OCR check instead."
AdvSceneSwitcher.condition.window.entry.currentFocus="Current focus window:{{focusWindow}}"
AdvSceneSwitcher.condition.file="File"
AdvSceneSwitcher.condition.file.type.match="matches"
AdvSceneSwitcher.condition.file.type.contentChange="content changed"
AdvSceneSwitcher.condition.file.type.dateChange="modification date changed"
AdvSceneSwitcher.condition.file.type.match="Matches"
AdvSceneSwitcher.condition.file.type.contentChange="Content changed"
AdvSceneSwitcher.condition.file.type.dateChange="Modification date changed"
AdvSceneSwitcher.condition.file.type.changesMatch="Changed lines match"
AdvSceneSwitcher.condition.file.remote="Remote file"
AdvSceneSwitcher.condition.file.local="Local file"
AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
AdvSceneSwitcher.condition.file.changeMatchType.any="Any lines"
AdvSceneSwitcher.condition.file.changeMatchType.added="Added lines"
AdvSceneSwitcher.condition.file.changeMatchType.removed="Removed lines"
AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}{{changeMatchType}}{{useRegex}}"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.media="Media"
AdvSceneSwitcher.condition.media.source="Source"

View File

@ -5,7 +5,8 @@
#include <QTextStream>
#include <QFileDialog>
#include <regex>
#include <array>
#include <tuple>
namespace advss {
@ -41,70 +42,63 @@ static std::string getRemoteData(std::string &url)
return readBuffer;
}
bool MacroConditionFile::MatchFileContent(QString &filedata) const
static bool stringsMatch(const QString &text, const std::string &pattern,
const RegexConfig &regex)
{
if (_regex.Enabled()) {
auto expr = _regex.GetRegularExpression(_text);
if (!expr.isValid()) {
return false;
}
auto match = expr.match(filedata);
return match.hasMatch();
auto qpattern = QString::fromStdString(pattern);
if (regex.Enabled()) {
return regex.Matches(text, qpattern);
}
QString text = QString::fromStdString(_text);
return CompareIgnoringLineEnding(text, filedata);
auto textCopy = text;
return CompareIgnoringLineEnding(qpattern, textCopy);
}
bool MacroConditionFile::CheckRemoteFileContent()
static std::optional<QString> getFileContent(MacroConditionFile::FileType type,
const std::string &filePath)
{
std::string path = _file;
std::string data = getRemoteData(path);
SetVariableValue(data);
QString qdata = QString::fromStdString(data);
return MatchFileContent(qdata);
QString filedata;
switch (type) {
case MacroConditionFile::FileType::LOCAL: {
std::string path = filePath;
QFile file(QString::fromStdString(path));
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return {};
}
filedata = QTextStream(&file).readAll();
file.close();
} break;
case MacroConditionFile::FileType::REMOTE: {
std::string path = filePath;
std::string data = getRemoteData(path);
filedata = QString::fromStdString(data);
} break;
default:
break;
}
return filedata;
}
bool MacroConditionFile::CheckLocalFileContent()
bool MacroConditionFile::CheckFileContentMatch()
{
QFile file(QString::fromStdString(_file));
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
auto fileData = getFileContent(_fileType, _file);
if (!fileData) {
return false;
}
QString filedata = QTextStream(&file).readAll();
SetVariableValue(filedata.toStdString());
bool match = MatchFileContent(filedata);
file.close();
const bool match = stringsMatch(*fileData, _text, _regex);
SetVariableValue(fileData->toStdString());
return match;
}
bool MacroConditionFile::CheckChangeContent()
{
QString filedata;
switch (_fileType) {
case FileType::LOCAL: {
std::string path = _file;
QFile file(QString::fromStdString(path));
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
}
filedata = QTextStream(&file).readAll();
file.close();
} break;
case FileType::REMOTE: {
std::string path = _file;
std::string data = getRemoteData(path);
QString filedata = QString::fromStdString(data);
} break;
default:
break;
auto filedata = getFileContent(_fileType, _file);
if (!filedata) {
return false;
}
size_t newHash = strHash(filedata.toUtf8().constData());
size_t newHash = strHash(filedata->toUtf8().constData());
const bool contentChanged = newHash != _lastHash;
_lastHash = newHash;
SetVariableValue(contentChanged ? "true" : "false");
return contentChanged;
}
@ -119,35 +113,94 @@ bool MacroConditionFile::CheckChangeDate()
SetVariableValue(newLastMod.toString().toStdString());
const bool dateChanged = _lastMod != newLastMod;
_lastMod = newLastMod;
SetVariableValue(dateChanged ? "true" : "false");
return dateChanged;
}
static QStringList splitLines(const QString &string)
{
static auto regex = QRegularExpression("\n|\r\n|\r");
return string.split(regex);
}
bool MacroConditionFile::CheckChangedMatch()
{
static bool setupDone = false;
if (!setupDone) {
auto content = getFileContent(_fileType, _file);
if (!content) {
return false;
}
_previousContent = splitLines(*content);
setupDone = true;
return false;
}
auto currentFileContent = getFileContent(_fileType, _file);
if (!currentFileContent) {
return false;
}
auto currentContent = splitLines(*currentFileContent);
auto newContent = currentContent;
QStringList addedLines;
QStringList removedLines;
for (const auto &line : newContent) {
if (auto it = std::find(_previousContent.begin(),
_previousContent.end(),
line) == _previousContent.end()) {
addedLines.push_back(line);
}
}
for (const auto &line : _previousContent) {
if (std::find(newContent.begin(), newContent.end(), line) ==
newContent.end()) {
removedLines.push_back(line);
}
}
_previousContent = currentContent;
if (_changeMatchType == ChangeMatchType::ADDED ||
_changeMatchType == ChangeMatchType::ANY) {
for (const auto &line : addedLines) {
if (stringsMatch(line, _text, _regex)) {
return true;
}
}
}
if (_changeMatchType == ChangeMatchType::REMOVED ||
_changeMatchType == ChangeMatchType::ANY) {
for (const auto &line : removedLines) {
if (stringsMatch(line, _text, _regex)) {
return true;
}
}
}
return false;
}
bool MacroConditionFile::CheckCondition()
{
bool ret = false;
switch (_condition) {
case MacroConditionFile::ConditionType::MATCH:
if (_fileType == FileType::REMOTE) {
ret = CheckRemoteFileContent();
break;
}
ret = CheckLocalFileContent();
break;
return CheckFileContentMatch();
case MacroConditionFile::ConditionType::CONTENT_CHANGE:
ret = CheckChangeContent();
break;
return CheckChangeContent();
case MacroConditionFile::ConditionType::DATE_CHANGE:
ret = CheckChangeDate();
break;
return CheckChangeDate();
case MacroConditionFile::ConditionType::CHANGES_MATCH:
return CheckChangedMatch();
default:
break;
}
if (GetVariableValue().empty()) {
SetVariableValue(ret ? "true" : "false");
}
return ret;
return false;
}
bool MacroConditionFile::Save(obs_data_t *obj) const
@ -157,6 +210,8 @@ bool MacroConditionFile::Save(obs_data_t *obj) const
_file.Save(obj, "file");
_text.Save(obj, "text");
obs_data_set_int(obj, "fileType", static_cast<int>(_fileType));
obs_data_set_int(obj, "changeMatchType",
static_cast<int>(_changeMatchType));
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
return true;
}
@ -173,6 +228,8 @@ bool MacroConditionFile::Load(obs_data_t *obj)
_file.Load(obj, "file");
_text.Load(obj, "text");
_fileType = static_cast<FileType>(obs_data_get_int(obj, "fileType"));
_changeMatchType = static_cast<ChangeMatchType>(
obs_data_get_int(obj, "changeMatchType"));
_condition =
static_cast<ConditionType>(obs_data_get_int(obj, "condition"));
return true;
@ -185,25 +242,66 @@ std::string MacroConditionFile::GetShortDesc() const
static void populateFileTypes(QComboBox *list)
{
list->addItem(obs_module_text("AdvSceneSwitcher.condition.file.local"));
list->addItem(
obs_module_text("AdvSceneSwitcher.condition.file.remote"));
static constexpr std::array<
std::tuple<MacroConditionFile::FileType, std::string_view>, 2>
fileTypes = {{{MacroConditionFile::FileType::LOCAL,
"AdvSceneSwitcher.condition.file.local"},
{MacroConditionFile::FileType::REMOTE,
"AdvSceneSwitcher.condition.file.remote"}}};
for (const auto &[type, name] : fileTypes) {
list->addItem(obs_module_text(name.data()),
static_cast<int>(type));
}
}
static void populateConditions(QComboBox *list)
{
list->addItem(
obs_module_text("AdvSceneSwitcher.condition.file.type.match"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.condition.file.type.contentChange"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.condition.file.type.dateChange"));
static constexpr std::array<
std::tuple<MacroConditionFile::ConditionType, std::string_view>,
4>
conditionTypes = {{
{MacroConditionFile::ConditionType::MATCH,
"AdvSceneSwitcher.condition.file.type.match"},
{MacroConditionFile::ConditionType::CONTENT_CHANGE,
"AdvSceneSwitcher.condition.file.type.contentChange"},
{MacroConditionFile::ConditionType::DATE_CHANGE,
"AdvSceneSwitcher.condition.file.type.dateChange"},
{MacroConditionFile::ConditionType::CHANGES_MATCH,
"AdvSceneSwitcher.condition.file.type.changesMatch"},
}};
for (const auto &[type, name] : conditionTypes) {
list->addItem(obs_module_text(name.data()),
static_cast<int>(type));
}
}
static void populateChangeMatchTypes(QComboBox *list)
{
static constexpr std::array<
std::tuple<MacroConditionFile::ChangeMatchType, std::string_view>,
3>
matchTypes = {{
{MacroConditionFile::ChangeMatchType::ANY,
"AdvSceneSwitcher.condition.file.changeMatchType.any"},
{MacroConditionFile::ChangeMatchType::ADDED,
"AdvSceneSwitcher.condition.file.changeMatchType.added"},
{MacroConditionFile::ChangeMatchType::REMOVED,
"AdvSceneSwitcher.condition.file.changeMatchType.removed"},
}};
for (const auto &[type, name] : matchTypes) {
list->addItem(obs_module_text(name.data()),
static_cast<int>(type));
}
}
MacroConditionFileEdit::MacroConditionFileEdit(
QWidget *parent, std::shared_ptr<MacroConditionFile> entryData)
: QWidget(parent),
_fileTypes(new QComboBox()),
_changeMatchTypes(new QComboBox()),
_conditions(new QComboBox()),
_filePath(new FileSelection()),
_matchText(new VariableTextEdit(this)),
@ -211,9 +309,12 @@ MacroConditionFileEdit::MacroConditionFileEdit(
{
populateFileTypes(_fileTypes);
populateConditions(_conditions);
populateChangeMatchTypes(_changeMatchTypes);
QWidget::connect(_fileTypes, SIGNAL(currentIndexChanged(int)), this,
SLOT(FileTypeChanged(int)));
QWidget::connect(_changeMatchTypes, SIGNAL(currentIndexChanged(int)),
this, SLOT(ChangeMatchTypeChanged(int)));
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_filePath, SIGNAL(PathChanged(const QString &)), this,
@ -224,8 +325,11 @@ MacroConditionFileEdit::MacroConditionFileEdit(
SLOT(RegexChanged(RegexConfig)));
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{fileType}}", _fileTypes}, {"{{conditions}}", _conditions},
{"{{filePath}}", _filePath}, {"{{matchText}}", _matchText},
{"{{fileType}}", _fileTypes},
{"{{changeMatchType}}", _changeMatchTypes},
{"{{conditions}}", _conditions},
{"{{filePath}}", _filePath},
{"{{matchText}}", _matchText},
{"{{useRegex}}", _regex},
};
@ -256,8 +360,12 @@ void MacroConditionFileEdit::UpdateEntryData()
return;
}
_fileTypes->setCurrentIndex(static_cast<int>(_entryData->_fileType));
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
_fileTypes->setCurrentIndex(
_fileTypes->findData(static_cast<int>(_entryData->_fileType)));
_conditions->setCurrentIndex(_conditions->findData(
static_cast<int>(_entryData->_condition)));
_changeMatchTypes->setCurrentIndex(_changeMatchTypes->findData(
static_cast<int>(_entryData->_changeMatchType)));
_filePath->SetPath(_entryData->_file);
_matchText->setPlainText(_entryData->_text);
_regex->SetRegexConfig(_entryData->_regex);
@ -271,13 +379,27 @@ void MacroConditionFileEdit::FileTypeChanged(int index)
return;
}
auto type = static_cast<MacroConditionFile::FileType>(index);
auto type = static_cast<MacroConditionFile::FileType>(
_fileTypes->itemData(index).toInt());
_filePath->Button()->setEnabled(type ==
MacroConditionFile::FileType::LOCAL);
auto lock = LockContext();
_entryData->_fileType = type;
}
void MacroConditionFileEdit::ChangeMatchTypeChanged(int index)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_changeMatchType =
static_cast<MacroConditionFile::ChangeMatchType>(
_changeMatchTypes->itemData(index).toInt());
SetWidgetVisibility();
}
void MacroConditionFileEdit::ConditionChanged(int index)
{
if (_loading || !_entryData) {
@ -285,8 +407,8 @@ void MacroConditionFileEdit::ConditionChanged(int index)
}
auto lock = LockContext();
_entryData->_condition =
static_cast<MacroConditionFile::ConditionType>(index);
_entryData->_condition = static_cast<MacroConditionFile::ConditionType>(
_conditions->itemData(index).toInt());
SetWidgetVisibility();
}
@ -333,10 +455,19 @@ void MacroConditionFileEdit::SetWidgetVisibility()
return;
}
_matchText->setVisible(_entryData->_condition ==
MacroConditionFile::ConditionType::MATCH);
_regex->setVisible(_entryData->_condition ==
MacroConditionFile::ConditionType::MATCH);
_changeMatchTypes->setVisible(
_entryData->_condition ==
MacroConditionFile::ConditionType::CHANGES_MATCH);
_matchText->setVisible(
_entryData->_condition ==
MacroConditionFile::ConditionType::MATCH ||
_entryData->_condition ==
MacroConditionFile::ConditionType::CHANGES_MATCH);
_regex->setVisible(
_entryData->_condition ==
MacroConditionFile::ConditionType::MATCH ||
_entryData->_condition ==
MacroConditionFile::ConditionType::CHANGES_MATCH);
adjustSize();
updateGeometry();
}

View File

@ -10,6 +10,7 @@
#include <QLineEdit>
#include <QPushButton>
#include <QCheckBox>
#include <QStringList>
namespace advss {
@ -35,23 +36,31 @@ public:
MATCH,
CONTENT_CHANGE,
DATE_CHANGE,
CHANGES_MATCH,
};
enum class ChangeMatchType {
ANY,
ADDED,
REMOVED,
};
StringVariable _file = obs_module_text("AdvSceneSwitcher.enterPath");
StringVariable _text = obs_module_text("AdvSceneSwitcher.enterText");
FileType _fileType = FileType::LOCAL;
ChangeMatchType _changeMatchType = ChangeMatchType::ANY;
ConditionType _condition = ConditionType::MATCH;
RegexConfig _regex;
private:
bool MatchFileContent(QString &filedata) const;
bool CheckRemoteFileContent();
bool CheckLocalFileContent();
bool CheckFileContentMatch();
bool CheckChangeContent();
bool CheckChangeDate();
bool CheckChangedMatch();
QDateTime _lastMod;
size_t _lastHash = 0;
QStringList _previousContent;
static bool _registered;
static const std::string id;
};
@ -74,6 +83,7 @@ public:
private slots:
void FileTypeChanged(int index);
void ChangeMatchTypeChanged(int index);
void ConditionChanged(int index);
void PathChanged(const QString &text);
void MatchTextChanged();
@ -83,6 +93,7 @@ signals:
protected:
QComboBox *_fileTypes;
QComboBox *_changeMatchTypes;
QComboBox *_conditions;
FileSelection *_filePath;
VariableTextEdit *_matchText;