summaryrefslogtreecommitdiffstats
path: root/logic/updater
diff options
context:
space:
mode:
Diffstat (limited to 'logic/updater')
-rw-r--r--logic/updater/DownloadUpdateTask.cpp436
-rw-r--r--logic/updater/DownloadUpdateTask.h49
-rw-r--r--logic/updater/UpdateChecker.cpp73
-rw-r--r--logic/updater/UpdateChecker.h11
4 files changed, 385 insertions, 184 deletions
diff --git a/logic/updater/DownloadUpdateTask.cpp b/logic/updater/DownloadUpdateTask.cpp
index d9aab826..6e0a92f0 100644
--- a/logic/updater/DownloadUpdateTask.cpp
+++ b/logic/updater/DownloadUpdateTask.cpp
@@ -26,9 +26,8 @@
#include <QDomDocument>
-
-DownloadUpdateTask::DownloadUpdateTask(QString repoUrl, int versionId, QObject* parent) :
- Task(parent)
+DownloadUpdateTask::DownloadUpdateTask(QString repoUrl, int versionId, QObject *parent)
+ : Task(parent)
{
m_cVersionId = MMC->version().build;
@@ -45,68 +44,69 @@ void DownloadUpdateTask::executeTask()
findCurrentVersionInfo();
}
-void DownloadUpdateTask::findCurrentVersionInfo()
+void DownloadUpdateTask::processChannels()
{
- setStatus(tr("Finding information about the current version."));
-
auto checker = MMC->updateChecker();
- // This runs after we've tried loading the channel list.
- // If the channel list doesn't need to be loaded, this will be called immediately.
- // If the channel list does need to be loaded, this will be called when it's done.
- auto processFunc = [this, &checker] () -> void
+ // Now, check the channel list again.
+ if (!checker->hasChannels())
{
- // Now, check the channel list again.
- if (checker->hasChannels())
- {
- // We still couldn't load the channel list. Give up. Call loadVersionInfo and return.
- QLOG_INFO() << "Reloading the channel list didn't work. Giving up.";
- loadVersionInfo();
- return;
- }
+ // We still couldn't load the channel list. Give up. Call loadVersionInfo and return.
+ QLOG_INFO() << "Reloading the channel list didn't work. Giving up.";
+ loadVersionInfo();
+ return;
+ }
- QList<UpdateChecker::ChannelListEntry> channels = checker->getChannelList();
- QString channelId = MMC->version().channel;
+ QList<UpdateChecker::ChannelListEntry> channels = checker->getChannelList();
+ QString channelId = MMC->version().channel;
- // Search through the channel list for a channel with the correct ID.
- for (auto channel : channels)
+ // Search through the channel list for a channel with the correct ID.
+ for (auto channel : channels)
+ {
+ if (channel.id == channelId)
{
- if (channel.id == channelId)
- {
- QLOG_INFO() << "Found matching channel.";
- m_cRepoUrl = channel.url;
- break;
- }
+ QLOG_INFO() << "Found matching channel.";
+ m_cRepoUrl = fixPathForTests(channel.url);
+ break;
}
+ }
- // Now that we've done that, load version info.
- loadVersionInfo();
- };
+ // Now that we've done that, load version info.
+ loadVersionInfo();
+}
+
+void DownloadUpdateTask::findCurrentVersionInfo()
+{
+ setStatus(tr("Finding information about the current version..."));
+
+ auto checker = MMC->updateChecker();
- if (checker->hasChannels())
+ if (!checker->hasChannels())
{
// Load the channel list and wait for it to finish loading.
QLOG_INFO() << "No channel list entries found. Will try reloading it.";
- QObject::connect(checker.get(), &UpdateChecker::channelListLoaded, processFunc);
+ QObject::connect(checker.get(), &UpdateChecker::channelListLoaded, this,
+ &DownloadUpdateTask::processChannels);
checker->updateChanList();
}
else
{
- processFunc();
+ processChannels();
}
}
void DownloadUpdateTask::loadVersionInfo()
{
- setStatus(tr("Loading version information."));
+ setStatus(tr("Loading version information..."));
// Create the net job for loading version info.
- NetJob* netJob = new NetJob("Version Info");
-
+ NetJob *netJob = new NetJob("Version Info");
+
// Find the index URL.
QUrl newIndexUrl = QUrl(m_nRepoUrl).resolved(QString::number(m_nVersionId) + ".json");
-
+ QLOG_DEBUG() << m_nRepoUrl << " turns into " << newIndexUrl;
+
// Add a net action to download the version info for the version we're updating to.
netJob->addNetAction(ByteArrayDownload::make(newIndexUrl));
@@ -115,10 +115,12 @@ void DownloadUpdateTask::loadVersionInfo()
{
QUrl cIndexUrl = QUrl(m_cRepoUrl).resolved(QString::number(m_cVersionId) + ".json");
netJob->addNetAction(ByteArrayDownload::make(cIndexUrl));
+ QLOG_DEBUG() << m_cRepoUrl << " turns into " << cIndexUrl;
}
// Connect slots so we know when it's done.
- QObject::connect(netJob, &NetJob::succeeded, this, &DownloadUpdateTask::vinfoDownloadFinished);
+ QObject::connect(netJob, &NetJob::succeeded, this,
+ &DownloadUpdateTask::vinfoDownloadFinished);
QObject::connect(netJob, &NetJob::failed, this, &DownloadUpdateTask::vinfoDownloadFailed);
// Store the NetJob in a class member. We don't want to lose it!
@@ -136,7 +138,8 @@ void DownloadUpdateTask::vinfoDownloadFinished()
void DownloadUpdateTask::vinfoDownloadFailed()
{
- // Something failed. We really need the second download (current version info), so parse downloads anyways as long as the first one succeeded.
+ // Something failed. We really need the second download (current version info), so parse
+ // downloads anyways as long as the first one succeeded.
if (m_vinfoNetJob->first()->m_status != Job_Failed)
{
parseDownloadedVersionInfo();
@@ -150,61 +153,73 @@ void DownloadUpdateTask::vinfoDownloadFailed()
void DownloadUpdateTask::parseDownloadedVersionInfo()
{
- setStatus(tr("Reading file lists."));
-
- parseVersionInfo(NEW_VERSION, &m_nVersionFileList);
+ setStatus(tr("Reading file list for new version..."));
+ QLOG_DEBUG() << "Reading file list for new version...";
+ QString error;
+ if (!parseVersionInfo(
+ std::dynamic_pointer_cast<ByteArrayDownload>(m_vinfoNetJob->first())->m_data,
+ &m_nVersionFileList, &error))
+ {
+ emitFailed(error);
+ return;
+ }
- // If there is a second entry in the network job's list, load it as the current version's info.
+ // If there is a second entry in the network job's list, load it as the current version's
+ // info.
if (m_vinfoNetJob->size() >= 2 && m_vinfoNetJob->operator[](1)->m_status != Job_Failed)
{
- parseVersionInfo(CURRENT_VERSION, &m_cVersionFileList);
+ setStatus(tr("Reading file list for current version..."));
+ QLOG_DEBUG() << "Reading file list for current version...";
+ QString error;
+ parseVersionInfo(
+ std::dynamic_pointer_cast<ByteArrayDownload>(m_vinfoNetJob->operator[](1))->m_data,
+ &m_cVersionFileList, &error);
}
// We don't need this any more.
m_vinfoNetJob.reset();
- // Now that we're done loading version info, we can move on to the next step. Process file lists and download files.
+ // Now that we're done loading version info, we can move on to the next step. Process file
+ // lists and download files.
processFileLists();
}
-void DownloadUpdateTask::parseVersionInfo(VersionInfoFileEnum vfile, VersionFileList* list)
+bool DownloadUpdateTask::parseVersionInfo(const QByteArray &data, VersionFileList *list,
+ QString *error)
{
- if (vfile == CURRENT_VERSION) setStatus(tr("Reading file list for current version."));
- else if (vfile == NEW_VERSION) setStatus(tr("Reading file list for new version."));
-
- QLOG_DEBUG() << "Reading file list for" << (vfile == NEW_VERSION ? "new" : "current") << "version.";
-
- QByteArray data;
- {
- ByteArrayDownloadPtr dl = std::dynamic_pointer_cast<ByteArrayDownload>(
- vfile == NEW_VERSION ? m_vinfoNetJob->first() : m_vinfoNetJob->operator[](1));
- data = dl->m_data;
- }
-
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{
- QLOG_ERROR() << "Failed to parse version info JSON:" << jsonError.errorString() << "at" << jsonError.offset;
- return;
+ *error = QString("Failed to parse version info JSON: %1 at %2")
+ .arg(jsonError.errorString())
+ .arg(jsonError.offset);
+ QLOG_ERROR() << error;
+ return false;
}
QJsonObject json = jsonDoc.object();
+ QLOG_DEBUG() << data;
QLOG_DEBUG() << "Loading version info from JSON.";
QJsonArray filesArray = json.value("Files").toArray();
for (QJsonValue fileValue : filesArray)
{
QJsonObject fileObj = fileValue.toObject();
- VersionFileEntry file{
- fileObj.value("Path").toString(),
- fileObj.value("Perms").toVariant().toInt(),
- FileSourceList(),
- fileObj.value("MD5").toString(),
- };
+ QString file_path = fileObj.value("Path").toString();
+#ifdef Q_OS_MAC
+ // On OSX, the paths for the updater need to be fixed.
+ // basically, anything that isn't in the .app folder is ignored.
+ // everything else is changed so the code that processes the files actually finds
+ // them and puts the replacements in the right spots.
+ if (!fixPathForOSX(file_path))
+ continue;
+#endif
+ VersionFileEntry file{file_path, fileObj.value("Perms").toVariant().toInt(),
+ FileSourceList(), fileObj.value("MD5").toString(), };
QLOG_DEBUG() << "File" << file.path << "with perms" << file.mode;
-
+
QJsonArray sourceArray = fileObj.value("Sources").toArray();
for (QJsonValue val : sourceArray)
{
@@ -213,11 +228,14 @@ void DownloadUpdateTask::parseVersionInfo(VersionInfoFileEnum vfile, VersionFile
QString type = sourceObj.value("SourceType").toString();
if (type == "http")
{
- file.sources.append(FileSource("http", sourceObj.value("Url").toString()));
+ file.sources.append(
+ FileSource("http", fixPathForTests(sourceObj.value("Url").toString())));
}
else if (type == "httpc")
{
- file.sources.append(FileSource("httpc", sourceObj.value("Url").toString(), sourceObj.value("CompressionType").toString()));
+ file.sources.append(
+ FileSource("httpc", fixPathForTests(sourceObj.value("Url").toString()),
+ sourceObj.value("CompressionType").toString()));
}
else
{
@@ -229,90 +247,185 @@ void DownloadUpdateTask::parseVersionInfo(VersionInfoFileEnum vfile, VersionFile
list->append(file);
}
+
+ return true;
}
void DownloadUpdateTask::processFileLists()
{
- setStatus(tr("Processing file lists. Figuring out how to install the update."));
+ // Create a network job for downloading files.
+ NetJob *netJob = new NetJob("Update Files");
+
+ if (!processFileLists(netJob, m_cVersionFileList, m_nVersionFileList, m_operationList))
+ {
+ emitFailed(tr("Failed to process update lists..."));
+ return;
+ }
+
+ // Add listeners to wait for the downloads to finish.
+ QObject::connect(netJob, &NetJob::succeeded, this,
+ &DownloadUpdateTask::fileDownloadFinished);
+ QObject::connect(netJob, &NetJob::progress, this,
+ &DownloadUpdateTask::fileDownloadProgressChanged);
+ QObject::connect(netJob, &NetJob::failed, this, &DownloadUpdateTask::fileDownloadFailed);
+
+ // Now start the download.
+ setStatus(tr("Downloading %1 update files.").arg(QString::number(netJob->size())));
+ QLOG_DEBUG() << "Begin downloading update files to" << m_updateFilesDir.path();
+ m_filesNetJob.reset(netJob);
+ netJob->start();
+
+ writeInstallScript(m_operationList, PathCombine(m_updateFilesDir.path(), "file_list.xml"));
+}
+
+bool
+DownloadUpdateTask::processFileLists(NetJob *job,
+ const DownloadUpdateTask::VersionFileList &currentVersion,
+ const DownloadUpdateTask::VersionFileList &newVersion,
+ DownloadUpdateTask::UpdateOperationList &ops)
+{
+ setStatus(tr("Processing file lists - figuring out how to install the update..."));
- // First, if we've loaded the current version's file list, we need to iterate through it and
+ // First, if we've loaded the current version's file list, we need to iterate through it and
// delete anything in the current one version's list that isn't in the new version's list.
- for (VersionFileEntry entry : m_cVersionFileList)
+ for (VersionFileEntry entry : currentVersion)
{
+ QFileInfo toDelete(entry.path);
+ if (!toDelete.exists())
+ {
+ QLOG_ERROR() << "Expected file " << toDelete.absoluteFilePath()
+ << " doesn't exist!";
+ QLOG_ERROR() << "CWD: " << QDir::currentPath();
+ }
bool keep = false;
- for (VersionFileEntry newEntry : m_nVersionFileList)
+
+ //
+ for (VersionFileEntry newEntry : newVersion)
{
if (newEntry.path == entry.path)
{
- QLOG_DEBUG() << "Not deleting" << entry.path << "because it is still present in the new version.";
+ QLOG_DEBUG() << "Not deleting" << entry.path
+ << "because it is still present in the new version.";
keep = true;
break;
}
}
+
// If the loop reaches the end and we didn't find a match, delete the file.
- if(!keep)
- m_operationList.append(UpdateOperation::DeleteOp(entry.path));
+ if (!keep)
+ {
+ QFileInfo toDelete(entry.path);
+ if (toDelete.exists())
+ ops.append(UpdateOperation::DeleteOp(entry.path));
+ }
}
- // Create a network job for downloading files.
- NetJob* netJob = new NetJob("Update Files");
-
// Next, check each file in MultiMC's folder and see if we need to update them.
- for (VersionFileEntry entry : m_nVersionFileList)
+ for (VersionFileEntry entry : newVersion)
{
- // TODO: Let's not MD5sum a ton of files on the GUI thread. We should probably find a way to do this in the background.
+ // TODO: Let's not MD5sum a ton of files on the GUI thread. We should probably find a
+ // way to do this in the background.
QString fileMD5;
QFile entryFile(entry.path);
- if (entryFile.open(QFile::ReadOnly))
+ QFileInfo entryInfo(entry.path);
+
+ bool needs_upgrade = false;
+ if (!entryFile.exists())
{
- QCryptographicHash hash(QCryptographicHash::Md5);
- hash.addData(entryFile.readAll());
- fileMD5 = hash.result().toHex();
+ needs_upgrade = true;
+ }
+ else
+ {
+ bool pass = true;
+ if (!entryInfo.isReadable())
+ {
+ QLOG_ERROR() << "File " << entry.path << " is not readable.";
+ pass = false;
+ }
+ if (!entryInfo.isWritable())
+ {
+ QLOG_ERROR() << "File " << entry.path << " is not writable.";
+ pass = false;
+ }
+ if (!entryFile.open(QFile::ReadOnly))
+ {
+ QLOG_ERROR() << "File " << entry.path << " cannot be opened for reading.";
+ pass = false;
+ }
+ if (!pass)
+ {
+ QLOG_ERROR() << "CWD: " << QDir::currentPath();
+ ops.clear();
+ return false;
+ }
}
- if (!entryFile.exists() || fileMD5.isEmpty() || fileMD5 != entry.md5)
+ QCryptographicHash hash(QCryptographicHash::Md5);
+ auto foo = entryFile.readAll();
+
+ hash.addData(foo);
+ fileMD5 = hash.result().toHex();
+ if ((fileMD5 != entry.md5))
{
- QLOG_DEBUG() << "Found file" << entry.path << "that needs updating.";
+ QLOG_DEBUG() << "MD5Sum does not match!";
+ QLOG_DEBUG() << "Expected:'" << entry.md5 << "'";
+ QLOG_DEBUG() << "Got: '" << fileMD5 << "'";
+ needs_upgrade = true;
+ }
+
+ // skip file. it doesn't need an upgrade.
+ if (!needs_upgrade)
+ {
+ QLOG_DEBUG() << "File" << entry.path << " does not need updating.";
+ continue;
+ }
- // Go through the sources list and find one to use.
- // TODO: Make a NetAction that takes a source list and tries each of them until one works. For now, we'll just use the first http one.
- for (FileSource source : entry.sources)
+ // yep. this file actually needs an upgrade. PROCEED.
+ QLOG_DEBUG() << "Found file" << entry.path << " that needs updating.";
+
+ // if it's the updater we want to treat it separately
+ bool isUpdater = entry.path.endsWith("updater") || entry.path.endsWith("updater.exe");
+
+ // Go through the sources list and find one to use.
+ // TODO: Make a NetAction that takes a source list and tries each of them until one
+ // works. For now, we'll just use the first http one.
+ for (FileSource source : entry.sources)
+ {
+ if (source.type == "http")
{
- if (source.type == "http")
- {
- QLOG_DEBUG() << "Will download" << entry.path << "from" << source.url;
+ QLOG_DEBUG() << "Will download" << entry.path << "from" << source.url;
- // Download it to updatedir/<filepath>-<md5> where filepath is the file's path with slashes replaced by underscores.
- QString dlPath = PathCombine(m_updateFilesDir.path(), QString(entry.path).replace("/", "_"));
+ // Download it to updatedir/<filepath>-<md5> where filepath is the file's
+ // path with slashes replaced by underscores.
+ QString dlPath =
+ PathCombine(m_updateFilesDir.path(), QString(entry.path).replace("/", "_"));
- // We need to download the file to the updatefiles folder and add a task to copy it to its install path.
+ if (isUpdater)
+ {
+ auto cache_entry = MMC->metacache()->resolveEntry("root", entry.path);
+ QLOG_DEBUG() << "Updater will be in " << cache_entry->getFullPath();
+ if(cache_entry->stale)
+ {
+ auto download = CacheDownload::make(QUrl(source.url), cache_entry);
+ job->addNetAction(download);
+ }
+ }
+ else
+ {
+ // We need to download the file to the updatefiles folder and add a task
+ // to copy it to its install path.
auto download = MD5EtagDownload::make(source.url, dlPath);
- download->m_check_md5 = true;
download->m_expected_md5 = entry.md5;
- netJob->addNetAction(download);
-
- // Now add a copy operation to our operations list to install the file.
- m_operationList.append(UpdateOperation::CopyOp(dlPath, entry.path, entry.mode));
+ job->addNetAction(download);
+ ops.append(UpdateOperation::CopyOp(dlPath, entry.path, entry.mode));
}
}
}
}
-
- // Add listeners to wait for the downloads to finish.
- QObject::connect(netJob, &NetJob::succeeded, this, &DownloadUpdateTask::fileDownloadFinished);
- QObject::connect(netJob, &NetJob::progress, this, &DownloadUpdateTask::fileDownloadProgressChanged);
- QObject::connect(netJob, &NetJob::failed, this, &DownloadUpdateTask::fileDownloadFailed);
-
- // Now start the download.
- setStatus(tr("Downloading %1 update files.").arg(QString::number(netJob->size())));
- QLOG_DEBUG() << "Begin downloading update files to" << m_updateFilesDir.path();
- m_filesNetJob.reset(netJob);
- netJob->start();
-
- writeInstallScript(m_operationList, PathCombine(m_updateFilesDir.path(), "file_list.xml"));
+ return true;
}
-void DownloadUpdateTask::writeInstallScript(UpdateOperationList& opsList, QString scriptFile)
+bool DownloadUpdateTask::writeInstallScript(UpdateOperationList &opsList, QString scriptFile)
{
// Build the base structure of the XML document.
QDomDocument doc;
@@ -334,36 +447,38 @@ void DownloadUpdateTask::writeInstallScript(UpdateOperationList& opsList, QStrin
switch (op.type)
{
- case UpdateOperation::OP_COPY:
- {
- // Install the file.
- QDomElement name = doc.createElement("source");
- QDomElement path = doc.createElement("dest");
- QDomElement mode = doc.createElement("mode");
- name.appendChild(doc.createTextNode(op.file));
- path.appendChild(doc.createTextNode(op.dest));
- // We need to add a 0 at the beginning here, because Qt doesn't convert to octal correctly.
- mode.appendChild(doc.createTextNode("0" + QString::number(op.mode, 8)));
- file.appendChild(name);
- file.appendChild(path);
- file.appendChild(mode);
- installFiles.appendChild(file);
- QLOG_DEBUG() << "Will install file" << op.file;
- }
- break;
+ case UpdateOperation::OP_COPY:
+ {
+ // Install the file.
+ QDomElement name = doc.createElement("source");
+ QDomElement path = doc.createElement("dest");
+ QDomElement mode = doc.createElement("mode");
+ name.appendChild(doc.createTextNode(op.file));
+ path.appendChild(doc.createTextNode(op.dest));
+ // We need to add a 0 at the beginning here, because Qt doesn't convert to octal
+ // correctly.
+ mode.appendChild(doc.createTextNode("0" + QString::number(op.mode, 8)));
+ file.appendChild(name);
+ file.appendChild(path);
+ file.appendChild(mode);
+ installFiles.appendChild(file);
+ QLOG_DEBUG() << "Will install file " << op.file << " to " << op.dest;
+ }
+ break;
- case UpdateOperation::OP_DELETE:
- {
- // Delete the file.
- file.appendChild(doc.createTextNode(op.file));
- removeFiles.appendChild(file);
- QLOG_DEBUG() << "Will remove file" << op.file;
- }
- break;
+ case UpdateOperation::OP_DELETE:
+ {
+ // Delete the file.
+ file.appendChild(doc.createTextNode(op.file));
+ removeFiles.appendChild(file);
+ QLOG_DEBUG() << "Will remove file" << op.file;
+ }
+ break;
- default:
- QLOG_WARN() << "Can't write update operation of type" << op.type << "to file. Not implemented.";
- continue;
+ default:
+ QLOG_WARN() << "Can't write update operation of type" << op.type
+ << "to file. Not implemented.";
+ continue;
}
}
@@ -377,6 +492,36 @@ void DownloadUpdateTask::writeInstallScript(UpdateOperationList& opsList, QStrin
else
{
emitFailed(tr("Failed to write update script file."));
+ return false;
+ }
+
+ return true;
+}
+
+QString DownloadUpdateTask::fixPathForTests(const QString &path)
+{
+ if (path.startsWith("$PWD"))
+ {
+ QString foo = path;
+ foo.replace("$PWD", qApp->applicationDirPath());
+ return QUrl::fromLocalFile(foo).toString(QUrl::FullyEncoded);
+ }
+ return path;
+}
+
+bool DownloadUpdateTask::fixPathForOSX(QString &path)
+{
+ if (path.startsWith("MultiMC.app/"))
+ {
+ // remove the prefix and add a new, more appropriate one.
+ path.remove(0, 12);
+ path = QString("../../") + path;
+ return true;
+ }
+ else
+ {
+ QLOG_ERROR() << "Update path not within .app: " << path;
+ return false;
}
}
@@ -394,11 +539,10 @@ void DownloadUpdateTask::fileDownloadFailed()
void DownloadUpdateTask::fileDownloadProgressChanged(qint64 current, qint64 total)
{
- setProgress((int)(((float)current / (float)total)*100));
+ setProgress((int)(((float)current / (float)total) * 100));
}
QString DownloadUpdateTask::updateFilesDir()
{
return m_updateFilesDir.path();
}
-
diff --git a/logic/updater/DownloadUpdateTask.h b/logic/updater/DownloadUpdateTask.h
index f5b23d12..b1d14846 100644
--- a/logic/updater/DownloadUpdateTask.h
+++ b/logic/updater/DownloadUpdateTask.h
@@ -34,7 +34,8 @@ public:
*/
QString updateFilesDir();
-protected:
+public:
+
// TODO: We should probably put these data structures into a separate header...
/*!
@@ -53,7 +54,6 @@ protected:
QString url;
QString compressionType;
};
-
typedef QList<FileSource> FileSourceList;
/*!
@@ -66,10 +66,8 @@ protected:
FileSourceList sources;
QString md5;
};
-
typedef QList<VersionFileEntry> VersionFileList;
-
/*!
* Structure that describes an operation to perform when installing updates.
*/
@@ -100,9 +98,12 @@ protected:
// Yeah yeah, polymorphism blah blah inheritance, blah blah object oriented. I'm lazy, OK?
};
-
typedef QList<UpdateOperation> UpdateOperationList;
+protected:
+ friend class DownloadUpdateTaskTest;
+
+
/*!
* Used for arguments to parseVersionInfo and friends to specify which version info file to parse.
*/
@@ -120,6 +121,13 @@ protected:
virtual void findCurrentVersionInfo();
/*!
+ * This runs after we've tried loading the channel list.
+ * If the channel list doesn't need to be loaded, this will be called immediately.
+ * If the channel list does need to be loaded, this will be called when it's done.
+ */
+ void processChannels();
+
+ /*!
* Downloads the version info files from the repository.
* The files for both the current build, and the build that we're updating to need to be downloaded.
* If the current version's info file can't be found, MultiMC will not delete files that
@@ -142,20 +150,25 @@ protected:
/*!
* Loads the file list from the given version info JSON object into the given list.
*/
- virtual void parseVersionInfo(VersionInfoFileEnum vfile, VersionFileList* list);
+ virtual bool parseVersionInfo(const QByteArray &data, VersionFileList* list, QString *error);
/*!
* Takes a list of file entries for the current version's files and the new version's files
* and populates the downloadList and operationList with information about how to download and install the update.
*/
+ virtual bool processFileLists(NetJob *job, const VersionFileList &currentVersion, const VersionFileList &newVersion, UpdateOperationList &ops);
+
+ /*!
+ * Calls \see processFileLists to populate the \see m_operationList and a NetJob, and then executes
+ * the NetJob to fetch all needed files
+ */
virtual void processFileLists();
/*!
* Takes the operations list and writes an install script for the updater to the update files directory.
*/
- virtual void writeInstallScript(UpdateOperationList& opsList, QString scriptFile);
+ virtual bool writeInstallScript(UpdateOperationList& opsList, QString scriptFile);
- VersionFileList m_downloadList;
UpdateOperationList m_operationList;
VersionFileList m_nVersionFileList;
@@ -181,6 +194,26 @@ protected:
*/
QTemporaryDir m_updateFilesDir;
+ /*!
+ * Filters paths
+ * Path of the format $PWD/path, it is converted to a file:///$PWD/ URL
+ */
+ static QString fixPathForTests(const QString &path);
+
+ /*!
+ * Filters paths
+ * This fixes destination paths for OSX.
+ * The updater runs in MultiMC.app/Contents/MacOs by default
+ * The destination paths are such as this: MultiMC.app/blah/blah
+ *
+ * Therefore we chop off the 'MultiMC.app' prefix and prepend ../..
+ *
+ * Returns false if the path couldn't be fixed (is invalid)
+ *
+ * Has no effect on systems that aren't OSX
+ */
+ static bool fixPathForOSX(QString &path);
+
protected slots:
void vinfoDownloadFinished();
void vinfoDownloadFailed();
diff --git a/logic/updater/UpdateChecker.cpp b/logic/updater/UpdateChecker.cpp
index 5ff1898e..d0795c0d 100644
--- a/logic/updater/UpdateChecker.cpp
+++ b/logic/updater/UpdateChecker.cpp
@@ -44,17 +44,19 @@ QList<UpdateChecker::ChannelListEntry> UpdateChecker::getChannelList() const
bool UpdateChecker::hasChannels() const
{
- return m_channels.isEmpty();
+ return !m_channels.isEmpty();
}
-void UpdateChecker::checkForUpdate()
+void UpdateChecker::checkForUpdate(bool notifyNoUpdate)
{
QLOG_DEBUG() << "Checking for updates.";
- // If the channel list hasn't loaded yet, load it and defer checking for updates until later.
+ // 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.";
+ QLOG_DEBUG() << "Channel list isn't loaded yet. Loading channel list and deferring "
+ "update check.";
m_checkUpdateWaiting = true;
updateChanList();
return;
@@ -72,7 +74,8 @@ void UpdateChecker::checkForUpdate()
// 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.
+ // 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)
{
@@ -91,20 +94,22 @@ void UpdateChecker::checkForUpdate()
auto job = new NetJob("GoUpdate Repository Index");
job->addNetAction(ByteArrayDownload::make(indexUrl));
- connect(job, SIGNAL(succeeded()), SLOT(updateCheckFinished()));
+ connect(job, &NetJob::succeeded, [this, notifyNoUpdate]()
+ { updateCheckFinished(notifyNoUpdate); });
connect(job, SIGNAL(failed()), SLOT(updateCheckFailed()));
indexJob.reset(job);
job->start();
}
-void UpdateChecker::updateCheckFinished()
+void UpdateChecker::updateCheckFinished(bool notifyNoUpdate)
{
QLOG_DEBUG() << "Finished downloading repo index. Checking for new versions.";
QJsonParseError jsonError;
QByteArray data;
{
- ByteArrayDownloadPtr dl = std::dynamic_pointer_cast<ByteArrayDownload>(indexJob->first());
+ ByteArrayDownloadPtr dl =
+ std::dynamic_pointer_cast<ByteArrayDownload>(indexJob->first());
data = dl->m_data;
indexJob.reset();
}
@@ -112,7 +117,8 @@ void UpdateChecker::updateCheckFinished()
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;
+ QLOG_ERROR() << "Failed to parse GoUpdate repository index. JSON error"
+ << jsonError.errorString() << "at offset" << jsonError.offset;
return;
}
@@ -122,7 +128,8 @@ void UpdateChecker::updateCheckFinished()
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;
+ QLOG_ERROR() << "Failed to check for updates. API version mismatch. We're using"
+ << API_VERSION << "server has" << apiVersion;
return;
}
@@ -132,19 +139,27 @@ void UpdateChecker::updateCheckFinished()
for (QJsonValue versionVal : versions)
{
QJsonObject version = versionVal.toObject();
- if (newestVersion.value("Id").toVariant().toInt() < version.value("Id").toVariant().toInt())
+ if (newestVersion.value("Id").toVariant().toInt() <
+ version.value("Id").toVariant().toInt())
{
- QLOG_DEBUG() << "Found newer version with ID" << 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.
+ // 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);
+ emit updateAvailable(m_repoUrl, newestVersion.value("Name").toVariant().toString(),
+ newBuildNumber);
+ }
+ else if (notifyNoUpdate)
+ {
+ emit noUpdateFound();
}
m_updateChecking = false;
@@ -163,12 +178,13 @@ void UpdateChecker::updateChanList()
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.";
+ << "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");
+ 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);
@@ -180,7 +196,8 @@ void UpdateChecker::chanListDownloadFinished()
{
QByteArray data;
{
- ByteArrayDownloadPtr dl = std::dynamic_pointer_cast<ByteArrayDownload>(chanListJob->first());
+ ByteArrayDownloadPtr dl =
+ std::dynamic_pointer_cast<ByteArrayDownload>(chanListJob->first());
data = dl->m_data;
chanListJob.reset();
}
@@ -190,17 +207,20 @@ void UpdateChecker::chanListDownloadFinished()
if (jsonError.error != QJsonParseError::NoError)
{
// TODO: Report errors to the user.
- QLOG_ERROR() << "Failed to parse channel list JSON:" << jsonError.errorString() << "at" << jsonError.offset;
+ 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;
+ QLOG_ERROR()
+ << "Failed to check for updates. Channel list format version mismatch. We're using"
+ << CHANLIST_FORMAT << "server has" << formatVersion;
return;
}
@@ -210,12 +230,10 @@ void UpdateChecker::chanListDownloadFinished()
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()
- };
+ 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.";
@@ -233,7 +251,7 @@ void UpdateChecker::chanListDownloadFinished()
// If we're waiting to check for updates, do that now.
if (m_checkUpdateWaiting)
- checkForUpdate();
+ checkForUpdate(false);
emit channelListLoaded();
}
@@ -244,4 +262,3 @@ void UpdateChecker::chanListDownloadFailed()
QLOG_ERROR() << "Failed to download channel list.";
emit channelListLoaded();
}
-
diff --git a/logic/updater/UpdateChecker.h b/logic/updater/UpdateChecker.h
index 59fb8e47..a47e8903 100644
--- a/logic/updater/UpdateChecker.h
+++ b/logic/updater/UpdateChecker.h
@@ -25,7 +25,10 @@ class UpdateChecker : public QObject
public:
UpdateChecker();
- void checkForUpdate();
+ void checkForUpdate(bool notifyNoUpdate);
+
+ void setCurrentChannel(const QString &channel) { m_currentChannel = channel; }
+ void setChannelListUrl(const QString &url) { m_channelListUrl = url; }
/*!
* Causes the update checker to download the channel list from the URL specified in config.h (generated by CMake).
@@ -62,14 +65,18 @@ signals:
//! Signal emitted when the channel list finishes loading or fails to load.
void channelListLoaded();
+ void noUpdateFound();
+
private slots:
- void updateCheckFinished();
+ void updateCheckFinished(bool notifyNoUpdate);
void updateCheckFailed();
void chanListDownloadFinished();
void chanListDownloadFailed();
private:
+ friend class UpdateCheckerTest;
+
NetJobPtr indexJob;
NetJobPtr chanListJob;