Add content cache and settings support to qt launcher

This commit is contained in:
Ryan Huffman 2019-09-23 16:58:06 -07:00
parent 67033fc13a
commit e6128003ec
4 changed files with 187 additions and 69 deletions

View file

@ -15,11 +15,11 @@ void launchClient(const QString& clientPath, const QString& homePath, const QStr
const QString& displayName, const QString& contentCachePath, QString loginResponseToken) {
// TODO Fix parameters
QString params = "--url " + homePath
+ " --setBookmark hqhome=\"" + homePath + "\""
+ " --defaultScriptsOverride " + defaultScriptsPath
+ " --displayName " + displayName
+ " --cache " + contentCachePath;
QString params = "--url \"" + homePath + "\""
+ " --setBookmark \"hqhome=" + homePath + "\""
+ " --defaultScriptsOverride \"" + defaultScriptsPath + "\""
+ " --displayName \"" + displayName + "\""
+ " --cache \"" + contentCachePath + "\"";
if (!loginResponseToken.isEmpty()) {
params += " --tokens \"" + loginResponseToken.replace("\"", "\\\"") + "\"";

View file

@ -29,28 +29,29 @@
#include <qregularexpression.h>
QString LauncherState::getCurrentClientVersion() {
QProcess client;
const QString METAVERSE_API_URL{ "https://metaverse.highfidelity.com" };
const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization";
client.start(getClientExecutablePath(), { "--version" });
QEventLoop loop;
connect(&client, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), &loop, &QEventLoop::exit);
loop.exec();
auto output = client.readAllStandardOutput();
QRegularExpression regex { "Interface (?<version>\\d+)(-.*)?" };
auto match = regex.match(output);
if (match.hasMatch()) {
return match.captured("version");
}
return QString::null;
QString LauncherState::getContentCachePath() const {
return _launcherDirectory.filePath("cache");
}
QString LauncherState::getClientDirectory() const {
return _launcherDirectory.filePath("interface_install");
}
QString LauncherState::getClientExecutablePath() const {
QDir clientDirectory = getClientDirectory();
#if defined(Q_OS_WIN)
return clientDirectory.absoluteFilePath("interface.exe");
#elif defined(Q_OS_MACOS)
return clientDirectory.absoluteFilePath("interface.app/Contents/MacOS/interface");
#endif
}
bool LauncherState::shouldDownloadContentCache() const {
return !_contentCacheURL.isNull() && !QFile::exists(getContentCachePath());
}
bool LatestBuilds::getBuild(QString tag, Build* outBuild) {
if (tag.isNull()) {
@ -68,7 +69,7 @@ bool LatestBuilds::getBuild(QString tag, Build* outBuild) {
}
static const std::array<QString, LauncherState::UIState::UI_STATE_NUM> QML_FILE_FOR_UI_STATE =
{ { "SplashScreen.qml", "qml/HFBase/CreateAccountBase.qml", "DisplayName.qml",
{ { "qml/SplashScreen.qml", "qml/HFBase/CreateAccountBase.qml", "DisplayName.qml",
"qml/Download.qml", "qml/DownloadFinished.qml", "qml/HFBase/Error.qml" } };
void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) {
@ -113,6 +114,7 @@ LauncherState::UIState LauncherState::getUIState() const {
switch (_applicationState) {
case ApplicationState::Init:
case ApplicationState::RequestingBuilds:
case ApplicationState::GettingCurrentClientVersion:
return SPLASH_SCREEN;
case ApplicationState::WaitingForLogin:
case ApplicationState::RequestingLogin:
@ -197,7 +199,7 @@ void LauncherState::receivedBuildsReply() {
#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"
#error "Launcher is only supported on Windows and Mac OS"
#endif
_latestBuilds.builds.push_back(build);
}
@ -212,16 +214,78 @@ void LauncherState::receivedBuildsReply() {
#elif defined(Q_OS_MACOS)
launcherBuild.installerZipURL = launcherResults["mac"].toObject()["url"].toString();
#else
#error "Launcher is only supported on Windows and Mac OS"
#error "Launcher is only supported on Windows and Mac OS"
#endif
_latestBuilds.launcherBuild = launcherBuild;
}
}
//downloadLauncher();
getCurrentClientVersion();
}
void LauncherState::getCurrentClientVersion() {
ASSERT_STATE(ApplicationState::RequestingBuilds);
setApplicationState(ApplicationState::GettingCurrentClientVersion);
QProcess client;
QEventLoop loop;
//connect(&client, &QProcess::errorOccurred, &loop, &QEventLoop::exit);
connect(&client, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), &loop, &QEventLoop::exit);
/*
connect(&client, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), [&]() {
qDebug() << "Finished";
});
connect(&client, &QProcess::errorOccurred, [&](QProcess::ProcessError err) {
qDebug() << "Error occurred" << err << client.error();
});
connect(&client, &QProcess::started, [&]() {
qDebug() << "Started";
});
connect(&client, &QProcess::stateChanged, [&]() {
qDebug() << "State changed " << client.state();
});
*/
//qDebug() << "Starting client";
client.start(getClientExecutablePath(), { "--version" });
//qDebug() << "Started" << client.error();
if (client.state() != QProcess::NotRunning) {
//qDebug() << "Starting loop";
loop.exec();
} else {
qDebug() << "Not waiting for client, there was an error starting it: " << client.error();
}
// TODO Handle errors
auto output = client.readAllStandardOutput();
QRegularExpression regex { "Interface (?<version>\\d+)(-.*)?" };
auto match = regex.match(output);
if (match.hasMatch()) {
_currentClientVersion = match.captured("version");
} else {
_currentClientVersion = QString::null;
}
qDebug() << "Current client version is: " << _currentClientVersion;
setApplicationState(ApplicationState::WaitingForLogin);
}
QString getUserAgent() {
#if defined(Q_OS_WIN)
return "HQLauncher/fixme (Windows)";
#elif defined(Q_OS_MACOS)
return "HQLauncher/fixme (MacOS)";
#else
#error Unsupported platform
#endif
}
void LauncherState::login(QString username, QString password) {
ASSERT_STATE(ApplicationState::WaitingForLogin);
@ -229,8 +293,9 @@ void LauncherState::login(QString username, QString password) {
qDebug() << "Got login: " << username << password;
auto request = new QNetworkRequest(QUrl("https://metaverse.highfidelity.com/oauth/token"));
auto request = new QNetworkRequest(QUrl(METAVERSE_API_URL + "/oauth/token"));
request->setHeader(QNetworkRequest::UserAgentHeader, getUserAgent());
request->setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QUrlQuery query;
query.addQueryItem("grant_type", "password");
@ -276,30 +341,64 @@ Q_INVOKABLE void LauncherState::receivedLoginReply() {
qDebug() << "Got response for login: " << data;
_loginTokenResponse = data;
requestSettings();
}
void LauncherState::requestSettings() {
QUrl lockerURL = METAVERSE_API_URL;
lockerURL.setPath("/api/v1/user/locker");
auto lockerRequest = new QNetworkRequest(lockerURL);
lockerRequest->setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
lockerRequest->setHeader(QNetworkRequest::UserAgentHeader, getUserAgent());
lockerRequest->setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, QString("Bearer %1").arg(_loginResponse.accessToken).toUtf8());
QNetworkReply* lockerReply = _networkAccessManager.get(*lockerRequest);
connect(lockerReply, &QNetworkReply::finished, this, &LauncherState::receivedSettingsReply);
}
void LauncherState::receivedSettingsReply() {
auto reply = static_cast<QNetworkReply*>(sender());
qDebug() << "Got reply: " << reply->error();
if (reply->error()) {
setApplicationState(ApplicationState::UnexpectedError);
return;
}
auto data = reply->readAll();
qDebug() << "Settings: " << data;
QJsonParseError parseError;
auto doc = QJsonDocument::fromJson(data, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qDebug() << "Error parsing settings";
setApplicationStateError("Error retreiving settings");
return;
}
auto root = doc.object();
if (root["status"] != "success") {
qDebug() << "Status is not \"success\"";
setApplicationStateError("Error retreiving settings");
return;
}
_homeLocation = "hifi://hq";
if (root["data"].toObject().contains("home_location")) {
auto homeLocation = root["data"].toObject()["home_location"];
if (homeLocation.isString()) {
_homeLocation = homeLocation.toString();
auto host = QUrl(_homeLocation).host();
_contentCacheURL = "http://orgs.highfidelity.com/host-content-cache/" + host + ".zip";
qDebug() << "Home location is: " << _homeLocation;
qDebug() << "Content cache url is: " << _contentCacheURL;
}
}
//qDebug() << "Home:" << _homeLocation << QUrl(_homeLocation).host();
downloadClient();
}
QString LauncherState::getContentCachePath() const {
return _launcherDirectory.filePath("cache");
}
QString LauncherState::getClientDirectory() const {
return _launcherDirectory.filePath("interface_install");
}
QString LauncherState::getClientExecutablePath() const {
QDir clientDirectory = getClientDirectory();
#if defined(Q_OS_WIN)
return clientDirectory.absoluteFilePath("interface.exe");
#elif defined(Q_OS_MACOS)
return clientDirectory.absoluteFilePath("interface.app/Contents/MacOS/interface");
#endif
}
bool LauncherState::shouldDownloadContentCache() const {
return !_contentCacheURL.isNull() && !QFile::exists(getContentCachePath());
}
void LauncherState::downloadClient() {
ASSERT_STATE(ApplicationState::RequestingLogin);
@ -310,8 +409,7 @@ void LauncherState::downloadClient() {
return;
}
auto currentVersion = getCurrentClientVersion();
if (QString::number(build.latestVersion) == currentVersion) {
if (QString::number(build.latestVersion) == _currentClientVersion) {
qDebug() << "Existing client install is up-to-date.";
downloadContentCache();
return;
@ -381,7 +479,7 @@ void LauncherState::installClient() {
auto unzipper = new Unzipper(_clientZipFile.fileName(), QDir(installDir));
unzipper->setAutoDelete(true);
connect(unzipper, &Unzipper::progress, this, [this](float progress) {
qDebug() << "Unzipper progress: " << progress;
//qDebug() << "Unzipper progress: " << progress;
_downloadProgress = progress;
emit downloadProgressChanged();
});
@ -463,8 +561,9 @@ void LauncherState::downloadContentCache() {
_downloadProgress = 0;
auto request = new QNetworkRequest(QUrl(_contentCacheURL));
auto reply = _networkAccessManager.get(*request);
QNetworkRequest request{ QUrl(_contentCacheURL) };
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
auto reply = _networkAccessManager.get(request);
_contentZipFile.setFileName(_launcherDirectory.absoluteFilePath("content_cache.zip"));
@ -475,17 +574,6 @@ void LauncherState::downloadContentCache() {
}
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();
@ -498,6 +586,21 @@ void LauncherState::downloadContentCache() {
void LauncherState::contentCacheDownloadComplete() {
ASSERT_STATE(ApplicationState::DownloadingContentCache);
auto reply = static_cast<QNetworkReply*>(sender());
if (reply->error()) {
qDebug() << "Error: " << reply->error() << reply->readAll();
setApplicationStateError("Failed to retrieve content cache");
return;
}
char buf[4096];
while (reply->bytesAvailable() > 0) {
qint64 size;
size = reply->read(buf, (qint64)sizeof(buf));
_contentZipFile.write(buf, size);
}
_contentZipFile.close();
installContentCache();
@ -552,7 +655,6 @@ void LauncherState::launchClient() {
#endif
// TODO Get correct home path
QString homePath = "hifi://hq";
QString defaultScriptsPath;
#if defined(Q_OS_WIN)
defaultScriptsPath = installDirectory.filePath("scripts/simplifiedUIBootstrapper.js");
@ -563,7 +665,12 @@ void LauncherState::launchClient() {
QString displayName = "fixMe";
QString contentCachePath = _launcherDirectory.filePath("cache");
::launchClient(clientPath, homePath, QDir::toNativeSeparators(defaultScriptsPath), displayName, contentCachePath, _loginTokenResponse);
::launchClient(clientPath, _homeLocation, QDir::toNativeSeparators(defaultScriptsPath), displayName, contentCachePath, _loginTokenResponse);
}
void LauncherState::setApplicationStateError(QString errorMessage) {
_applicationErrorMessage = errorMessage;
setApplicationState(ApplicationState::UnexpectedError);
}
void LauncherState::setApplicationState(ApplicationState state) {

View file

@ -55,6 +55,7 @@ public:
UnexpectedError,
RequestingBuilds,
GettingCurrentClientVersion,
WaitingForLogin,
RequestingLogin,
@ -93,6 +94,7 @@ public:
void setLastLoginError(LastLoginError lastLoginError);
LastLoginError getLastLoginError() const;
void setApplicationStateError(QString errorMessage);
void setApplicationState(ApplicationState state);
ApplicationState getApplicationState() const;
@ -100,10 +102,14 @@ public:
void requestBuilds();
Q_INVOKABLE void receivedBuildsReply();
// Login
Q_INVOKABLE void login(QString username, QString password);
Q_INVOKABLE void receivedLoginReply();
void requestSettings();
Q_INVOKABLE void receivedSettingsReply();
// Launcher
void downloadLauncher();
void installLauncher();
@ -134,7 +140,7 @@ private slots:
private:
bool shouldDownloadContentCache() const;
QString getCurrentClientVersion();
void getCurrentClientVersion();
QString getContentCachePath() const;
QString getClientDirectory() const;
@ -148,9 +154,12 @@ private:
ApplicationState _applicationState { ApplicationState::Init };
LoginResponse _loginResponse;
LastLoginError _lastLoginError { NONE };
QString _applicationErrorMessage;
QString _currentClientVersion;
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;
QString _homeLocation;
QFile _clientZipFile;
QFile _launcherZipFile;
QFile _contentZipFile;

View file

@ -16,6 +16,8 @@ Unzipper::Unzipper(const QString& zipFilePath, const QDir& outputDirectory) :
void Unzipper::run() {
qDebug() << "Reading zip file" << _zipFilePath << ", extracting to" << _outputDirectory.absolutePath();
_outputDirectory.mkpath(_outputDirectory.absolutePath());
mz_zip_archive zip_archive;
memset(&zip_archive, 0, sizeof(zip_archive));