From 5582e8e063c53a639b30e4ada4d3e479cc48dff9 Mon Sep 17 00:00:00 2001 From: moonshadow565 Date: Sat, 27 Aug 2022 23:10:59 +0200 Subject: [PATCH] add mod updating facility --- CMakeLists.txt | 6 +- src/CSLOLTools.cpp | 3 +- src/CSLOLTools.h | 2 + src/CSLOLToolsImpl.cpp | 82 ++++++++++++++++++ src/CSLOLToolsImpl.h | 6 ++ src/qml/CSLOLDialogSettings.qml | 17 +++- src/qml/CSLOLDialogUpdateMods.qml | 135 ++++++++++++++++++++++++++++++ src/qml/CSLOLModsView.qml | 13 +++ src/qml/main.qml | 15 +++- src/qml/qml.qrc | 1 + 10 files changed, 274 insertions(+), 6 deletions(-) create mode 100644 src/qml/CSLOLDialogUpdateMods.qml diff --git a/CMakeLists.txt b/CMakeLists.txt index abb88cbf..dd697b1c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,9 +12,9 @@ add_subdirectory(cslol-tools) option(USE_QT6 "Use Qt6 instead of Qt5" OFF) if (USE_QT6) - find_package(Qt6 6.1 COMPONENTS Core Gui Quick QmlImportScanner REQUIRED) + find_package(Qt6 6.1 COMPONENTS Core Gui Quick QmlImportScanner Network REQUIRED) else() - find_package(Qt5 5.15 COMPONENTS Core Gui Quick QmlImportScanner REQUIRED) + find_package(Qt5 5.15 COMPONENTS Core Gui Quick QmlImportScanner Network REQUIRED) endif() find_package(Threads REQUIRED) find_package(Git QUIET REQUIRED) @@ -83,7 +83,7 @@ target_sources(cslol-manager PRIVATE ) target_compile_definitions(cslol-manager PRIVATE $<$,$>:QT_QML_DEBUG>) -target_link_libraries(cslol-manager PRIVATE Qt::Core Qt::Gui Qt::Quick Threads::Threads) +target_link_libraries(cslol-manager PRIVATE Qt::Core Qt::Gui Qt::Quick Qt::Network Threads::Threads) target_include_directories(cslol-manager PRIVATE src/) qt_import_qml_plugins(cslol-manager) diff --git a/src/CSLOLTools.cpp b/src/CSLOLTools.cpp index 25fcf7d7..fc26ba1e 100644 --- a/src/CSLOLTools.cpp +++ b/src/CSLOLTools.cpp @@ -28,7 +28,7 @@ CSLOLTools::CSLOLTools(QObject *parent) : QObject(parent) { connect(worker_, &CSLOLToolsImpl::modInfoChanged, this, &CSLOLTools::modInfoChanged); connect(worker_, &CSLOLToolsImpl::modWadsAdded, this, &CSLOLTools::modWadsAdded); connect(worker_, &CSLOLToolsImpl::modWadsRemoved, this, &CSLOLTools::modWadsRemoved); - connect(worker_, &CSLOLToolsImpl::refreshed, this, &CSLOLTools::refreshed); + connect(worker_, &CSLOLToolsImpl::updatedMods, this, &CSLOLTools::updatedMods); connect(this, &CSLOLTools::changeLeaguePath, worker_, &CSLOLToolsImpl::changeLeaguePath); connect(this, &CSLOLTools::changeBlacklist, worker_, &CSLOLToolsImpl::changeBlacklist); @@ -47,6 +47,7 @@ CSLOLTools::CSLOLTools(QObject *parent) : QObject(parent) { connect(this, &CSLOLTools::addModWad, worker_, &CSLOLToolsImpl::addModWad); connect(this, &CSLOLTools::removeModWads, worker_, &CSLOLToolsImpl::removeModWads); connect(this, &CSLOLTools::refreshMods, worker_, &CSLOLToolsImpl::refreshMods); + connect(this, &CSLOLTools::doUpdate, worker_, &CSLOLToolsImpl::doUpdate); connect(this, &CSLOLTools::destroyed, worker_, &CSLOLToolsImpl::deleteLater); connect(worker_, &CSLOLTools::destroyed, thread_, &QThread::deleteLater); diff --git a/src/CSLOLTools.h b/src/CSLOLTools.h index 826b8640..303a70ed 100644 --- a/src/CSLOLTools.h +++ b/src/CSLOLTools.h @@ -44,6 +44,7 @@ class CSLOLTools : public QObject { void modWadsAdded(QString modFileName, QJsonArray wads); void modWadsRemoved(QString modFileName, QJsonArray wads); void refreshed(QJsonObject mods); + void updatedMods(QJsonArray mods); void reportError(QString name, QString message, QString stack_trace); void changeLeaguePath(QString newLeaguePath); @@ -63,6 +64,7 @@ class CSLOLTools : public QObject { void addModWad(QString modFileName, QString wad, bool removeUnknownNames); void removeModWads(QString modFileName, QJsonArray wads); void refreshMods(); + void doUpdate(QString urls); public slots: CSLOLToolsImpl::CSLOLState getState(); diff --git a/src/CSLOLToolsImpl.cpp b/src/CSLOLToolsImpl.cpp index 17eb428b..4c4f8e76 100644 --- a/src/CSLOLToolsImpl.cpp +++ b/src/CSLOLToolsImpl.cpp @@ -7,9 +7,12 @@ #include #include #include +#include #include +#include #include #include +#include #include #include "CSLOLVersion.h" @@ -407,6 +410,85 @@ void CSLOLToolsImpl::refreshMods() { } } +void CSLOLToolsImpl::doUpdate(QString urls) { + if (!networkManager_) { + this->networkManager_ = new QNetworkAccessManager(this); + this->networkManager_->setTransferTimeout(30000); + connect(this->networkManager_, &QNetworkAccessManager::finished, this, [this](QNetworkReply* reply) { + if (reply->error()) { + auto error = reply->errorString(); + setStatus("[WRN] Failed to fetch update index: " + error); + this->networkResults_.push_back(QJsonDocument::fromJson("[]")); + } else { + auto result = reply->readAll(); + this->networkResults_.push_back(QJsonDocument::fromJson(result)); + } + + if (this->networkResults_.size() < this->networkRequests_.size()) { + return; + } + + auto lookup = QMap{}; + for (auto name : modList()) { + auto info = modInfoRead(name); + auto key = info["Name"].toString().toLower(); + auto version = QVersionNumber::fromString(info["Version"].toString()); + lookup[key] = version; + } + + auto mods = QJsonArray{}; + for (auto const& result : this->networkResults_) { + if (!result.isArray()) { + continue; + } + for (auto const& value : result.array()) { + auto info = modInfoFixup("", value.toObject()); + if (info["Download"].toString().isEmpty() || !info["Download"].isString()) { + info["Download"] = info["Home"]; + } + auto key = info["Name"].toString().toLower(); + if (lookup.contains(key)) { + auto const& old_version = lookup[key]; + auto new_version = QVersionNumber::fromString(info["Version"].toString()); + if (old_version < new_version) { + mods.append(info); + } + } + } + } + + emit updatedMods(mods); + + setState(CSLOLState::StateIdle); + }); + } + + if (state_ == CSLOLState::StateIdle) { + setState(CSLOLState::StateBusy); + setStatus("Updating mods"); + + this->networkResults_.clear(); + this->networkRequests_.clear(); + for (auto url : urls.split('\n')) { + url = url.trimmed(); + if (url.isEmpty() || url.startsWith("#")) { + continue; + } + this->networkRequests_.emplace_back(QUrl(url)); + } + + if (this->networkRequests_.empty()) { + doReportError("Update mods", "Make sure to set update urls in settings!", ""); + setState(CSLOLState::StateIdle); + return; + } + + for (auto& req : this->networkRequests_) { + this->networkManager_->get(req); + } + } +} + void CSLOLToolsImpl::saveProfile(QString name, QJsonObject mods, bool run, bool skipConflict) { if (state_ == CSLOLState::StateIdle) { setState(CSLOLState::StateBusy); diff --git a/src/CSLOLToolsImpl.h b/src/CSLOLToolsImpl.h index 01c50799..ee507503 100644 --- a/src/CSLOLToolsImpl.h +++ b/src/CSLOLToolsImpl.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +45,7 @@ class CSLOLToolsImpl : public QObject { void modWadsAdded(QString modFileName, QJsonArray wads); void modWadsRemoved(QString modFileName, QJsonArray wads); void refreshed(QJsonObject mods); + void updatedMods(QJsonArray mods); void reportError(QString name, QString message, QString stack_trace); public slots: @@ -60,6 +62,7 @@ public slots: void stopProfile(); void makeMod(QString fileName, QJsonObject infoData, QString image); void refreshMods(); + void doUpdate(QString urls); void startEditMod(QString fileName); void changeModInfo(QString fileName, QJsonObject infoData, QString image); @@ -70,6 +73,9 @@ public slots: QString getLeaguePath(); private: + QNetworkAccessManager* networkManager_ = nullptr; + std::vector networkResults_ = {}; + std::vector networkRequests_ = {}; QLockFile* lockfile_ = nullptr; QProcess* patcherProcess_ = nullptr; QString prog_ = ""; diff --git a/src/qml/CSLOLDialogSettings.qml b/src/qml/CSLOLDialogSettings.qml index 2c129939..59064509 100644 --- a/src/qml/CSLOLDialogSettings.qml +++ b/src/qml/CSLOLDialogSettings.qml @@ -28,6 +28,7 @@ Dialog { property alias suppressInstallConflicts: suppressInstallConflictsCheck.checked property alias enableSystray: enableSystrayCheck.checked property alias enableAutoRun: enableAutoRunCheck.checked + property alias updateUrls: updateUrlsTextArea.text property var colors_LIST: [ "Red", @@ -78,7 +79,7 @@ Dialog { StackLayout { id: settingsStackLayout width: parent.width - Layout.fillHeight: true + height: parent.height - settingsTabBar.height - 5 currentIndex: settingsTabBar.currentIndex ColumnLayout { id: settingsGameTab @@ -108,6 +109,7 @@ Dialog { Layout.fillWidth: true } } + ColumnLayout { id: settingsSystemTab spacing: 5 @@ -121,6 +123,19 @@ Dialog { onClicked: Qt.openUrlExternally(cslolDialogUpdate.update_url) Layout.fillWidth: true } + ScrollView { + Layout.fillHeight: true + Layout.fillWidth: true + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.vertical.policy: ScrollBar.AlwaysOn + padding: ScrollBar.vertical.width + clip: true + TextArea { + id: updateUrlsTextArea + placeholderText: qsTr("Update urls") + textFormat: TextEdit.PlainText + } + } CheckBox { id: enableUpdatesCheck text: qsTr("Enable updates") diff --git a/src/qml/CSLOLDialogUpdateMods.qml b/src/qml/CSLOLDialogUpdateMods.qml new file mode 100644 index 00000000..cc9117c0 --- /dev/null +++ b/src/qml/CSLOLDialogUpdateMods.qml @@ -0,0 +1,135 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.15 +import QtQuick.Controls.Material 2.15 + +Dialog { + id: cslolDialogUpdateMods + width: parent.width * 0.9 + height: parent.height * 0.9 + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + standardButtons: Dialog.Ok + closePolicy: Popup.CloseOnEscape + modal: true + title: qsTr("Mod updates and fixes:") + Overlay.modal: Rectangle { + color: "#aa333333" + } + onOpened: window.show() + + property int columnCount: 1 + property real rowHeight: 0 + property alias updatedMods: cslolUpdateModsView.model + + updatedMods: [] + + ScrollView { + id: cslolUpdateModsScrollView + width: parent.width + height: parent.height + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.vertical.policy: ScrollBar.AlwaysOn + padding: ScrollBar.vertical.width + spacing: 5 + + GridView { + id: cslolUpdateModsView + cellWidth: cslolUpdateModsView.width / cslolDialogUpdateMods.columnCount + cellHeight: 75 + delegate: Pane { + property var model: modelData + width: cslolUpdateModsView.width / cslolDialogUpdateMods.columnCount - cslolUpdateModsScrollView.spacing + Component.onCompleted: { + let newCellHeight = height + cslolUpdateModsScrollView.spacing + if (cslolUpdateModsView.cellHeight < newCellHeight) { + cslolUpdateModsView.cellHeight = newCellHeight; + } + } + Material.elevation: 3 + Row { + width: parent.width + Row { + id: modUpdateButtons + width: parent.width * 0.3 + + ToolButton { + anchors.verticalCenter: parent.verticalCenter + text: "\uf0ed " + font.family: "FontAwesome" + onClicked: { + let url = model.Download + if (window.validUrl.test(url)) { + Qt.openUrlExternally(url) + } + } + CSLOLToolTip { + text: qsTr("Download") + visible: parent.hovered + } + } + Label { + anchors.verticalCenter: parent.verticalCenter + horizontalAlignment: Text.AlignHCenter + text: model.Name + elide: Text.ElideRight + Layout.fillWidth: true + } + } + Column { + width: parent.width * 0.39 + anchors.verticalCenter: parent.verticalCenter + Label { + horizontalAlignment: Text.AlignHCenter + text: "V" + model.Version + " by " + model.Author + elide: Text.ElideRight + width: parent.width + } + Label { + horizontalAlignment: Text.AlignHCenter + text: model.Description ? model.Description : "" + wrapMode: Text.Wrap + elide: Text.ElideRight + maximumLineCount: 2 + width: parent.width + } + } + Row { + id: modUpdateButtons2 + width: parent.width * 0.3 + anchors.verticalCenter: parent.verticalCenter + layoutDirection: Qt.RightToLeft + ToolButton { + text: "\uf059" + font.family: "FontAwesome" + onClicked: { + let url = model.Home + if (window.validUrl.test(url)) { + Qt.openUrlExternally(url) + } + } + CSLOLToolTip { + text: qsTr("Mod updates") + visible: parent.hovered + } + } + ToolButton { + text: "\uf004" + font.family: "FontAwesome" + onClicked: { + let url = model.Heart + if (window.validUrl.test(url)) { + Qt.openUrlExternally(url) + } + } + CSLOLToolTip { + text: qsTr("Support this author") + visible: parent.hovered + } + } + } + } + } + } + } +} diff --git a/src/qml/CSLOLModsView.qml b/src/qml/CSLOLModsView.qml index 98621a22..88129d24 100644 --- a/src/qml/CSLOLModsView.qml +++ b/src/qml/CSLOLModsView.qml @@ -33,6 +33,8 @@ ColumnLayout { signal tryRefresh() + signal getUpdates() + function addMod(fileName, info, enabled) { let infoData = { "FileName": fileName, @@ -465,6 +467,17 @@ ColumnLayout { visible: parent.hovered } } + RoundButton { + enabled: !isBussy + text: "\uf0ad" + font.family: "FontAwesome" + onClicked: cslolModsView.getUpdates() + Material.background: Material.primaryColor + CSLOLToolTip { + text: qsTr("Mod updates and fixes") + visible: parent.hovered + } + } RoundButton { enabled: !isBussy text: "\uf067" diff --git a/src/qml/main.qml b/src/qml/main.qml index 7067ccae..95879fb2 100644 --- a/src/qml/main.qml +++ b/src/qml/main.qml @@ -23,6 +23,7 @@ ApplicationWindow { property alias blacklist: cslolDialogSettings.blacklist property alias ignorebad: cslolDialogSettings.ignorebad property alias suppressInstallConflicts: cslolDialogSettings.suppressInstallConflicts + property alias updateUrls: cslolDialogSettings.updateUrls property alias enableUpdates: cslolDialogSettings.enableUpdates property alias enableAutoRun: cslolDialogSettings.enableAutoRun property alias enableSystray: cslolDialogSettings.enableSystray @@ -216,6 +217,7 @@ ApplicationWindow { } } onTryRefresh: cslolTools.refreshMods() + onGetUpdates: cslolTools.doUpdate(settings.updateUrls) } footer: CSLOLStatusBar { @@ -307,6 +309,12 @@ ApplicationWindow { enableUpdates: settings.enableUpdates } + CSLOLDialogUpdateMods { + id: cslolDialogUpdateMods + rowHeight: cslolToolBar.height + columnCount: Math.max(1, Math.floor(window.width / window.minimumWidth)) + } + CSLOLTools { id: cslolTools onInitialized: function(mods, profiles, profileName, profileMods) { @@ -366,9 +374,14 @@ ApplicationWindow { onModWadsRemoved: function(fileName, wads) { cslolDialogEditMod.wadsRemoved(wads) } - onRefreshed: { + onRefreshed: function(mods) { cslolModsView.refereshedMods(mods) } + onUpdatedMods: function(mods) { + console.log("updated: " + JSON.stringify(mods)) + cslolDialogUpdateMods.updatedMods = mods + cslolDialogUpdateMods.open() + } onReportError: function(name, message, trace) { let log_data = ""; if (trace) { diff --git a/src/qml/qml.qrc b/src/qml/qml.qrc index 505892e8..11fa2ce2 100644 --- a/src/qml/qml.qrc +++ b/src/qml/qml.qrc @@ -23,5 +23,6 @@ CSLOLModInfoEdit.qml CSLOLDialogGame.qml CSLOLToolTip.qml + CSLOLDialogUpdateMods.qml