mirror of
https://github.com/Armored-Dragon/overte.git
synced 2025-03-11 16:13:16 +01:00
fixing merge conflicts
This commit is contained in:
commit
54a827c67f
14 changed files with 9644 additions and 76 deletions
|
@ -127,7 +127,12 @@ set(src_files
|
|||
src/LauncherWindow.h
|
||||
src/LauncherWindow.cpp
|
||||
src/PathUtils.cpp
|
||||
src/PathUtils.h)
|
||||
src/PathUtils.h
|
||||
src/Unzipper.h
|
||||
src/Unzipper.cpp
|
||||
deps/miniz/miniz.h
|
||||
deps/miniz/miniz.cpp
|
||||
)
|
||||
|
||||
set(TARGET_NAME ${PROJECT_NAME})
|
||||
|
||||
|
@ -176,6 +181,7 @@ elseif (APPLE)
|
|||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/deps/
|
||||
${Qt5Core_INCLUDE_DIRS}
|
||||
${Qt5Quick_INCLUDE_DIRS}
|
||||
${Qt5Gui_INCLUDE_DIRS}
|
||||
|
|
7657
launchers/qt/deps/miniz/miniz.cpp
Normal file
7657
launchers/qt/deps/miniz/miniz.cpp
Normal file
File diff suppressed because it is too large
Load diff
1338
launchers/qt/deps/miniz/miniz.h
Normal file
1338
launchers/qt/deps/miniz/miniz.h
Normal file
File diff suppressed because it is too large
Load diff
0
launchers/qt/deps/miniz/stdafx.h
Normal file
0
launchers/qt/deps/miniz/stdafx.h
Normal file
|
@ -62,6 +62,8 @@ Item {
|
|||
width: 394
|
||||
height: 8
|
||||
|
||||
value: LauncherState.downloadProgress;
|
||||
|
||||
anchors {
|
||||
top: secondText.bottom
|
||||
topMargin: 30
|
||||
|
@ -88,14 +90,14 @@ Item {
|
|||
}
|
||||
|
||||
|
||||
PropertyAnimation {
|
||||
target: progressBar;
|
||||
loops: Animation.Infinite
|
||||
property: "value"
|
||||
from: 0;
|
||||
to: 1;
|
||||
duration: 5000
|
||||
running: true
|
||||
}
|
||||
//PropertyAnimation {
|
||||
//target: progressBar;
|
||||
//loops: Animation.Infinite
|
||||
//property: "value"
|
||||
//from: 0;
|
||||
//to: 1;
|
||||
//duration: 5000
|
||||
//running: true
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ TextField {
|
|||
horizontalAlignment: TextInput.AlignLeft
|
||||
placeholderText: "PlaceHolder"
|
||||
property string seperatorColor: "#FFFFFF"
|
||||
selectByMouse: true
|
||||
background: Item {
|
||||
anchors.fill: parent
|
||||
Rectangle {
|
||||
|
|
|
@ -4,6 +4,7 @@ import QtQuick 2.3
|
|||
import QtQuick.Controls 2.1
|
||||
import HQLauncher 1.0
|
||||
import "HFControls"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
width: 515
|
||||
|
@ -21,4 +22,14 @@ Item {
|
|||
loader.source = url;
|
||||
});
|
||||
}
|
||||
|
||||
Text {
|
||||
font.pixelSize: 12
|
||||
|
||||
anchors.right: root.right
|
||||
anchors.bottom: root.bottom
|
||||
|
||||
color: "#FFFFFF"
|
||||
text: LauncherState.uiState.toString() + " - " + LauncherState.applicationState
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) {
|
|||
QString resourceBinaryLocation = QGuiApplication::applicationDirPath() + "/resources.rcc";
|
||||
QResource::registerResource(resourceBinaryLocation);
|
||||
_launcherState = std::make_shared<LauncherState>();
|
||||
_launcherState->setUIState(LauncherState::SPLASH_SCREEN);
|
||||
//_launcherState->setUIState(LauncherState::SPLASH_SCREEN);
|
||||
_launcherWindow = std::make_unique<LauncherWindow>();
|
||||
_launcherWindow->rootContext()->setContextProperty("LauncherState", _launcherState.get());
|
||||
_launcherWindow->rootContext()->setContextProperty("PathUtils", new PathUtils());
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
#include "LauncherState.h"
|
||||
|
||||
#include "PathUtils.h"
|
||||
#include "Unzipper.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
#include <array>
|
||||
|
||||
#include <QNetworkRequest>
|
||||
|
@ -14,56 +19,58 @@
|
|||
#include <QDebug>
|
||||
#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 =
|
||||
{ { "SplashScreen.qml", "HFBase/CreateAccountBase.qml", "DisplayName.qml",
|
||||
"Download.qml", "DownloadFinished.qml", "HFBase/Error.qml" } };
|
||||
{ { "SplashScreen.qml", "qml/HFBase/CreateAccountBase.qml", "DisplayName.qml",
|
||||
"Download.qml", "DownloadFinished.qml", "qml/HFBase/Error.qml" } };
|
||||
|
||||
void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) {
|
||||
if (_applicationState != state) {
|
||||
#ifdef Q_OS_WIN
|
||||
__debugbreak();
|
||||
#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() {
|
||||
// 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 reply = _networkAccessManager.get(*request);
|
||||
QObject::connect(reply, &QNetworkReply::finished, [reply, this]() {
|
||||
if (reply->error()) {
|
||||
qDebug() << "Error getting builds from thunder: " << reply->errorString();
|
||||
} else {
|
||||
qDebug() << "Builds reply has been received";
|
||||
auto data = reply->readAll();
|
||||
QJsonParseError parseError;
|
||||
auto doc = QJsonDocument::fromJson(data, &parseError);
|
||||
if (parseError.error) {
|
||||
qDebug() << "Error parsing response from thunder: " << data;
|
||||
} else {
|
||||
auto root = doc.object();
|
||||
if (!root.contains("default_tag")) {
|
||||
return;
|
||||
}
|
||||
|
||||
_latestBuilds.defaultTag = root["default_tag"].toString();
|
||||
|
||||
auto results = root["results"];
|
||||
if (!results.isArray()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto result : results.toArray()) {
|
||||
auto entry = result.toObject();
|
||||
Build build;
|
||||
build.tag = entry["name"].toString();
|
||||
build.latestVersion = entry["latest_version"].toInt();
|
||||
build.buildNumber = entry["build_number"].toInt();
|
||||
#ifdef Q_OS_WIN
|
||||
build.installerZipURL = entry["installers"].toObject()["windows"].toObject()["zip_url"].toString();
|
||||
#elif defined(Q_OS_MACOS)
|
||||
build.installerZipURL = entry["installers"].toObject()["mac"].toObject()["zip_url"].toString();
|
||||
#else
|
||||
#error "Launcher is only supported on Windows and Mac OS"
|
||||
#endif
|
||||
_latestBuilds.builds.push_back(build);
|
||||
}
|
||||
}
|
||||
}
|
||||
//setUIState(LauncherState::LOGIN_SCREEN);
|
||||
});
|
||||
_launcherDirectory = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
// TODO Fix launcher directory
|
||||
qDebug() << "Launcher directory: " << _launcherDirectory.absolutePath();
|
||||
_launcherDirectory.mkpath(_launcherDirectory.absolutePath());
|
||||
requestBuilds();
|
||||
}
|
||||
|
||||
QString LauncherState::getCurrentUISource() const {
|
||||
|
@ -74,13 +81,33 @@ void LauncherState::declareQML() {
|
|||
qmlRegisterType<LauncherState>("HQLauncher", 1, 0, "LauncherStateEnums");
|
||||
}
|
||||
|
||||
void LauncherState::setUIState(UIState state) {
|
||||
_uiState = state;
|
||||
emit updateSourceUrl(PathUtils::resourcePath(getCurrentUISource()));
|
||||
}
|
||||
|
||||
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:
|
||||
#ifdef Q_OS_WIN
|
||||
__debugbreak();
|
||||
#endif
|
||||
return ERROR_SCREEN;
|
||||
default:
|
||||
qDebug() << "FATAL: No UI for" << _applicationState;
|
||||
#ifdef Q_OS_WIN
|
||||
__debugbreak();
|
||||
#endif
|
||||
return ERROR_SCREEN;
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherState::setLastLoginError(LastLoginError lastLoginError) {
|
||||
|
@ -91,7 +118,71 @@ LauncherState::LastLoginError LauncherState::getLastLoginError() const {
|
|||
return _lastLoginError;
|
||||
}
|
||||
|
||||
void LauncherState::requestBuilds() {
|
||||
ASSERT_STATE(ApplicationState::Init);
|
||||
setApplicationState(ApplicationState::RequestingBuilds);
|
||||
|
||||
// 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 reply = _networkAccessManager.get(*request);
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, this, &LauncherState::receivedBuildsReply);
|
||||
}
|
||||
|
||||
void LauncherState::receivedBuildsReply() {
|
||||
auto reply = static_cast<QNetworkReply*>(sender());
|
||||
|
||||
ASSERT_STATE(ApplicationState::RequestingBuilds);
|
||||
|
||||
if (reply->error()) {
|
||||
qDebug() << "Error getting builds from thunder: " << reply->errorString();
|
||||
} else {
|
||||
qDebug() << "Builds reply has been received";
|
||||
auto data = reply->readAll();
|
||||
QJsonParseError parseError;
|
||||
auto doc = QJsonDocument::fromJson(data, &parseError);
|
||||
if (parseError.error) {
|
||||
qDebug() << "Error parsing response from thunder: " << data;
|
||||
} else {
|
||||
auto root = doc.object();
|
||||
if (!root.contains("default_tag")) {
|
||||
setApplicationState(ApplicationState::UnexpectedError);
|
||||
return;
|
||||
}
|
||||
|
||||
_latestBuilds.defaultTag = root["default_tag"].toString();
|
||||
|
||||
auto results = root["results"];
|
||||
if (!results.isArray()) {
|
||||
setApplicationState(ApplicationState::UnexpectedError);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto result : results.toArray()) {
|
||||
auto entry = result.toObject();
|
||||
Build build;
|
||||
build.tag = entry["name"].toString();
|
||||
build.latestVersion = entry["latest_version"].toInt();
|
||||
build.buildNumber = entry["build_number"].toInt();
|
||||
#ifdef Q_OS_WIN
|
||||
build.installerZipURL = entry["installers"].toObject()["windows"].toObject()["zip_url"].toString();
|
||||
#elif defined(Q_OS_MACOS)
|
||||
build.installerZipURL = entry["installers"].toObject()["mac"].toObject()["zip_url"].toString();
|
||||
#else
|
||||
#error "Launcher is only supported on Windows and Mac OS"
|
||||
#endif
|
||||
_latestBuilds.builds.push_back(build);
|
||||
}
|
||||
}
|
||||
}
|
||||
setApplicationState(ApplicationState::WaitingForLogin);
|
||||
}
|
||||
|
||||
void LauncherState::login(QString username, QString password) {
|
||||
ASSERT_STATE(ApplicationState::WaitingForLogin);
|
||||
|
||||
setApplicationState(ApplicationState::RequestingLogin);
|
||||
|
||||
qDebug() << "Got login: " << username << password;
|
||||
|
||||
auto request = new QNetworkRequest(QUrl("https://metaverse.highfidelity.com/oauth/token"));
|
||||
|
@ -104,8 +195,294 @@ void LauncherState::login(QString username, QString password) {
|
|||
query.addQueryItem("scope", "owner");
|
||||
|
||||
auto reply = _networkAccessManager.post(*request, query.toString().toUtf8());
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, [reply, this]() {
|
||||
qDebug() << "Got response for login: " << reply->readAll();
|
||||
});
|
||||
QObject::connect(reply, &QNetworkReply::finished, this, &LauncherState::receivedLoginReply);
|
||||
}
|
||||
|
||||
Q_INVOKABLE void LauncherState::receivedLoginReply() {
|
||||
ASSERT_STATE(ApplicationState::RequestingLogin);
|
||||
|
||||
// TODO Check for errors
|
||||
auto reply = static_cast<QNetworkReply*>(sender());
|
||||
|
||||
if (reply->error()) {
|
||||
setApplicationState(ApplicationState::UnexpectedError);
|
||||
return;
|
||||
}
|
||||
|
||||
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")) {
|
||||
|
||||
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;
|
||||
_loginTokenResponse = data;
|
||||
|
||||
downloadClient();
|
||||
}
|
||||
|
||||
QString LauncherState::getContentCachePath() const {
|
||||
return _launcherDirectory.filePath("cache");
|
||||
}
|
||||
|
||||
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() {
|
||||
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() {
|
||||
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 (!_loginTokenResponse.isEmpty()) {
|
||||
params += " --tokens \"" + _loginTokenResponse.replace("\"", "\\\"") + "\"";
|
||||
}
|
||||
|
||||
#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) {
|
||||
#ifdef Q_OS_WIN
|
||||
__debugbreak();
|
||||
#endif
|
||||
}
|
||||
|
||||
_applicationState = state;
|
||||
|
||||
emit uiStateChanged();
|
||||
emit updateSourceUrl(PathUtils::resourcePath(getCurrentUISource()));
|
||||
|
||||
emit applicationStateChanged();
|
||||
}
|
||||
|
||||
LauncherState::ApplicationState LauncherState::getApplicationState() const {
|
||||
return _applicationState;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QFile>
|
||||
|
||||
struct Build {
|
||||
QString tag;
|
||||
|
@ -11,13 +15,25 @@ struct Build {
|
|||
};
|
||||
|
||||
struct LatestBuilds {
|
||||
bool getBuild(QString tag, Build* outBuild);
|
||||
|
||||
QString defaultTag;
|
||||
std::vector<Build> builds;
|
||||
};
|
||||
|
||||
struct LoginResponse {
|
||||
QString accessToken;
|
||||
QString tokenType;
|
||||
QString refreshToken;
|
||||
};
|
||||
|
||||
class LauncherState : public QObject {
|
||||
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:
|
||||
LauncherState();
|
||||
~LauncherState() = default;
|
||||
|
@ -31,7 +47,25 @@ public:
|
|||
ERROR_SCREEN,
|
||||
UI_STATE_NUM
|
||||
};
|
||||
Q_ENUMS(UIState)
|
||||
|
||||
enum class ApplicationState {
|
||||
Init,
|
||||
|
||||
UnexpectedError,
|
||||
|
||||
RequestingBuilds,
|
||||
|
||||
WaitingForLogin,
|
||||
RequestingLogin,
|
||||
|
||||
DownloadingClient,
|
||||
DownloadingContentCache,
|
||||
|
||||
InstallingClient,
|
||||
InstallingContentCache,
|
||||
|
||||
LaunchingHighFidelity
|
||||
};
|
||||
|
||||
enum LastLoginError {
|
||||
NONE = 0,
|
||||
|
@ -39,29 +73,74 @@ public:
|
|||
CREDENTIALS,
|
||||
LAST_ERROR_NUM
|
||||
};
|
||||
Q_ENUMS(LastLoginError)
|
||||
|
||||
Q_ENUM(UIState);
|
||||
Q_ENUM(ApplicationState)
|
||||
Q_ENUM(LastLoginError)
|
||||
|
||||
Q_INVOKABLE QString getCurrentUISource() const;
|
||||
|
||||
void ASSERT_STATE(LauncherState::ApplicationState state);
|
||||
void ASSERT_STATE(std::vector<LauncherState::ApplicationState> states);
|
||||
|
||||
static void declareQML();
|
||||
|
||||
void setUIState(UIState state);
|
||||
UIState getUIState() const;
|
||||
|
||||
void setLastLoginError(LastLoginError lastLoginError);
|
||||
LastLoginError getLastLoginError() const;
|
||||
|
||||
void setApplicationState(ApplicationState state);
|
||||
ApplicationState getApplicationState() const;
|
||||
|
||||
// Request builds
|
||||
void requestBuilds();
|
||||
Q_INVOKABLE void receivedBuildsReply();
|
||||
|
||||
// LOGIN
|
||||
// Login
|
||||
Q_INVOKABLE void login(QString username, QString password);
|
||||
Q_INVOKABLE void receivedLoginReply();
|
||||
|
||||
// Client
|
||||
void downloadClient();
|
||||
void installClient();
|
||||
|
||||
// Content Cache
|
||||
void downloadContentCache();
|
||||
void installContentCache();
|
||||
|
||||
// Launching
|
||||
void launchClient();
|
||||
|
||||
Q_INVOKABLE float getDownloadProgress() const { return _downloadProgress; }
|
||||
|
||||
signals:
|
||||
void updateSourceUrl(QString sourceUrl);
|
||||
void uiStateChanged();
|
||||
void applicationStateChanged();
|
||||
void downloadProgressChanged();
|
||||
|
||||
private slots:
|
||||
void clientDownloadComplete();
|
||||
void contentCacheDownloadComplete();
|
||||
|
||||
private:
|
||||
bool shouldDownloadContentCache() const;
|
||||
QString getContentCachePath() const;
|
||||
|
||||
QNetworkAccessManager _networkAccessManager;
|
||||
LatestBuilds _latestBuilds;
|
||||
QDir _launcherDirectory;
|
||||
|
||||
UIState _uiState { SPLASH_SCREEN };
|
||||
// Application State
|
||||
ApplicationState _applicationState { ApplicationState::Init };
|
||||
LoginResponse _loginResponse;
|
||||
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
|
||||
QString _loginTokenResponse;
|
||||
QFile _clientZipFile;
|
||||
QFile _contentZipFile;
|
||||
|
||||
float _downloadProgress { 0 };
|
||||
};
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
void LauncherWindow::keyPressEvent(QKeyEvent* event) {
|
||||
QQuickView::keyPressEvent(event);
|
||||
if (!event->isAccepted()) {
|
||||
if (event->key() == Qt::Key_Escape) {
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
74
launchers/qt/src/Unzipper.cpp
Normal file
74
launchers/qt/src/Unzipper.cpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
#include "Unzipper.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QDebug>
|
||||
#include <miniz/miniz.h>
|
||||
|
||||
Unzipper::Unzipper(const QString& zipFilePath, const QDir& outputDirectory) :
|
||||
_zipFilePath(zipFilePath), _outputDirectory(outputDirectory) {
|
||||
}
|
||||
|
||||
void Unzipper::run() {
|
||||
qDebug() << "Reading zip file" << _zipFilePath << ", extracting to" << _outputDirectory.absolutePath();
|
||||
|
||||
mz_zip_archive zip_archive;
|
||||
memset(&zip_archive, 0, sizeof(zip_archive));
|
||||
|
||||
auto status = mz_zip_reader_init_file(&zip_archive, _zipFilePath.toUtf8().data(), 0);
|
||||
|
||||
if (!status) {
|
||||
auto zip_error = mz_zip_get_last_error(&zip_archive);
|
||||
auto zip_error_msg = mz_zip_get_error_string(zip_error);
|
||||
emit finished(true, "Failed to initialize miniz: " + QString::number(zip_error) + " " + zip_error_msg);
|
||||
return;
|
||||
}
|
||||
|
||||
int fileCount = (int)mz_zip_reader_get_num_files(&zip_archive);
|
||||
|
||||
qDebug() << "Zip archive has a file count of " << fileCount;
|
||||
|
||||
if (fileCount == 0) {
|
||||
mz_zip_reader_end(&zip_archive);
|
||||
emit finished(false, "");
|
||||
return;
|
||||
}
|
||||
|
||||
mz_zip_archive_file_stat file_stat;
|
||||
if (!mz_zip_reader_file_stat(&zip_archive, 0, &file_stat)) {
|
||||
mz_zip_reader_end(&zip_archive);
|
||||
emit finished(true, "Zip archive cannot be stat'd");
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t totalSize = 0;
|
||||
uint64_t totalCompressedSize = 0;
|
||||
bool _shouldFail = false;
|
||||
for (int i = 0; i < fileCount; i++) {
|
||||
if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) continue;
|
||||
|
||||
QString filename = file_stat.m_filename;
|
||||
QString fullFilename = _outputDirectory.filePath(filename);
|
||||
if (mz_zip_reader_is_file_a_directory(&zip_archive, i)) {
|
||||
if (!_outputDirectory.mkpath(fullFilename)) {
|
||||
mz_zip_reader_end(&zip_archive);
|
||||
emit finished(true, "Unzipping error while creating folder: " + fullFilename);
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (mz_zip_reader_extract_to_file(&zip_archive, i, fullFilename.toUtf8().data(), 0)) {
|
||||
totalCompressedSize += (uint64_t)file_stat.m_comp_size;
|
||||
totalSize += (uint64_t)file_stat.m_uncomp_size;
|
||||
emit progress((float)totalCompressedSize / (float)zip_archive.m_archive_size);
|
||||
} else {
|
||||
emit finished(true, "Unzipping error unzipping file: " + fullFilename);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Done unzipping archive, total size:" << totalSize;
|
||||
|
||||
mz_zip_reader_end(&zip_archive);
|
||||
|
||||
emit finished(false, "");
|
||||
}
|
21
launchers/qt/src/Unzipper.h
Normal file
21
launchers/qt/src/Unzipper.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QDir>
|
||||
#include <QObject>
|
||||
#include <QRunnable>
|
||||
|
||||
class Unzipper : public QObject, public QRunnable {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Unzipper(const QString& zipFilePath, const QDir& outputDirectory);
|
||||
void run() override;
|
||||
|
||||
signals:
|
||||
void progress(float progress);
|
||||
void finished(bool error, QString errorMessage);
|
||||
|
||||
private:
|
||||
const QString _zipFilePath;
|
||||
const QDir _outputDirectory;
|
||||
};
|
|
@ -14,9 +14,8 @@ Q_IMPORT_PLUGIN(QtQuick2Plugin);
|
|||
Q_IMPORT_PLUGIN(QtQuickControls2Plugin);
|
||||
Q_IMPORT_PLUGIN(QtQuickTemplates2Plugin);
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QString name { "HQLauncher" };
|
||||
int main(int argc, char *argv[]) {
|
||||
QString name { "High Fidelity" };
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
QCoreApplication::setOrganizationName(name);
|
||||
|
||||
|
|
Loading…
Reference in a new issue