mirror of
https://github.com/moonlight-stream/moonlight-qt.git
synced 2026-02-16 10:40:59 +00:00
Add automatic update checking. Fixes #8
This commit is contained in:
@@ -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\\\"
|
||||
|
||||
142
app/backend/autoupdatechecker.cpp
Normal file
142
app/backend/autoupdatechecker.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
25
app/backend/autoupdatechecker.h
Normal file
25
app/backend/autoupdatechecker.h
Normal 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;
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
5
app/res/update.svg
Normal 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 |
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user