summaryrefslogtreecommitdiffstats
path: root/logic
diff options
context:
space:
mode:
Diffstat (limited to 'logic')
-rw-r--r--logic/GoUpdate.cpp99
-rw-r--r--logic/GoUpdate.h43
-rw-r--r--logic/updater/UpdateChecker.cpp234
-rw-r--r--logic/updater/UpdateChecker.h93
4 files changed, 327 insertions, 142 deletions
diff --git a/logic/GoUpdate.cpp b/logic/GoUpdate.cpp
deleted file mode 100644
index 6ac53d19..00000000
--- a/logic/GoUpdate.cpp
+++ /dev/null
@@ -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/
-*/
diff --git a/logic/GoUpdate.h b/logic/GoUpdate.h
deleted file mode 100644
index 756a71cf..00000000
--- a/logic/GoUpdate.h
+++ /dev/null
@@ -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;
-};
diff --git a/logic/updater/UpdateChecker.cpp b/logic/updater/UpdateChecker.cpp
new file mode 100644
index 00000000..68c32ae8
--- /dev/null
+++ b/logic/updater/UpdateChecker.cpp
@@ -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.";
+}
+
diff --git a/logic/updater/UpdateChecker.h b/logic/updater/UpdateChecker.h
new file mode 100644
index 00000000..829f7303
--- /dev/null
+++ b/logic/updater/UpdateChecker.h
@@ -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;
+};
+