Add automatic update checking. Fixes #8

This commit is contained in:
Cameron Gutman
2018-08-09 22:51:27 -07:00
parent 8f61a11452
commit 0fb3bb0727
7 changed files with 216 additions and 5 deletions

View File

@@ -82,7 +82,8 @@ SOURCES += \
streaming/audio.cpp \
gui/computermodel.cpp \
gui/appmodel.cpp \
streaming/streamutils.cpp
streaming/streamutils.cpp \
backend/autoupdatechecker.cpp
HEADERS += \
utils.h \
@@ -97,7 +98,8 @@ HEADERS += \
gui/computermodel.h \
gui/appmodel.h \
streaming/video/decoder.h \
streaming/streamutils.h
streaming/streamutils.h \
backend/autoupdatechecker.h
# Platform-specific renderers and decoders
ffmpeg {
@@ -203,3 +205,4 @@ macx {
}
VERSION = 0.1.0
DEFINES += VERSION_STR=\\\"0.1.0\\\"

View File

@@ -0,0 +1,142 @@
#include "autoupdatechecker.h"
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
AutoUpdateChecker::AutoUpdateChecker(QObject *parent) :
QObject(parent),
m_Nam(this)
{
// Never communicate over HTTP
m_Nam.setStrictTransportSecurityEnabled(true);
connect(&m_Nam, SIGNAL(finished(QNetworkReply*)),
this, SLOT(handleUpdateCheckRequestFinished(QNetworkReply*)));
QString currentVersion(VERSION_STR);
qDebug() << "Current Moonlight version:" << currentVersion;
parseStringToVersionQuad(currentVersion, m_CurrentVersionQuad);
// Should at least have a 1.0-style version number
Q_ASSERT(m_CurrentVersionQuad.count() > 1);
}
void AutoUpdateChecker::start()
{
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN) // Only run update checker on platforms without auto-update
// We'll get a callback when this is finished
QUrl url("https://moonlight-stream.com/updates/qt.json");
m_Nam.get(QNetworkRequest(url));
#endif
}
void AutoUpdateChecker::parseStringToVersionQuad(QString& string, QVector<int>& version)
{
QStringList list = string.split('.');
for (const QString& component : list) {
version.append(component.toInt());
}
}
void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
{
Q_ASSERT(reply->isFinished());
if (reply->error() == QNetworkReply::NoError) {
QTextStream stream(reply);
stream.setCodec("UTF-8");
// Read all data and queue the reply for deletion
QString jsonString = stream.readAll();
reply->deleteLater();
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonString.toUtf8(), &error);
if (jsonDoc.isNull()) {
qWarning() << "Update manifest malformed:" << error.errorString();
return;
}
QJsonArray array = jsonDoc.array();
if (array.isEmpty()) {
qWarning() << "Update manifest doesn't contain an array";
return;
}
for (QJsonValueRef updateEntry : array) {
if (updateEntry.isObject()) {
QJsonObject updateObj = updateEntry.toObject();
if (!updateObj.contains("platform") ||
!updateObj.contains("arch") ||
!updateObj.contains("version") ||
!updateObj.contains("browser_url")) {
qWarning() << "Update manifest entry missing vital field";
continue;
}
if (!updateObj["platform"].isString() ||
!updateObj["arch"].isString() ||
!updateObj["version"].isString() ||
!updateObj["browser_url"].isString()) {
qWarning() << "Update manifest entry has unexpected vital field type";
continue;
}
if (updateObj["arch"] == QSysInfo::buildCpuArchitecture() &&
updateObj["platform"] == QSysInfo::productType()) {
qDebug() << "Found update manifest match for current platform";
QString latestVersion = updateObj["version"].toString();
qDebug() << "Latest version of Moonlight for this platform is:" << latestVersion;
QVector<int> latestVersionQuad;
parseStringToVersionQuad(latestVersion, latestVersionQuad);
for (int i = 0;; i++) {
int latestVer = 0;
int currentVer = 0;
// Treat missing decimal places as 0
if (i < latestVersionQuad.count()) {
latestVer = latestVersionQuad[i];
}
if (i < m_CurrentVersionQuad.count()) {
currentVer = m_CurrentVersionQuad[i];
}
if (i >= latestVersionQuad.count() && i >= m_CurrentVersionQuad.count()) {
break;
}
if (currentVer < latestVer) {
qDebug() << "Update available";
emit onUpdateAvailable(updateObj["browser_url"].toString());
return;
}
else if (currentVer > latestVer) {
qDebug() << "Update manifest version lower than current version";
return;
}
}
qDebug() << "Update manifest version equal to current version";
return;
}
}
else {
qWarning() << "Update manifest contained unrecognized entry:" << updateEntry.toString();
}
}
qWarning() << "No entry in update manifest found for current platform: "
<< QSysInfo::buildCpuArchitecture() << QSysInfo::productType();
}
else {
qWarning() << "Update checking failed with error: " << reply->error();
reply->deleteLater();
}
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include <QObject>
#include <QNetworkAccessManager>
class AutoUpdateChecker : public QObject
{
Q_OBJECT
public:
explicit AutoUpdateChecker(QObject *parent = nullptr);
Q_INVOKABLE void start();
signals:
void onUpdateAvailable(QString url);
private slots:
void handleUpdateCheckRequestFinished(QNetworkReply* reply);
private:
void parseStringToVersionQuad(QString& string, QVector<int>& version);
QVector<int> m_CurrentVersionQuad;
QNetworkAccessManager m_Nam;
};

