diff --git a/CI.mk b/CI.mk index c94e36d..a8ada9e 100644 --- a/CI.mk +++ b/CI.mk @@ -12,7 +12,7 @@ qmake=$(wine) $(pwd)/Qt/5.11.1/mingw53_32/bin/qmake.exe .PHONY: build build: - mkdir -p GitAddonsManager && cd GitAddonsManager && qmake-qt5 .. -spec linux-g++ && make + mkdir -p GitAddonsManager && cd GitAddonsManager && qmake-qt5 .. -spec linux-g++ GAM_BUILD_NAME="Linux_x64" && make cmake-3.12.0-rc3-win32-x86.msi: wget https://cmake.org/files/v3.12/cmake-3.12.0-rc3-win32-x86.msi @@ -75,7 +75,7 @@ libgit2-0.27.2/build/libgit2.dll: wine/drive_c/Program\ Files\ (x86)/CMake libgi $(wine) mingw32-make -C libgit2-0.27.2/build -j $(shell nproc) build_win32/GitAddonsManager.exe: Qt libgit2-0.27.2/build/libgit2.dll - mkdir -p build_win32 && cd build_win32 && $(qmake) $(qmake_opts) ../GitAddonsManager.pro GIT_DESCRIBE="$(shell git describe --tags)" + mkdir -p build_win32 && cd build_win32 && $(qmake) $(qmake_opts) ../GitAddonsManager.pro GIT_DESCRIBE="$(shell git describe --tags)" GAM_BUILD_NAME="Win32" $(wine) mingw32-make -C build_win32 winbuild: build_win32/GitAddonsManager.exe diff --git a/control.cpp b/control.cpp index d3b4dfe..4e5ff1c 100644 --- a/control.cpp +++ b/control.cpp @@ -20,13 +20,19 @@ #include #include #include +#include #include "addon.h" #include #include #include #include +#include +#include +#include +#include Control *Control::m_instance = nullptr; +QNetworkAccessManager nam; Control::Control(QObject *parent) : QObject(parent), m_progress(0), m_total(0), m_status(Status::Ready), m_statusMessage(""), m_pool(new QThreadPool(this)), @@ -39,6 +45,7 @@ Control::Control(QObject *parent) : QObject(parent), connect(this, &Control::addonsPathChanged, this, &Control::scanForAddons); connect(this, &Control::addonsPathChanged, this, &Control::saveAddonsPath); delegate("Initializing libgit2...", [](){git_libgit2_init();},[this](){scanForAddons();}); + checkForUpdates(); } void Control::delegate(QString taskname, auto work, auto callback) @@ -147,6 +154,11 @@ QStringList Control::availableStyles() const return m_availableStyles; } +Control::UpdateStatus Control::updateStatus() const +{ + return m_updateStatus; +} + void Control::setAddons(QList addons) { if (m_addons == addons) @@ -326,3 +338,65 @@ void Control::setAvailableStyles(QStringList availableStyles) m_availableStyles = availableStyles; emit availableStylesChanged(m_availableStyles); } + +void Control::checkForUpdates() +{ +#ifdef GAM_BUILD_NAME + QNetworkRequest req(QUrl(QString("https://gitlab.com/woblight/GitAddonsManager/-/jobs/artifacts/master/download?job=%1").arg(GAM_BUILD_NAME))); + req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + auto reply = nam.head(req); + setUpdateStatus(UpdateStatus::CheckingForUpdate); + connect(reply, &QNetworkReply::finished,[this, reply](){ + QRegExp shaCapt(QString("GitAddonsManager_%1-(\\w{40})\\.zip").arg(GAM_BUILD_NAME)); + if (shaCapt.indexIn(reply->url().toString()) != -1) { + if (!QString(GIT_DESCRIBE).endsWith(shaCapt.cap(1).chopped(33))) + setUpdateStatus(UpdateStatus::UpdateAvailable); + else setUpdateStatus(UpdateStatus::NoUpdate); + }; + }); +#endif +} + +void Control::downloadUpdate() +{ +#ifdef GAM_BUILD_NAME + QFile *zip = new QFile("GitAddonsManager.zip"); + if (!zip->open(QFile::WriteOnly)) { + setUpdateStatus(UpdateStatus::UpdateError); + return; + } + + QNetworkRequest req(QUrl(QString("https://gitlab.com/woblight/GitAddonsManager/-/jobs/artifacts/master/download?job=%1").arg(GAM_BUILD_NAME))); + req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + auto reply = nam.get(req); + setUpdateStatus(UpdateStatus::DownloadingUpdate); + connect(reply, &QNetworkReply::readyRead,[reply, zip]() { + zip->write(reply->readAll()); + }); + connect(reply, &QNetworkReply::finished, [this, reply, zip]() { + zip->close(); + delete zip; + setUpdateStatus(reply->error() == QNetworkReply::NoError ? UpdateStatus::UpdateReady : UpdateStatus::UpdateError); + reply->deleteLater(); + }); + connect(reply, &QNetworkReply::downloadProgress, [this](qint64 recieved, qint64 bytesTotal) { + setTotal(bytesTotal); + setProgress(recieved); + }); +#endif +} + +void Control::executeUpdate() +{ + QDesktopServices::openUrl(QDir().absolutePath()); + QApplication::exit(2); +} + +void Control::setUpdateStatus(Control::UpdateStatus updateStatus) +{ + if (m_updateStatus == updateStatus) + return; + + m_updateStatus = updateStatus; + emit updateStatusChanged(m_updateStatus); +} diff --git a/control.h b/control.h index 3d80b1a..4e78941 100644 --- a/control.h +++ b/control.h @@ -63,6 +63,16 @@ public: Q_ENUM(MinimizeToTray) Q_PROPERTY(MinimizeToTray minimizeToTray READ minimizeToTray WRITE setMinimizeToTray NOTIFY minimizeToTrayChanged) + enum class UpdateStatus { + UpdateError = -1, + NoUpdate, + UpdateAvailable, + DownloadingUpdate, + UpdateReady, + CheckingForUpdate + }; + Q_ENUM(UpdateStatus) + Q_PROPERTY(UpdateStatus updateStatus READ updateStatus WRITE setUpdateStatus NOTIFY updateStatusChanged) QList addons() const; QString addonsPath() const; @@ -85,6 +95,8 @@ public: QStringList availableStyles() const; + UpdateStatus updateStatus() const; + private: static Control *m_instance; explicit Control(QObject *parent = nullptr); @@ -110,6 +122,8 @@ private: QStringList m_availableStyles; + UpdateStatus m_updateStatus = UpdateStatus::NoUpdate; + signals: void addonsChanged(QList addons); @@ -131,6 +145,8 @@ signals: void availableStylesChanged(QStringList availableStyles); + void updateStatusChanged(UpdateStatus updateStatus); + public slots: void setAddons(QList addons); void setAddonsPath(QString addonsPath); @@ -145,6 +161,10 @@ public slots: void setMinimizeToTray(MinimizeToTray minimizeToTray); void setStyle(QString style); void setAvailableStyles(QStringList availableStyles); + void checkForUpdates(); + void downloadUpdate(); + void executeUpdate(); + void setUpdateStatus(UpdateStatus updateStatus); }; Q_DECLARE_METATYPE(Control::MinimizeToTray) diff --git a/main.qml b/main.qml index 193f6aa..a7e2ba5 100644 --- a/main.qml +++ b/main.qml @@ -149,6 +149,37 @@ ApplicationWindow { selectByMouse: true onFocusChanged: if (focus) selectAll() } + Button { + id: updateButton + contentItem: RowLayout { + BusyIndicator { + implicitHeight: parent.height + implicitWidth: implicitHeight + running: !updateButton.enabled + visible: running + } + Label { + text: + Engine.updateStatus === Engine.NoUpdate ? + qsTr("Check for update") : + Engine.updateStatus === Engine.CheckingForUpdate ? + qsTr("Checking for update...") : + Engine.updateStatus === Engine.UpdateAvailable ? + qsTr("Download update") : + Engine.updateStatus === Engine.DownloadingUpdate ? + qsTr("Downloading update...") : + Engine.updateStatus === Engine.UpdateReady ? + qsTr("Apply update") : qsTr("error") + } + } + onClicked: Engine.updateStatus === Engine.NoUpdate ? + Engine.checkForUpdates() : + Engine.updateStatus === Engine.UpdateAvailable ? + Engine.downloadUpdate() : + Engine.updateStatus === Engine.UpdateReady ? + Engine.executeUpdate() : {} + enabled: Engine.updateStatus === Engine.NoUpdate || Engine.updateStatus === Engine.UpdateAvailable || Engine.updateStatus === Engine.UpdateReady + } } } } @@ -315,4 +346,47 @@ ApplicationWindow { modal: true standardButtons: Dialog.Close } + Dialog { + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + visible: Engine.updateStatus === Engine.UpdateAvailable || Engine.updateStatus === Engine.DownloadingUpdate || Engine.updateStatus === Engine.UpdateReady + id: updateDialog + ColumnLayout { + anchors.fill: parent + Label { + Layout.fillWidth: true + id: updateMessage + text: Engine.updateStatus === Engine.UpdateAvailable ? + qsTr("

A newer version of GitAddonsManager is available

") : + Engine.updateStatus === Engine.DownloadingUpdate ? + qsTr("Downloading update...") : qsTr("Download finished.
To complete the update click \"Close & Update\", then extract GitAddonsManager.zip and replace old files.") + textFormat: Text.RichText + wrapMode: Text.WordWrap + } + ProgressBar { + Layout.fillWidth: true + id: updateProgress + to: Engine.total + value: Engine.progress + indeterminate: to == -1 + visible: Engine.updateStatus === Engine.DownloadingUpdate + } + } + + modal: true + footer: DialogButtonBox { + Button { + visible: Engine.updateStatus === Engine.UpdateAvailable + text: qsTr("Download") + onClicked: Engine.downloadUpdate() + } + Button { + visible: Engine.updateStatus === Engine.UpdateReady + text: qsTr("Close && Update") + onClicked: Engine.executeUpdate() + } + } + closePolicy: Engine.updateStatus !== Engine.DownloadingUpdate ? Dialog.CloseOnPressOutside | Dialog.CloseOnEscape : Dialog.NoAutoClose + standardButtons: Engine.updateStatus !== Engine.DownloadingUpdate ? Dialog.Close : Dialog.NoButton + } }