Rework the update checking system
This commit is contained in:
@ -1,99 +0,0 @@
|
||||
|
||||
#include "GoUpdate.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "logger/QsLog.h"
|
||||
|
||||
GoUpdate::GoUpdate()
|
||||
{
|
||||
currentBuildIndex = VERSION_BUILD;
|
||||
builderName = VERSION_BUILD_TYPE;
|
||||
repoUrlBase = VERSION_REPO;
|
||||
}
|
||||
|
||||
void GoUpdate::updateCheckFailed()
|
||||
{
|
||||
// TODO: log errors better
|
||||
QLOG_ERROR() << "Update check failed for reasons unknown.";
|
||||
}
|
||||
|
||||
void GoUpdate::updateCheckFinished()
|
||||
{
|
||||
QJsonParseError jsonError;
|
||||
QByteArray data;
|
||||
{
|
||||
ByteArrayDownloadPtr dl =
|
||||
std::dynamic_pointer_cast<ByteArrayDownload>(index_job->first());
|
||||
data = dl->m_data;
|
||||
index_job.reset();
|
||||
}
|
||||
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
|
||||
if (jsonError.error != QJsonParseError::NoError || !jsonDoc.isObject())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QVariant doc = jsonDoc.toVariant();
|
||||
auto stuff = doc.toMap();
|
||||
|
||||
// check api version (or later, branch?)
|
||||
int ApiVersion = stuff["ApiVersion"].toInt();
|
||||
if (ApiVersion != 0)
|
||||
return;
|
||||
|
||||
// parse and store the channel list
|
||||
auto parsedChannels = stuff["Channels"].toList();
|
||||
for (auto channel : parsedChannels)
|
||||
{
|
||||
auto chanMap = channel.toMap();
|
||||
channels.append({chanMap["Id"].toString(), chanMap["Name"].toString(),
|
||||
chanMap["CurrentVersion"].toInt()});
|
||||
}
|
||||
|
||||
// parse and store the version list
|
||||
auto parsedVersions = stuff["Versions"].toList();
|
||||
for (auto version : parsedVersions)
|
||||
{
|
||||
auto verMap = version.toMap();
|
||||
int versionId = verMap["Id"].toInt();
|
||||
versions.append({versionId, verMap["Name"].toString()});
|
||||
if (currentBuildIndex < versionId)
|
||||
{
|
||||
newBuildIndex = versionId;
|
||||
}
|
||||
}
|
||||
|
||||
if (newBuildIndex != -1)
|
||||
{
|
||||
QLOG_INFO() << "Update is available.";
|
||||
emit updateAvailable();
|
||||
}
|
||||
else
|
||||
{
|
||||
QLOG_INFO() << "Update check finished.";
|
||||
}
|
||||
}
|
||||
|
||||
void GoUpdate::checkForUpdate()
|
||||
{
|
||||
if (repoUrlBase == "invalid")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto job = new NetJob("Assets index");
|
||||
job->addNetAction(
|
||||
ByteArrayDownload::make(QUrl(repoUrlBase + "/" + VERSION_BRANCH + "/index.json")));
|
||||
connect(job, SIGNAL(succeeded()), SLOT(updateCheckFinished()));
|
||||
connect(job, SIGNAL(failed()), SLOT(updateCheckFailed()));
|
||||
index_job.reset(job);
|
||||
job->start();
|
||||
}
|
||||
|
||||
/*
|
||||
<Forkk> files.multimc.org/lin64/
|
||||
<manmaed> Hi Forkkie
|
||||
<Forkk> files.multimc.org/win32/
|
||||
<Forkk> files.multimc.org/lin32/
|
||||
*/
|
@ -1,43 +0,0 @@
|
||||
#pragma once
|
||||
#include "net/NetJob.h"
|
||||
|
||||
class GoUpdate : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct version_channel
|
||||
{
|
||||
QString id;
|
||||
QString name;
|
||||
int latestVersion;
|
||||
};
|
||||
|
||||
struct version_summary
|
||||
{
|
||||
int id;
|
||||
QString name;
|
||||
};
|
||||
|
||||
signals:
|
||||
void updateAvailable();
|
||||
|
||||
private slots:
|
||||
void updateCheckFinished();
|
||||
void updateCheckFailed();
|
||||
|
||||
public:
|
||||
GoUpdate();
|
||||
void checkForUpdate();
|
||||
private:
|
||||
NetJobPtr index_job;
|
||||
NetJobPtr fromto_job;
|
||||
|
||||
QString repoUrlBase;
|
||||
QString builderName;
|
||||
int currentBuildIndex;
|
||||
int newBuildIndex = -1;
|
||||
|
||||
QList<version_summary> versions;
|
||||
QList<version_channel> channels;
|
||||
};
|
234
logic/updater/UpdateChecker.cpp
Normal file
234
logic/updater/UpdateChecker.cpp
Normal file
@ -0,0 +1,234 @@
|
||||
/* Copyright 2013 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "UpdateChecker.h"
|
||||
|
||||
#include "MultiMC.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "logger/QsLog.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
|
||||
#define API_VERSION 0
|
||||
#define CHANLIST_FORMAT 0
|
||||
|
||||
UpdateChecker::UpdateChecker()
|
||||
{
|
||||
m_currentChannel = VERSION_CHANNEL;
|
||||
m_channelListUrl = CHANLIST_URL;
|
||||
m_updateChecking = false;
|
||||
m_chanListLoading = false;
|
||||
m_checkUpdateWaiting = false;
|
||||
m_chanListLoaded = false;
|
||||
}
|
||||
|
||||
void UpdateChecker::checkForUpdate()
|
||||
{
|
||||
QLOG_DEBUG() << "Checking for updates.";
|
||||
|
||||
// If the channel list hasn't loaded yet, load it and defer checking for updates until later.
|
||||
if (!m_chanListLoaded)
|
||||
{
|
||||
QLOG_DEBUG() << "Channel list isn't loaded yet. Loading channel list and deferring update check.";
|
||||
m_checkUpdateWaiting = true;
|
||||
updateChanList();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_updateChecking)
|
||||
{
|
||||
QLOG_DEBUG() << "Ignoring update check request. Already checking for updates.";
|
||||
return;
|
||||
}
|
||||
|
||||
m_updateChecking = true;
|
||||
|
||||
// Get the URL for the channel we're using.
|
||||
// TODO: Allow user to select channels. For now, we'll just use the current channel.
|
||||
QString updateChannel = m_currentChannel;
|
||||
|
||||
// Find the desired channel within the channel list and get its repo URL. If if cannot be found, error.
|
||||
m_repoUrl = "";
|
||||
for (ChannelListEntry entry : m_channels)
|
||||
{
|
||||
if (entry.id == updateChannel)
|
||||
m_repoUrl = entry.url;
|
||||
}
|
||||
|
||||
// If we didn't find our channel, error.
|
||||
if (m_repoUrl.isEmpty())
|
||||
{
|
||||
emit updateCheckFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
QUrl indexUrl = QUrl(m_repoUrl).resolved(QUrl("index.json"));
|
||||
|
||||
auto job = new NetJob("GoUpdate Repository Index");
|
||||
job->addNetAction(ByteArrayDownload::make(indexUrl));
|
||||
connect(job, SIGNAL(succeeded()), SLOT(updateCheckFinished()));
|
||||
connect(job, SIGNAL(failed()), SLOT(updateCheckFailed()));
|
||||
indexJob.reset(job);
|
||||
job->start();
|
||||
}
|
||||
|
||||
void UpdateChecker::updateCheckFinished()
|
||||
{
|
||||
QLOG_DEBUG() << "Finished downloading repo index. Checking for new versions.";
|
||||
|
||||
QJsonParseError jsonError;
|
||||
QByteArray data;
|
||||
{
|
||||
ByteArrayDownloadPtr dl = std::dynamic_pointer_cast<ByteArrayDownload>(indexJob->first());
|
||||
data = dl->m_data;
|
||||
indexJob.reset();
|
||||
}
|
||||
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
|
||||
if (jsonError.error != QJsonParseError::NoError || !jsonDoc.isObject())
|
||||
{
|
||||
QLOG_ERROR() << "Failed to parse GoUpdate repository index. JSON error" << jsonError.errorString() << "at offset" << jsonError.offset;
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject object = jsonDoc.object();
|
||||
|
||||
bool success = false;
|
||||
int apiVersion = object.value("ApiVersion").toVariant().toInt(&success);
|
||||
if (apiVersion != API_VERSION || !success)
|
||||
{
|
||||
QLOG_ERROR() << "Failed to check for updates. API version mismatch. We're using" << API_VERSION << "server has" << apiVersion;
|
||||
return;
|
||||
}
|
||||
|
||||
QLOG_DEBUG() << "Processing repository version list.";
|
||||
QJsonObject newestVersion;
|
||||
QJsonArray versions = object.value("Versions").toArray();
|
||||
for (QJsonValue versionVal : versions)
|
||||
{
|
||||
QJsonObject version = versionVal.toObject();
|
||||
if (newestVersion.value("Id").toVariant().toInt() < version.value("Id").toVariant().toInt())
|
||||
{
|
||||
QLOG_DEBUG() << "Found newer version with ID" << version.value("Id").toVariant().toInt();
|
||||
newestVersion = version;
|
||||
}
|
||||
}
|
||||
|
||||
// We've got the version with the greatest ID number. Now compare it to our current build number and update if they're different.
|
||||
int newBuildNumber = newestVersion.value("Id").toVariant().toInt();
|
||||
if (newBuildNumber != MMC->version().build)
|
||||
{
|
||||
// Update!
|
||||
emit updateAvailable(m_repoUrl, newestVersion.value("Name").toVariant().toString(), newBuildNumber);
|
||||
}
|
||||
|
||||
m_updateChecking = false;
|
||||
}
|
||||
|
||||
void UpdateChecker::updateCheckFailed()
|
||||
{
|
||||
// TODO: log errors better
|
||||
QLOG_ERROR() << "Update check failed for reasons unknown.";
|
||||
}
|
||||
|
||||
void UpdateChecker::updateChanList()
|
||||
{
|
||||
QLOG_DEBUG() << "Loading the channel list.";
|
||||
|
||||
if (m_channelListUrl.isEmpty())
|
||||
{
|
||||
QLOG_ERROR() << "Failed to update channel list. No channel list URL set."
|
||||
<< "If you'd like to use MultiMC's update system, please pass the channel list URL to CMake at compile time.";
|
||||
return;
|
||||
}
|
||||
|
||||
m_chanListLoading = true;
|
||||
NetJob* job = new NetJob("Update System Channel List");
|
||||
job->addNetAction(ByteArrayDownload::make(QUrl(m_channelListUrl)));
|
||||
QObject::connect(job, &NetJob::succeeded, this, &UpdateChecker::chanListDownloadFinished);
|
||||
QObject::connect(job, &NetJob::failed, this, &UpdateChecker::chanListDownloadFailed);
|
||||
chanListJob.reset(job);
|
||||
job->start();
|
||||
}
|
||||
|
||||
void UpdateChecker::chanListDownloadFinished()
|
||||
{
|
||||
QByteArray data;
|
||||
{
|
||||
ByteArrayDownloadPtr dl = std::dynamic_pointer_cast<ByteArrayDownload>(chanListJob->first());
|
||||
data = dl->m_data;
|
||||
chanListJob.reset();
|
||||
}
|
||||
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
|
||||
if (jsonError.error != QJsonParseError::NoError)
|
||||
{
|
||||
// TODO: Report errors to the user.
|
||||
QLOG_ERROR() << "Failed to parse channel list JSON:" << jsonError.errorString() << "at" << jsonError.offset;
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject object = jsonDoc.object();
|
||||
|
||||
bool success = false;
|
||||
int formatVersion = object.value("format_version").toVariant().toInt(&success);
|
||||
if (formatVersion != CHANLIST_FORMAT || !success)
|
||||
{
|
||||
QLOG_ERROR() << "Failed to check for updates. Channel list format version mismatch. We're using" << CHANLIST_FORMAT << "server has" << formatVersion;
|
||||
return;
|
||||
}
|
||||
|
||||
// Load channels into a temporary array.
|
||||
QList<ChannelListEntry> loadedChannels;
|
||||
QJsonArray channelArray = object.value("channels").toArray();
|
||||
for (QJsonValue chanVal : channelArray)
|
||||
{
|
||||
QJsonObject channelObj = chanVal.toObject();
|
||||
ChannelListEntry entry{
|
||||
channelObj.value("id").toVariant().toString(),
|
||||
channelObj.value("name").toVariant().toString(),
|
||||
channelObj.value("description").toVariant().toString(),
|
||||
channelObj.value("url").toVariant().toString()
|
||||
};
|
||||
if (entry.id.isEmpty() || entry.name.isEmpty() || entry.url.isEmpty())
|
||||
{
|
||||
QLOG_ERROR() << "Channel list entry with empty ID, name, or URL. Skipping.";
|
||||
continue;
|
||||
}
|
||||
loadedChannels.append(entry);
|
||||
}
|
||||
|
||||
// Swap the channel list we just loaded into the object's channel list.
|
||||
m_channels.swap(loadedChannels);
|
||||
|
||||
m_chanListLoading = false;
|
||||
m_chanListLoaded = true;
|
||||
QLOG_INFO() << "Successfully loaded UpdateChecker channel list.";
|
||||
|
||||
// If we're waiting to check for updates, do that now.
|
||||
if (m_checkUpdateWaiting)
|
||||
checkForUpdate();
|
||||
}
|
||||
|
||||
void UpdateChecker::chanListDownloadFailed()
|
||||
{
|
||||
m_chanListLoading = false;
|
||||
QLOG_ERROR() << "Failed to download channel list.";
|
||||
}
|
||||
|
93
logic/updater/UpdateChecker.h
Normal file
93
logic/updater/UpdateChecker.h
Normal file
@ -0,0 +1,93 @@
|
||||
/* Copyright 2013 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "logic/net/NetJob.h"
|
||||
|
||||
#include <QUrl>
|
||||
|
||||
class UpdateChecker : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
signals:
|
||||
//! Signal emitted when an update is available. Passes the URL for the repo and the ID and name for the version.
|
||||
void updateAvailable(QString repoUrl, QString versionName, int versionId);
|
||||
|
||||
private slots:
|
||||
void updateCheckFinished();
|
||||
void updateCheckFailed();
|
||||
|
||||
void chanListDownloadFinished();
|
||||
void chanListDownloadFailed();
|
||||
|
||||
public:
|
||||
UpdateChecker();
|
||||
void checkForUpdate();
|
||||
|
||||
/*!
|
||||
* Causes the update checker to download the channel list from the URL specified in config.h (generated by CMake).
|
||||
* If this isn't called before checkForUpdate(), it will automatically be called.
|
||||
*/
|
||||
void updateChanList();
|
||||
|
||||
/*!
|
||||
* An entry in the channel list.
|
||||
*/
|
||||
struct ChannelListEntry
|
||||
{
|
||||
QString id;
|
||||
QString name;
|
||||
QString description;
|
||||
QString url;
|
||||
};
|
||||
|
||||
private:
|
||||
NetJobPtr indexJob;
|
||||
NetJobPtr chanListJob;
|
||||
|
||||
QString m_repoUrl;
|
||||
|
||||
QString m_channelListUrl;
|
||||
QString m_currentChannel;
|
||||
|
||||
QList<ChannelListEntry> m_channels;
|
||||
|
||||
/*!
|
||||
* True while the system is checking for updates.
|
||||
* If checkForUpdate is called while this is true, it will be ignored.
|
||||
*/
|
||||
bool m_updateChecking;
|
||||
|
||||
/*!
|
||||
* True if the channel list has loaded.
|
||||
* If this is false, trying to check for updates will call updateChanList first.
|
||||
*/
|
||||
bool m_chanListLoaded;
|
||||
|
||||
/*!
|
||||
* Set to true while the channel list is currently loading.
|
||||
*/
|
||||
bool m_chanListLoading;
|
||||
|
||||
/*!
|
||||
* Set to true when checkForUpdate is called while the channel list isn't loaded.
|
||||
* When the channel list finishes loading, if this is true, the update checker will check for updates.
|
||||
*/
|
||||
bool m_checkUpdateWaiting;
|
||||
};
|
||||
|
Reference in New Issue
Block a user