diff --git a/launchers/qt/src/Helper.cpp b/launchers/qt/src/Helper.cpp index 2c7f89f0df..999241111a 100644 --- a/launchers/qt/src/Helper.cpp +++ b/launchers/qt/src/Helper.cpp @@ -3,6 +3,11 @@ #include #endif +#include +#include +#include +#include + #if defined(Q_OS_WIN) void launchClient(const QString& homePath, const QString& defaultScriptOverride, const QString& displayName, @@ -45,5 +50,24 @@ void launchClient(const QString& homePath, const QString& defaultScriptOverride, CloseHandle(pi.hThread); exit(0); } - #endif + + + +void swapLaunchers(const QString& oldLauncherPath, const QString& newLauncherPath) { + if (!(QFileInfo::exists(oldLauncherPath) && QFileInfo::exists(newLauncherPath))) { + qDebug() << "old launcher: " << oldLauncherPath << "new launcher: " << newLauncherPath << " file does not exist"; + } + + bool success = false; +#ifdef Q_OS_MAC + success = replaceDirectory(oldLauncherPath, newLauncherPath); +#endif + + if (success) { + qDebug() << "succeessfully replaced"; + } else { + qDebug() << "failed"; + exit(0); + } +} diff --git a/launchers/qt/src/Helper.h b/launchers/qt/src/Helper.h index ee145e56f3..de47b45f70 100644 --- a/launchers/qt/src/Helper.h +++ b/launchers/qt/src/Helper.h @@ -1,4 +1,13 @@ #include +#include void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptOverride, const QString& displayName, const QString& contentCachePath, const QString& loginResponseToken = QString()); + + +void launchAutoUpdater(const QString& autoUpdaterPath); +void swapLaunchers(const QString& oldLauncherPath = QString(), const QString& newLauncherPath = QString()); + +#ifdef Q_OS_MAC +bool replaceDirectory(const QString& orginalDirectory, const QString& newDirectory); +#endif diff --git a/launchers/qt/src/Launcher.cpp b/launchers/qt/src/Launcher.cpp index 2daaa415de..2d5fd8dc21 100644 --- a/launchers/qt/src/Launcher.cpp +++ b/launchers/qt/src/Launcher.cpp @@ -10,6 +10,7 @@ Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) { QString resourceBinaryLocation = QGuiApplication::applicationDirPath() + "/resources.rcc"; + qDebug() << "resources.rcc path: " << resourceBinaryLocation; QResource::registerResource(resourceBinaryLocation); _launcherState = std::make_shared(); //_launcherState->setUIState(LauncherState::SPLASH_SCREEN); diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 0f32aeb6b7..3ac05a0bf2 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -174,8 +174,24 @@ void LauncherState::receivedBuildsReply() { #endif _latestBuilds.builds.push_back(build); } + + auto launcherResults = root["launcher"].toObject(); + + Build launcherBuild; + launcherBuild.latestVersion = launcherResults["version"].toInt(); + +#ifdef Q_OS_WIN + launcherBuild.installerZipURL = launcherResults["windows"].toObject()["url"].toString(); +#elif defined(Q_OS_MACOS) + launcherBuild.installerZipURL = launcherResults["mac"].toObject()["url"].toString(); +#else + #error "Launcher is only supported on Windows and Mac OS" +#endif + _latestBuilds.launcherBuild = launcherBuild; } } + + //downloadLauncher(); setApplicationState(ApplicationState::WaitingForLogin); } @@ -290,6 +306,11 @@ void LauncherState::downloadClient() { } } +void LauncherState::launcherDownloadComplete() { + _launcherZipFile.close(); + installLauncher(); +} + void LauncherState::clientDownloadComplete() { ASSERT_STATE(ApplicationState::DownloadingClient); @@ -330,6 +351,61 @@ void LauncherState::installClient() { //launchClient(); } +void LauncherState::downloadLauncher() { + auto request = new QNetworkRequest(QUrl(_latestBuilds.launcherBuild.installerZipURL)); + auto reply = _networkAccessManager.get(*request); + + _launcherZipFile.setFileName(_launcherDirectory.absoluteFilePath("launcher.zip")); + + qDebug() << "opening " << _launcherZipFile.fileName(); + + if (!_launcherZipFile.open(QIODevice::WriteOnly)) { + setApplicationState(ApplicationState::UnexpectedError); + return; + } + + connect(reply, &QNetworkReply::finished, this, &LauncherState::launcherDownloadComplete); + connect(reply, &QNetworkReply::readyRead, this, [this, reply]() { + char buf[4096]; + while (reply->bytesAvailable() > 0) { + qint64 size; + size = reply->read(buf, (qint64)sizeof(buf)); + if (size == 0) { + break; + } + _launcherZipFile.write(buf, size); + } + }); +} + +void LauncherState::installLauncher() { + auto installDir = _launcherDirectory.absoluteFilePath("launcher_install"); + _launcherDirectory.mkpath("launcher_install"); + + qDebug() << "Uzipping " << _launcherZipFile.fileName() << " to " << installDir; + + auto unzipper = new Unzipper(_launcherZipFile.fileName(), QDir(installDir)); + unzipper->setAutoDelete(true); + connect(unzipper, &Unzipper::finished, this, [this](bool error, QString errorMessage) { + if (error) { + qDebug() << "Unzipper finished with error: " << errorMessage; + } else { + qDebug() << "Unzipper finished without error"; + + QDir installDirectory = _launcherDirectory.filePath("launcher_install"); + QString launcherPath; +#if defined(Q_OS_WIN) + launcherPath = installDirectory.absoluteFilePath("HQ Launcher.exe"); +#elif defined(Q_OS_MACOS) + launcherPath = installDirectory.absoluteFilePath("HQ Launcher.app"); +#endif + //::launchAutoUpdater(launcherPath); + } + }); + + QThreadPool::globalInstance()->start(unzipper); +} + void LauncherState::downloadContentCache() { ASSERT_STATE(ApplicationState::InstallingClient); @@ -431,7 +507,6 @@ void LauncherState::launchClient() { defaultScriptsPath = installDirectory.filePath("interface.app/Contents/Resources/scripts/simplifiedUIBootstrapper.js"); #endif - qDebug() << "------> " << defaultScriptsPath; QString displayName = "fixMe"; QString contentCachePath = _launcherDirectory.filePath("cache"); diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index d4590519a1..1f0ea2ffdf 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -19,6 +19,7 @@ struct LatestBuilds { QString defaultTag; std::vector builds; + Build launcherBuild; }; struct LoginResponse { @@ -59,9 +60,11 @@ public: RequestingLogin, DownloadingClient, + DownloadingLauncher, DownloadingContentCache, InstallingClient, + InstallingLauncher, InstallingContentCache, LaunchingHighFidelity @@ -101,6 +104,10 @@ public: Q_INVOKABLE void login(QString username, QString password); Q_INVOKABLE void receivedLoginReply(); + // Launcher + void downloadLauncher(); + void installLauncher(); + // Client void downloadClient(); void installClient(); @@ -123,6 +130,7 @@ signals: private slots: void clientDownloadComplete(); void contentCacheDownloadComplete(); + void launcherDownloadComplete(); private: bool shouldDownloadContentCache() const; @@ -140,6 +148,7 @@ private: QString _contentCacheURL{ "https://orgs.highfidelity.com/content-cache/content_cache_small-only_data8.zip" }; // QString::null }; // If null, there is no content cache to download QString _loginTokenResponse; QFile _clientZipFile; + QFile _launcherZipFile; QFile _contentZipFile; float _downloadProgress { 0 }; diff --git a/launchers/qt/src/darwin/Helper.mm b/launchers/qt/src/darwin/Helper.mm index 6bc0059fc8..5f4d875c82 100644 --- a/launchers/qt/src/darwin/Helper.mm +++ b/launchers/qt/src/darwin/Helper.mm @@ -40,3 +40,33 @@ void launchClient(const QString& clientPath, const QString& homePath, const QStr [task replaceThisProcess]; } + + +void launchAutoUpdater(const QString& autoUpdaterPath) { + NSTask* task = [[NSTask alloc] init]; + task.launchPath = [autoUpdaterPath.toNSString() stringByAppendingString:@"/Contents/Resources/updater"]; + task.arguments = @[[[NSBundle mainBundle] bundlePath], autoUpdaterPath.toNSString()]; + [task launch]; + + exit(0); +} + + +@interface UpdaterHelper : NSObject ++(NSURL*) NSStringToNSURL: (NSString*) path; +@end + +@implementation UpdaterHelper ++(NSURL*) NSStringToNSURL: (NSString*) path +{ + return [NSURL URLWithString: [path stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]] relativeToURL: [NSURL URLWithString:@"file://"]]; +} +@end + + +bool replaceDirectory(const QString& orginalDirectory, const QString& newDirectory) { + NSFileManager* fileManager = [NSFileManager defaultManager]; + NSURL* destinationUrl = [UpdaterHelper NSStringToNSURL:newDirectory.toNSString()]; + return (bool) [fileManager replaceItemAtURL:[UpdaterHelper NSStringToNSURL:orginalDirectory.toNSString()] withItemAtURL:[UpdaterHelper NSStringToNSURL:newDirectory.toNSString()] + backupItemName:nil options:NSFileManagerItemReplacementUsingNewMetadataOnly resultingItemURL:&destinationUrl error:nil]; +} diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index 72df44c03c..c848f13b9f 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -2,7 +2,9 @@ #include "LauncherWindow.h" #include "Launcher.h" - +#include +#include +#include "Helper.h" #ifdef Q_OS_WIN Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); @@ -14,7 +16,29 @@ Q_IMPORT_PLUGIN(QtQuick2Plugin); Q_IMPORT_PLUGIN(QtQuickControls2Plugin); Q_IMPORT_PLUGIN(QtQuickTemplates2Plugin); + + +bool hasSuffix(const std::string path, const std::string suffix) { + if (path.substr(path.find_last_of(".") + 1) == suffix) { + return true; + } + + return false; +} + int main(int argc, char *argv[]) { + +#ifdef Q_OS_MAC + // auto updater + if (argc == 3) { + if (hasSuffix(argv[1], "app") && hasSuffix(argv[2], "app")) { + std::cout << "swapping launcher \n"; + swapLaunchers(argv[1], argv[2]); + } else { + std::cout << "not swapping launcher \n"; + } + } +#endif QString name { "High Fidelity" }; QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setOrganizationName(name);