diff --git a/samples/CPlusPlusQt/CMakeLists.txt b/samples/CPlusPlusQt/CMakeLists.txt new file mode 100644 index 00000000..b00d2f97 --- /dev/null +++ b/samples/CPlusPlusQt/CMakeLists.txt @@ -0,0 +1,73 @@ +cmake_minimum_required(VERSION 3.21) + +project(VelopackQtSample VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + + +# Disable C and C++ compiler extensions. +# C/CXX_EXTENSIONS are ON by default to allow the compilers to use extended +# variants of the C/CXX language. +# However, this could expose cross-platform bugs in user code or in the headers +# of third-party dependencies and thus it is strongly suggested to turn +# extensions off. +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_C_EXTENSIONS OFF) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 6.2 REQUIRED COMPONENTS Quick) + + +qt_add_executable(appVelopackQtSample + main.cpp + constants.h + Velopack.hpp + Velopack.cpp + autoupdater.h + autoupdater.cpp +) + +qt_add_qml_module(appVelopackQtSample + URI VelopackQtSample + VERSION 1.0 + QML_FILES Main.qml +) + +# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. +# If you are developing for iOS or macOS you should consider setting an +# explicit, fixed bundle identifier manually though. +set_target_properties(appVelopackQtSample PROPERTIES +# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.appVelopackQtSample + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +target_link_libraries(appVelopackQtSample + PRIVATE Qt6::Quick +) + +include(GNUInstallDirs) +install(TARGETS appVelopackQtSample + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +if (APPLE) + # Copy macOS updater/installer binary that needs to be next to our binary + install(FILES ${PROJECT_SOURCE_DIR}/platforms/macos/VfusionMac DESTINATION ${CMAKE_INSTALL_BINDIR}) +elseif(WIN32) + # Copy Windows updater/installer binary that needs to be next to our binary + install(FILES ${PROJECT_SOURCE_DIR}/platforms/windows/Vfusion.exe DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif() + +qt_generate_deploy_qml_app_script( + TARGET appVelopackQtSample + OUTPUT_SCRIPT deploy_script +) + +install(SCRIPT ${deploy_script}) diff --git a/samples/CPlusPlusQt/LICENSE b/samples/CPlusPlusQt/LICENSE new file mode 100644 index 00000000..4e6ad0af --- /dev/null +++ b/samples/CPlusPlusQt/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Bob Jelica + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/samples/CPlusPlusQt/Main.qml b/samples/CPlusPlusQt/Main.qml new file mode 100644 index 00000000..057f205d --- /dev/null +++ b/samples/CPlusPlusQt/Main.qml @@ -0,0 +1,117 @@ +import QtQuick +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Dialogs + +import VelopackQt 1.0 + +Window { + width: 640 + height: 480 + visible: true + title: qsTr("Velopack Qt c++ example") + + Connections { + target: AutoUpdater + // User checked for updates, but there were none, show text about it + function onNoNewUpdatesAvailable() { + noNewUpdatesTxt.visible = true + } + + // There's a new version, hide the text about no new version available + function onNewVersionChanged() { + noNewUpdatesTxt.visible = false + } + + function onUpdatingFailed(errorMsg) { + errDialog.text = errorMsg + errDialog.open() + } + } + + Column { + anchors.fill: parent + anchors.topMargin: 40 + spacing: 10 + + Text { + anchors.horizontalCenter: parent.horizontalCenter + font.bold: true + text: "Welcome to Velopack Qt C++ Sample App." + } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + font.bold: true + text: "Current version: %1".arg(AutoUpdater.currentVersion).arg(AutoUpdater.updateUrl()) + } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + font.bold: true + font.pixelSize: 18 + color: "red" + text: "New update available! v%1 ".arg(AutoUpdater.newVersion) + visible: AutoUpdater.newVersion !== "" + } + + Text { + id: noNewUpdatesTxt + anchors.horizontalCenter: parent.horizontalCenter + font.bold: true + font.pixelSize: 18 + color: "crimson" + text: "No new updates right now..." + visible: false + } + + Button { + width: 400 + height: 100 + anchors.horizontalCenter: parent.horizontalCenter + text: "Check for updates" + onClicked: { + AutoUpdater.checkForUpdates() + } + } + + Button { + width: 400 + height: 100 + anchors.horizontalCenter: parent.horizontalCenter + text: "Download update" + enabled: AutoUpdater.newVersion !== "" + && !AutoUpdater.updateReadyToInstall + onClicked: { + AutoUpdater.downloadLatestUpdate() + } + } + + Button { + width: 400 + height: 100 + anchors.horizontalCenter: parent.horizontalCenter + text: "Apply update and restart" + enabled: AutoUpdater.updateReadyToInstall + onClicked: { + AutoUpdater.applyUpdateAndRestart() + } + } + } + + Text { + anchors.bottom: parent.bottom + anchors.bottomMargin: 8 + anchors.horizontalCenter: parent.horizontalCenter + font.bold: false + font.italic: true + font.pixelSize: 12 + text: "Updates URL: %1".arg(AutoUpdater.updateUrl()) + } + + MessageDialog { + id: errDialog + buttons: MessageDialog.Ok + } + +} diff --git a/samples/CPlusPlusQt/README.md b/samples/CPlusPlusQt/README.md new file mode 100644 index 00000000..e4905caf --- /dev/null +++ b/samples/CPlusPlusQt/README.md @@ -0,0 +1 @@ +# This sample is outdated and non-functional \ No newline at end of file diff --git a/samples/CPlusPlusQt/autoupdater.cpp b/samples/CPlusPlusQt/autoupdater.cpp new file mode 100644 index 00000000..d39ac2de --- /dev/null +++ b/samples/CPlusPlusQt/autoupdater.cpp @@ -0,0 +1,141 @@ +#include "autoupdater.h" + +#include +#include + +#include "constants.h" + +auto kUpdateUrl = UPDATE_URL; + +AutoUpdater::AutoUpdater(QObject *parent) : QObject(parent) { + + try { + // Init Velopack auto-updater + manager.setUrlOrPath(kUpdateUrl); + + QString currentVersion = + QString::fromStdString(manager.getCurrentVersion()); + + qInfo() << "Current version: " << currentVersion; + setCurrentVersion(currentVersion); + } catch (const std::exception &err) { + qInfo() << "Error initating auto-updater, msg: " << err.what(); + } + // Do a check on startup, if desired + // checkForUpdates(); +} + +AutoUpdater::~AutoUpdater(){}; + +void AutoUpdater::checkForUpdates() { + + try { + // Check for updates + updInfo = manager.checkForUpdates(); + + if (updInfo == nullptr) { + // no updates available + qInfo() << "No updates available, running latest version \\o/"; + Q_EMIT noNewUpdatesAvailable(); + } else { + setNewVersion( + QString::fromStdString(updInfo->targetFullRelease->version)); + qInfo() << "Update available: " << newVersion(); + } + } catch (const std::exception &err) { + qInfo() << "Error checking for new updates, msg: " << err.what(); + Q_EMIT noNewUpdatesAvailable(); + } +} + +void AutoUpdater::downloadLatestUpdate() { + try { + if (updInfo != nullptr) { + qInfo() << __FUNCTION__ << "Downloading new version: " + << QString::fromStdString(updInfo->targetFullRelease->version); + manager.downloadUpdates(updInfo->targetFullRelease.get()); + + qInfo() << __FUNCTION__ << "Downloaded version: " + << QString::fromStdString(updInfo->targetFullRelease->version); + + setUpdateReadyToInstall(true); + Q_EMIT updateDownloaded(); + } else { + qInfo() << __FUNCTION__ + << "Trying to update, even though we don't have a new version! " + "This shouldn't happen..."; + setUpdateReadyToInstall(false); + } + } catch (const std::exception &err) { + qWarning() << __FUNCTION__ << "Updating failed with error: " << err.what(); + setUpdateReadyToInstall(false); + Q_EMIT updateDownloadFailed(); + } +} + +QString AutoUpdater::updateUrl() { return QString::fromStdString(kUpdateUrl); } + +void AutoUpdater::applyUpdateAndRestart() { + if (!updateReadyToInstall()) { + Q_EMIT updatingFailed("Update not ready, try restarting the sample app"); + return; + } + + try { + if (updInfo != nullptr) { + qInfo() << __FUNCTION__ << "Downloading and installing new update: " + << QString::fromStdString(updInfo->targetFullRelease->version); + // We should now have the package downloaded, so update and restart the + // app + manager.applyUpdatesAndRestart(updInfo->targetFullRelease.get()); + } else { + qInfo() << __FUNCTION__ + << "Trying to update, even tho we don't have a new version! This " + "shouldn't happen..."; + } + } catch (const std::exception &err) { + qWarning() << __FUNCTION__ << "Updating failed with error: " << err.what(); + Q_EMIT updatingFailed(QString::fromStdString(err.what())); + } +} + +bool AutoUpdater::updateReadyToInstall() const { + return m_updateReadyToInstall; +} + +void AutoUpdater::setUpdateReadyToInstall(bool newUpdateReady) { + if (m_updateReadyToInstall == newUpdateReady) + return; + m_updateReadyToInstall = newUpdateReady; + Q_EMIT updateReadyToInstallChanged(); +} + +QString AutoUpdater::currentUpdateChannel() const { + return m_currentUpdateChannel; +} + +void AutoUpdater::setCurrentUpdateChannel( + const QString &newCurrentUpdateChannel) { + if (m_currentUpdateChannel == newCurrentUpdateChannel) + return; + m_currentUpdateChannel = newCurrentUpdateChannel; + Q_EMIT currentUpdateChannelChanged(); +} + +QString AutoUpdater::currentVersion() const { return m_currentVersion; } + +void AutoUpdater::setCurrentVersion(const QString &newCurrentVersion) { + if (m_currentVersion == newCurrentVersion) + return; + m_currentVersion = newCurrentVersion; + Q_EMIT currentVersionChanged(); +} + +QString AutoUpdater::newVersion() const { return m_newVersion; } + +void AutoUpdater::setNewVersion(const QString &newNewVersion) { + if (m_newVersion == newNewVersion) + return; + m_newVersion = newNewVersion; + Q_EMIT newVersionChanged(); +} diff --git a/samples/CPlusPlusQt/autoupdater.h b/samples/CPlusPlusQt/autoupdater.h new file mode 100644 index 00000000..5a09a8ea --- /dev/null +++ b/samples/CPlusPlusQt/autoupdater.h @@ -0,0 +1,69 @@ +#ifndef AUTOUPDATER_H +#define AUTOUPDATER_H + +#include "Velopack.hpp" +#include +#include +#include + +class AutoUpdater : public QObject { + Q_OBJECT + + Q_PROPERTY( + QString currentUpdateChannel READ currentUpdateChannel WRITE + setCurrentUpdateChannel NOTIFY currentUpdateChannelChanged FINAL) + Q_PROPERTY(QString currentVersion READ currentVersion WRITE setCurrentVersion + NOTIFY currentVersionChanged FINAL) + Q_PROPERTY( + bool updateReadyToInstall READ updateReadyToInstall WRITE + setUpdateReadyToInstall NOTIFY updateReadyToInstallChanged FINAL) + Q_PROPERTY(QString newVersion READ newVersion WRITE setNewVersion NOTIFY + newVersionChanged FINAL) + +public: + explicit AutoUpdater(QObject *parent = nullptr); + ~AutoUpdater() override; + +public: + static AutoUpdater &instance() { + static AutoUpdater _instance; + return _instance; + } + bool updateReadyToInstall() const; + void setUpdateReadyToInstall(bool newUpdateReady); + + QString currentUpdateChannel() const; + void setCurrentUpdateChannel(const QString &newCurrentUpdateChannel); + + QString currentVersion() const; + void setCurrentVersion(const QString &newCurrentVersion); + + QString newVersion() const; + void setNewVersion(const QString &newNewVersion); + + Q_INVOKABLE void applyUpdateAndRestart(); + Q_INVOKABLE void checkForUpdates(); + Q_INVOKABLE void downloadLatestUpdate(); + Q_INVOKABLE QString updateUrl(); + +signals: + void noNewUpdatesAvailable(); + void updateReadyToInstallChanged(); + void currentUpdateChannelChanged(); + void currentVersionChanged(); + void newVersionChanged(); + void updatingFailed(QString errorMg); + void updateDownloadFailed(); + void updateDownloaded(); + +private: + QString m_currentUpdateChannel; + QString m_currentVersion; + QString m_newVersion; + bool m_updateReadyToInstall = false; + bool m_updateDownloaded = false; + Velopack::UpdateManagerSync manager{}; + std::shared_ptr updInfo{}; +}; + +#endif // AUTOUPDATER_H diff --git a/samples/CPlusPlusQt/build-osx.sh b/samples/CPlusPlusQt/build-osx.sh new file mode 100644 index 00000000..a04799cf --- /dev/null +++ b/samples/CPlusPlusQt/build-osx.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# Find the absolute path of the script +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Check if version and Qt path parameters are provided +if [ "$#" -ne 2 ]; then + echo "Version number and path to Qt installation are required." + echo "Usage: ./build.sh [version] [path-to-qt]" + echo "" + echo "Example: ./build.sh 1.0.4 /Users/kalle/Qt/6.53/macos" + echo "" + exit 1 +fi + +BUILD_VERSION="$1" +QT_DIR="$2" + +BUILD_DIR="$SCRIPT_DIR/build" +RELEASE_DIR="$SCRIPT_DIR/releases" +PUBLISH_DIR="$SCRIPT_DIR/publish" + +echo "" +echo "Compiling Velopack Qt sample..." + +# Remove build directory if it exists +if [ -d "$BUILD_DIR" ]; then + rm -rf "$BUILD_DIR" +fi + +# Remove publish directory if it exists +if [ -d "$PUBLISH_DIR" ]; then + rm -rf "$PUBLISH_DIR" +fi + +# Create build directory to run cmake in +mkdir "$BUILD_DIR" + +# Navigate to the build directory +cd "$BUILD_DIR" || exit + +cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$PUBLISH_DIR -DCMAKE_PREFIX_PATH=$QT_DIR .. + +# Check if cmake was successful +if [ $? -eq 0 ]; then + # Run cmake build + cmake --build . + + # Run cmake install and mute the output + echo "" + echo "Packaging Qt app..." + cmake --build . --target install > /dev/null 2>&1 +else + echo "CMake configuration failed." + cd $SCRIPT_DIR + exit 1 +fi + +echo "" +echo "Building Velopack Qt Sample Release v$BUILD_VERSION" +vpk pack --packId appVelopackQtSample \ + --mainExe appVelopackQtSample \ + --packTitle VelopackQtSample \ + -v $BUILD_VERSION \ + -o "$RELEASE_DIR" \ + -p "$PUBLISH_DIR/appVelopackQtSample.app" \ + -i "$SCRIPT_DIR/artwork/DefaultApp.icns" \ No newline at end of file diff --git a/samples/CPlusPlusQt/build-win.bat b/samples/CPlusPlusQt/build-win.bat new file mode 100644 index 00000000..81a32b9c --- /dev/null +++ b/samples/CPlusPlusQt/build-win.bat @@ -0,0 +1,61 @@ +@echo off +setlocal + +rem Find the absolute path of the script +set "SCRIPT_DIR=%~dp0" + +rem Check if version and Qt path parameters are provided +if "%~2"=="" ( + echo Version number and path to Qt installation are required. + echo Usage: %~nx0 [version] [path-to-qt] + echo. + echo Example: %~nx0 1.0.4 C:\Users\kalle\Qt\6.5.3\msvc2019_64 + echo. + exit /b 1 +) + +set BUILD_VERSION=%~1 +set QT_DIR=%~2 + +set BUILD_DIR=%SCRIPT_DIR%build +set RELEASE_DIR=%SCRIPT_DIR%releases +set PUBLISH_DIR=%SCRIPT_DIR%publish + +echo. +echo Compiling Velopack Qt sample... + +echo #define UPDATE_URL R"(%RELEASE_DIR%)" > constants.h + +rem Remove build directory if it exists +if exist "%BUILD_DIR%" ( + rmdir /s /q "%BUILD_DIR%" +) + +rem Create build directory to run cmake in +echo Creating %BUILD_DIR%... +mkdir "%BUILD_DIR%" + +rem Navigate to the build directory +cd /d "%BUILD_DIR%" || exit /b + +cmake -G"Ninja" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="%PUBLISH_DIR%" -DCMAKE_PREFIX_PATH="%QT_DIR%" .. + +if %errorlevel% neq 0 ( + echo I couldn't run cmake. Exiting. + cd /d "%SCRIPT_DIR%" + exit /b 1 +) + +ninja +ninja install + +echo. +echo Building Velopack Qt Sample Release v%BUILD_VERSION% + vpk pack --packId appVelopackQtSample ^ + --mainExe bin\appVelopackQtSample.exe ^ + --packTitle VelopackQtSample ^ + -v %BUILD_VERSION% ^ + -o "%RELEASE_DIR%" ^ + -p "%PUBLISH_DIR%" + +:end diff --git a/samples/CPlusPlusQt/constants.h b/samples/CPlusPlusQt/constants.h new file mode 100644 index 00000000..b8bc86cf --- /dev/null +++ b/samples/CPlusPlusQt/constants.h @@ -0,0 +1 @@ +#define UPDATE_URL R"(C:\Source\velopack\samples\CPlusPlusQt\releases)" diff --git a/samples/CPlusPlusQt/main.cpp b/samples/CPlusPlusQt/main.cpp new file mode 100644 index 00000000..9a67c7ff --- /dev/null +++ b/samples/CPlusPlusQt/main.cpp @@ -0,0 +1,34 @@ +#include +#include + +#include "autoupdater.h" + +int main(int argc, char *argv[]) { + + try { + // Init Velopack hooks, MUST be as early as possible in main() + // Velopack may exit / restart our app at this statement + Velopack::startup(argv, argc); + } catch (const std::exception &err) { + qInfo() << "Error initating auto-updater, msg: " << err.what(); + } + + QGuiApplication app(argc, argv); + + // Init Auto Updater as a singleton + qmlRegisterSingletonInstance("VelopackQt", 1, 0, "AutoUpdater", + &AutoUpdater::instance()); + + QQmlApplicationEngine engine; + const QUrl url(u"qrc:/VelopackQtSample/Main.qml"_qs); + QObject::connect( + &engine, &QQmlApplicationEngine::objectCreated, &app, + [url](QObject *obj, const QUrl &objUrl) { + if (!obj && url == objUrl) + QCoreApplication::exit(-1); + }, + Qt::QueuedConnection); + engine.load(url); + + return app.exec(); +}