diff --git a/CI.mk b/CI.mk index 4cdec5a..a047ac2 100644 --- a/CI.mk +++ b/CI.mk @@ -7,12 +7,12 @@ winepath=$(wineenv) winepath wine=$(WINEPATH) $(wineenv) wine WINEPREFIX=WINEPREFIX="$(pwd)/wine" WINEPATH=WINEPATH='C:\Program Files (x86)\CMake\bin;$(shell $(winepath) -w "$(pwd)/mingw32/bin");$(shell $(winepath) -w "$(pwd)/Qt/5.11.1/mingw53_32/bin")' QTDIR="Z:$(pwd)/Qt/5.11.1/mingw53_32/" -qmake_opts="QMAKE_INCDIR+=Z:$(pwd)/libgit2-0.27.2/include" "QMAKE_LIBDIR+=Z:$(pwd)/libgit2-0.27.2/build/" -spec win32-g++ +qmake_opts="QMAKE_INCDIR+=Z:$(pwd)/libgit2-0.27.2/include Z:$(pwd)/quazip/quazip" "QMAKE_LIBDIR+=Z:$(pwd)/libgit2-0.27.2/build/ Z:$(pwd)/quazip/build/release Z:$(pwd)/libz/build" -spec win32-g++ LIBS+=-lquazip qmake=$(wine) $(pwd)/Qt/5.11.1/mingw53_32/bin/qmake.exe .PHONY: build winbuild windeploy build: - mkdir -p GitAddonsManager && cd GitAddonsManager && qmake-qt5 .. -spec linux-g++ DEFINES+='GAM_BUILD_NAME=\\\"Linux_x64\\\"' && make + mkdir -p GitAddonsManager && cd GitAddonsManager && qmake-qt5 .. -spec linux-g++ DEFINES+='GAM_BUILD_NAME=\\\"Linux_x64\\\"' LIBS+=-lquazip5 INCLUDEPATH+=/usr/include/quazip5 && make cmake-3.12.0-rc3-win32-x86.msi: wget https://cmake.org/files/v3.12/cmake-3.12.0-rc3-win32-x86.msi @@ -74,7 +74,23 @@ libgit2-0.27.2/build/libgit2.dll: wine/drive_c/Program\ Files\ (x86)/CMake libgi cd libgit2-0.27.2/build && $(wine) cmake .. -DCMAKE_BINARY_DIR=libgit2-0.27.2/build -G"MinGW Makefiles" -DBUILD_CLAR=OFF $(wine) mingw32-make -C libgit2-0.27.2/build -j $(shell nproc) -build_win32/GitAddonsManager.exe: Qt libgit2-0.27.2/build/libgit2.dll +zlib/CMakeLists.txt: + git clone https://github.com/madler/zlib.git + +zlib/build/libzlib.dll: zlib/CMakeLists.txt + mkdir -p zlib/build + cd zlib/build && $(wine) cmake .. -DCMAKE_BINARY_DIR=zlib/build -G"MinGW Makefiles" + $(wine) mingw32-make -C zlib/build -j $(shell nproc) + +quazip/quazip.pro: + git clone https://github.com/stachenov/quazip.git + +quazip/build/release/quazip.dll: quazip/quazip.pro + mkdir -p quazip/build + cd quazip/build && $(qmake) ../quazip/quazip.pro QMAKE_LIBDIR+="Z:$(pwd)/zlib/build" LIBS+=-lzlib QMAKE_INCDIR+="Z:$(pwd)/zlib" -spec win32-g++ + $(wine) mingw32-make -C quazip/build + +build_win32/GitAddonsManager.exe: Qt libgit2-0.27.2/build/libgit2.dll zlib/build/libzlib.dll quazip/build/release/quazip.dll mkdir -p build_win32 && cd build_win32 && $(qmake) $(qmake_opts) ../GitAddonsManager.pro GIT_DESCRIBE="$(shell git describe --tags)" DEFINES+='GAM_BUILD_NAME=\\\"Win32\\\"' $(wine) mingw32-make -C build_win32 @@ -85,6 +101,10 @@ build_win32/release/GitAddonsManager.exe: winbuild windeploy: build_win32/release/GitAddonsManager.exe libgit2-0.27.2/build/libgit2.dll mkdir -p GitAddonsManager/ cp libgit2-0.27.2/build/libgit2.dll -f GitAddonsManager/libgit2.dll + cp zlib/build/libzlib.dll -f GitAddonsManager/libzlib.dll + cp quazip/build/release/quazip.dll -f GitAddonsManager/quazip.dll + cp mingw32/opt/bin/libeay32.dll -f GitAddonsManager/libeay32.dll + cp mingw32/opt/bin/ssleay32.dll -f GitAddonsManager/ssleay32.dll cp build_win32/release/GitAddonsManager.exe -f GitAddonsManager/ mkdir -p tmpqmls && cp -f *.qml tmpqmls/ $(wine) Qt/5.11.1/mingw53_32/bin/windeployqt.exe --qmldir tmpqmls GitAddonsManager/GitAddonsManager.exe diff --git a/Update.qml b/Update.qml new file mode 100644 index 0000000..c0bd532 --- /dev/null +++ b/Update.qml @@ -0,0 +1,49 @@ +/* Copyright 2018 WobLight + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import GitAddonsManager.engine 1.0 +import QtQuick.Controls 2.4 +import QtQuick.Layouts 1.3 + +ApplicationWindow { + visible: true + title: "Updating..." + width: errorDialog.visible?errorDialog.implicitWidth:bar.implicitWidth + height: errorDialog.visible?errorDialog.implicitHeight:bar.implicitHeight + flags: Qt.FramelessWindowHint + color: "transparent" + ProgressBar { + visible: Engine.status !== Engine.Error + id: bar + anchors.fill: parent + to: Engine.total + value: Engine.progress + indeterminate: to <= 0 + } + Dialog { + id: errorDialog + visible: Engine.status === Engine.Error + standardButtons: Dialog.Close + onClosed: Qt.exit(-2) + title: qsTr("GitAddonsManager update error") + Label { + text: Engine.statusMessage + } + } + + onClosing: {} +} diff --git a/control.cpp b/control.cpp index 4e5ff1c..25be0d0 100644 --- a/control.cpp +++ b/control.cpp @@ -30,21 +30,28 @@ #include #include #include +#include +#include Control *Control::m_instance = nullptr; -QNetworkAccessManager nam; +QNetworkAccessManager *nam = nullptr; Control::Control(QObject *parent) : QObject(parent), m_progress(0), m_total(0), m_status(Status::Ready), m_statusMessage(""), m_pool(new QThreadPool(this)), m_style(QQuickStyle::name()),m_availableStyles(QQuickStyle::availableStyles()) { + connect(this, &Control::addonsPathChanged, this, &Control::scanForAddons); + connect(this, &Control::addonsPathChanged, this, &Control::saveAddonsPath); +} + +void Control::init() { QSettings settings; setAddonsPath(settings.value("addonsPath").toString()); setFirstBoot(settings.value("firstBoot",true).toBool()); setMinimizeToTray((MinimizeToTray)settings.value("minimizeToTray",(int)MinimizeToTrayAsk).toInt()); - connect(this, &Control::addonsPathChanged, this, &Control::scanForAddons); - connect(this, &Control::addonsPathChanged, this, &Control::saveAddonsPath); - delegate("Initializing libgit2...", [](){git_libgit2_init();},[this](){scanForAddons();}); + git_libgit2_init(); + scanForAddons(); + nam = new QNetworkAccessManager(this); checkForUpdates(); } @@ -159,6 +166,68 @@ Control::UpdateStatus Control::updateStatus() const return m_updateStatus; } + +void walkFoldersIf(const QFileInfo &info, auto f, auto t){ + int ret = t(info); + if (ret <= 0) { + if (info.isDir() && !info.isSymLink()) { + foreach (QFileInfo sub, QDir(info.absoluteFilePath()).entryInfoList(QDir::AllEntries|QDir::NoDotAndDotDot|QDir::Hidden)) + walkFoldersIf(sub, f, t); + } + } + if (ret >= 0) + f(info); +} + +void Control::completeUpdate(const QString &path) +{ + delegate("Update Application",[this, path](){ + QStringList files; + QStringList oldFiles; + QDir root(QApplication::applicationDirPath()); + QDir newRoot(path); + if (!newRoot.mkpath(newRoot.absolutePath())) return false; + walkFoldersIf({root.absolutePath()},[&newRoot, &files, &root, &oldFiles](const QFileInfo &info){ + QFileInfo dest(newRoot.absoluteFilePath(root.relativeFilePath(info.absoluteFilePath()))); + files << info.absoluteFilePath(); + if (dest.exists()) + walkFoldersIf(dest,[&oldFiles](const QFileInfo &info){ + oldFiles << info.absoluteFilePath(); + }, [](const auto&){return 0;}); + + }, [&path, &root](const QFileInfo &info){ + QFileInfo dest(path + "/" + root.relativeFilePath(info.absoluteFilePath())); + return !dest.exists() ? 1 : dest.isDir() && info.isDir() && !info.isSymLink() && !dest.isSymLink() ? -1 : 1; + }); + setTotal(files.size() + oldFiles.size()); + int count = 0; + bool success = true; + foreach (auto file, oldFiles) { + QFileInfo info(file); + if (info.isDir() && !info.isSymLink()) + success &= newRoot.rmdir(file); + else + success &= newRoot.remove(file); + setProgress(++count); + } + foreach (auto file, files) { + success &= root.rename(file, newRoot.absoluteFilePath(root.relativeFilePath(file))) || root.rmdir(file); + setProgress(++count); + } + setTotal(-1); + if (success) + root.removeRecursively(); + return success; + },[this, &path](bool success){ + if (success) + QApplication::exit(3); + else { + setStatusMessage(tr("Failed to move files to %1.").arg(path)); + setStatus(Status::Error); + } + }); +} + void Control::setAddons(QList addons) { if (m_addons == addons) @@ -344,7 +413,10 @@ 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); + connect(nam, &QNetworkAccessManager::finished, [](auto reply){ + QApplication::setApplicationDisplayName(reply->errorString()); + }); + 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)); @@ -360,7 +432,7 @@ void Control::checkForUpdates() void Control::downloadUpdate() { #ifdef GAM_BUILD_NAME - QFile *zip = new QFile("GitAddonsManager.zip"); + QFile *zip = new QFile(QApplication::applicationDirPath() + "/GitAddonsManager.zip"); if (!zip->open(QFile::WriteOnly)) { setUpdateStatus(UpdateStatus::UpdateError); return; @@ -368,7 +440,7 @@ void Control::downloadUpdate() 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); + auto reply = nam->get(req); setUpdateStatus(UpdateStatus::DownloadingUpdate); connect(reply, &QNetworkReply::readyRead,[reply, zip]() { zip->write(reply->readAll()); @@ -388,8 +460,36 @@ void Control::downloadUpdate() void Control::executeUpdate() { - QDesktopServices::openUrl(QDir().absolutePath()); - QApplication::exit(2); + delegate("Apply Update", [this](){ + QuaZip zip("GitAddonsManager.zip"); + if (!zip.open(QuaZip::mdUnzip)) + return; + QDir root(QApplication::applicationDirPath()); + unsigned int size = 0; + foreach (const QuaZipFileInfo &info, zip.getFileInfoList()) + size += info.uncompressedSize; + setTotal(size); + size = 0; + foreach (const QuaZipFileInfo &info, zip.getFileInfoList()) { + QuaZipFile z(zip.getZipName(), info.name); + z.open(QuaZipFile::ReadOnly); + QFileInfo finfo(root.absolutePath() + "/" + info.name); + QFile f(finfo.absoluteFilePath()); + root.mkpath(finfo.absolutePath()); + f.open(QFile::WriteOnly); + QByteArray chunk; + while (!(chunk = z.read(32*1024)).isEmpty()) { + f.write(chunk); + size += chunk.size(); + setProgress(size); + } + f.close(); + } + + },[this](){ + setUpdateStatus(UpdateStatus::UpdateDone); + exit(2); + }); } void Control::setUpdateStatus(Control::UpdateStatus updateStatus) diff --git a/control.h b/control.h index 4e78941..d4dc824 100644 --- a/control.h +++ b/control.h @@ -69,7 +69,8 @@ public: UpdateAvailable, DownloadingUpdate, UpdateReady, - CheckingForUpdate + CheckingForUpdate, + UpdateDone }; Q_ENUM(UpdateStatus) Q_PROPERTY(UpdateStatus updateStatus READ updateStatus WRITE setUpdateStatus NOTIFY updateStatusChanged) @@ -97,6 +98,10 @@ public: UpdateStatus updateStatus() const; + void completeUpdate(const QString &path); + + void init(); + private: static Control *m_instance; explicit Control(QObject *parent = nullptr); diff --git a/main.cpp b/main.cpp index 07f9fa1..e6ebc14 100644 --- a/main.cpp +++ b/main.cpp @@ -25,49 +25,67 @@ #include #include #include +#include int main(int argc, char *argv[]) { - int res; - - qmlClearTypeRegistrations(); QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication app(argc, argv); + + + QCommandLineParser parser; + parser.addOption({"update", "directory to update", "updateLocation"}); + parser.process(app); + QString update = parser.value("update"); app.setApplicationDisplayName(QObject::tr("Git Addons Manager")); QCoreApplication::setOrganizationName("GitAddonsManager"); QCoreApplication::setApplicationName("Git Addons Manager"); - QFontDatabase::addApplicationFont(":/fonts/Hack-Bold.ttf"); QFontDatabase::addApplicationFont(":/fonts/Hack-BoldItalic.ttf"); QFontDatabase::addApplicationFont(":/fonts/Hack-Italic.ttf"); QFontDatabase::addApplicationFont(":/fonts/Hack-Regular.ttf"); QIcon::setThemeName("breeze"); - QQmlApplicationEngine engine; - bool ok; - QSettings setts; - QStringList styles = QQuickStyle::availableStyles(); - QString style = setts.value("style").toString(); - if (style.isNull()) - style = QInputDialog::getItem(nullptr, QObject::tr("Choose a style"), QObject::tr("You will be able to pick another style later by visiting the Options tab."), styles, styles.indexOf(QQuickStyle::name()), false, &ok); - QQuickStyle::setStyle(style); - if (ok) { - setts.setValue("style", style); - setts.sync(); - } - qmlRegisterSingletonType("GitAddonsManager.engine",1,0,"Engine",[](QQmlEngine *, QJSEngine *)->QObject*{ - return Control::instance(); - }); - qmlRegisterUncreatableType("GitAddonsManager.engine",1,0,"Addon",""); - engine.rootContext()->setContextProperty("gitVersion", GIT_DESCRIBE); - engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); - if (engine.rootObjects().isEmpty()) - return -1; - QObject::connect(Control::instance(), &Control::styleChanged,[&setts](const QString &style){ - setts.setValue("style", style); - }); + int res; + do { + qmlClearTypeRegistrations(); - res = app.exec(); - if (res == 1) - QProcess().startDetached(app.arguments()[0],app.arguments().mid(1)); + QQmlApplicationEngine engine; + bool ok; + QSettings setts; + QStringList styles = QQuickStyle::availableStyles(); + QString style = setts.value("style").toString(); + if (style.isNull()) + style = QInputDialog::getItem(nullptr, QObject::tr("Choose a style"), QObject::tr("You will be able to pick another style later by visiting the Options tab."), styles, styles.indexOf(QQuickStyle::name()), false, &ok); + QQuickStyle::setStyle(style); + if (ok) { + setts.setValue("style", style); + setts.sync(); + } + qmlRegisterSingletonType("GitAddonsManager.engine",1,0,"Engine",[](QQmlEngine *, QJSEngine *)->QObject*{ + return Control::instance(); + }); + if (!update.isEmpty()) { + Control::instance()->completeUpdate(update); + engine.load(QUrl(QStringLiteral("qrc:/Update.qml"))); + } else { + Control::instance()->init(); + qmlRegisterUncreatableType("GitAddonsManager.engine",1,0,"Addon",""); + engine.rootContext()->setContextProperty("gitVersion", GIT_DESCRIBE); + engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); + } + if (engine.rootObjects().isEmpty()) + return -1; + QObject::connect(Control::instance(), &Control::styleChanged,[&setts](const QString &style){ + setts.setValue("style", style); + }); + res = app.exec(); + } while (res == 1); + if (res == 2) { + QStringList args = app.arguments().mid(1); + args << "--update" << QString("\"%1\"").arg(app.applicationDirPath()); + QProcess().startDetached(app.applicationDirPath()+"/GitAddonsManager/GitAddonsManager", args); + } + if (res == 3) + QProcess().startDetached(update + "/GitAddonsManager", QApplication::arguments().mid(1) << "--update" << ""); return res; } diff --git a/qml.qrc b/qml.qrc index c8b2577..a14a573 100644 --- a/qml.qrc +++ b/qml.qrc @@ -25,5 +25,6 @@ fonts/Hack-BoldItalic.ttf fonts/Hack-Italic.ttf fonts/Hack-Regular.ttf + Update.qml