diff --git a/src/Lichtsteuerung.pro b/src/Lichtsteuerung.pro index 2754b6289e385a561c82e53a8c63c97724208989..2815c4565bccaee9f1562cc29fb9562c4321c502 100644 --- a/src/Lichtsteuerung.pro +++ b/src/Lichtsteuerung.pro @@ -47,6 +47,7 @@ SOURCES += \ test/testloopprogramm.cpp \ settings.cpp \ test/DriverDummy.cpp \ + updater.cpp \ usermanagment.cpp \ modules/modulemanager.cpp \ modules/programblock.cpp \ @@ -76,7 +77,8 @@ SOURCES += \ spotify/segmentobject.cpp \ spotify/audioanalysisobject.cpp \ spotify/userobject.cpp \ - sortedmodelview.cpp + sortedmodelview.cpp \ + zip.cpp # The following define makes your compiler emit warnings if you use # any feature of Qt which as been marked deprecated (the exact warnings @@ -104,6 +106,7 @@ HEADERS += \ modules/controlpoint.hpp \ modules/scanner.hpp \ scanner.h \ + updater.h \ usermanagment.h \ gui/channelprogrammeditor.h \ modelmanager.h \ @@ -165,7 +168,8 @@ HEADERS += \ spotify/util.h \ modules/spotifyobjetcs.hpp \ modules/spotify.hpp \ - sortedmodelview.h + sortedmodelview.h \ + zip.h # Default rules for deployment. diff --git a/src/main.cpp b/src/main.cpp index 93df0844cb9660bdd150ec6b1157f2c0aed101e0..934ddd6ade2def7a57969ea30d95be84c3a1f7aa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -44,9 +44,15 @@ #include "test/testsampleclass.h" #include "spotify/spotify.h" #include "modules/dmxconsumer.h" +#include "updater.h" int main(int argc, char *argv[]) { + Updater updater; + QObject::connect(&updater,&Updater::needUpdate,[&](){ + updater.update(); + }); + updater.checkForUpdate(); /*Test::TestModulSystem testModulSystem; testModulSystem.runTest(); return 0;*/ @@ -171,6 +177,7 @@ int main(int argc, char *argv[]) QFile savePath(settings.getJsonSettingsFilePath()); ApplicationData::saveData(savePath); Driver::stopAndUnloadDriver(); + updater.runUpdateInstaller(); }); settings.connect(&settings,&Settings::driverFilePathChanged,[&](){ Driver::loadAndStartDriver(settings.getDriverFilePath()); @@ -208,6 +215,7 @@ int main(int argc, char *argv[]) engine.rootContext()->setContextProperty("UserManagment",UserManagment::get()); engine.rootContext()->setContextProperty("Settings",&settings); engine.rootContext()->setContextProperty("spotify",&spotify); + engine.rootContext()->setContextProperty("updater",&updater); QQmlEngine::setObjectOwnership(&Driver::dmxValueModel,QQmlEngine::CppOwnership); engine.rootContext()->setContextProperty("dmxOutputValues",&Driver::dmxValueModel); engine.load(QUrl(QLatin1String("qrc:/qml/main.qml"))); diff --git a/src/qml/main.qml b/src/qml/main.qml index 3930de0309ed01bdbbaa8c878759c552a30ea29a..b1fff6ce7cbf8966c6771c5c4a26823d3c64fc11 100644 --- a/src/qml/main.qml +++ b/src/qml/main.qml @@ -27,6 +27,11 @@ ApplicationWindow { } } + ToolTip.visible: updater.progress >= 0 && updater.progress <= 100 + ToolTip.delay: 0 + ToolTip.timeout: 20000 + ToolTip.text: updater.progress < 100 ? "Downloading update. Progress : " + updater.progress + "%" : "Downloading update finished. Restart to update." + VerticalTabBar{ id: tabBar diff --git a/src/updater.cpp b/src/updater.cpp new file mode 100644 index 0000000000000000000000000000000000000000..81c25677f122ade6efe3438d74e6fcb96a14dcb9 --- /dev/null +++ b/src/updater.cpp @@ -0,0 +1,170 @@ +#include "updater.h" + +#include "zip.h" +#include +#include +#include +#include +#include +#include +#include + + +Updater::Updater(){ + if(!QFile::exists(QDir::currentPath() + "/Lichtsteuerung.exe")){ + state = UpdaterState::IDE_ENV; + } +} + +void Updater::checkForUpdate(){ + if(state == UpdaterState::IDE_ENV){ + return; + } + if(!QFile::exists(VERSION_FILE_NAME)){ + qDebug() << "version file does not exists in application folder"; + state = UpdaterState::UpdateAvailible; + emit needUpdate(); + return; + } + auto redirect = http->get(QNetworkRequest(QUrl(versionDownloadURL))); + QObject::connect(redirect,&QNetworkReply::finished,[this,redirect](){ + redirect->deleteLater(); + auto redirectURL = redirect->header(QNetworkRequest::KnownHeaders::LocationHeader); + if(!redirectURL.isValid()){ + qDebug() << "can not redirect version.zip"; + return; + } + auto response = http->get(QNetworkRequest(redirectURL.toUrl())); + QObject::connect(response,&QNetworkReply::finished,[this,response](){ + std::thread thread([this,response](){ + + QFile version(QDir::tempPath() + QStringLiteral("/version.zip")); + version.open(QFile::WriteOnly); + version.write(response->readAll()); + version.close(); + response->deleteLater(); + if(!Zip::unzip(QFileInfo(version),QFileInfo(version).absolutePath())){ + qDebug() << "not successful when unzipping"; + state = UpdaterState::NoUpdateAvailible; + return; + } + if(!QFile::exists(QFileInfo(version).absolutePath() + "/" + VERSION_FILE_NAME)){ + qDebug() << "version file does not exists in version.zip"; + state = UpdaterState::NoUpdateAvailible; + return; + } + if(QFile(VERSION_FILE_NAME).readAll() != QFile(QFileInfo(version).absolutePath() + "/" + VERSION_FILE_NAME).readAll()){ + state = UpdaterState::UpdateAvailible; + emit needUpdate(); + }else{ + state = UpdaterState::NoUpdateAvailible; + } + + }); + thread.detach(); + }); + }); +} + +void Updater::update(){ + if(state != UpdaterState::UpdateAvailible) { + qDebug() << "we are not in a state to make updates"; + return; + } + qDebug() << "Update"; + state = UpdaterState::DownloadingUpdate; + auto redirect = http->get(QNetworkRequest(QUrl(deployDownloadURL))); + QObject::connect(redirect,&QNetworkReply::finished,[this,redirect](){ + redirect->deleteLater(); + auto redirectURL = redirect->header(QNetworkRequest::KnownHeaders::LocationHeader); + if(!redirectURL.isValid()){ + qDebug() << "can not redirect deploy.zip"; + return; + } + auto response = http->get(QNetworkRequest(redirectURL.toUrl())); + QFile * deploy = new QFile(QDir::tempPath() + QStringLiteral("/deploy.zip")); + deploy->open(QFile::WriteOnly); + QObject::connect(response,static_cast(&QNetworkReply::error),[this,response,deploy](auto error){ + qWarning() << "Error while downloading deploy.zip! " << error << response->errorString(); + deploy->close(); + deploy->deleteLater(); + response->deleteLater(); + state = UpdaterState::DownloadUpdateFailed; + }); + QObject::connect(response,&QNetworkReply::readyRead,[response,deploy](){ + deploy->write(response->readAll()); + }); + QObject::connect(response,&QNetworkReply::downloadProgress,[this](auto rec, auto max){ + progress = 100 * rec / max; + qDebug() << progress; + emit updateProgressChanged(); + }); + QObject::connect(response,&QNetworkReply::finished,[this,response,deploy](){ + std::thread thread([this,response,deploy](){ + qDebug() << response->atEnd(); + deploy->close(); + std::unique_ptr deleteMe(deploy); + response->deleteLater(); + if(!Zip::unzip(QFileInfo(*deploy),QFileInfo(*deploy).absolutePath())){ + qDebug() << "not successful when unzipping deploy.zip"; + state = UpdaterState::DownloadUpdateFailed; + return; + } + deployPath = QFileInfo(*deploy).absolutePath() + "/" + NAME_OF_DEPLOY_FOLDER; + // all important files like QTJSONFile.json + auto entries = QDir(QDir::currentPath()).entryInfoList(QStringList() << QStringLiteral("QTJSONFile.json*"),QDir::Filter::Files); + for(const auto & e : entries){ + QFile::copy(e.absoluteFilePath() , deployPath + "/" + e.fileName()); + } + state = UpdaterState::UpdateDownloaded; + }); + thread.detach(); + }); + }); + +} + +void Updater::runUpdateInstaller(){ + if(state != UpdaterState::UpdateDownloaded){ + return; + } + QFile batchFile(QDir::tempPath() + "/installLichtsteuerung.bat"); + batchFile.open(QFile::WriteOnly); + //https://stackoverflow.com/questions/1894967/how-to-request-administrator-access-inside-a-batch-file + auto content = "@echo off\r\n" + ":: BatchGotAdmin\r\n" + ":-------------------------------------\r\n" + "REM --> Check for permissions\r\n" + " IF \"%PROCESSOR_ARCHITECTURE%\" EQU \"amd64\" (\r\n" + ">nul 2>&1 \"%SYSTEMROOT%\\SysWOW64\\cacls.exe\" \"%SYSTEMROOT%\\SysWOW64\\config\\system\"\r\n" + ") ELSE (\r\n" + ">nul 2>&1 \"%SYSTEMROOT%\\system32\\cacls.exe\" \"%SYSTEMROOT%\\system32\\config\\system\"\r\n" + ")\r\n" + "\r\n" + "REM --> If error flag set, we do not have admin.\r\n" + "if '%errorlevel%' NEQ '0' (\r\n" + " echo Requesting administrative privileges...\r\n" + " goto UACPrompt\r\n" + ") else ( goto gotAdmin )\r\n" + "\r\n" + ":UACPrompt\r\n" + " echo Set UAC = CreateObject^(\"Shell.Application\"^) > \"%temp%\\getadmin.vbs\"\r\n" + " set params= %*\r\n" + " echo UAC.ShellExecute \"cmd.exe\", \"/c \"\"%~s0\"\" %params:\"=\"\"%\", \"\", \"runas\", 1 >> \"%temp%\\getadmin.vbs\"\r\n" + "\r\n" + " \"%temp%\\getadmin.vbs\"\r\n" + " del \"%temp%\\getadmin.vbs\"\r\n" + " exit /B\r\n" + "\r\n" + ":gotAdmin\r\n" + " pushd \"%CD%\"\r\n" + " CD /D \"%~dp0\"\r\n" + ":-------------------------------------- \r\n" + ": Own Code\r\n" + "RMDIR /s /q \"" + QFileInfo(QDir::currentPath()).absoluteFilePath() + "\"\r\n" + "move \"" + deployPath + "\" \"" + QFileInfo(QDir::currentPath()).absoluteFilePath() + "\"\r\n"; + batchFile.write(content.toUtf8()); + batchFile.close(); + qDebug () << "run " << QFileInfo(batchFile).absoluteFilePath(); + QProcess::startDetached(QStringLiteral("cmd"), QStringList() << QStringLiteral("/c") << QFileInfo(batchFile).absoluteFilePath()); +} diff --git a/src/updater.h b/src/updater.h new file mode 100644 index 0000000000000000000000000000000000000000..4dff360f773da48c902196d0110e342e3e53f459 --- /dev/null +++ b/src/updater.h @@ -0,0 +1,35 @@ +#ifndef UPDATER_H +#define UPDATER_H + +#include +#include +#include +#include + +class Updater : public QObject{ + Q_OBJECT + Q_PROPERTY(int progress READ getUpdateProgress NOTIFY updateProgressChanged) + const inline static QString VERSION_FILE_NAME = QStringLiteral("version.txt"); + const inline static QString NAME_OF_DEPLOY_FOLDER = QStringLiteral("windows-release-5.12.3"); + QString versionDownloadURL = QStringLiteral("https://git.rwth-aachen.de/leander.schulten/Lichtsteuerung/-/jobs/artifacts/windows-release/download?job=version"); + QString deployDownloadURL = QStringLiteral("https://git.rwth-aachen.de/leander.schulten/Lichtsteuerung/-/jobs/artifacts/windows-release/download?job=deploy"); + std::unique_ptr http = std::make_unique(); + enum class UpdaterState {IDE_ENV, NotChecked, NoUpdateAvailible, UpdateAvailible, DownloadingUpdate, UpdateDownloaded, DownloadUpdateFailed} state = UpdaterState::NotChecked; + int progress = -1; + QString deployPath; +public: + Updater(); + void checkForUpdate(); + UpdaterState getState()const {return state;} + void update(); + /** + * @brief runUpdateInstaller starts the installer script, call when the application is closing + */ + void runUpdateInstaller(); + int getUpdateProgress()const{return progress;} +signals: + void needUpdate(); + void updateProgressChanged(); +}; + +#endif // UPDATER_H diff --git a/src/zip.cpp b/src/zip.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b30ef0ee366fc7bd3dde241b466771f862956074 --- /dev/null +++ b/src/zip.cpp @@ -0,0 +1,82 @@ +#include "zip.h" + +#include +#include +#include +#include +#include + +bool unzipPowershellNew(const QFileInfo& zip, const QFileInfo& unzip){ + // https://www.reddit.com/r/Windows10/comments/71z96c/unzip_from_command_line/ + // Expand-Archive "" "" -Force + QProcess p; + p.start(QStringLiteral("powershell.exe"), QStringList() << QStringLiteral("Expand-Archive") << QStringLiteral("-Force") << "\"" + zip.absoluteFilePath() + "\"" << "\"" + unzip.absoluteFilePath() + "\""); + p.waitForFinished(); + if(p.exitCode() != 0){ + qDebug() << "Failed to unzip " << zip << " to " << unzip << " with powershell new"; + qDebug().noquote() << "stderr : " << p.readAllStandardError(); + qDebug().noquote() << "stdout : " << p.readAllStandardOutput(); + } + return !p.exitCode(); +} +bool unzipPowershell(const QFileInfo& zip, const QFileInfo& unzip){ + // from https://stackoverflow.com/questions/17546016/how-can-you-zip-or-unzip-from-the-script-using-only-windows-built-in-capabiliti/26843122#26843122 + // powershell.exe -nologo -noprofile -command "& { Add-Type -A 'System.IO.Compression.FileSystem'; [IO.Compression.ZipFile]::ExtractToDirectory('foo.zip', 'bar'); }" + QProcess p; + p.start(QStringLiteral("powershell.exe"), QStringList() << QStringLiteral("-nologo") << QStringLiteral("-noprofile") << "& { Add-Type -A 'System.IO.Compression.FileSystem'; [IO.Compression.ZipFile]::ExtractToDirectory('"+zip.absoluteFilePath()+"', '"+unzip.absoluteFilePath()+"/unzippedDir'); }"); + p.waitForFinished(); + if(p.exitCode() != 0){ + qDebug() << "Failed to unzip " << zip << " to " << unzip << " with powershell"; + qDebug().noquote() << "stderr : " << p.readAllStandardError(); + qDebug().noquote() << "stdout : " << p.readAllStandardOutput(); + } + return !p.exitCode(); +} + +bool unzipWinrar(const QFileInfo& zip, const QFileInfo& unzip){ + // from https://stackoverflow.com/questions/1315662/how-to-extract-zip-files-with-winrar-command-line + // "%ProgramFiles%\WinRAR\winrar.exe" x -ibck c:\file.zip *.* c:\folder + QProcess p; + p.start(QStringLiteral("C:\\Program Files\\WinRAR\\winrar.exe"), QStringList() << QStringLiteral("x") << QStringLiteral("-ibck") << zip.absoluteFilePath() << QStringLiteral("*.*") << unzip.absoluteFilePath()); + p.waitForFinished(); + if(p.exitCode() != 0){ + qDebug() << "Failed to unzip " << zip << " to " << unzip << " with winrar"; + qDebug().noquote() << "stderr : " << p.readAllStandardError(); + qDebug().noquote() << "stdout : " << p.readAllStandardOutput(); + } + return !p.exitCode(); +} + +bool unzip7Zip(const QFileInfo& zip, const QFileInfo& unzip){ + // from https://superuser.com/questions/95902/7-zip-and-unzipping-from-command-line + // 7z x example.zip -oexample + QProcess p; + p.start(QStringLiteral("C:\\Program Files\\7-Zip\\7z.exe"), QStringList() << QStringLiteral("x") << QStringLiteral("-y") << zip.absoluteFilePath() << "-o" + unzip.absoluteFilePath()); + p.waitForFinished(); + if(p.exitCode() != 0){ + qDebug() << "Failed to unzip " << zip << " to " << unzip << " with 7zip"; + qDebug().noquote() << "stderr : " << p.readAllStandardError(); + qDebug().noquote() << "stdout : " << p.readAllStandardOutput(); + } + return !p.exitCode(); +} + +bool Zip::unzip(const QFileInfo& zip, const QFileInfo& unzip){ + if(!zip.exists()){ + return false; + } + QDir().mkpath(unzip.absoluteFilePath()); + if(unzip7Zip(zip,unzip)){ + return true; + } + if(unzipWinrar(zip,unzip)){ + return true; + } + if(unzipPowershellNew(zip,unzip)){ + return true; + } + if(unzipPowershell(zip,unzip)){ + return true; + } + return false; +} diff --git a/src/zip.h b/src/zip.h new file mode 100644 index 0000000000000000000000000000000000000000..d5f7e7df4b6c1f9d2e361c56be7d8baf5e619574 --- /dev/null +++ b/src/zip.h @@ -0,0 +1,11 @@ +#ifndef ZIP_H +#define UZIP_H + +#include + +namespace Zip { + bool unzip(const QFileInfo& zip, const QFileInfo& unzip); +} // namespace Zip + + +#endif // UZIP_H