mirror of
https://github.com/Xaymar/obs-StreamFX
synced 2024-12-28 02:21:25 +00:00
project: Add support for multi stage release cycles
To ensure better stability of future releases, we need to adopt multiple stages in the release cycle. As we already label Alpha, Beta, Candidate and Stable differently, simply adopting this classification system already does everything for us. This also allows us to maintain compatibility with the existing system, while offering something new entirely.
This commit is contained in:
parent
ba15203c67
commit
e9283aec37
7 changed files with 551 additions and 414 deletions
204
CMakeLists.txt
204
CMakeLists.txt
|
@ -50,15 +50,88 @@ endif()
|
|||
# Versioning
|
||||
################################################################################
|
||||
|
||||
set(VERSION_MAJOR 0)
|
||||
set(VERSION_MINOR 12)
|
||||
set(VERSION_PATCH 0)
|
||||
set(VERSION_TWEAK 0)
|
||||
set(VERSION_SUFFIX "a1")
|
||||
# Variables for Versioning
|
||||
set(VERSION_MAJOR 0) # Major
|
||||
set(VERSION_MINOR 0) # Minor
|
||||
set(VERSION_PATCH 0) # Patch
|
||||
set(VERSION_STAGE "") # Prefix for Tweak, if left empty will assume '.'.
|
||||
set(VERSION_TWEAK 0) # Tweak
|
||||
set(VERSION_COMMIT "00000000")
|
||||
# Final format will be [MAJOR].[MINOR].[PATCH]([TYPE][TWEAK])(-[COMMIT])
|
||||
|
||||
function(parse_version_string)
|
||||
# Parses a version in the format A.B.C[.|a|b|rc]D-E
|
||||
cmake_parse_arguments(
|
||||
PARSE_ARGV 0
|
||||
_ARGS
|
||||
""
|
||||
"INPUT;OUTPUT"
|
||||
""
|
||||
)
|
||||
|
||||
set(_tmp_MAJOR 0)
|
||||
set(_tmp_MINOR 0)
|
||||
set(_tmp_PATCH 0)
|
||||
set(_tmp_STAGE "")
|
||||
set(_tmp_TWEAK "")
|
||||
set(_tmp_COMMIT "")
|
||||
|
||||
# Replace separators with list separators
|
||||
string(REPLACE "-" "." _tmp "${_ARGS_INPUT}")
|
||||
string(REPLACE "." ";" _tmp "${_tmp}")
|
||||
|
||||
# Parse version differently depending on total string length.
|
||||
list(LENGTH _tmp _tmp_len)
|
||||
if(_tmp_len GREATER_EQUAL 1) # A[...]
|
||||
list(GET _tmp 0 _tmp_MAJOR)
|
||||
endif()
|
||||
if(_tmp_len GREATER_EQUAL 2) # A.B[...]
|
||||
list(GET _tmp 1 _tmp_MINOR)
|
||||
endif()
|
||||
if(_tmp_len GREATER_EQUAL 3) # A.B.C[...]
|
||||
list(GET _tmp 2 _tmp_PATCH)
|
||||
endif()
|
||||
if(_tmp_len EQUAL 4) # A.B.C.D or A.B.C-gEEEEEEEE
|
||||
list(GET _tmp 3 _tmp2)
|
||||
# No support for '{N,M}' in CMake-Regex!
|
||||
if(_tmp2 MATCHES "^g[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]")
|
||||
# A.B.C-gEEEEEEEE
|
||||
set(_tmp_COMMIT "${_tmp2}")
|
||||
else()
|
||||
# It is A.B.C.D
|
||||
set(_tmp_TWEAK "${_tmp2}")
|
||||
set(_tmp_STAGE "a")
|
||||
endif()
|
||||
elseif(_tmp_len EQUAL 5) # A.B.C.D-gEEEEEEEE
|
||||
set(_tmp_STAGE "a")
|
||||
list(GET _tmp 3 _tmp_TWEAK)
|
||||
list(GET _tmp 4 _tmp_COMMIT)
|
||||
endif()
|
||||
if(_tmp_TWEAK STREQUAL "") # Is A.B.C-gEEEEEEEE actually A.B.CxD-gEEEEEEEE?
|
||||
string(REGEX MATCHALL "^([0-9]+)([_a-c]+)([0-9]+)" T_MATCHES "${_tmp_PATCH}")
|
||||
if(T_MATCHES)
|
||||
set(_tmp_PATCH ${CMAKE_MATCH_1})
|
||||
set(_tmp_STAGE ${CMAKE_MATCH_2})
|
||||
set(_tmp_TWEAK ${CMAKE_MATCH_3})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(${_ARGS_OUTPUT}_MAJOR ${_tmp_MAJOR} PARENT_SCOPE)
|
||||
set(${_ARGS_OUTPUT}_MINOR ${_tmp_MINOR} PARENT_SCOPE)
|
||||
set(${_ARGS_OUTPUT}_PATCH ${_tmp_PATCH} PARENT_SCOPE)
|
||||
set(${_ARGS_OUTPUT}_STAGE ${_tmp_STAGE} PARENT_SCOPE)
|
||||
set(${_ARGS_OUTPUT}_TWEAK ${_tmp_TWEAK} PARENT_SCOPE)
|
||||
set(${_ARGS_OUTPUT}_COMMIT ${_tmp_COMMIT} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Check if we are in a git repository.
|
||||
if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/.git")
|
||||
set(VERSION_BASE "0.11.0") # Automatic versioning base
|
||||
set(VERSION_TARGET "0.12.0") # Automatic versioning target
|
||||
|
||||
# Parse target version as it is for output.
|
||||
parse_version_string(OUTPUT "VERSION" INPUT "${VERSION_TARGET}")
|
||||
|
||||
# Try and figure out where git is.
|
||||
find_program(GIT git
|
||||
PATHS
|
||||
|
@ -69,12 +142,9 @@ if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/.git")
|
|||
)
|
||||
|
||||
if(GIT)
|
||||
set(GIT_RESULT)
|
||||
set(GIT_OUTPUT)
|
||||
set(GIT_ERROR)
|
||||
|
||||
# Tweak
|
||||
execute_process(
|
||||
COMMAND "${GIT}" describe --tags --long --match "[0-9]*.[0-9]*.[0-9]*" --abbrev=8 HEAD
|
||||
COMMAND "${GIT}" describe --tags --long --match "${VERSION_BASE}" --abbrev=8 HEAD
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
|
||||
RESULT_VARIABLE GIT_RESULT
|
||||
OUTPUT_VARIABLE GIT_OUTPUT
|
||||
|
@ -83,29 +153,34 @@ if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/.git")
|
|||
ERROR_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET
|
||||
)
|
||||
|
||||
if(GIT_RESULT EQUAL 0)
|
||||
string(REPLACE "-" "." GIT_OUTPUT "${GIT_OUTPUT}")
|
||||
string(REPLACE "." ";" GIT_OUTPUT "${GIT_OUTPUT}")
|
||||
|
||||
# Parse Version
|
||||
list(GET GIT_OUTPUT 0 VERSION_MAJOR)
|
||||
list(GET GIT_OUTPUT 1 VERSION_MINOR)
|
||||
list(GET GIT_OUTPUT 2 VERSION_PATCH)
|
||||
list(GET GIT_OUTPUT 3 VERSION_TWEAK)
|
||||
list(GET GIT_OUTPUT 4 VERSION_COMMIT)
|
||||
|
||||
# Patch needs additional parsing.
|
||||
# This may be a [0-9]*[a-z]*[0-9]+ string.
|
||||
string(REGEX MATCHALL "^([0-9]+)([a-z]+[0-9]+)?" T_MATCHES "${VERSION_PATCH}")
|
||||
set(VERSION_PATCH "${CMAKE_MATCH_1}")
|
||||
if(CMAKE_MATCH_2)
|
||||
set(VERSION_SUFFIX "${CMAKE_MATCH_2}")
|
||||
else()
|
||||
set(VERSION_SUFFIX "")
|
||||
endif()
|
||||
else()
|
||||
if(NOT GIT_RESULT EQUAL 0)
|
||||
message(WARNING "${LOGPREFIX}Failed to detect version, using default instead.")
|
||||
else()
|
||||
parse_version_string(OUTPUT "GIT_VERSION" INPUT "${GIT_OUTPUT}")
|
||||
set(VERSION_STAGE ${GIT_VERSION_STAGE})
|
||||
set(VERSION_TWEAK ${GIT_VERSION_TWEAK})
|
||||
set(VERSION_COMMIT ${GIT_VERSION_COMMIT})
|
||||
endif()
|
||||
|
||||
# Is there a tag on the current commit?
|
||||
execute_process(
|
||||
COMMAND "${GIT}" tag "--sort=-v:refname" "--points-at" HEAD
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
|
||||
RESULT_VARIABLE GIT_RESULT
|
||||
OUTPUT_VARIABLE TAG_OUTPUT
|
||||
ERROR_VARIABLE GIT_ERROR
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET
|
||||
)
|
||||
if((GIT_RESULT EQUAL 0) AND (NOT "${TAG_OUTPUT}" STREQUAL ""))
|
||||
string(REGEX REPLACE "[\r\n]+.*" "" T_MATCHES "${TAG_OUTPUT}")
|
||||
parse_version_string(OUTPUT "TAG_VERSION" INPUT "${T_MATCHES}")
|
||||
set(VERSION_MAJOR "${TAG_VERSION_MAJOR}")
|
||||
set(VERSION_MINOR "${TAG_VERSION_MINOR}")
|
||||
set(VERSION_PATCH "${TAG_VERSION_PATCH}")
|
||||
set(VERSION_STAGE "${TAG_VERSION_STAGE}")
|
||||
set(VERSION_TWEAK "${TAG_VERSION_TWEAK}")
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
|
@ -113,42 +188,16 @@ else()
|
|||
endif()
|
||||
|
||||
# Allow manual overrides of the detected version.
|
||||
set(${PREFIX}VERSION "" CACHE STRING "Override StreamFX version with this string. Format: Major.Minor.Patch[Suffix][-Tweak[-Commit8c]]")
|
||||
set(${PREFIX}VERSION "" CACHE STRING "Override StreamFX version with this string. Format: Major.Minor.Patch[Stage][Tweak[-Commit8c]]")
|
||||
if(NOT (${PREFIX}VERSION STREQUAL ""))
|
||||
string(REPLACE "-" "." T_VERSION "${${PREFIX}VERSION}")
|
||||
string(REPLACE "." ";" T_VERSION "${${PREFIX}VERSION}")
|
||||
|
||||
list(LENGTH T_VERSION T_VERSIONLEN)
|
||||
list(GET T_VERSION 0 VERSION_MAJOR)
|
||||
list(GET T_VERSION 1 VERSION_MINOR)
|
||||
list(GET T_VERSION 2 VERSION_PATCH)
|
||||
if(T_VERSIONLEN GREATER_EQUAL 3)
|
||||
list(GET T_VERSION 3 VERSION_TWEAK)
|
||||
else()
|
||||
set(VERSION_BUILD 0)
|
||||
endif()
|
||||
if(T_VERSIONLEN GREATER_EQUAL 4)
|
||||
list(GET T_VERSION 4 VERSION_COMMIT)
|
||||
else()
|
||||
set(VERSION_COMMIT "")
|
||||
endif()
|
||||
|
||||
# Patch needs additional parsing.
|
||||
# This may be a [0-9]*[a-z]*[0-9]+ string.
|
||||
string(REGEX MATCHALL "^([0-9]+)([a-z]+[0-9]+)?" T_MATCHES "${VERSION_PATCH}")
|
||||
set(VERSION_PATCH "${CMAKE_MATCH_1}")
|
||||
if(CMAKE_MATCH_2)
|
||||
set(VERSION_SUFFIX "${CMAKE_MATCH_2}")
|
||||
else()
|
||||
set(VERSION_SUFFIX "")
|
||||
endif()
|
||||
parse_version_string(OUTPUT "VERSION" INPUT "${${PREFIX}VERSION}")
|
||||
endif()
|
||||
|
||||
# Generate Version String
|
||||
if(NOT (VERSION_COMMIT STREQUAL ""))
|
||||
set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}.${VERSION_TWEAK}${VERSION_SUFFIX}-${VERSION_COMMIT}")
|
||||
if(VERSION_COMMIT)
|
||||
set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}${VERSION_STAGE}${VERSION_TWEAK}-${VERSION_COMMIT}")
|
||||
else()
|
||||
set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}.${VERSION_TWEAK}${VERSION_SUFFIX}")
|
||||
set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}${VERSION_STAGE}${VERSION_TWEAK}")
|
||||
endif()
|
||||
|
||||
# Log the detected version.
|
||||
|
@ -157,13 +206,40 @@ message(STATUS "${LOGPREFIX}Version ${VERSION_STRING}")
|
|||
################################################################################
|
||||
# Project
|
||||
################################################################################
|
||||
set(_VERSION_TWEAK "0${VERSION_TWEAK}")
|
||||
project(
|
||||
StreamFX
|
||||
VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}.${VERSION_TWEAK}
|
||||
VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}.${_VERSION_TWEAK}
|
||||
DESCRIPTION "Additional sources, filters, transitions and encoders for OBS Studio."
|
||||
HOMEPAGE_URL "https://streamfx.xaymar.com/"
|
||||
)
|
||||
|
||||
# Helpers for CI
|
||||
if(VERSION_STAGE STREQUAL "")
|
||||
file(
|
||||
GENERATE
|
||||
OUTPUT "$<TARGET_PROPERTY:${PROJECT_NAME},BINARY_DIR>/tag_name"
|
||||
CONTENT "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}"
|
||||
TARGET ${PROJECT_NAME}
|
||||
FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_READ GROUP_EXECUTE
|
||||
)
|
||||
else()
|
||||
file(
|
||||
GENERATE
|
||||
OUTPUT "$<TARGET_PROPERTY:${PROJECT_NAME},BINARY_DIR>/tag_name"
|
||||
CONTENT "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}${VERSION_STAGE}${VERSION_TWEAK}"
|
||||
TARGET ${PROJECT_NAME}
|
||||
FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_READ GROUP_EXECUTE
|
||||
)
|
||||
endif()
|
||||
file(
|
||||
GENERATE
|
||||
OUTPUT "$<TARGET_PROPERTY:${PROJECT_NAME},BINARY_DIR>/version"
|
||||
CONTENT "${VERSION_STRING}"
|
||||
TARGET ${PROJECT_NAME}
|
||||
FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_READ GROUP_EXECUTE
|
||||
)
|
||||
|
||||
# Full Project Name
|
||||
set(PROJECT_FULL_NAME "StreamFX (for OBS Studio)")
|
||||
|
||||
|
|
|
@ -59,8 +59,10 @@ UI.Updater.GitHubPermission.Text="In order to provide manual or automated update
|
|||
UI.Updater.Menu.CheckForUpdates="Check for Updates"
|
||||
UI.Updater.Menu.CheckForUpdates.Automatically="Automatically check for Updates"
|
||||
UI.Updater.Menu.Channel="Update Channel"
|
||||
UI.Updater.Menu.Channel.Release="Release"
|
||||
UI.Updater.Menu.Channel.Testing="Testing"
|
||||
UI.Updater.Menu.Channel.Stable="Stable"
|
||||
UI.Updater.Menu.Channel.Candidate="Candidate"
|
||||
UI.Updater.Menu.Channel.Beta="Beta"
|
||||
UI.Updater.Menu.Channel.Alpha="Alpha"
|
||||
|
||||
# Encoder/AOM-AV1
|
||||
Encoder.AOM.AV1="AOM AV1 (direct)"
|
||||
|
|
|
@ -39,8 +39,10 @@
|
|||
#define D_I18N_MENU_CHECKFORUPDATES "UI.Updater.Menu.CheckForUpdates"
|
||||
#define D_I18N_MENU_CHECKFORUPDATES_AUTOMATICALLY "UI.Updater.Menu.CheckForUpdates.Automatically"
|
||||
#define D_I18N_MENU_CHANNEL "UI.Updater.Menu.Channel"
|
||||
#define D_I18N_MENU_CHANNEL_RELEASE "UI.Updater.Menu.Channel.Release"
|
||||
#define D_I18N_MENU_CHANNEL_TESTING "UI.Updater.Menu.Channel.Testing"
|
||||
#define D_I18N_MENU_CHANNEL_STABLE "UI.Updater.Menu.Channel.Stable"
|
||||
#define D_I18N_MENU_CHANNEL_CANDIDATE "UI.Updater.Menu.Channel.Candidate"
|
||||
#define D_I18N_MENU_CHANNEL_BETA "UI.Updater.Menu.Channel.Beta"
|
||||
#define D_I18N_MENU_CHANNEL_ALPHA "UI.Updater.Menu.Channel.Alpha"
|
||||
#define D_I18N_DIALOG_TITLE "UI.Updater.Dialog.Title"
|
||||
#define D_I18N_GITHUBPERMISSION_TITLE "UI.Updater.GitHubPermission.Title"
|
||||
#define D_I18N_GITHUBPERMISSION_TEXT "UI.Updater.GitHubPermission.Text"
|
||||
|
@ -51,6 +53,7 @@ streamfx::ui::updater_dialog::updater_dialog() : QDialog(reinterpret_cast<QWidge
|
|||
setWindowFlag(Qt::WindowContextHelpButtonHint, false);
|
||||
setWindowFlag(Qt::WindowMinimizeButtonHint, false);
|
||||
setWindowFlag(Qt::WindowMaximizeButtonHint, false);
|
||||
setAttribute(Qt::WA_DeleteOnClose, false); // Do not delete on close.
|
||||
|
||||
connect(ok, &QPushButton::clicked, this, &streamfx::ui::updater_dialog::on_ok);
|
||||
connect(cancel, &QPushButton::clicked, this, &streamfx::ui::updater_dialog::on_cancel);
|
||||
|
@ -58,52 +61,17 @@ streamfx::ui::updater_dialog::updater_dialog() : QDialog(reinterpret_cast<QWidge
|
|||
|
||||
streamfx::ui::updater_dialog::~updater_dialog() {}
|
||||
|
||||
void streamfx::ui::updater_dialog::show(streamfx::update_info current, streamfx::update_info update)
|
||||
void streamfx::ui::updater_dialog::show(streamfx::version_info current, streamfx::version_info update)
|
||||
{
|
||||
{
|
||||
std::vector<char> buf;
|
||||
if (current.version_type) {
|
||||
buf.resize(static_cast<size_t>(snprintf(nullptr, 0, "%" PRIu16 ".%" PRIu16 ".%" PRIu16 "%.1s%" PRIu16,
|
||||
current.version_major, current.version_minor, current.version_patch,
|
||||
¤t.version_type, current.version_index))
|
||||
+ 1);
|
||||
snprintf(buf.data(), buf.size(), "%" PRIu16 ".%" PRIu16 ".%" PRIu16 "%.1s%" PRIu16, current.version_major,
|
||||
current.version_minor, current.version_patch, ¤t.version_type, current.version_index);
|
||||
} else {
|
||||
buf.resize(
|
||||
static_cast<size_t>(snprintf(nullptr, 0, "%" PRIu16 ".%" PRIu16 ".%" PRIu16, current.version_major,
|
||||
current.version_minor, current.version_patch))
|
||||
+ 1);
|
||||
snprintf(buf.data(), buf.size(), "%" PRIu16 ".%" PRIu16 ".%" PRIu16, current.version_major,
|
||||
current.version_minor, current.version_patch);
|
||||
}
|
||||
currentVersion->setText(QString::fromUtf8(buf.data()));
|
||||
}
|
||||
currentVersion->setText(QString::fromStdString(static_cast<std::string>(current)));
|
||||
latestVersion->setText(QString::fromStdString(static_cast<std::string>(update)));
|
||||
|
||||
{
|
||||
std::vector<char> buf;
|
||||
if (update.version_type) {
|
||||
buf.resize(static_cast<size_t>(snprintf(nullptr, 0, "%" PRIu16 ".%" PRIu16 ".%" PRIu16 "%.1s%" PRIu16,
|
||||
update.version_major, update.version_minor, update.version_patch,
|
||||
&update.version_type, update.version_index))
|
||||
+ 1);
|
||||
snprintf(buf.data(), buf.size(), "%" PRIu16 ".%" PRIu16 ".%" PRIu16 "%.1s%" PRIu16, update.version_major,
|
||||
update.version_minor, update.version_patch, &update.version_type, update.version_index);
|
||||
} else {
|
||||
buf.resize(static_cast<size_t>(snprintf(nullptr, 0, "%" PRIu16 ".%" PRIu16 ".%" PRIu16,
|
||||
update.version_major, update.version_minor, update.version_patch))
|
||||
+ 1);
|
||||
snprintf(buf.data(), buf.size(), "%" PRIu16 ".%" PRIu16 ".%" PRIu16, update.version_major,
|
||||
update.version_minor, update.version_patch);
|
||||
}
|
||||
latestVersion->setText(QString::fromUtf8(buf.data()));
|
||||
|
||||
{
|
||||
std::vector<char> buf2;
|
||||
buf2.resize(static_cast<size_t>(snprintf(nullptr, 0, D_TRANSLATE(D_I18N_DIALOG_TITLE), buf.data())) + 1);
|
||||
snprintf(buf2.data(), buf2.size(), D_TRANSLATE(D_I18N_DIALOG_TITLE), buf.data());
|
||||
setWindowTitle(QString::fromUtf8(buf2.data()));
|
||||
}
|
||||
std::string buf = latestVersion->text().toStdString();
|
||||
std::vector<char> buf2;
|
||||
buf2.resize(static_cast<size_t>(snprintf(nullptr, 0, D_TRANSLATE(D_I18N_DIALOG_TITLE), buf.data())) + 1);
|
||||
snprintf(buf2.data(), buf2.size(), D_TRANSLATE(D_I18N_DIALOG_TITLE), buf.data());
|
||||
setWindowTitle(QString::fromUtf8(buf2.data()));
|
||||
}
|
||||
|
||||
_update_url = QUrl(QString::fromStdString(update.url));
|
||||
|
@ -122,16 +90,19 @@ void streamfx::ui::updater_dialog::on_ok()
|
|||
{
|
||||
QDesktopServices::openUrl(_update_url);
|
||||
hide();
|
||||
accept();
|
||||
}
|
||||
|
||||
void streamfx::ui::updater_dialog::on_cancel()
|
||||
{
|
||||
hide();
|
||||
reject();
|
||||
}
|
||||
|
||||
streamfx::ui::updater::updater(QMenu* menu)
|
||||
: _updater(), _dialog(nullptr), _gdpr(nullptr), _cfu(nullptr), _cfu_auto(nullptr), _channel(nullptr),
|
||||
_channel_menu(nullptr), _channel_stable(nullptr), _channel_preview(nullptr), _channel_group(nullptr)
|
||||
_channel_menu(nullptr), _channel_stable(nullptr), _channel_candidate(nullptr), _channel_beta(nullptr),
|
||||
_channel_alpha(nullptr), _channel_group(nullptr)
|
||||
{
|
||||
// Create dialog.
|
||||
_dialog = new updater_dialog();
|
||||
|
@ -154,17 +125,27 @@ streamfx::ui::updater::updater(QMenu* menu)
|
|||
_channel_menu = menu->addMenu(QString::fromUtf8(D_TRANSLATE(D_I18N_MENU_CHANNEL)));
|
||||
_channel_menu->menuAction()->setMenuRole(QAction::NoRole);
|
||||
|
||||
_channel_stable = _channel_menu->addAction(QString::fromUtf8(D_TRANSLATE(D_I18N_MENU_CHANNEL_RELEASE)));
|
||||
_channel_stable = _channel_menu->addAction(QString::fromUtf8(D_TRANSLATE(D_I18N_MENU_CHANNEL_STABLE)));
|
||||
_channel_stable->setMenuRole(QAction::NoRole);
|
||||
_channel_stable->setCheckable(true);
|
||||
|
||||
_channel_preview = _channel_menu->addAction(QString::fromUtf8(D_TRANSLATE(D_I18N_MENU_CHANNEL_TESTING)));
|
||||
_channel_preview->setMenuRole(QAction::NoRole);
|
||||
_channel_preview->setCheckable(true);
|
||||
_channel_candidate = _channel_menu->addAction(QString::fromUtf8(D_TRANSLATE(D_I18N_MENU_CHANNEL_CANDIDATE)));
|
||||
_channel_candidate->setMenuRole(QAction::NoRole);
|
||||
_channel_candidate->setCheckable(true);
|
||||
|
||||
_channel_beta = _channel_menu->addAction(QString::fromUtf8(D_TRANSLATE(D_I18N_MENU_CHANNEL_BETA)));
|
||||
_channel_beta->setMenuRole(QAction::NoRole);
|
||||
_channel_beta->setCheckable(true);
|
||||
|
||||
_channel_alpha = _channel_menu->addAction(QString::fromUtf8(D_TRANSLATE(D_I18N_MENU_CHANNEL_ALPHA)));
|
||||
_channel_alpha->setMenuRole(QAction::NoRole);
|
||||
_channel_alpha->setCheckable(true);
|
||||
|
||||
_channel_group = new QActionGroup(_channel_menu);
|
||||
_channel_group->addAction(_channel_stable);
|
||||
_channel_group->addAction(_channel_preview);
|
||||
_channel_group->addAction(_channel_candidate);
|
||||
_channel_group->addAction(_channel_beta);
|
||||
_channel_group->addAction(_channel_alpha);
|
||||
connect(_channel_group, &QActionGroup::triggered, this, &streamfx::ui::updater::on_channel_group_triggered);
|
||||
}
|
||||
|
||||
|
@ -188,8 +169,8 @@ streamfx::ui::updater::updater(QMenu* menu)
|
|||
std::bind(&streamfx::ui::updater::on_updater_refreshed, this, std::placeholders::_1));
|
||||
|
||||
// Sync with updater information.
|
||||
emit autoupdate_changed(_updater->automation());
|
||||
emit channel_changed(_updater->channel());
|
||||
emit autoupdate_changed(_updater->is_automated());
|
||||
emit channel_changed(_updater->get_channel());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -200,7 +181,7 @@ void streamfx::ui::updater::on_updater_automation_changed(streamfx::updater&, bo
|
|||
emit autoupdate_changed(value);
|
||||
}
|
||||
|
||||
void streamfx::ui::updater::on_updater_channel_changed(streamfx::updater&, streamfx::update_channel channel)
|
||||
void streamfx::ui::updater::on_updater_channel_changed(streamfx::updater&, streamfx::version_stage channel)
|
||||
{
|
||||
emit channel_changed(channel);
|
||||
}
|
||||
|
@ -209,7 +190,7 @@ void streamfx::ui::updater::on_updater_refreshed(streamfx::updater&)
|
|||
{
|
||||
emit check_active(false);
|
||||
|
||||
if (!_updater->have_update())
|
||||
if (!_updater->is_update_available())
|
||||
return;
|
||||
|
||||
emit update_detected();
|
||||
|
@ -217,8 +198,8 @@ void streamfx::ui::updater::on_updater_refreshed(streamfx::updater&)
|
|||
|
||||
void streamfx::ui::updater::obs_ready()
|
||||
{
|
||||
if (_updater->automation()) {
|
||||
if (_updater->gdpr()) {
|
||||
if (_updater->is_automated()) {
|
||||
if (_updater->is_data_sharing_allowed()) {
|
||||
_updater->refresh();
|
||||
} else {
|
||||
create_gdpr_box();
|
||||
|
@ -227,11 +208,17 @@ void streamfx::ui::updater::obs_ready()
|
|||
}
|
||||
}
|
||||
|
||||
void streamfx::ui::updater::on_channel_changed(streamfx::update_channel channel)
|
||||
void streamfx::ui::updater::on_channel_changed(streamfx::version_stage ch)
|
||||
{
|
||||
bool is_stable = channel == streamfx::update_channel::RELEASE;
|
||||
_channel_stable->setChecked(is_stable);
|
||||
_channel_preview->setChecked(!is_stable);
|
||||
QSignalBlocker bgroup(_channel_group);
|
||||
QSignalBlocker bs(_channel_stable);
|
||||
QSignalBlocker bc(_channel_candidate);
|
||||
QSignalBlocker bb(_channel_beta);
|
||||
QSignalBlocker ba(_channel_alpha);
|
||||
_channel_stable->setChecked(ch == streamfx::version_stage::STABLE);
|
||||
_channel_candidate->setChecked(ch == streamfx::version_stage::CANDIDATE);
|
||||
_channel_beta->setChecked(ch == streamfx::version_stage::BETA);
|
||||
_channel_alpha->setChecked(ch == streamfx::version_stage::ALPHA);
|
||||
}
|
||||
|
||||
void streamfx::ui::updater::on_update_detected()
|
||||
|
@ -241,24 +228,25 @@ void streamfx::ui::updater::on_update_detected()
|
|||
|
||||
void streamfx::ui::updater::on_autoupdate_changed(bool enabled)
|
||||
{
|
||||
QSignalBlocker blocker(_cfu_auto);
|
||||
_cfu_auto->setChecked(enabled);
|
||||
}
|
||||
|
||||
void streamfx::ui::updater::on_gdpr_button(QAbstractButton* btn)
|
||||
{
|
||||
if (_gdpr->standardButton(btn) == QMessageBox::Ok) {
|
||||
_updater->set_gdpr(true);
|
||||
_updater->set_data_sharing_allowed(true);
|
||||
emit check_active(true);
|
||||
_updater->refresh();
|
||||
} else {
|
||||
_updater->set_gdpr(false);
|
||||
_updater->set_data_sharing_allowed(false);
|
||||
_updater->set_automation(false);
|
||||
}
|
||||
}
|
||||
|
||||
void streamfx::ui::updater::on_cfu_triggered(bool)
|
||||
{
|
||||
if (!_updater->gdpr()) {
|
||||
if (!_updater->is_data_sharing_allowed()) {
|
||||
create_gdpr_box();
|
||||
_gdpr->exec();
|
||||
} else {
|
||||
|
@ -274,10 +262,14 @@ void streamfx::ui::updater::on_cfu_auto_toggled(bool flag)
|
|||
|
||||
void streamfx::ui::updater::on_channel_group_triggered(QAction* action)
|
||||
{
|
||||
if (action == _channel_stable) {
|
||||
_updater->set_channel(update_channel::RELEASE);
|
||||
if (action == _channel_alpha) {
|
||||
_updater->set_channel(streamfx::version_stage::ALPHA);
|
||||
} else if (action == _channel_beta) {
|
||||
_updater->set_channel(streamfx::version_stage::BETA);
|
||||
} else if (action == _channel_candidate) {
|
||||
_updater->set_channel(streamfx::version_stage::CANDIDATE);
|
||||
} else {
|
||||
_updater->set_channel(update_channel::TESTING);
|
||||
_updater->set_channel(streamfx::version_stage::STABLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -300,7 +292,9 @@ void streamfx::ui::updater::on_check_active(bool active)
|
|||
{
|
||||
_cfu->setEnabled(!active);
|
||||
_channel_group->setEnabled(!active);
|
||||
_channel_preview->setEnabled(!active);
|
||||
_channel_alpha->setEnabled(!active);
|
||||
_channel_beta->setEnabled(!active);
|
||||
_channel_candidate->setEnabled(!active);
|
||||
_channel_stable->setEnabled(!active);
|
||||
_channel_menu->setEnabled(!active);
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
#include <QWidgetAction>
|
||||
#include "ui_updater.h"
|
||||
|
||||
Q_DECLARE_METATYPE(streamfx::update_channel);
|
||||
Q_DECLARE_METATYPE(::streamfx::version_stage);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
|
@ -53,7 +53,7 @@ namespace streamfx::ui {
|
|||
updater_dialog();
|
||||
~updater_dialog();
|
||||
|
||||
void show(streamfx::update_info current, streamfx::update_info update);
|
||||
void show(streamfx::version_info current, streamfx::version_info update);
|
||||
void hide();
|
||||
|
||||
public slots:
|
||||
|
@ -77,7 +77,9 @@ namespace streamfx::ui {
|
|||
QAction* _channel;
|
||||
QMenu* _channel_menu;
|
||||
QAction* _channel_stable;
|
||||
QAction* _channel_preview;
|
||||
QAction* _channel_candidate;
|
||||
QAction* _channel_beta;
|
||||
QAction* _channel_alpha;
|
||||
QActionGroup* _channel_group;
|
||||
|
||||
public:
|
||||
|
@ -87,7 +89,7 @@ namespace streamfx::ui {
|
|||
void create_gdpr_box();
|
||||
|
||||
void on_updater_automation_changed(streamfx::updater&, bool);
|
||||
void on_updater_channel_changed(streamfx::updater&, streamfx::update_channel);
|
||||
void on_updater_channel_changed(streamfx::updater&, streamfx::version_stage);
|
||||
void on_updater_refreshed(streamfx::updater&);
|
||||
|
||||
void obs_ready();
|
||||
|
@ -96,7 +98,7 @@ namespace streamfx::ui {
|
|||
; // Needed by some linters.
|
||||
|
||||
void autoupdate_changed(bool);
|
||||
void channel_changed(streamfx::update_channel);
|
||||
void channel_changed(streamfx::version_stage);
|
||||
void update_detected();
|
||||
void check_active(bool);
|
||||
|
||||
|
@ -105,7 +107,7 @@ namespace streamfx::ui {
|
|||
|
||||
// Internal
|
||||
void on_autoupdate_changed(bool);
|
||||
void on_channel_changed(streamfx::update_channel);
|
||||
void on_channel_changed(streamfx::version_stage);
|
||||
void on_update_detected();
|
||||
void on_check_active(bool);
|
||||
|
||||
|
|
|
@ -20,7 +20,9 @@
|
|||
|
||||
#include "updater.hpp"
|
||||
#include "version.hpp"
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <regex>
|
||||
#include <string_view>
|
||||
#include "configuration.hpp"
|
||||
#include "plugin.hpp"
|
||||
|
@ -45,179 +47,308 @@
|
|||
// - Move 'autoupdater.last_checked_at' to out of the configuration.
|
||||
// - Figure out if nightly updates are viable at all.
|
||||
|
||||
#define ST_CFG_GDPR "updater.gdpr"
|
||||
#define ST_CFG_DATASHARING "updater.datasharing"
|
||||
#define ST_CFG_AUTOMATION "updater.automation"
|
||||
#define ST_CFG_CHANNEL "updater.channel"
|
||||
#define ST_CFG_LASTCHECKEDAT "updater.lastcheckedat"
|
||||
|
||||
void streamfx::to_json(nlohmann::json& json, const update_info& info)
|
||||
streamfx::version_stage streamfx::stage_from_string(std::string_view str)
|
||||
{
|
||||
if (str == "a") {
|
||||
return version_stage::ALPHA;
|
||||
} else if (str == "b") {
|
||||
return version_stage::BETA;
|
||||
} else if (str == "c") {
|
||||
return version_stage::CANDIDATE;
|
||||
} else {
|
||||
return version_stage::STABLE;
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view streamfx::stage_to_string(version_stage t)
|
||||
{
|
||||
switch (t) {
|
||||
case version_stage::ALPHA:
|
||||
return "a";
|
||||
case version_stage::BETA:
|
||||
return "b";
|
||||
case version_stage::CANDIDATE:
|
||||
return "c";
|
||||
default:
|
||||
case version_stage::STABLE:
|
||||
return ".";
|
||||
}
|
||||
}
|
||||
|
||||
void streamfx::to_json(nlohmann::json& json, const version_stage& stage)
|
||||
{
|
||||
json = stage_to_string(stage);
|
||||
}
|
||||
|
||||
void streamfx::from_json(const nlohmann::json& json, version_stage& stage)
|
||||
{
|
||||
stage = stage_from_string(json.get<std::string>());
|
||||
}
|
||||
|
||||
streamfx::version_info::version_info()
|
||||
: major(0), minor(0), patch(0), tweak(0), stage(version_stage::STABLE), url(""), name("")
|
||||
{}
|
||||
|
||||
streamfx::version_info::version_info(const std::string text) : version_info()
|
||||
{
|
||||
// text can be:
|
||||
// 0.0.0 (Stable)
|
||||
// 0.0.0a0 (Testing)
|
||||
// 0.0.0b0 (Testing)
|
||||
// 0.0.0c0 (Testing)
|
||||
// 0.0.0_0 (Development)
|
||||
static const std::regex re_version(
|
||||
"([0-9]+)\\.([0-9]+)\\.([0-9]+)(([\\._abc]{1,1})([0-9]+|)|)(-g([0-9a-fA-F]{8,8})|)");
|
||||
std::smatch matches;
|
||||
if (std::regex_match(text, matches, re_version,
|
||||
std::regex_constants::match_any | std::regex_constants::match_continuous)) {
|
||||
major = static_cast<uint16_t>(strtoul(matches[1].str().c_str(), nullptr, 10));
|
||||
minor = static_cast<uint16_t>(strtoul(matches[2].str().c_str(), nullptr, 10));
|
||||
patch = static_cast<uint16_t>(strtoul(matches[3].str().c_str(), nullptr, 10));
|
||||
if (matches.size() >= 5) {
|
||||
stage = stage_from_string(matches[5].str());
|
||||
tweak = static_cast<uint16_t>(strtoul(matches[6].str().c_str(), nullptr, 10));
|
||||
}
|
||||
} else {
|
||||
throw std::invalid_argument("Provided string is not a version.");
|
||||
}
|
||||
}
|
||||
|
||||
void streamfx::to_json(nlohmann::json& json, const version_info& info)
|
||||
{
|
||||
auto version = nlohmann::json::object();
|
||||
version["major"] = info.version_major;
|
||||
version["minor"] = info.version_minor;
|
||||
version["patch"] = info.version_patch;
|
||||
if (info.version_type)
|
||||
version["alpha"] = info.version_type == 'a' ? true : false;
|
||||
version["index"] = info.version_index;
|
||||
version["major"] = info.major;
|
||||
version["minor"] = info.minor;
|
||||
version["patch"] = info.patch;
|
||||
version["type"] = info.stage;
|
||||
version["tweak"] = info.tweak;
|
||||
json["version"] = version;
|
||||
json["preview"] = info.channel == update_channel::TESTING;
|
||||
json["url"] = info.url;
|
||||
json["name"] = info.name;
|
||||
}
|
||||
|
||||
void streamfx::from_json(const nlohmann::json& json, update_info& info)
|
||||
void streamfx::from_json(const nlohmann::json& json, version_info& info)
|
||||
{
|
||||
if (json.find("html_url") != json.end()) {
|
||||
if (json.find("html_url") == json.end()) {
|
||||
auto version = json.at("version");
|
||||
info.major = version.at("major").get<uint16_t>();
|
||||
info.minor = version.at("minor").get<uint16_t>();
|
||||
info.patch = version.at("patch").get<uint16_t>();
|
||||
info.stage = version.at("type");
|
||||
info.tweak = version.at("tweak").get<uint16_t>();
|
||||
info.url = json.at("url").get<std::string>();
|
||||
info.name = json.at("name").get<std::string>();
|
||||
} else {
|
||||
// This is a response from GitHub.
|
||||
|
||||
// Retrieve entries from the release object.
|
||||
auto entry_tag_name = json.find("tag_name");
|
||||
auto entry_name = json.find("name");
|
||||
auto entry_url = json.find("html_url");
|
||||
auto entry_preview = json.find("prerelease");
|
||||
if ((entry_tag_name == json.end()) || (entry_name == json.end()) || (entry_url == json.end())
|
||||
|| (entry_preview == json.end())) {
|
||||
if ((entry_tag_name == json.end()) || (entry_name == json.end()) || (entry_url == json.end())) {
|
||||
throw std::runtime_error("JSON is missing one or more required keys.");
|
||||
}
|
||||
|
||||
// Initialize update information.
|
||||
info.channel = entry_preview->get<bool>() ? update_channel::TESTING : update_channel::RELEASE;
|
||||
info.url = entry_url->get<std::string>();
|
||||
info.name = entry_name->get<std::string>();
|
||||
|
||||
// Parse the tag name as SemVer (A.B.C)
|
||||
{
|
||||
std::string tag_name = entry_tag_name->get<std::string>();
|
||||
|
||||
size_t dot_1 = tag_name.find_first_of(".", 0) + 1;
|
||||
info.version_major = static_cast<uint16_t>(strtoul(&tag_name.at(0), nullptr, 10));
|
||||
if (dot_1 < tag_name.size()) {
|
||||
info.version_minor = static_cast<uint16_t>(strtoul(&tag_name.at(dot_1), nullptr, 10));
|
||||
}
|
||||
|
||||
char* endptr = nullptr;
|
||||
size_t dot_2 = tag_name.find_first_of(".", dot_1) + 1;
|
||||
if (dot_2 < tag_name.size()) {
|
||||
info.version_patch = static_cast<uint16_t>(strtoul(&tag_name.at(dot_2), &endptr, 10));
|
||||
}
|
||||
|
||||
// Check if there's data following the SemVer structure. (A.B.CdE)
|
||||
if ((endptr != nullptr) && (endptr < (tag_name.data() + tag_name.size()))) {
|
||||
size_t last_num = static_cast<size_t>(endptr - tag_name.data()) + 1;
|
||||
info.version_type = *endptr;
|
||||
if (last_num < tag_name.size())
|
||||
info.version_index = static_cast<uint16_t>(strtoul(&tag_name.at(last_num), nullptr, 10));
|
||||
} else {
|
||||
info.version_type = 0;
|
||||
info.version_index = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto version = json.at("version");
|
||||
info.version_major = version.at("major").get<uint16_t>();
|
||||
info.version_minor = version.at("minor").get<uint16_t>();
|
||||
info.version_patch = version.at("patch").get<uint16_t>();
|
||||
if (version.find("type") != version.end())
|
||||
info.version_type = version.at("alpha").get<bool>() ? 'a' : 'b';
|
||||
info.version_index = version.at("index").get<uint16_t>();
|
||||
info.channel = json.at("preview").get<bool>() ? update_channel::TESTING : update_channel::RELEASE;
|
||||
info.url = json.at("url").get<std::string>();
|
||||
info.name = json.at("name").get<std::string>();
|
||||
// Parse the information.
|
||||
std::string tag_name = entry_tag_name->get<std::string>();
|
||||
info = {tag_name};
|
||||
info.url = entry_url->get<std::string>();
|
||||
info.name = entry_name->get<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
bool streamfx::update_info::is_newer(update_info& other)
|
||||
bool streamfx::version_info::is_older_than(const version_info other)
|
||||
{
|
||||
// 'true' if other is newer, otherwise false.
|
||||
|
||||
// 1. Compare Major version:
|
||||
// A. Ours is greater: Remote is older.
|
||||
// B. Theirs is greater: Remote is newer.
|
||||
// C. Continue the check.
|
||||
if (version_major > other.version_major)
|
||||
if (major > other.major)
|
||||
return false;
|
||||
if (version_major < other.version_major)
|
||||
if (major < other.major)
|
||||
return true;
|
||||
|
||||
// 2. Compare Minor version:
|
||||
// A. Ours is greater: Remote is older.
|
||||
// B. Theirs is greater: Remote is newer.
|
||||
// C. Continue the check.
|
||||
if (version_minor > other.version_minor)
|
||||
if (minor > other.minor)
|
||||
return false;
|
||||
if (version_minor < other.version_minor)
|
||||
if (minor < other.minor)
|
||||
return true;
|
||||
|
||||
// 3. Compare Patch version:
|
||||
// A. Ours is greater: Remote is older.
|
||||
// B. Theirs is greater: Remote is newer.
|
||||
// C. Continue the check.
|
||||
if (version_patch > other.version_patch)
|
||||
if (patch > other.patch)
|
||||
return false;
|
||||
if (version_patch < other.version_patch)
|
||||
if (patch < other.patch)
|
||||
return true;
|
||||
|
||||
// 4. Compare Type:
|
||||
// A. Ours empty: Remote is older.
|
||||
// B. Theirs empty: Remote is newer.
|
||||
// A. Outs is smaller: Remote is older.
|
||||
// B. Theirs is smaller: Remote is newer.
|
||||
// C. Continue the check.
|
||||
// A automatically implies that ours is not empty for B. A&B combined imply that both are not empty for step 5.
|
||||
if (version_type == 0)
|
||||
if (stage < other.stage)
|
||||
return false;
|
||||
if (other.version_type == 0)
|
||||
if (stage > other.stage)
|
||||
return true;
|
||||
|
||||
// 5. Compare Type value:
|
||||
// A. Ours is greater: Remote is older.
|
||||
// B. Theirs is greater: Remote is newer.
|
||||
// C. Continue the check.
|
||||
if (version_type > other.version_type)
|
||||
return false;
|
||||
if (version_type < other.version_type)
|
||||
return true;
|
||||
|
||||
// 6. Compare Index:
|
||||
// 5. Compare Tweak:
|
||||
// A. Ours is greater or equal: Remote is older or identical.
|
||||
// B. Remote is newer
|
||||
if (version_index >= other.version_index)
|
||||
if (tweak >= other.tweak)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
streamfx::version_info::operator std::string()
|
||||
{
|
||||
std::vector<char> buffer(25, 0);
|
||||
if (stage != version_stage::STABLE) {
|
||||
auto types = stage_to_string(stage);
|
||||
size_t len = snprintf(buffer.data(), buffer.size(), "%" PRIu16 ".%" PRIu16 ".%" PRIu16 "%.1s%" PRIu16, major,
|
||||
minor, patch, types.data(), tweak);
|
||||
return std::string(buffer.data(), buffer.data() + len);
|
||||
} else {
|
||||
size_t len = snprintf(buffer.data(), buffer.size(), "%" PRIu16 ".%" PRIu16 ".%" PRIu16, major, minor, patch);
|
||||
return std::string(buffer.data(), buffer.data() + len);
|
||||
}
|
||||
}
|
||||
|
||||
void streamfx::updater::task(streamfx::util::threadpool_data_t)
|
||||
try {
|
||||
{
|
||||
std::vector<char> buffer;
|
||||
task_query(buffer);
|
||||
task_parse(buffer);
|
||||
}
|
||||
auto query_fn = [this](std::vector<char>& buffer) {
|
||||
static constexpr std::string_view ST_API_URL =
|
||||
"https://api.github.com/repos/Xaymar/obs-StreamFX/releases?per_page=25&page=1";
|
||||
|
||||
#ifdef _DEBUG
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
nlohmann::json current = _current_info;
|
||||
nlohmann::json stable = _release_info;
|
||||
nlohmann::json preview = _testing_info;
|
||||
streamfx::util::curl curl;
|
||||
size_t buffer_offset = 0;
|
||||
|
||||
D_LOG_DEBUG("Current Version: %s", current.dump().c_str());
|
||||
D_LOG_DEBUG("Stable Version: %s", stable.dump().c_str());
|
||||
D_LOG_DEBUG("Preview Version: %s", preview.dump().c_str());
|
||||
}
|
||||
#endif
|
||||
// Set headers (User-Agent is needed so Github can contact us!).
|
||||
curl.set_header("User-Agent", "StreamFX Updater v" STREAMFX_VERSION_STRING);
|
||||
curl.set_header("Accept", "application/vnd.github.v3+json");
|
||||
|
||||
// Log that we have a potential update.
|
||||
if (have_update()) {
|
||||
auto info = get_update_info();
|
||||
// Set up request.
|
||||
curl.set_option(CURLOPT_HTTPGET, true); // GET
|
||||
curl.set_option(CURLOPT_POST, false); // Not POST
|
||||
curl.set_option(CURLOPT_URL, ST_API_URL);
|
||||
curl.set_option(CURLOPT_TIMEOUT, 30); // 10s until we fail.
|
||||
|
||||
if (info.version_type) {
|
||||
D_LOG_INFO("Update to version %d.%d.%d%.1s%d is available at \"%s\".", info.version_major,
|
||||
info.version_minor, info.version_patch, &info.version_type, info.version_index,
|
||||
info.url.c_str());
|
||||
} else {
|
||||
D_LOG_INFO("Update to version %d.%d.%d is available at \"%s\".", info.version_major, info.version_minor,
|
||||
info.version_patch, info.url.c_str());
|
||||
// Callbacks
|
||||
curl.set_write_callback([this, &buffer, &buffer_offset](void* data, size_t s1, size_t s2) {
|
||||
size_t size = s1 * s2;
|
||||
if (buffer.size() < (size + buffer_offset))
|
||||
buffer.resize(buffer_offset + size);
|
||||
|
||||
memcpy(buffer.data() + buffer_offset, data, size);
|
||||
buffer_offset += size;
|
||||
|
||||
return s1 * s2;
|
||||
});
|
||||
//std::bind(&streamfx::updater::task_write_cb, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)
|
||||
|
||||
// Clear any unknown data and reserve 64KiB of memory.
|
||||
buffer.clear();
|
||||
buffer.reserve(0xFFFF);
|
||||
|
||||
// Finally, execute the request.
|
||||
D_LOG_DEBUG("Querying for latest releases...", "");
|
||||
if (CURLcode res = curl.perform(); res != CURLE_OK) {
|
||||
D_LOG_ERROR("Performing query failed with error: %s", curl_easy_strerror(res));
|
||||
throw std::runtime_error(curl_easy_strerror(res));
|
||||
}
|
||||
} else {
|
||||
D_LOG_DEBUG("No update available.", "");
|
||||
|
||||
int32_t status_code = 0;
|
||||
if (CURLcode res = curl.get_info(CURLINFO_HTTP_CODE, status_code); res != CURLE_OK) {
|
||||
D_LOG_ERROR("Retrieving status code failed with error: %s", curl_easy_strerror(res));
|
||||
throw std::runtime_error(curl_easy_strerror(res));
|
||||
}
|
||||
D_LOG_DEBUG("API returned status code %d.", status_code);
|
||||
|
||||
if (status_code != 200) {
|
||||
D_LOG_ERROR("API returned unexpected status code %d.", status_code);
|
||||
throw std::runtime_error("Request failed due to one or more reasons.");
|
||||
}
|
||||
};
|
||||
auto parse_fn = [this](nlohmann::json json) {
|
||||
// Check if it was parsed as an object.
|
||||
if (json.type() != nlohmann::json::value_t::array) {
|
||||
throw std::runtime_error("Invalid response from API.");
|
||||
}
|
||||
|
||||
// Decide on the latest version for all update channels.
|
||||
std::lock_guard<decltype(_lock)> lock(_lock);
|
||||
_updates.clear();
|
||||
for (auto obj : json) {
|
||||
try {
|
||||
auto info = obj.get<streamfx::version_info>();
|
||||
|
||||
switch (info.stage) {
|
||||
case version_stage::STABLE:
|
||||
if (get_update_info(version_stage::STABLE).is_older_than(info)) {
|
||||
_updates.emplace(version_stage::STABLE, info);
|
||||
}
|
||||
[[fallthrough]];
|
||||
case version_stage::CANDIDATE:
|
||||
if (get_update_info(version_stage::CANDIDATE).is_older_than(info)) {
|
||||
_updates.emplace(version_stage::CANDIDATE, info);
|
||||
}
|
||||
[[fallthrough]];
|
||||
case version_stage::BETA:
|
||||
if (get_update_info(version_stage::BETA).is_older_than(info)) {
|
||||
_updates.emplace(version_stage::BETA, info);
|
||||
}
|
||||
[[fallthrough]];
|
||||
case version_stage::ALPHA:
|
||||
if (get_update_info(version_stage::ALPHA).is_older_than(info)) {
|
||||
_updates.emplace(version_stage::ALPHA, info);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (const std::exception& ex) {
|
||||
D_LOG_DEBUG("Failed to parse entry, error: %s", ex.what());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
{ // Query and parse the response.
|
||||
nlohmann::json json;
|
||||
|
||||
// Query the API or parse a crafted response.
|
||||
auto debug_path = streamfx::config_file_path("github_release_query_response.json");
|
||||
if (std::filesystem::exists(debug_path)) {
|
||||
std::ifstream fs{debug_path};
|
||||
json = nlohmann::json::parse(fs);
|
||||
fs.close();
|
||||
} else {
|
||||
std::vector<char> buffer;
|
||||
query_fn(buffer);
|
||||
json = nlohmann::json::parse(buffer.begin(), buffer.end());
|
||||
}
|
||||
|
||||
// Parse the JSON response from the API.
|
||||
parse_fn(json);
|
||||
}
|
||||
|
||||
// Print all update information to the log file.
|
||||
D_LOG_INFO("Current Version: %s", static_cast<std::string>(_current_info).c_str());
|
||||
D_LOG_INFO("Latest Stable Version: %s", static_cast<std::string>(get_update_info(version_stage::STABLE)).c_str());
|
||||
D_LOG_INFO("Latest Candidate Version: %s",
|
||||
static_cast<std::string>(get_update_info(version_stage::CANDIDATE)).c_str());
|
||||
D_LOG_INFO("Latest Beta Version: %s", static_cast<std::string>(get_update_info(version_stage::BETA)).c_str());
|
||||
D_LOG_INFO("Latest Alpha Version: %s", static_cast<std::string>(get_update_info(version_stage::ALPHA)).c_str());
|
||||
if (is_update_available()) {
|
||||
D_LOG_INFO("Update is available.", "");
|
||||
}
|
||||
|
||||
// Notify listeners of the update.
|
||||
|
@ -228,91 +359,6 @@ try {
|
|||
events.error.call(*this, message);
|
||||
}
|
||||
|
||||
void streamfx::updater::task_query(std::vector<char>& buffer)
|
||||
{
|
||||
static constexpr std::string_view ST_API_URL = "https://api.github.com/repos/Xaymar/obs-StreamFX/releases";
|
||||
|
||||
streamfx::util::curl curl;
|
||||
size_t buffer_offset = 0;
|
||||
|
||||
// Set headers (User-Agent is needed so Github can contact us!).
|
||||
curl.set_header("User-Agent", "StreamFX Updater v" STREAMFX_VERSION_STRING);
|
||||
curl.set_header("Accept", "application/vnd.github.v3+json");
|
||||
|
||||
// Set up request.
|
||||
curl.set_option(CURLOPT_HTTPGET, true); // GET
|
||||
curl.set_option(CURLOPT_POST, false); // Not POST
|
||||
curl.set_option(CURLOPT_URL, ST_API_URL);
|
||||
curl.set_option(CURLOPT_TIMEOUT, 10); // 10s until we fail.
|
||||
|
||||
// Callbacks
|
||||
curl.set_write_callback([this, &buffer, &buffer_offset](void* data, size_t s1, size_t s2) {
|
||||
size_t size = s1 * s2;
|
||||
if (buffer.size() < (size + buffer_offset))
|
||||
buffer.resize(buffer_offset + size);
|
||||
|
||||
memcpy(buffer.data() + buffer_offset, data, size);
|
||||
buffer_offset += size;
|
||||
|
||||
return s1 * s2;
|
||||
});
|
||||
//std::bind(&streamfx::updater::task_write_cb, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)
|
||||
|
||||
// Clear any unknown data and reserve 64KiB of memory.
|
||||
buffer.clear();
|
||||
buffer.reserve(0xFFFF);
|
||||
|
||||
// Finally, execute the request.
|
||||
D_LOG_DEBUG("Querying for latest releases...", "");
|
||||
if (CURLcode res = curl.perform(); res != CURLE_OK) {
|
||||
D_LOG_ERROR("Performing query failed with error: %s", curl_easy_strerror(res));
|
||||
throw std::runtime_error(curl_easy_strerror(res));
|
||||
}
|
||||
|
||||
int32_t status_code = 0;
|
||||
if (CURLcode res = curl.get_info(CURLINFO_HTTP_CODE, status_code); res != CURLE_OK) {
|
||||
D_LOG_ERROR("Retrieving status code failed with error: %s", curl_easy_strerror(res));
|
||||
throw std::runtime_error(curl_easy_strerror(res));
|
||||
}
|
||||
D_LOG_DEBUG("API returned status code %d.", status_code);
|
||||
|
||||
if (status_code != 200) {
|
||||
D_LOG_ERROR("API returned unexpected status code %d.", status_code);
|
||||
throw std::runtime_error("Request failed due to one or more reasons.");
|
||||
}
|
||||
}
|
||||
|
||||
void streamfx::updater::task_parse(std::vector<char>& buffer)
|
||||
{
|
||||
// Parse the JSON response from the API.
|
||||
nlohmann::json json = nlohmann::json::parse(buffer.begin(), buffer.end());
|
||||
|
||||
// Check if it was parsed as an object.
|
||||
if (json.type() != nlohmann::json::value_t::array) {
|
||||
throw std::runtime_error("Response is not a JSON array.");
|
||||
}
|
||||
|
||||
// Iterate over all entries.
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
for (auto obj : json) {
|
||||
try {
|
||||
auto info = obj.get<streamfx::update_info>();
|
||||
|
||||
if (info.channel == update_channel::RELEASE) {
|
||||
if (_release_info.is_newer(info))
|
||||
_release_info = info;
|
||||
if (_testing_info.is_newer(info))
|
||||
_testing_info = info;
|
||||
} else {
|
||||
if (_testing_info.is_newer(info))
|
||||
_testing_info = info;
|
||||
}
|
||||
} catch (const std::exception& ex) {
|
||||
D_LOG_DEBUG("Failed to parse entry, error: %s", ex.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool streamfx::updater::can_check()
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
|
@ -326,16 +372,16 @@ bool streamfx::updater::can_check()
|
|||
|
||||
void streamfx::updater::load()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
std::lock_guard<decltype(_lock)> lock(_lock);
|
||||
if (auto config = streamfx::configuration::instance(); config) {
|
||||
auto dataptr = config->get();
|
||||
|
||||
if (obs_data_has_user_value(dataptr.get(), ST_CFG_GDPR))
|
||||
_gdpr = obs_data_get_bool(dataptr.get(), ST_CFG_GDPR);
|
||||
if (obs_data_has_user_value(dataptr.get(), ST_CFG_DATASHARING))
|
||||
_data_sharing_allowed = obs_data_get_bool(dataptr.get(), ST_CFG_DATASHARING);
|
||||
if (obs_data_has_user_value(dataptr.get(), ST_CFG_AUTOMATION))
|
||||
_automation = obs_data_get_bool(dataptr.get(), ST_CFG_AUTOMATION);
|
||||
if (obs_data_has_user_value(dataptr.get(), ST_CFG_CHANNEL))
|
||||
_channel = static_cast<update_channel>(obs_data_get_int(dataptr.get(), ST_CFG_CHANNEL));
|
||||
_channel = static_cast<version_stage>(obs_data_get_int(dataptr.get(), ST_CFG_CHANNEL));
|
||||
if (obs_data_has_user_value(dataptr.get(), ST_CFG_LASTCHECKEDAT))
|
||||
_lastcheckedat = std::chrono::seconds(obs_data_get_int(dataptr.get(), ST_CFG_LASTCHECKEDAT));
|
||||
}
|
||||
|
@ -346,7 +392,7 @@ void streamfx::updater::save()
|
|||
if (auto config = streamfx::configuration::instance(); config) {
|
||||
auto dataptr = config->get();
|
||||
|
||||
obs_data_set_bool(dataptr.get(), ST_CFG_GDPR, _gdpr);
|
||||
obs_data_set_bool(dataptr.get(), ST_CFG_DATASHARING, _data_sharing_allowed);
|
||||
obs_data_set_bool(dataptr.get(), ST_CFG_AUTOMATION, _automation);
|
||||
obs_data_set_int(dataptr.get(), ST_CFG_CHANNEL, static_cast<long long>(_channel));
|
||||
obs_data_set_int(dataptr.get(), ST_CFG_LASTCHECKEDAT, static_cast<long long>(_lastcheckedat.count()));
|
||||
|
@ -356,24 +402,16 @@ void streamfx::updater::save()
|
|||
streamfx::updater::updater()
|
||||
: _lock(), _task(),
|
||||
|
||||
_gdpr(false), _automation(true), _channel(update_channel::RELEASE), _lastcheckedat(),
|
||||
_data_sharing_allowed(false), _automation(true), _channel(version_stage::STABLE), _lastcheckedat(),
|
||||
|
||||
_current_info(), _release_info(), _testing_info(), _dirty(false)
|
||||
_current_info(), _updates(), _dirty(false)
|
||||
{
|
||||
// Load information from configuration.
|
||||
load();
|
||||
|
||||
// Build current version information.
|
||||
try {
|
||||
_current_info.version_major = STREAMFX_VERSION_MAJOR;
|
||||
_current_info.version_minor = STREAMFX_VERSION_MINOR;
|
||||
_current_info.version_patch = STREAMFX_VERSION_PATCH;
|
||||
_current_info.channel = _channel;
|
||||
std::string suffix = STREAMFX_VERSION_SUFFIX;
|
||||
if (suffix.length()) {
|
||||
_current_info.version_type = suffix.at(0);
|
||||
_current_info.version_index = static_cast<uint16_t>(strtoul(&suffix.at(1), nullptr, 10));
|
||||
}
|
||||
_current_info = {STREAMFX_VERSION_STRING};
|
||||
} catch (...) {
|
||||
D_LOG_ERROR("Failed to parse current version information, results may be inaccurate.", "");
|
||||
}
|
||||
|
@ -384,26 +422,26 @@ streamfx::updater::~updater()
|
|||
save();
|
||||
}
|
||||
|
||||
bool streamfx::updater::gdpr()
|
||||
bool streamfx::updater::is_data_sharing_allowed()
|
||||
{
|
||||
return _gdpr;
|
||||
return _data_sharing_allowed;
|
||||
}
|
||||
|
||||
void streamfx::updater::set_gdpr(bool value)
|
||||
void streamfx::updater::set_data_sharing_allowed(bool value)
|
||||
{
|
||||
_dirty = true;
|
||||
_gdpr = value;
|
||||
events.gdpr_changed(*this, _gdpr);
|
||||
_dirty = true;
|
||||
_data_sharing_allowed = value;
|
||||
events.gdpr_changed(*this, _data_sharing_allowed);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
std::lock_guard<decltype(_lock)> lock(_lock);
|
||||
save();
|
||||
}
|
||||
|
||||
D_LOG_INFO("User %s the processing of data.", _gdpr ? "allowed" : "disallowed");
|
||||
D_LOG_INFO("User %s the processing of data.", _data_sharing_allowed ? "allowed" : "disallowed");
|
||||
}
|
||||
|
||||
bool streamfx::updater::automation()
|
||||
bool streamfx::updater::is_automated()
|
||||
{
|
||||
return _automation;
|
||||
}
|
||||
|
@ -414,21 +452,21 @@ void streamfx::updater::set_automation(bool value)
|
|||
events.automation_changed(*this, _automation);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
std::lock_guard<decltype(_lock)> lock(_lock);
|
||||
save();
|
||||
}
|
||||
|
||||
D_LOG_INFO("Automatic checks at launch are now %s.", value ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
streamfx::update_channel streamfx::updater::channel()
|
||||
streamfx::version_stage streamfx::updater::get_channel()
|
||||
{
|
||||
return _channel;
|
||||
}
|
||||
|
||||
void streamfx::updater::set_channel(update_channel value)
|
||||
void streamfx::updater::set_channel(version_stage value)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
std::lock_guard<decltype(_lock)> lock(_lock);
|
||||
|
||||
_dirty = true;
|
||||
_channel = value;
|
||||
|
@ -436,17 +474,17 @@ void streamfx::updater::set_channel(update_channel value)
|
|||
|
||||
save();
|
||||
|
||||
D_LOG_INFO("Update channel changed to %s.", value == update_channel::RELEASE ? "Release" : "Testing");
|
||||
D_LOG_INFO("Update channel changed to '%s'.", stage_to_string(value).data());
|
||||
}
|
||||
|
||||
void streamfx::updater::refresh()
|
||||
{
|
||||
if (!_task.expired() || !gdpr()) {
|
||||
if (!_task.expired() || !is_data_sharing_allowed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (can_check()) {
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
std::lock_guard<decltype(_lock)> lock(_lock);
|
||||
|
||||
// Update last checked time.
|
||||
_lastcheckedat =
|
||||
|
@ -460,25 +498,34 @@ void streamfx::updater::refresh()
|
|||
}
|
||||
}
|
||||
|
||||
bool streamfx::updater::have_update()
|
||||
bool streamfx::updater::is_update_available()
|
||||
{
|
||||
auto info = get_update_info();
|
||||
return _current_info.is_newer(info);
|
||||
return _current_info.is_older_than(get_update_info());
|
||||
}
|
||||
|
||||
streamfx::update_info streamfx::updater::get_current_info()
|
||||
bool streamfx::updater::is_update_available(version_stage channel)
|
||||
{
|
||||
return _current_info.is_older_than(get_update_info(channel));
|
||||
}
|
||||
|
||||
streamfx::version_info streamfx::updater::get_current_info()
|
||||
{
|
||||
return _current_info;
|
||||
}
|
||||
|
||||
streamfx::update_info streamfx::updater::get_update_info()
|
||||
streamfx::version_info streamfx::updater::get_update_info()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
update_info info = _release_info;
|
||||
if (info.is_newer(_testing_info) && (_channel == update_channel::TESTING))
|
||||
info = _testing_info;
|
||||
return get_update_info(_channel);
|
||||
}
|
||||
|
||||
return info;
|
||||
streamfx::version_info streamfx::updater::get_update_info(version_stage channel)
|
||||
{
|
||||
std::lock_guard<decltype(_lock)> lock(_lock);
|
||||
if (auto iter = _updates.find(channel); iter != _updates.end()) {
|
||||
return iter->second;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<streamfx::updater> streamfx::updater::instance()
|
||||
|
|
|
@ -21,54 +21,65 @@
|
|||
#pragma once
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "util/util-curl.hpp"
|
||||
#include "util/util-event.hpp"
|
||||
#include "util/util-threadpool.hpp"
|
||||
|
||||
namespace streamfx {
|
||||
enum class update_channel {
|
||||
RELEASE,
|
||||
TESTING,
|
||||
enum class version_stage : uint8_t {
|
||||
STABLE, // A.B.C
|
||||
CANDIDATE, // A.B.CcD
|
||||
BETA, // A.B.CbD
|
||||
ALPHA, // A.B.CaD
|
||||
};
|
||||
version_stage stage_from_string(std::string_view str);
|
||||
std::string_view stage_to_string(version_stage t);
|
||||
|
||||
void to_json(nlohmann::json&, const version_stage&);
|
||||
void from_json(const nlohmann::json&, version_stage&);
|
||||
|
||||
struct version_info {
|
||||
public:
|
||||
uint16_t major;
|
||||
uint16_t minor;
|
||||
uint16_t patch;
|
||||
uint16_t tweak;
|
||||
version_stage stage;
|
||||
std::string url;
|
||||
std::string name;
|
||||
|
||||
public:
|
||||
version_info();
|
||||
version_info(const std::string text);
|
||||
|
||||
bool is_older_than(const version_info other);
|
||||
|
||||
operator std::string();
|
||||
};
|
||||
|
||||
struct update_info {
|
||||
uint16_t version_major = 0;
|
||||
uint16_t version_minor = 0;
|
||||
uint16_t version_patch = 0;
|
||||
char version_type = 0;
|
||||
uint16_t version_index = 0;
|
||||
update_channel channel = update_channel::RELEASE;
|
||||
std::string url = "";
|
||||
std::string name = "";
|
||||
|
||||
bool is_newer(update_info& other);
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json&, const update_info&);
|
||||
void from_json(const nlohmann::json&, update_info&);
|
||||
void to_json(nlohmann::json&, const version_info&);
|
||||
void from_json(const nlohmann::json&, version_info&);
|
||||
|
||||
class updater {
|
||||
// Internal
|
||||
std::mutex _lock;
|
||||
std::recursive_mutex _lock;
|
||||
std::weak_ptr<::streamfx::util::threadpool::task> _task;
|
||||
|
||||
// Options
|
||||
std::atomic_bool _gdpr;
|
||||
std::atomic_bool _data_sharing_allowed;
|
||||
std::atomic_bool _automation;
|
||||
update_channel _channel;
|
||||
version_stage _channel;
|
||||
std::chrono::seconds _lastcheckedat;
|
||||
|
||||
// Update Information
|
||||
update_info _current_info;
|
||||
update_info _release_info;
|
||||
update_info _testing_info;
|
||||
bool _dirty;
|
||||
version_info _current_info;
|
||||
std::map<version_stage, version_info> _updates;
|
||||
bool _dirty;
|
||||
|
||||
private:
|
||||
void task(streamfx::util::threadpool_data_t);
|
||||
void task_query(std::vector<char>& buffer);
|
||||
void task_parse(std::vector<char>& buffer);
|
||||
|
||||
bool can_check();
|
||||
|
||||
|
@ -80,30 +91,34 @@ namespace streamfx {
|
|||
~updater();
|
||||
|
||||
// GDPR compliance (must require user interaction!)
|
||||
bool gdpr();
|
||||
void set_gdpr(bool);
|
||||
bool is_data_sharing_allowed();
|
||||
void set_data_sharing_allowed(bool);
|
||||
|
||||
// Automatic Update checks
|
||||
bool automation();
|
||||
bool is_automated();
|
||||
void set_automation(bool);
|
||||
|
||||
// Update Channel
|
||||
update_channel channel();
|
||||
void set_channel(update_channel channel);
|
||||
version_stage get_channel();
|
||||
void set_channel(version_stage channel);
|
||||
|
||||
// Refresh information.
|
||||
void refresh();
|
||||
|
||||
// Check current data.
|
||||
bool have_update();
|
||||
update_info get_current_info();
|
||||
update_info get_update_info();
|
||||
version_info get_current_info();
|
||||
|
||||
// Check update data.
|
||||
bool is_update_available();
|
||||
bool is_update_available(version_stage channel);
|
||||
version_info get_update_info();
|
||||
version_info get_update_info(version_stage channel);
|
||||
|
||||
public:
|
||||
struct _ {
|
||||
streamfx::util::event<updater&, bool> gdpr_changed;
|
||||
streamfx::util::event<updater&, bool> automation_changed;
|
||||
streamfx::util::event<updater&, update_channel> channel_changed;
|
||||
streamfx::util::event<updater&, bool> gdpr_changed;
|
||||
streamfx::util::event<updater&, bool> automation_changed;
|
||||
streamfx::util::event<updater&, version_stage> channel_changed;
|
||||
|
||||
streamfx::util::event<updater&, std::string&> error;
|
||||
streamfx::util::event<updater&> refreshed;
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#define STREAMFX_VERSION_PATCH @PROJECT_VERSION_PATCH@
|
||||
#define STREAMFX_VERSION_BUILD @PROJECT_VERSION_TWEAK@
|
||||
#define STREAMFX_VERSION_TWEAK @PROJECT_VERSION_TWEAK@
|
||||
#define STREAMFX_VERSION_SUFFIX "@VERSION_SUFFIX@"
|
||||
#define STREAMFX_VERSION_SUFFIX "@VERSION_STAGE@"
|
||||
#define STREAMFX_VERSION_STAGE "@VERSION_STAGE@"
|
||||
#define STREAMFX_VERSION_COMMIT "@VERSION_COMMIT@"
|
||||
#define STREAMFX_VERSION_STRING "@VERSION_STRING@"
|
||||
|
|
Loading…
Reference in a new issue