View File

@@ -4,6 +4,8 @@ import QtQuick.Layouts 1.3
import QtQuick.Controls.Material 2.1
import AutoUpdateChecker 1.0
ApplicationWindow {
id: window
visible: true
@@ -66,6 +68,36 @@ ApplicationWindow {
Layout.fillWidth: true
}
ToolButton {
property string browserUrl: ""
id: updateButton
icon.source: "qrc:/res/update.svg"
// Invisible until we get a callback notifying us that
// an update is available
visible: false
onClicked: Qt.openUrlExternally(browserUrl);
Menu {
x: parent.width
transformOrigin: Menu.TopRight
}
function updateAvailable(url)
{
updateButton.browserUrl = url
updateButton.visible = true
}
Component.onCompleted: {
AutoUpdateChecker.onUpdateAvailable.connect(updateAvailable)
AutoUpdateChecker.start()
}
}
ToolButton {
icon.source: "qrc:/res/question_mark.svg"
@@ -73,7 +105,6 @@ ApplicationWindow {
onClicked: Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide");
Menu {
id: helpButton
x: parent.width
transformOrigin: Menu.TopRight
}
@@ -87,7 +118,6 @@ ApplicationWindow {
onClicked: navigateTo("qrc:/gui/GamepadMapper.qml", "Gamepad Mapping")
Menu {
id: gamepadMappingMenu
x: parent.width
transformOrigin: Menu.TopRight
}
@@ -98,7 +128,6 @@ ApplicationWindow {
onClicked: navigateTo("qrc:/gui/SettingsView.qml", "Settings")
Menu {
id: optionsMenu
x: parent.width
transformOrigin: Menu.TopRight
}

View File

@@ -13,6 +13,7 @@
#include "gui/computermodel.h"
#include "gui/appmodel.h"
#include "backend/autoupdatechecker.h"
#include "streaming/session.hpp"
#include "settings/streamingpreferences.h"
@@ -172,6 +173,11 @@ int main(int argc, char *argv[])
[](QQmlEngine*, QJSEngine*) -> QObject* {
return new ComputerManager();
});
qmlRegisterSingletonType<AutoUpdateChecker>("AutoUpdateChecker", 1, 0,
"AutoUpdateChecker",
[](QQmlEngine*, QJSEngine*) -> QObject* {
return new AutoUpdateChecker();
});
QQuickStyle::setStyle("Material");

5
app/res/update.svg Normal file
View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#009624" version="1.1" width="24" height="24" viewBox="0 0 24 24">
<path d="M21,10.12H14.22L16.96,7.3C14.23,4.6 9.81,4.5 7.08,7.2C4.35,9.91 4.35,14.28 7.08,17C9.81,19.7 14.23,19.7 16.96,17C18.32,15.65 19,14.08 19,12.1H21C21,14.08 20.12,16.65 18.36,18.39C14.85,21.87 9.15,21.87 5.64,18.39C2.14,14.92 2.11,9.28 5.62,5.81C9.13,2.34 14.76,2.34 18.27,5.81L21,3V10.12M12.5,8V12.25L16,14.33L15.28,15.54L11,13V8H12.5Z" />
</svg>

After

Width:  |  Height:  |  Size: 651 B

View File

@@ -13,5 +13,6 @@
<file>res/arrow_left.svg</file>
<file>res/question_mark.svg</file>
<file>res/moonlight.svg</file>
<file>res/update.svg</file>
</qresource>
</RCC>