mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-07 10:33:24 -08:00
[Move refactor] Reparent orphan classes (#6236)
* Move orphaned classes to their correct parent folders. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
1ef07309d6
commit
d9c65d4ae0
143 changed files with 171 additions and 169 deletions
|
|
@ -0,0 +1,227 @@
|
|||
#include "spoiler_background_updater.h"
|
||||
|
||||
#include "../../../../interface/window_main.h"
|
||||
#include "../../../../main.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QLocale>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkReply>
|
||||
#include <QUrl>
|
||||
#include <QtConcurrent>
|
||||
#include <libcockatrice/card/card_database/card_database.h>
|
||||
#include <libcockatrice/card/card_database/card_database_manager.h>
|
||||
#include <libcockatrice/settings/cache_settings.h>
|
||||
|
||||
#define SPOILERS_STATUS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/SpoilerSeasonEnabled"
|
||||
#define SPOILERS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/spoiler.xml"
|
||||
|
||||
SpoilerBackgroundUpdater::SpoilerBackgroundUpdater(QObject *apParent) : QObject(apParent), cardUpdateProcess(nullptr)
|
||||
{
|
||||
isSpoilerDownloadEnabled = SettingsCache::instance().getDownloadSpoilersStatus();
|
||||
if (isSpoilerDownloadEnabled) {
|
||||
// Start the process of checking if we're in spoiler season
|
||||
// File exists means we're in spoiler season
|
||||
startSpoilerDownloadProcess(SPOILERS_STATUS_URL, false);
|
||||
} else {
|
||||
qCInfo(SpoilerBackgroundUpdaterLog) << "Spoilers Disabled";
|
||||
}
|
||||
}
|
||||
|
||||
void SpoilerBackgroundUpdater::startSpoilerDownloadProcess(QString url, bool saveResults)
|
||||
{
|
||||
auto spoilerURL = QUrl(url);
|
||||
downloadFromURL(spoilerURL, saveResults);
|
||||
}
|
||||
|
||||
void SpoilerBackgroundUpdater::downloadFromURL(QUrl url, bool saveResults)
|
||||
{
|
||||
auto *nam = new QNetworkAccessManager(this);
|
||||
QNetworkReply *reply = nam->get(QNetworkRequest(url));
|
||||
|
||||
if (saveResults) {
|
||||
// This will write out to the file (used for spoiler.xml)
|
||||
connect(reply, &QNetworkReply::finished, this, &SpoilerBackgroundUpdater::actDownloadFinishedSpoilersFile);
|
||||
} else {
|
||||
// This will check the status (used to see if we're in spoiler season or not)
|
||||
connect(reply, &QNetworkReply::finished, this, &SpoilerBackgroundUpdater::actCheckIfSpoilerSeasonEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
void SpoilerBackgroundUpdater::actDownloadFinishedSpoilersFile()
|
||||
{
|
||||
// Check for server reply
|
||||
auto *reply = dynamic_cast<QNetworkReply *>(sender());
|
||||
QNetworkReply::NetworkError errorCode = reply->error();
|
||||
|
||||
if (errorCode == QNetworkReply::NoError) {
|
||||
spoilerData = reply->readAll();
|
||||
|
||||
// Save the spoiler.xml file to the disk
|
||||
saveDownloadedFile(spoilerData);
|
||||
|
||||
reply->deleteLater();
|
||||
emit spoilerCheckerDone();
|
||||
} else {
|
||||
qCWarning(SpoilerBackgroundUpdaterLog) << "Error downloading spoilers file" << errorCode;
|
||||
emit spoilerCheckerDone();
|
||||
}
|
||||
}
|
||||
|
||||
bool SpoilerBackgroundUpdater::deleteSpoilerFile()
|
||||
{
|
||||
QString fileName = SettingsCache::instance().getSpoilerCardDatabasePath();
|
||||
QFileInfo fi(fileName);
|
||||
QDir fileDir(fi.path());
|
||||
QFile file(fileName);
|
||||
|
||||
// Delete the spoiler.xml file
|
||||
if (file.exists() && file.remove()) {
|
||||
qCInfo(SpoilerBackgroundUpdaterLog) << "Deleting spoiler.xml";
|
||||
return true;
|
||||
}
|
||||
|
||||
qCInfo(SpoilerBackgroundUpdaterLog) << "Error: Spoiler.xml not found or not deleted";
|
||||
return false;
|
||||
}
|
||||
|
||||
void SpoilerBackgroundUpdater::actCheckIfSpoilerSeasonEnabled()
|
||||
{
|
||||
auto *response = dynamic_cast<QNetworkReply *>(sender());
|
||||
QNetworkReply::NetworkError errorCode = response->error();
|
||||
|
||||
if (errorCode == QNetworkReply::ContentNotFoundError) {
|
||||
// Spoiler season is offline at this point, so the spoiler.xml file can be safely deleted
|
||||
// The user should run Oracle to get the latest card information
|
||||
if (deleteSpoilerFile() && trayIcon) {
|
||||
trayIcon->showMessage(tr("Spoilers season has ended"), tr("Deleting spoiler.xml. Please run Oracle"));
|
||||
}
|
||||
|
||||
qCInfo(SpoilerBackgroundUpdaterLog) << "Spoiler Season Offline";
|
||||
emit spoilerCheckerDone();
|
||||
} else if (errorCode == QNetworkReply::NoError) {
|
||||
qCInfo(SpoilerBackgroundUpdaterLog) << "Spoiler Service Online";
|
||||
startSpoilerDownloadProcess(SPOILERS_URL, true);
|
||||
} else if (errorCode == QNetworkReply::HostNotFoundError) {
|
||||
if (trayIcon) {
|
||||
trayIcon->showMessage(tr("Spoilers download failed"), tr("No internet connection"));
|
||||
}
|
||||
|
||||
qCWarning(SpoilerBackgroundUpdaterLog) << "Spoiler download failed due to no internet connection";
|
||||
emit spoilerCheckerDone();
|
||||
} else {
|
||||
if (trayIcon) {
|
||||
trayIcon->showMessage(tr("Spoilers download failed"), tr("Error") + " " + (short)errorCode);
|
||||
}
|
||||
|
||||
qCWarning(SpoilerBackgroundUpdaterLog) << "Spoiler download failed with reason" << errorCode;
|
||||
emit spoilerCheckerDone();
|
||||
}
|
||||
}
|
||||
|
||||
bool SpoilerBackgroundUpdater::saveDownloadedFile(QByteArray data)
|
||||
{
|
||||
QString fileName = SettingsCache::instance().getSpoilerCardDatabasePath();
|
||||
QFileInfo fi(fileName);
|
||||
QDir fileDir(fi.path());
|
||||
|
||||
if (!fileDir.exists() && !fileDir.mkpath(fileDir.absolutePath())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the data matches. If it does, then spoilers are up to date.
|
||||
if (getHash(fileName) == getHash(data)) {
|
||||
if (trayIcon) {
|
||||
trayIcon->showMessage(tr("Spoilers already up to date"), tr("No new spoilers added"));
|
||||
}
|
||||
|
||||
qCInfo(SpoilerBackgroundUpdaterLog) << "Spoilers Up to Date";
|
||||
return false;
|
||||
}
|
||||
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
qCWarning(SpoilerBackgroundUpdaterLog) << "Spoiler Service Error: File open (w) failed for" << fileName;
|
||||
file.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.write(data) == -1) {
|
||||
qCWarning(SpoilerBackgroundUpdaterLog) << "Spoiler Service Error: File write (w) failed for" << fileName;
|
||||
file.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
// Data written, so reload the card database
|
||||
qCInfo(SpoilerBackgroundUpdaterLog) << "Spoiler Service Data Written";
|
||||
const auto reloadOk = QtConcurrent::run([] { CardDatabaseManager::getInstance()->loadCardDatabases(); });
|
||||
|
||||
// If the user has notifications enabled, let them know
|
||||
// when the database was last updated
|
||||
if (trayIcon) {
|
||||
QList<QByteArray> lines = data.split('\n');
|
||||
|
||||
for (const QByteArray &line : lines) {
|
||||
if (line.contains("Created At:")) {
|
||||
QString timeStamp = QString(line).replace("Created At:", "").trimmed();
|
||||
timeStamp.chop(6); // Remove " (UTC)"
|
||||
|
||||
auto utcTime = QLocale().toDateTime(timeStamp, "ddd, MMM dd yyyy, hh:mm:ss");
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
utcTime.setTimeZone(QTimeZone::UTC);
|
||||
#else
|
||||
utcTime.setTimeSpec(Qt::UTC);
|
||||
#endif
|
||||
|
||||
QString localTime = utcTime.toLocalTime().toString("MMM d, hh:mm");
|
||||
|
||||
trayIcon->showMessage(tr("Spoilers have been updated!"), tr("Last change:") + " " + localTime);
|
||||
emit spoilersUpdatedSuccessfully();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray SpoilerBackgroundUpdater::getHash(const QString fileName)
|
||||
{
|
||||
QFile file(fileName);
|
||||
|
||||
if (file.open(QFile::ReadOnly)) {
|
||||
// Only read the first 512 bytes (enough to get the "created" tag)
|
||||
const QByteArray bytes = file.read(512);
|
||||
|
||||
QCryptographicHash hash(QCryptographicHash::Algorithm::Md5);
|
||||
hash.addData(bytes);
|
||||
|
||||
qCInfo(SpoilerBackgroundUpdaterLog) << "File Hash =" << hash.result();
|
||||
|
||||
file.close();
|
||||
return hash.result();
|
||||
} else {
|
||||
qCWarning(SpoilerBackgroundUpdaterLog) << "getHash ReadOnly failed!";
|
||||
file.close();
|
||||
return QByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray SpoilerBackgroundUpdater::getHash(QByteArray data)
|
||||
{
|
||||
// Only read the first 512 bytes (enough to get the "created" tag)
|
||||
const QByteArray bytes = data.left(512);
|
||||
|
||||
QCryptographicHash hash(QCryptographicHash::Algorithm::Md5);
|
||||
hash.addData(bytes);
|
||||
|
||||
qCInfo(SpoilerBackgroundUpdaterLog) << "Data Hash =" << hash.result();
|
||||
|
||||
return hash.result();
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* @file spoiler_background_updater.h
|
||||
* @ingroup Client
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_SPOILER_DOWNLOADER_H
|
||||
#define COCKATRICE_SPOILER_DOWNLOADER_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QLoggingCategory>
|
||||
#include <QObject>
|
||||
#include <QProcess>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(SpoilerBackgroundUpdaterLog, "spoiler_background_updater");
|
||||
|
||||
class SpoilerBackgroundUpdater : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SpoilerBackgroundUpdater(QObject *apParent = nullptr);
|
||||
inline QString getCardUpdaterBinaryName()
|
||||
{
|
||||
return "oracle";
|
||||
};
|
||||
QByteArray getHash(const QString fileName);
|
||||
QByteArray getHash(QByteArray data);
|
||||
static bool deleteSpoilerFile();
|
||||
|
||||
private slots:
|
||||
void actDownloadFinishedSpoilersFile();
|
||||
void actCheckIfSpoilerSeasonEnabled();
|
||||
|
||||
private:
|
||||
bool isSpoilerDownloadEnabled;
|
||||
QProcess *cardUpdateProcess;
|
||||
QByteArray spoilerData;
|
||||
void startSpoilerDownloadProcess(QString url, bool saveResults);
|
||||
void downloadFromURL(QUrl url, bool saveResults);
|
||||
bool saveDownloadedFile(QByteArray data);
|
||||
|
||||
signals:
|
||||
void spoilersUpdatedSuccessfully();
|
||||
void spoilerCheckerDone();
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_SPOILER_DOWNLOADER_H
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#include "client_update_checker.h"
|
||||
|
||||
#include "release_channel.h"
|
||||
|
||||
#include <libcockatrice/settings/cache_settings.h>
|
||||
|
||||
ClientUpdateChecker::ClientUpdateChecker(QObject *parent) : QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void ClientUpdateChecker::check()
|
||||
{
|
||||
auto releaseChannel = SettingsCache::instance().getUpdateReleaseChannel();
|
||||
|
||||
finishedCheckConnection =
|
||||
connect(releaseChannel, &ReleaseChannel::finishedCheck, this, &ClientUpdateChecker::actFinishedCheck);
|
||||
errorConnection = connect(releaseChannel, &ReleaseChannel::error, this, &ClientUpdateChecker::actError);
|
||||
|
||||
releaseChannel->checkForUpdates();
|
||||
}
|
||||
|
||||
void ClientUpdateChecker::actFinishedCheck(bool needToUpdate, bool isCompatible, Release *release)
|
||||
{
|
||||
disconnect(finishedCheckConnection);
|
||||
disconnect(errorConnection);
|
||||
|
||||
emit finishedCheck(needToUpdate, isCompatible, release);
|
||||
}
|
||||
|
||||
void ClientUpdateChecker::actError(const QString &errorString)
|
||||
{
|
||||
disconnect(finishedCheckConnection);
|
||||
disconnect(errorConnection);
|
||||
|
||||
emit error(errorString);
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* @file client_update_checker.h
|
||||
* @ingroup ClientUpdate
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef CLIENT_UPDATE_CHECKER_H
|
||||
#define CLIENT_UPDATE_CHECKER_H
|
||||
#include <QObject>
|
||||
|
||||
class Release;
|
||||
|
||||
/**
|
||||
* We use a singleton instance of UpdateChannel, which can cause interference and feedback loops when multiple objects
|
||||
* connect to it.
|
||||
*
|
||||
* This class encapsulates the usage of that UpdateChannel to ensure that the check only happens once per connection and
|
||||
* the connection is destroyed after it's been used.
|
||||
*/
|
||||
class ClientUpdateChecker : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QMetaObject::Connection finishedCheckConnection;
|
||||
QMetaObject::Connection errorConnection;
|
||||
|
||||
void actFinishedCheck(bool needToUpdate, bool isCompatible, Release *release);
|
||||
void actError(const QString &errorString);
|
||||
|
||||
public:
|
||||
explicit ClientUpdateChecker(QObject *parent = nullptr);
|
||||
/**
|
||||
* Actually performs the check, using the currently selected update channel in the settings.
|
||||
* Any resulting signals will only be sent once.
|
||||
* This method should only be called ONCE per instance.
|
||||
*/
|
||||
void check();
|
||||
|
||||
signals:
|
||||
/**
|
||||
* Forwarded from UpdateChannel::finishedCheck
|
||||
*/
|
||||
void finishedCheck(bool needToUpdate, bool isCompatible, Release *release);
|
||||
|
||||
/**
|
||||
* Forwarded from UpdateChannel::error
|
||||
*/
|
||||
void error(const QString &errorString);
|
||||
};
|
||||
|
||||
#endif // CLIENT_UPDATE_CHECKER_H
|
||||
308
cockatrice/src/client/network/update/client/release_channel.cpp
Normal file
308
cockatrice/src/client/network/update/client/release_channel.cpp
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
#include "release_channel.h"
|
||||
|
||||
#include "version_string.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkReply>
|
||||
#include <QRegularExpression>
|
||||
#include <QSysInfo>
|
||||
#include <QtGlobal>
|
||||
|
||||
#if defined(Q_OS_MACOS)
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
#define STABLERELEASE_URL "https://api.github.com/repos/Cockatrice/Cockatrice/releases/latest"
|
||||
#define STABLEMANUALDOWNLOAD_URL "https://github.com/Cockatrice/Cockatrice/releases/latest"
|
||||
#define STABLETAG_URL "https://api.github.com/repos/Cockatrice/Cockatrice/git/refs/tags/"
|
||||
|
||||
#define BETARELEASE_URL "https://api.github.com/repos/Cockatrice/Cockatrice/releases"
|
||||
#define BETAMANUALDOWNLOAD_URL "https://github.com/Cockatrice/Cockatrice/releases/"
|
||||
#define BETARELEASE_CHANGESURL "https://github.com/Cockatrice/Cockatrice/compare/%1...%2"
|
||||
|
||||
#define GIT_SHORT_HASH_LEN 7
|
||||
|
||||
ReleaseChannel::ReleaseChannel() : netMan(new QNetworkAccessManager(this)), response(nullptr), lastRelease(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
ReleaseChannel::~ReleaseChannel()
|
||||
{
|
||||
netMan->deleteLater();
|
||||
}
|
||||
|
||||
void ReleaseChannel::checkForUpdates()
|
||||
{
|
||||
QString releaseChannelUrl = getReleaseChannelUrl();
|
||||
qCInfo(ReleaseChannelLog) << "Searching for updates on the channel: " << releaseChannelUrl;
|
||||
response = netMan->get(QNetworkRequest(releaseChannelUrl));
|
||||
connect(response, &QNetworkReply::finished, this, &ReleaseChannel::releaseListFinished);
|
||||
}
|
||||
|
||||
// Different release channel checking functions for different operating systems
|
||||
bool ReleaseChannel::downloadMatchesCurrentOS(const QString &fileName)
|
||||
{
|
||||
#if defined(Q_OS_MACOS)
|
||||
static QRegularExpression version_regex("macOS(\\d+)");
|
||||
auto match = version_regex.match(fileName);
|
||||
if (!match.hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto getSystemVersion = [] {
|
||||
// QSysInfo does not go through translation layers
|
||||
// We need to use sysctl to reliably detect the underlying architecture
|
||||
char arch[255];
|
||||
size_t len = sizeof(arch);
|
||||
if (sysctlbyname("machdep.cpu.brand_string", arch, &len, nullptr, 0) == 0) {
|
||||
// Intel mac is only supported on macOS 13 versions
|
||||
if (QString::fromUtf8(arch).contains("Intel")) {
|
||||
return 13;
|
||||
}
|
||||
}
|
||||
|
||||
return QSysInfo::productVersion().split(".")[0].toInt();
|
||||
};
|
||||
|
||||
// older(smaller) releases are compatible with a newer or the same system version
|
||||
int sys_maj = getSystemVersion();
|
||||
int rel_maj = match.captured(1).toInt();
|
||||
return rel_maj == sys_maj;
|
||||
|
||||
#elif defined(Q_OS_WIN)
|
||||
#if Q_PROCESSOR_WORDSIZE == 4
|
||||
return fileName.contains("32bit");
|
||||
#elif Q_PROCESSOR_WORDSIZE == 8
|
||||
const QString &version = QSysInfo::productVersion();
|
||||
if (version.startsWith("7") || version.startsWith("8")) {
|
||||
return fileName.contains("Win7");
|
||||
} else {
|
||||
return fileName.contains("Win10");
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(fileName);
|
||||
return false;
|
||||
#endif
|
||||
|
||||
#else // If the OS doesn't fit one of the above #defines, then it will never match
|
||||
Q_UNUSED(fileName);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
QString StableReleaseChannel::getManualDownloadUrl() const
|
||||
{
|
||||
return QString(STABLEMANUALDOWNLOAD_URL);
|
||||
}
|
||||
|
||||
QString StableReleaseChannel::getName() const
|
||||
{
|
||||
return tr("Default");
|
||||
}
|
||||
|
||||
QString StableReleaseChannel::getReleaseChannelUrl() const
|
||||
{
|
||||
return QString(STABLERELEASE_URL);
|
||||
}
|
||||
|
||||
void StableReleaseChannel::releaseListFinished()
|
||||
{
|
||||
auto *reply = static_cast<QNetworkReply *>(sender());
|
||||
QJsonParseError parseError{};
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(reply->readAll(), &parseError);
|
||||
reply->deleteLater();
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qCWarning(ReleaseChannelLog) << "No reply received from the release update server.";
|
||||
emit error(tr("No reply received from the release update server."));
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap resultMap = jsonResponse.toVariant().toMap();
|
||||
if (!(resultMap.contains("name") && resultMap.contains("html_url") && resultMap.contains("tag_name") &&
|
||||
resultMap.contains("published_at"))) {
|
||||
qCWarning(ReleaseChannelLog) << "Invalid received from the release update server:" << resultMap;
|
||||
emit error(tr("Invalid reply received from the release update server."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!lastRelease)
|
||||
lastRelease = new Release;
|
||||
|
||||
lastRelease->setName(resultMap["name"].toString());
|
||||
lastRelease->setDescriptionUrl(resultMap["html_url"].toString());
|
||||
lastRelease->setPublishDate(resultMap["published_at"].toDate());
|
||||
|
||||
if (resultMap.contains("assets")) {
|
||||
auto rawAssets = resultMap["assets"].toList();
|
||||
for (const auto &rawAsset : rawAssets) {
|
||||
QVariantMap asset = rawAsset.toMap();
|
||||
QString name = asset["name"].toString();
|
||||
QString url = asset["browser_download_url"].toString();
|
||||
|
||||
if (downloadMatchesCurrentOS(name)) {
|
||||
lastRelease->setDownloadUrl(url);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString shortHash = lastRelease->getCommitHash().left(GIT_SHORT_HASH_LEN);
|
||||
QString myHash = QString(VERSION_COMMIT);
|
||||
qCInfo(ReleaseChannelLog) << "Current hash=" << myHash << "update hash=" << shortHash;
|
||||
|
||||
qCInfo(ReleaseChannelLog) << "Got reply from release server, name=" << lastRelease->getName()
|
||||
<< "desc=" << lastRelease->getDescriptionUrl() << "date=" << lastRelease->getPublishDate()
|
||||
<< "url=" << lastRelease->getDownloadUrl();
|
||||
|
||||
const QString &tagName = resultMap["tag_name"].toString();
|
||||
QString url = QString(STABLETAG_URL) + tagName;
|
||||
qCInfo(ReleaseChannelLog) << "Searching for commit hash corresponding to stable channel tag: " << tagName;
|
||||
response = netMan->get(QNetworkRequest(url));
|
||||
connect(response, &QNetworkReply::finished, this, &StableReleaseChannel::tagListFinished);
|
||||
}
|
||||
|
||||
void StableReleaseChannel::tagListFinished()
|
||||
{
|
||||
auto *reply = static_cast<QNetworkReply *>(sender());
|
||||
QJsonParseError parseError{};
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(reply->readAll(), &parseError);
|
||||
reply->deleteLater();
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qCWarning(ReleaseChannelLog) << "No reply received from the tag update server.";
|
||||
emit error(tr("No reply received from the tag update server."));
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap resultMap = jsonResponse.toVariant().toMap();
|
||||
if (!(resultMap.contains("object") && resultMap["object"].toMap().contains("sha"))) {
|
||||
qCWarning(ReleaseChannelLog) << "Invalid received from the tag update server.";
|
||||
emit error(tr("Invalid reply received from the tag update server."));
|
||||
return;
|
||||
}
|
||||
|
||||
lastRelease->setCommitHash(resultMap["object"].toMap()["sha"].toString());
|
||||
qCInfo(ReleaseChannelLog) << "Got reply from tag server, commit=" << lastRelease->getCommitHash();
|
||||
|
||||
QString shortHash = lastRelease->getCommitHash().left(GIT_SHORT_HASH_LEN);
|
||||
QString myHash = QString(VERSION_COMMIT);
|
||||
qCInfo(ReleaseChannelLog) << "Current hash=" << myHash << "update hash=" << shortHash;
|
||||
const bool needToUpdate = (QString::compare(shortHash, myHash, Qt::CaseInsensitive) != 0);
|
||||
|
||||
emit finishedCheck(needToUpdate, lastRelease->isCompatibleVersionFound(), lastRelease);
|
||||
}
|
||||
|
||||
void StableReleaseChannel::fileListFinished()
|
||||
{
|
||||
// Only implemented to satisfy interface
|
||||
}
|
||||
|
||||
QString BetaReleaseChannel::getManualDownloadUrl() const
|
||||
{
|
||||
return QString(BETAMANUALDOWNLOAD_URL);
|
||||
}
|
||||
|
||||
QString BetaReleaseChannel::getName() const
|
||||
{
|
||||
return tr("Beta");
|
||||
}
|
||||
|
||||
QString BetaReleaseChannel::getReleaseChannelUrl() const
|
||||
{
|
||||
return QString(BETARELEASE_URL);
|
||||
}
|
||||
|
||||
void BetaReleaseChannel::releaseListFinished()
|
||||
{
|
||||
auto *reply = static_cast<QNetworkReply *>(sender());
|
||||
QByteArray jsonData = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(jsonData);
|
||||
QJsonArray array = doc.array();
|
||||
|
||||
/*
|
||||
* Get the latest release on GitHub
|
||||
* This can be either a pre-release or a published release
|
||||
* depending on timing. Both are acceptable.
|
||||
*/
|
||||
QVariantMap resultMap = array.at(0).toObject().toVariantMap();
|
||||
|
||||
if (array.empty() || resultMap.empty()) {
|
||||
qCWarning(ReleaseChannelLog) << "No reply received from the release update server:" << QString(jsonData);
|
||||
emit error(tr("No reply received from the release update server."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure resultMap has all elements we'll need
|
||||
if (!resultMap.contains("assets") || !resultMap.contains("author") || !resultMap.contains("tag_name") ||
|
||||
!resultMap.contains("target_commitish") || !resultMap.contains("assets_url") ||
|
||||
!resultMap.contains("published_at")) {
|
||||
qCWarning(ReleaseChannelLog) << "Invalid received from the release update server:" << resultMap;
|
||||
emit error(tr("Invalid reply received from the release update server."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastRelease == nullptr)
|
||||
lastRelease = new Release;
|
||||
|
||||
lastRelease->setCommitHash(resultMap["target_commitish"].toString());
|
||||
lastRelease->setPublishDate(resultMap["published_at"].toDate());
|
||||
|
||||
QString shortHash = lastRelease->getCommitHash().left(GIT_SHORT_HASH_LEN);
|
||||
lastRelease->setName(QString("%1 (%2)").arg(resultMap["tag_name"].toString()).arg(shortHash));
|
||||
lastRelease->setDescriptionUrl(QString(BETARELEASE_CHANGESURL).arg(VERSION_COMMIT, shortHash));
|
||||
|
||||
qCInfo(ReleaseChannelLog) << "Got reply from release server, size=" << resultMap.size()
|
||||
<< "name=" << lastRelease->getName() << "desc=" << lastRelease->getDescriptionUrl()
|
||||
<< "commit=" << lastRelease->getCommitHash() << "date=" << lastRelease->getPublishDate();
|
||||
|
||||
QString betaBuildDownloadUrl = resultMap["assets_url"].toString();
|
||||
|
||||
qCInfo(ReleaseChannelLog) << "Searching for a corresponding file on the beta channel: " << betaBuildDownloadUrl;
|
||||
response = netMan->get(QNetworkRequest(betaBuildDownloadUrl));
|
||||
connect(response, &QNetworkReply::finished, this, &BetaReleaseChannel::fileListFinished);
|
||||
}
|
||||
|
||||
void BetaReleaseChannel::fileListFinished()
|
||||
{
|
||||
auto *reply = static_cast<QNetworkReply *>(sender());
|
||||
QJsonParseError parseError{};
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(reply->readAll(), &parseError);
|
||||
reply->deleteLater();
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qCWarning(ReleaseChannelLog) << "No reply received from the file update server.";
|
||||
emit error(tr("No reply received from the file update server."));
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantList resultList = jsonResponse.toVariant().toList();
|
||||
QString shortHash = lastRelease->getCommitHash().left(GIT_SHORT_HASH_LEN);
|
||||
QString myHash = QString(VERSION_COMMIT);
|
||||
qCInfo(ReleaseChannelLog) << "Current hash=" << myHash << "update hash=" << shortHash;
|
||||
|
||||
bool needToUpdate = (QString::compare(shortHash, myHash, Qt::CaseInsensitive) != 0);
|
||||
bool compatibleVersion = false;
|
||||
|
||||
QStringList resultUrlList{};
|
||||
for (QVariant file : resultList) {
|
||||
QVariantMap map = file.toMap();
|
||||
resultUrlList << map["browser_download_url"].toString();
|
||||
}
|
||||
|
||||
resultUrlList.sort();
|
||||
// iterate in reverse so the first item is the latest os version
|
||||
for (auto url = resultUrlList.rbegin(); url < resultUrlList.rend(); ++url) {
|
||||
if (downloadMatchesCurrentOS(*url)) {
|
||||
compatibleVersion = true;
|
||||
lastRelease->setDownloadUrl(*url);
|
||||
qCInfo(ReleaseChannelLog) << "Found compatible version url=" << *url;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
emit finishedCheck(needToUpdate, compatibleVersion, lastRelease);
|
||||
}
|
||||
159
cockatrice/src/client/network/update/client/release_channel.h
Normal file
159
cockatrice/src/client/network/update/client/release_channel.h
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/**
|
||||
* @file release_channel.h
|
||||
* @ingroup ClientUpdate
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef RELEASECHANNEL_H
|
||||
#define RELEASECHANNEL_H
|
||||
|
||||
#include <QDate>
|
||||
#include <QLoggingCategory>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QVariantMap>
|
||||
#include <utility>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(ReleaseChannelLog, "release_channel");
|
||||
|
||||
class QNetworkReply;
|
||||
class QNetworkAccessManager;
|
||||
|
||||
class Release
|
||||
{
|
||||
friend class StableReleaseChannel;
|
||||
friend class BetaReleaseChannel;
|
||||
|
||||
public:
|
||||
Release() = default;
|
||||
~Release() = default;
|
||||
|
||||
private:
|
||||
QString name, descriptionUrl, downloadUrl, commitHash;
|
||||
QDate publishDate;
|
||||
bool compatibleVersionFound = false;
|
||||
|
||||
protected:
|
||||
void setName(QString _name)
|
||||
{
|
||||
name = std::move(_name);
|
||||
}
|
||||
void setDescriptionUrl(QString _descriptionUrl)
|
||||
{
|
||||
descriptionUrl = std::move(_descriptionUrl);
|
||||
}
|
||||
void setDownloadUrl(QString _downloadUrl)
|
||||
{
|
||||
downloadUrl = std::move(_downloadUrl);
|
||||
compatibleVersionFound = true;
|
||||
}
|
||||
void setCommitHash(QString _commitHash)
|
||||
{
|
||||
commitHash = std::move(_commitHash);
|
||||
}
|
||||
void setPublishDate(QDate _publishDate)
|
||||
{
|
||||
publishDate = _publishDate;
|
||||
}
|
||||
|
||||
public:
|
||||
QString getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
QString getDescriptionUrl() const
|
||||
{
|
||||
return descriptionUrl;
|
||||
}
|
||||
QString getDownloadUrl() const
|
||||
{
|
||||
return downloadUrl;
|
||||
}
|
||||
QString getCommitHash() const
|
||||
{
|
||||
return commitHash;
|
||||
}
|
||||
QDate getPublishDate() const
|
||||
{
|
||||
return publishDate;
|
||||
}
|
||||
bool isCompatibleVersionFound() const
|
||||
{
|
||||
return compatibleVersionFound;
|
||||
}
|
||||
};
|
||||
|
||||
class ReleaseChannel : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ReleaseChannel();
|
||||
~ReleaseChannel() override;
|
||||
|
||||
protected:
|
||||
QNetworkAccessManager *netMan;
|
||||
QNetworkReply *response;
|
||||
Release *lastRelease;
|
||||
|
||||
protected:
|
||||
static bool downloadMatchesCurrentOS(const QString &fileName);
|
||||
virtual QString getReleaseChannelUrl() const = 0;
|
||||
|
||||
public:
|
||||
Release *getLastRelease()
|
||||
{
|
||||
return lastRelease;
|
||||
}
|
||||
virtual QString getManualDownloadUrl() const = 0;
|
||||
virtual QString getName() const = 0;
|
||||
void checkForUpdates();
|
||||
signals:
|
||||
void finishedCheck(bool needToUpdate, bool isCompatible, Release *release);
|
||||
void error(QString errorString);
|
||||
protected slots:
|
||||
virtual void releaseListFinished() = 0;
|
||||
virtual void fileListFinished() = 0;
|
||||
};
|
||||
|
||||
class StableReleaseChannel : public ReleaseChannel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit StableReleaseChannel() = default;
|
||||
~StableReleaseChannel() override = default;
|
||||
|
||||
QString getManualDownloadUrl() const override;
|
||||
|
||||
QString getName() const override;
|
||||
|
||||
protected:
|
||||
QString getReleaseChannelUrl() const override;
|
||||
protected slots:
|
||||
|
||||
void releaseListFinished() override;
|
||||
void tagListFinished();
|
||||
|
||||
void fileListFinished() override;
|
||||
};
|
||||
|
||||
class BetaReleaseChannel : public ReleaseChannel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
BetaReleaseChannel() = default;
|
||||
~BetaReleaseChannel() override = default;
|
||||
|
||||
QString getManualDownloadUrl() const override;
|
||||
|
||||
QString getName() const override;
|
||||
|
||||
protected:
|
||||
QString getReleaseChannelUrl() const override;
|
||||
protected slots:
|
||||
|
||||
void releaseListFinished() override;
|
||||
|
||||
void fileListFinished() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
#include "update_downloader.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QUrl>
|
||||
|
||||
UpdateDownloader::UpdateDownloader(QObject *parent) : QObject(parent), response(nullptr)
|
||||
{
|
||||
netMan = new QNetworkAccessManager(this);
|
||||
}
|
||||
|
||||
void UpdateDownloader::beginDownload(QUrl downloadUrl)
|
||||
{
|
||||
// Save the original URL because we need it for the filename
|
||||
if (originalUrl.isEmpty())
|
||||
originalUrl = downloadUrl;
|
||||
|
||||
response = netMan->get(QNetworkRequest(downloadUrl));
|
||||
connect(response, &QNetworkReply::finished, this, &UpdateDownloader::fileFinished);
|
||||
connect(response, &QNetworkReply::downloadProgress, this, &UpdateDownloader::downloadProgress);
|
||||
connect(this, &UpdateDownloader::stopDownload, response, &QNetworkReply::abort);
|
||||
}
|
||||
|
||||
void UpdateDownloader::downloadError(QNetworkReply::NetworkError)
|
||||
{
|
||||
if (response == nullptr)
|
||||
return;
|
||||
|
||||
emit error(response->errorString().toUtf8());
|
||||
}
|
||||
|
||||
void UpdateDownloader::fileFinished()
|
||||
{
|
||||
// If we finished but there's a redirect, follow it
|
||||
QVariant redirect = response->attribute(QNetworkRequest::RedirectionTargetAttribute);
|
||||
if (!redirect.isNull()) {
|
||||
beginDownload(redirect.toUrl());
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle any errors we had
|
||||
if (response->error()) {
|
||||
emit error(response->errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
// Work out the file name of the download
|
||||
QString fileName = QDir::temp().path() + QDir::separator() + originalUrl.toString().section('/', -1);
|
||||
|
||||
// Save the build in a temporary directory
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
emit error(tr("Could not open the file for reading."));
|
||||
return;
|
||||
}
|
||||
|
||||
file.write(response->readAll());
|
||||
file.close();
|
||||
|
||||
// Emit the success signal with a URL to the download file
|
||||
emit downloadSuccessful(QUrl::fromLocalFile(fileName));
|
||||
}
|
||||
|
||||
void UpdateDownloader::downloadProgress(qint64 bytesRead, qint64 totalBytes)
|
||||
{
|
||||
emit progressMade(bytesRead, totalBytes);
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* @file update_downloader.h
|
||||
* @ingroup ClientUpdate
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_UPDATEDOWNLOADER_H
|
||||
#define COCKATRICE_UPDATEDOWNLOADER_H
|
||||
|
||||
#include <QDate>
|
||||
#include <QObject>
|
||||
#include <QUrl>
|
||||
#include <QtNetwork>
|
||||
|
||||
class UpdateDownloader : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit UpdateDownloader(QObject *parent);
|
||||
void beginDownload(QUrl url);
|
||||
signals:
|
||||
void downloadSuccessful(QUrl filepath);
|
||||
void progressMade(qint64 bytesRead, qint64 totalBytes);
|
||||
void error(QString errorString);
|
||||
void stopDownload();
|
||||
|
||||
private:
|
||||
QUrl originalUrl;
|
||||
QNetworkAccessManager *netMan;
|
||||
QNetworkReply *response;
|
||||
private slots:
|
||||
void fileFinished();
|
||||
void downloadProgress(qint64 bytesRead, qint64 totalBytes);
|
||||
void downloadError(QNetworkReply::NetworkError);
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_UPDATEDOWNLOADER_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue