mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-09 06:29:30 +02:00
Add qt launcher states, download, and install
This commit is contained in:
parent
93367ce8a4
commit
93c443abc8
7 changed files with 428 additions and 63 deletions
|
@ -62,6 +62,8 @@ Item {
|
||||||
width: 394
|
width: 394
|
||||||
height: 8
|
height: 8
|
||||||
|
|
||||||
|
value: LauncherState.downloadProgress;
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
top: secondText.bottom
|
top: secondText.bottom
|
||||||
topMargin: 30
|
topMargin: 30
|
||||||
|
@ -88,14 +90,14 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
PropertyAnimation {
|
//PropertyAnimation {
|
||||||
target: progressBar;
|
//target: progressBar;
|
||||||
loops: Animation.Infinite
|
//loops: Animation.Infinite
|
||||||
property: "value"
|
//property: "value"
|
||||||
from: 0;
|
//from: 0;
|
||||||
to: 1;
|
//to: 1;
|
||||||
duration: 5000
|
//duration: 5000
|
||||||
running: true
|
//running: true
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ TextField {
|
||||||
horizontalAlignment: TextInput.AlignLeft
|
horizontalAlignment: TextInput.AlignLeft
|
||||||
placeholderText: "PlaceHolder"
|
placeholderText: "PlaceHolder"
|
||||||
property string seperatorColor: "#FFFFFF"
|
property string seperatorColor: "#FFFFFF"
|
||||||
|
selectByMouse: true
|
||||||
background: Item {
|
background: Item {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import QtQuick 2.3
|
||||||
import QtQuick.Controls 2.1
|
import QtQuick.Controls 2.1
|
||||||
import HQLauncher 1.0
|
import HQLauncher 1.0
|
||||||
import "HFControls"
|
import "HFControls"
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: root
|
id: root
|
||||||
width: 515
|
width: 515
|
||||||
|
@ -21,4 +22,14 @@ Image {
|
||||||
loader.source = url;
|
loader.source = url;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
font.pixelSize: 12
|
||||||
|
|
||||||
|
anchors.right: root.right
|
||||||
|
anchors.bottom: root.bottom
|
||||||
|
|
||||||
|
color: "#FFFFFF"
|
||||||
|
text: LauncherState.uiState.toString() + " - " + LauncherState.applicationState
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) {
|
||||||
QString resourceBinaryLocation = QGuiApplication::applicationDirPath() + "/resources.rcc";
|
QString resourceBinaryLocation = QGuiApplication::applicationDirPath() + "/resources.rcc";
|
||||||
QResource::registerResource(resourceBinaryLocation);
|
QResource::registerResource(resourceBinaryLocation);
|
||||||
_launcherState = std::make_shared<LauncherState>();
|
_launcherState = std::make_shared<LauncherState>();
|
||||||
_launcherState->setUIState(LauncherState::SPLASH_SCREEN);
|
//_launcherState->setUIState(LauncherState::SPLASH_SCREEN);
|
||||||
_launcherWindow = std::make_unique<LauncherWindow>();
|
_launcherWindow = std::make_unique<LauncherWindow>();
|
||||||
_launcherWindow->rootContext()->setContextProperty("LauncherState", _launcherState.get());
|
_launcherWindow->rootContext()->setContextProperty("LauncherState", _launcherState.get());
|
||||||
_launcherWindow->setFlags(Qt::FramelessWindowHint);
|
_launcherWindow->setFlags(Qt::FramelessWindowHint);
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
#include "LauncherState.h"
|
#include "LauncherState.h"
|
||||||
|
|
||||||
|
#include "Unzipper.h"
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
@ -13,18 +17,56 @@
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
|
|
||||||
|
#include <QThreadPool>
|
||||||
|
|
||||||
|
#include <QStandardPaths>
|
||||||
|
|
||||||
|
|
||||||
|
bool LatestBuilds::getBuild(QString tag, Build* outBuild) {
|
||||||
|
if (tag.isNull()) {
|
||||||
|
tag = defaultTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& build : builds) {
|
||||||
|
if (build.tag == tag) {
|
||||||
|
*outBuild = build;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static const std::array<QString, LauncherState::UIState::UI_STATE_NUM> QML_FILE_FOR_UI_STATE =
|
static const std::array<QString, LauncherState::UIState::UI_STATE_NUM> QML_FILE_FOR_UI_STATE =
|
||||||
{ { "qrc:/qml/SplashScreen.qml", "qrc:/qml/Login.qml", "qrc:/qml/DisplayName.qml",
|
{ { "qrc:/qml/SplashScreen.qml", "qrc:/qml/Login.qml", "qrc:/qml/DisplayName.qml",
|
||||||
"qrc:/qml/Download.qml", "qrc:/qml/DownloadFinshed.qml", "qrc:/qml/Error.qml" } };
|
"qrc:/qml/Download.qml", "qrc:/qml/DownloadFinshed.qml", "qrc:/qml/Error.qml" } };
|
||||||
|
|
||||||
void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) const {
|
void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) {
|
||||||
if (_appState != state) {
|
if (_applicationState != state) {
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
__debugbreak();
|
__debugbreak();
|
||||||
exit(0);
|
#endif
|
||||||
|
setApplicationState(ApplicationState::UnexpectedError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LauncherState::ASSERT_STATE(std::vector<LauncherState::ApplicationState> states) {
|
||||||
|
for (auto state : states) {
|
||||||
|
if (_applicationState == state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
__debugbreak();
|
||||||
|
#endif
|
||||||
|
setApplicationState(ApplicationState::UnexpectedError);
|
||||||
|
}
|
||||||
|
|
||||||
LauncherState::LauncherState() {
|
LauncherState::LauncherState() {
|
||||||
|
_launcherDirectory = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||||
|
// TODO Fix launcher directory
|
||||||
|
qDebug() << "Launcher directory: " << _launcherDirectory.absolutePath();
|
||||||
|
_launcherDirectory.mkpath(_launcherDirectory.absolutePath());
|
||||||
requestBuilds();
|
requestBuilds();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,13 +78,29 @@ void LauncherState::declareQML() {
|
||||||
qmlRegisterType<LauncherState>("HQLauncher", 1, 0, "LauncherStateEnums");
|
qmlRegisterType<LauncherState>("HQLauncher", 1, 0, "LauncherStateEnums");
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherState::setUIState(UIState state) {
|
|
||||||
_uiState = state;
|
|
||||||
emit updateSourceUrl(getCurrentUISource());
|
|
||||||
}
|
|
||||||
|
|
||||||
LauncherState::UIState LauncherState::getUIState() const {
|
LauncherState::UIState LauncherState::getUIState() const {
|
||||||
return _uiState;
|
switch (_applicationState) {
|
||||||
|
case ApplicationState::Init:
|
||||||
|
case ApplicationState::RequestingBuilds:
|
||||||
|
return SPLASH_SCREEN;
|
||||||
|
case ApplicationState::WaitingForLogin:
|
||||||
|
case ApplicationState::RequestingLogin:
|
||||||
|
return LOGIN_SCREEN;
|
||||||
|
case ApplicationState::DownloadingClient:
|
||||||
|
case ApplicationState::InstallingClient:
|
||||||
|
case ApplicationState::DownloadingContentCache:
|
||||||
|
case ApplicationState::InstallingContentCache:
|
||||||
|
return DOWNLOAD_SCREEN;
|
||||||
|
case ApplicationState::LaunchingHighFidelity:
|
||||||
|
return DOWNLOAD_FINSISHED;
|
||||||
|
case ApplicationState::UnexpectedError:
|
||||||
|
__debugbreak();
|
||||||
|
return ERROR_SCREEN;
|
||||||
|
default:
|
||||||
|
qDebug() << "FATAL: No UI for" << _applicationState;
|
||||||
|
__debugbreak();
|
||||||
|
return ERROR_SCREEN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherState::setLastLoginError(LastLoginError lastLoginError) {
|
void LauncherState::setLastLoginError(LastLoginError lastLoginError) {
|
||||||
|
@ -54,8 +112,8 @@ LauncherState::LastLoginError LauncherState::getLastLoginError() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherState::requestBuilds() {
|
void LauncherState::requestBuilds() {
|
||||||
ASSERT_STATE(ApplicationState::INIT);
|
ASSERT_STATE(ApplicationState::Init);
|
||||||
_appState = ApplicationState::REQUESTING_BUILDS;
|
setApplicationState(ApplicationState::RequestingBuilds);
|
||||||
|
|
||||||
// TODO Show splash screen until this request is complete
|
// TODO Show splash screen until this request is complete
|
||||||
auto request = new QNetworkRequest(QUrl("https://thunder.highfidelity.com/builds/api/tags/latest/?format=json"));
|
auto request = new QNetworkRequest(QUrl("https://thunder.highfidelity.com/builds/api/tags/latest/?format=json"));
|
||||||
|
@ -67,7 +125,7 @@ void LauncherState::requestBuilds() {
|
||||||
void LauncherState::receivedBuildsReply() {
|
void LauncherState::receivedBuildsReply() {
|
||||||
auto reply = static_cast<QNetworkReply*>(sender());
|
auto reply = static_cast<QNetworkReply*>(sender());
|
||||||
|
|
||||||
ASSERT_STATE(ApplicationState::REQUESTING_BUILDS);
|
ASSERT_STATE(ApplicationState::RequestingBuilds);
|
||||||
|
|
||||||
if (reply->error()) {
|
if (reply->error()) {
|
||||||
qDebug() << "Error getting builds from thunder: " << reply->errorString();
|
qDebug() << "Error getting builds from thunder: " << reply->errorString();
|
||||||
|
@ -81,8 +139,7 @@ void LauncherState::receivedBuildsReply() {
|
||||||
} else {
|
} else {
|
||||||
auto root = doc.object();
|
auto root = doc.object();
|
||||||
if (!root.contains("default_tag")) {
|
if (!root.contains("default_tag")) {
|
||||||
_appState = ApplicationState::REQUESTING_BUILDS_FAILED;
|
setApplicationState(ApplicationState::UnexpectedError);
|
||||||
setUIState(LauncherState::ERROR_SCREEN);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,8 +147,7 @@ void LauncherState::receivedBuildsReply() {
|
||||||
|
|
||||||
auto results = root["results"];
|
auto results = root["results"];
|
||||||
if (!results.isArray()) {
|
if (!results.isArray()) {
|
||||||
_appState = ApplicationState::REQUESTING_BUILDS_FAILED;
|
setApplicationState(ApplicationState::UnexpectedError);
|
||||||
setUIState(LauncherState::ERROR_SCREEN);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,14 +168,13 @@ void LauncherState::receivedBuildsReply() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_appState = ApplicationState::WAITING_FOR_LOGIN;
|
setApplicationState(ApplicationState::WaitingForLogin);
|
||||||
setUIState(LauncherState::LOGIN_SCREEN);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherState::login(QString username, QString password) {
|
void LauncherState::login(QString username, QString password) {
|
||||||
ASSERT_STATE(ApplicationState::WAITING_FOR_LOGIN);
|
ASSERT_STATE(ApplicationState::WaitingForLogin);
|
||||||
|
|
||||||
_appState = ApplicationState::REQUESTING_LOGIN;
|
setApplicationState(ApplicationState::RequestingLogin);
|
||||||
|
|
||||||
qDebug() << "Got login: " << username << password;
|
qDebug() << "Got login: " << username << password;
|
||||||
|
|
||||||
|
@ -133,32 +188,287 @@ void LauncherState::login(QString username, QString password) {
|
||||||
query.addQueryItem("scope", "owner");
|
query.addQueryItem("scope", "owner");
|
||||||
|
|
||||||
auto reply = _networkAccessManager.post(*request, query.toString().toUtf8());
|
auto reply = _networkAccessManager.post(*request, query.toString().toUtf8());
|
||||||
|
|
||||||
QObject::connect(reply, &QNetworkReply::finished, this, &LauncherState::receivedLoginReply);
|
QObject::connect(reply, &QNetworkReply::finished, this, &LauncherState::receivedLoginReply);
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_INVOKABLE void LauncherState::receivedLoginReply() {
|
Q_INVOKABLE void LauncherState::receivedLoginReply() {
|
||||||
|
ASSERT_STATE(ApplicationState::RequestingLogin);
|
||||||
|
|
||||||
|
// TODO Check for errors
|
||||||
auto reply = static_cast<QNetworkReply*>(sender());
|
auto reply = static_cast<QNetworkReply*>(sender());
|
||||||
|
|
||||||
ASSERT_STATE(ApplicationState::REQUESTING_LOGIN);
|
if (reply->error()) {
|
||||||
|
setApplicationState(ApplicationState::UnexpectedError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
qDebug() << "Got response for login: " << reply->readAll();
|
auto data = reply->readAll();
|
||||||
|
QJsonParseError parseError;
|
||||||
|
auto doc = QJsonDocument::fromJson(data, &parseError);
|
||||||
|
auto root = doc.object();
|
||||||
|
|
||||||
|
if (!root.contains("access_token")
|
||||||
|
|| !root.contains("token_type")
|
||||||
|
|| !root.contains("expires_in")
|
||||||
|
|| !root.contains("refresh_token")
|
||||||
|
|| !root.contains("scope")
|
||||||
|
|| !root.contains("created_at")) {
|
||||||
|
|
||||||
download();
|
setApplicationState(ApplicationState::UnexpectedError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_loginResponse.accessToken = root["access_token"].toString();
|
||||||
|
_loginResponse.refreshToken = root["refresh_token"].toString();
|
||||||
|
_loginResponse.tokenType = root["token_type"].toString();
|
||||||
|
|
||||||
|
qDebug() << "Got response for login: " << data;
|
||||||
|
|
||||||
|
downloadClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherState::download() {
|
QString LauncherState::getContentCachePath() const {
|
||||||
_appState = ApplicationState::DOWNLOADING_CONTENT;
|
return _launcherDirectory.filePath("cache");
|
||||||
setUIState(LauncherState::DOWNLOAD_SCREEN);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherState::contentDownloadComplete() {
|
bool LauncherState::shouldDownloadContentCache() const {
|
||||||
|
return !_contentCacheURL.isNull() && !QFile::exists(getContentCachePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::downloadClient() {
|
||||||
|
ASSERT_STATE(ApplicationState::RequestingLogin);
|
||||||
|
|
||||||
|
Build build;
|
||||||
|
if (!_latestBuilds.getBuild(_buildTag, &build)) {
|
||||||
|
qDebug() << "Cannot determine latest build";
|
||||||
|
setApplicationState(ApplicationState::UnexpectedError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_downloadProgress = 0;
|
||||||
|
setApplicationState(ApplicationState::DownloadingClient);
|
||||||
|
|
||||||
|
// Start client download
|
||||||
|
{
|
||||||
|
qDebug() << "Latest build: " << build.tag << build.buildNumber << build.latestVersion << build.installerZipURL;
|
||||||
|
auto request = new QNetworkRequest(QUrl(build.installerZipURL));
|
||||||
|
auto reply = _networkAccessManager.get(*request);
|
||||||
|
|
||||||
|
_clientZipFile.setFileName(_launcherDirectory.absoluteFilePath("client.zip"));
|
||||||
|
|
||||||
|
qDebug() << "Opening " << _clientZipFile.fileName();
|
||||||
|
if (!_clientZipFile.open(QIODevice::WriteOnly)) {
|
||||||
|
setApplicationState(ApplicationState::UnexpectedError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::finished, this, &LauncherState::clientDownloadComplete);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
_clientZipFile.write(buf, size);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(reply, &QNetworkReply::downloadProgress, this, [this](qint64 received, qint64 total) {
|
||||||
|
_downloadProgress = (float)received / (float)total;
|
||||||
|
emit downloadProgressChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherState::clientDownloadComplete() {
|
void LauncherState::clientDownloadComplete() {
|
||||||
|
ASSERT_STATE(ApplicationState::DownloadingClient);
|
||||||
|
|
||||||
|
_clientZipFile.close();
|
||||||
|
|
||||||
|
installClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::installClient() {
|
||||||
|
ASSERT_STATE(ApplicationState::DownloadingClient);
|
||||||
|
setApplicationState(ApplicationState::InstallingClient);
|
||||||
|
|
||||||
|
auto installDir = _launcherDirectory.absoluteFilePath("interface_install");
|
||||||
|
_launcherDirectory.mkpath("interface_install");
|
||||||
|
|
||||||
|
_downloadProgress = 0;
|
||||||
|
|
||||||
|
qDebug() << "Unzipping " << _clientZipFile.fileName() << " to " << installDir;
|
||||||
|
|
||||||
|
auto unzipper = new Unzipper(_clientZipFile.fileName(), QDir(installDir));
|
||||||
|
unzipper->setAutoDelete(true);
|
||||||
|
connect(unzipper, &Unzipper::progress, this, [this](float progress) {
|
||||||
|
qDebug() << "Unzipper progress: " << progress;
|
||||||
|
_downloadProgress = progress;
|
||||||
|
emit downloadProgressChanged();
|
||||||
|
});
|
||||||
|
connect(unzipper, &Unzipper::finished, this, [this](bool error, QString errorMessage) {
|
||||||
|
if (error) {
|
||||||
|
qDebug() << "Unzipper finished with error: " << errorMessage;
|
||||||
|
setApplicationState(ApplicationState::UnexpectedError);
|
||||||
|
} else {
|
||||||
|
qDebug() << "Unzipper finished without error";
|
||||||
|
downloadContentCache();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
QThreadPool::globalInstance()->start(unzipper);
|
||||||
|
|
||||||
|
//launchClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::downloadContentCache() {
|
||||||
|
ASSERT_STATE(ApplicationState::InstallingClient);
|
||||||
|
|
||||||
|
// Start content set cache download
|
||||||
|
if (shouldDownloadContentCache()) {
|
||||||
|
setApplicationState(ApplicationState::DownloadingContentCache);
|
||||||
|
|
||||||
|
_downloadProgress = 0;
|
||||||
|
|
||||||
|
auto request = new QNetworkRequest(QUrl(_contentCacheURL));
|
||||||
|
auto reply = _networkAccessManager.get(*request);
|
||||||
|
|
||||||
|
_contentZipFile.setFileName(_launcherDirectory.absoluteFilePath("content_cache.zip"));
|
||||||
|
|
||||||
|
qDebug() << "Opening " << _contentZipFile.fileName();
|
||||||
|
if (!_contentZipFile.open(QIODevice::WriteOnly)) {
|
||||||
|
setApplicationState(ApplicationState::UnexpectedError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::finished, this, &LauncherState::contentCacheDownloadComplete);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
_contentZipFile.write(buf, size);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(reply, &QNetworkReply::downloadProgress, this, [this](qint64 received, qint64 total) {
|
||||||
|
_downloadProgress = (float)received / (float)total;
|
||||||
|
emit downloadProgressChanged();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
launchClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::contentCacheDownloadComplete() {
|
||||||
|
ASSERT_STATE(ApplicationState::DownloadingContentCache);
|
||||||
|
|
||||||
|
_contentZipFile.close();
|
||||||
|
|
||||||
|
installContentCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LauncherState::installContentCache() {
|
||||||
|
ASSERT_STATE(ApplicationState::DownloadingContentCache);
|
||||||
|
setApplicationState(ApplicationState::InstallingContentCache);
|
||||||
|
|
||||||
|
auto installDir = getContentCachePath();
|
||||||
|
|
||||||
|
qDebug() << "Unzipping " << _contentZipFile.fileName() << " to " << installDir;
|
||||||
|
|
||||||
|
_downloadProgress = 0;
|
||||||
|
|
||||||
|
auto unzipper = new Unzipper(_contentZipFile.fileName(), QDir(installDir));
|
||||||
|
unzipper->setAutoDelete(true);
|
||||||
|
connect(unzipper, &Unzipper::progress, this, [this](float progress) {
|
||||||
|
qDebug() << "Unzipper progress (content cache): " << progress;
|
||||||
|
_downloadProgress = progress;
|
||||||
|
emit downloadProgressChanged();
|
||||||
|
});
|
||||||
|
connect(unzipper, &Unzipper::finished, this, [this](bool error, QString errorMessage) {
|
||||||
|
if (error) {
|
||||||
|
qDebug() << "Unzipper finished with error: " << errorMessage;
|
||||||
|
setApplicationState(ApplicationState::UnexpectedError);
|
||||||
|
} else {
|
||||||
|
qDebug() << "Unzipper finished without error";
|
||||||
|
launchClient();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
QThreadPool::globalInstance()->start(unzipper);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherState::launchClient() {
|
void LauncherState::launchClient() {
|
||||||
_appState = ApplicationState::LAUNCHING_HIGH_FIDELITY;
|
ASSERT_STATE({ ApplicationState::InstallingClient, ApplicationState::InstallingContentCache });
|
||||||
|
|
||||||
|
setApplicationState(ApplicationState::LaunchingHighFidelity);
|
||||||
|
|
||||||
|
QDir installDirectory = _launcherDirectory.filePath("interface_install");
|
||||||
|
auto clientPath = installDirectory.absoluteFilePath("interface.exe");
|
||||||
|
|
||||||
|
QString homePath = "hifi://hq";
|
||||||
|
QString defaultScriptsPath = installDirectory.filePath("scripts/simplifiedUIBootstrapper");
|
||||||
|
QString displayName = "fixMe";
|
||||||
|
QString contentCachePath = _launcherDirectory.filePath("cache");
|
||||||
|
|
||||||
|
// TODO Fix parameters
|
||||||
|
QString params = "--url " + homePath
|
||||||
|
+ " --setBookmark hqhome=\"" + homePath + "\""
|
||||||
|
+ " --defaultScriptsOverride " + QDir::toNativeSeparators(defaultScriptsPath)
|
||||||
|
+ " --displayName " + displayName
|
||||||
|
+ " --cache " + contentCachePath;
|
||||||
|
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
STARTUPINFO si;
|
||||||
|
PROCESS_INFORMATION pi;
|
||||||
|
|
||||||
|
// set the size of the structures
|
||||||
|
ZeroMemory(&si, sizeof(si));
|
||||||
|
si.cb = sizeof(si);
|
||||||
|
ZeroMemory(&pi, sizeof(pi));
|
||||||
|
|
||||||
|
// start the program up
|
||||||
|
BOOL success = CreateProcess(
|
||||||
|
clientPath.toUtf8().data(),
|
||||||
|
params.toUtf8().data(),
|
||||||
|
nullptr, // Process handle not inheritable
|
||||||
|
nullptr, // Thread handle not inheritable
|
||||||
|
FALSE, // Set handle inheritance to FALSE
|
||||||
|
CREATE_NEW_CONSOLE, // Opens file in a separate console
|
||||||
|
nullptr, // Use parent's environment block
|
||||||
|
nullptr, // Use parent's starting directory
|
||||||
|
&si, // Pointer to STARTUPINFO structure
|
||||||
|
&pi // Pointer to PROCESS_INFORMATION structure
|
||||||
|
);
|
||||||
|
// Close process and thread handles.
|
||||||
|
CloseHandle(pi.hProcess);
|
||||||
|
CloseHandle(pi.hThread);
|
||||||
|
exit(0);
|
||||||
|
#elif defined(Q_OS_MACOS)
|
||||||
|
// TODO Implement launching of client
|
||||||
|
#else
|
||||||
|
#error UNSUPPORTED PLATFORM
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::setApplicationState(ApplicationState state) {
|
||||||
|
qDebug() << "Changing application state: " << _applicationState << " -> " << state;
|
||||||
|
|
||||||
|
if (state == ApplicationState::UnexpectedError) {
|
||||||
|
__debugbreak();
|
||||||
|
}
|
||||||
|
|
||||||
|
_applicationState = state;
|
||||||
|
|
||||||
|
emit uiStateChanged();
|
||||||
|
emit updateSourceUrl(getCurrentUISource());
|
||||||
|
|
||||||
|
emit applicationStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
LauncherState::ApplicationState LauncherState::getApplicationState() const {
|
||||||
|
return _applicationState;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QFile>
|
||||||
|
|
||||||
struct Build {
|
struct Build {
|
||||||
QString tag;
|
QString tag;
|
||||||
|
@ -10,12 +14,24 @@ struct Build {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct LatestBuilds {
|
struct LatestBuilds {
|
||||||
|
bool getBuild(QString tag, Build* outBuild);
|
||||||
|
|
||||||
QString defaultTag;
|
QString defaultTag;
|
||||||
std::vector<Build> builds;
|
std::vector<Build> builds;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct LoginResponse {
|
||||||
|
QString accessToken;
|
||||||
|
QString tokenType;
|
||||||
|
QString refreshToken;
|
||||||
|
};
|
||||||
|
|
||||||
class LauncherState : public QObject {
|
class LauncherState : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(UIState uiState READ getUIState NOTIFY uiStateChanged);
|
||||||
|
Q_PROPERTY(ApplicationState applicationState READ getApplicationState NOTIFY applicationStateChanged);
|
||||||
|
|
||||||
|
Q_PROPERTY(float downloadProgress READ getDownloadProgress NOTIFY downloadProgressChanged);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
LauncherState();
|
LauncherState();
|
||||||
|
@ -30,29 +46,27 @@ public:
|
||||||
ERROR_SCREEN,
|
ERROR_SCREEN,
|
||||||
UI_STATE_NUM
|
UI_STATE_NUM
|
||||||
};
|
};
|
||||||
Q_ENUMS(UIState);
|
Q_ENUM(UIState);
|
||||||
|
|
||||||
enum class ApplicationState {
|
enum class ApplicationState {
|
||||||
INIT,
|
Init,
|
||||||
|
|
||||||
REQUESTING_BUILDS,
|
UnexpectedError,
|
||||||
REQUESTING_BUILDS_FAILED,
|
|
||||||
|
|
||||||
WAITING_FOR_LOGIN,
|
RequestingBuilds,
|
||||||
REQUESTING_LOGIN,
|
|
||||||
|
|
||||||
WAITING_FOR_SIGNUP,
|
WaitingForLogin,
|
||||||
REQUESTING_SIGNUP,
|
RequestingLogin,
|
||||||
|
|
||||||
DOWNLOADING_CONTENT,
|
DownloadingClient,
|
||||||
DOWNLOADING_HIGH_FIDELITY,
|
DownloadingContentCache,
|
||||||
|
|
||||||
EXTRACTING_DATA,
|
InstallingClient,
|
||||||
|
InstallingContentCache,
|
||||||
|
|
||||||
LAUNCHING_HIGH_FIDELITY
|
LaunchingHighFidelity
|
||||||
};
|
};
|
||||||
Q_ENUMS(ApplicationState);
|
Q_ENUM(ApplicationState);
|
||||||
|
|
||||||
|
|
||||||
enum LastLoginError {
|
enum LastLoginError {
|
||||||
NONE = 0,
|
NONE = 0,
|
||||||
|
@ -60,19 +74,23 @@ public:
|
||||||
CREDENTIALS,
|
CREDENTIALS,
|
||||||
LAST_ERROR_NUM
|
LAST_ERROR_NUM
|
||||||
};
|
};
|
||||||
Q_ENUMS(LastLoginError);
|
Q_ENUM(LastLoginError);
|
||||||
|
|
||||||
Q_INVOKABLE QString getCurrentUISource() const;
|
Q_INVOKABLE QString getCurrentUISource() const;
|
||||||
|
|
||||||
void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) const;
|
void ASSERT_STATE(LauncherState::ApplicationState state);
|
||||||
|
void ASSERT_STATE(std::vector<LauncherState::ApplicationState> states);
|
||||||
|
|
||||||
static void declareQML();
|
static void declareQML();
|
||||||
|
|
||||||
void setUIState(UIState state);
|
|
||||||
UIState getUIState() const;
|
UIState getUIState() const;
|
||||||
|
|
||||||
void setLastLoginError(LastLoginError lastLoginError);
|
void setLastLoginError(LastLoginError lastLoginError);
|
||||||
LastLoginError getLastLoginError() const;
|
LastLoginError getLastLoginError() const;
|
||||||
|
|
||||||
|
void setApplicationState(ApplicationState state);
|
||||||
|
ApplicationState getApplicationState() const;
|
||||||
|
|
||||||
// Request builds
|
// Request builds
|
||||||
void requestBuilds();
|
void requestBuilds();
|
||||||
Q_INVOKABLE void receivedBuildsReply();
|
Q_INVOKABLE void receivedBuildsReply();
|
||||||
|
@ -81,22 +99,45 @@ public:
|
||||||
Q_INVOKABLE void login(QString username, QString password);
|
Q_INVOKABLE void login(QString username, QString password);
|
||||||
Q_INVOKABLE void receivedLoginReply();
|
Q_INVOKABLE void receivedLoginReply();
|
||||||
|
|
||||||
// Download
|
// Client
|
||||||
void download();
|
void downloadClient();
|
||||||
Q_INVOKABLE void contentDownloadComplete();
|
void installClient();
|
||||||
Q_INVOKABLE void clientDownloadComplete();
|
|
||||||
|
// Content Cache
|
||||||
|
void downloadContentCache();
|
||||||
|
void installContentCache();
|
||||||
|
|
||||||
// Launching
|
// Launching
|
||||||
void launchClient();
|
void launchClient();
|
||||||
|
|
||||||
|
Q_INVOKABLE float getDownloadProgress() const { return _downloadProgress; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void updateSourceUrl(QString sourceUrl);
|
void updateSourceUrl(QString sourceUrl);
|
||||||
|
void uiStateChanged();
|
||||||
|
void applicationStateChanged();
|
||||||
|
void downloadProgressChanged();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void clientDownloadComplete();
|
||||||
|
void contentCacheDownloadComplete();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool shouldDownloadContentCache() const;
|
||||||
|
QString getContentCachePath() const;
|
||||||
|
|
||||||
QNetworkAccessManager _networkAccessManager;
|
QNetworkAccessManager _networkAccessManager;
|
||||||
LatestBuilds _latestBuilds;
|
LatestBuilds _latestBuilds;
|
||||||
|
QDir _launcherDirectory;
|
||||||
|
|
||||||
ApplicationState _appState { ApplicationState::INIT };
|
// Application State
|
||||||
UIState _uiState { SPLASH_SCREEN };
|
ApplicationState _applicationState { ApplicationState::Init };
|
||||||
|
LoginResponse _loginResponse;
|
||||||
LastLoginError _lastLoginError { NONE };
|
LastLoginError _lastLoginError { NONE };
|
||||||
|
QString _buildTag { QString::null };
|
||||||
|
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
|
||||||
|
QFile _clientZipFile;
|
||||||
|
QFile _contentZipFile;
|
||||||
|
|
||||||
|
float _downloadProgress { 0 };
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,7 +10,7 @@ Q_IMPORT_PLUGIN(QtQuickControls2Plugin);
|
||||||
Q_IMPORT_PLUGIN(QtQuickTemplates2Plugin);
|
Q_IMPORT_PLUGIN(QtQuickTemplates2Plugin);
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
QString name { "HQLauncher" };
|
QString name { "High Fidelity" };
|
||||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||||
QCoreApplication::setOrganizationName(name);
|
QCoreApplication::setOrganizationName(name);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue