Implement DownloadUpdateTask
Installing updates is not implemented yet. That's next.
This commit is contained in:
		
							
								
								
									
										322
									
								
								logic/updater/DownloadUpdateTask.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										322
									
								
								logic/updater/DownloadUpdateTask.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,322 @@
 | 
			
		||||
/* 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 "DownloadUpdateTask.h"
 | 
			
		||||
 | 
			
		||||
#include "MultiMC.h"
 | 
			
		||||
#include "logic/updater/UpdateChecker.h"
 | 
			
		||||
#include "logic/net/NetJob.h"
 | 
			
		||||
#include "pathutils.h"
 | 
			
		||||
 | 
			
		||||
#include <QFile>
 | 
			
		||||
#include <QTemporaryDir>
 | 
			
		||||
#include <QCryptographicHash>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
DownloadUpdateTask::DownloadUpdateTask(QString repoUrl, int versionId, QObject* parent) :
 | 
			
		||||
	Task(parent)
 | 
			
		||||
{
 | 
			
		||||
	m_cVersionId = MMC->version().build;
 | 
			
		||||
 | 
			
		||||
	m_nRepoUrl = repoUrl;
 | 
			
		||||
	m_nVersionId = versionId;
 | 
			
		||||
 | 
			
		||||
	m_updateFilesDir.setAutoRemove(false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DownloadUpdateTask::executeTask()
 | 
			
		||||
{
 | 
			
		||||
	// GO!
 | 
			
		||||
	// This will call the next step when it's done.
 | 
			
		||||
	findCurrentVersionInfo();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DownloadUpdateTask::findCurrentVersionInfo()
 | 
			
		||||
{
 | 
			
		||||
	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())
 | 
			
		||||
		{ 
 | 
			
		||||
			// 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;
 | 
			
		||||
 | 
			
		||||
		// Search through the channel list for a channel with the correct ID.
 | 
			
		||||
		for (auto channel : channels)
 | 
			
		||||
		{
 | 
			
		||||
			if (channel.id == channelId)
 | 
			
		||||
			{
 | 
			
		||||
				QLOG_INFO() << "Found matching channel.";
 | 
			
		||||
				m_cRepoUrl = channel.url;
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Now that we've done that, load version info.
 | 
			
		||||
		loadVersionInfo();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
		checker->updateChanList();
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		processFunc();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DownloadUpdateTask::loadVersionInfo()
 | 
			
		||||
{
 | 
			
		||||
	setStatus(tr("Loading version information."));
 | 
			
		||||
 | 
			
		||||
	// Create the net job for loading version info.
 | 
			
		||||
	NetJob* netJob = new NetJob("Version Info");
 | 
			
		||||
	
 | 
			
		||||
	// Find the index URL.
 | 
			
		||||
	QUrl newIndexUrl = QUrl(m_nRepoUrl).resolved(QString::number(m_nVersionId) + ".json");
 | 
			
		||||
	
 | 
			
		||||
	// Add a net action to download the version info for the version we're updating to.
 | 
			
		||||
	netJob->addNetAction(ByteArrayDownload::make(newIndexUrl));
 | 
			
		||||
 | 
			
		||||
	// If we have a current version URL, get that one too.
 | 
			
		||||
	if (!m_cRepoUrl.isEmpty())
 | 
			
		||||
	{
 | 
			
		||||
		QUrl cIndexUrl = QUrl(m_cRepoUrl).resolved(QString::number(m_cVersionId) + ".json");
 | 
			
		||||
		netJob->addNetAction(ByteArrayDownload::make(cIndexUrl));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Connect slots so we know when it's done.
 | 
			
		||||
	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!
 | 
			
		||||
	m_vinfoNetJob.reset(netJob);
 | 
			
		||||
 | 
			
		||||
	// Finally, we start the network job and the thread's event loop to wait for it to finish.
 | 
			
		||||
	netJob->start();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DownloadUpdateTask::vinfoDownloadFinished()
 | 
			
		||||
{
 | 
			
		||||
	// Both downloads succeeded. OK. Parse stuff.
 | 
			
		||||
	parseDownloadedVersionInfo();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
	if (m_vinfoNetJob->first()->m_status != Job_Failed)
 | 
			
		||||
	{
 | 
			
		||||
		parseDownloadedVersionInfo();
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: Give a more detailed error message.
 | 
			
		||||
	QLOG_ERROR() << "Failed to download version info files.";
 | 
			
		||||
	emitFailed(tr("Failed to download version info files."));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DownloadUpdateTask::parseDownloadedVersionInfo()
 | 
			
		||||
{
 | 
			
		||||
	setStatus(tr("Reading file lists."));
 | 
			
		||||
 | 
			
		||||
	parseVersionInfo(NEW_VERSION, &m_nVersionFileList);
 | 
			
		||||
 | 
			
		||||
	// 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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 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.
 | 
			
		||||
	processFileLists();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DownloadUpdateTask::parseVersionInfo(VersionInfoFileEnum vfile, VersionFileList* list)
 | 
			
		||||
{
 | 
			
		||||
	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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	QJsonObject json = jsonDoc.object();
 | 
			
		||||
 | 
			
		||||
	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("Executable").toBool(false),
 | 
			
		||||
			FileSourceList(),
 | 
			
		||||
			fileObj.value("MD5").toString(),
 | 
			
		||||
		};
 | 
			
		||||
		
 | 
			
		||||
		QJsonArray sourceArray = fileObj.value("Sources").toArray();
 | 
			
		||||
		for (QJsonValue val : sourceArray)
 | 
			
		||||
		{
 | 
			
		||||
			QJsonObject sourceObj = val.toObject();
 | 
			
		||||
 | 
			
		||||
			QString type = sourceObj.value("SourceType").toString();
 | 
			
		||||
			if (type == "http")
 | 
			
		||||
			{
 | 
			
		||||
				file.sources.append(FileSource("http", sourceObj.value("Url").toString()));
 | 
			
		||||
			}
 | 
			
		||||
			else if (type == "httpc")
 | 
			
		||||
			{
 | 
			
		||||
				file.sources.append(FileSource("httpc", sourceObj.value("Url").toString(), sourceObj.value("CompressionType").toString()));
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				QLOG_WARN() << "Unknown source type" << type << "ignored.";
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		QLOG_DEBUG() << "Loaded info for" << file.path;
 | 
			
		||||
 | 
			
		||||
		list->append(file);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DownloadUpdateTask::processFileLists()
 | 
			
		||||
{
 | 
			
		||||
	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 
 | 
			
		||||
	// 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 newEntry : m_nVersionFileList)
 | 
			
		||||
		{ 
 | 
			
		||||
			if (newEntry.path == entry.path)
 | 
			
		||||
				continue;
 | 
			
		||||
		}
 | 
			
		||||
		// If the loop reaches the end, we didn't find a match. Delete the file.
 | 
			
		||||
		m_operationList.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)
 | 
			
		||||
	{
 | 
			
		||||
		// 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))
 | 
			
		||||
		{
 | 
			
		||||
			QCryptographicHash hash(QCryptographicHash::Md5);
 | 
			
		||||
			hash.addData(entryFile.readAll());
 | 
			
		||||
			fileMD5 = hash.result().toHex();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (!entryFile.exists() || fileMD5.isEmpty() || fileMD5 != entry.md5)
 | 
			
		||||
		{
 | 
			
		||||
			QLOG_DEBUG() << "Found file" << entry.path << "that needs updating.";
 | 
			
		||||
 | 
			
		||||
			// 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")
 | 
			
		||||
				{
 | 
			
		||||
					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(), entry.path.replace("/", "_"));
 | 
			
		||||
 | 
			
		||||
					// We need to download the file to the updatefiles folder and add a task to copy it to its install path.
 | 
			
		||||
					FileDownloadPtr download = FileDownload::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));
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 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();
 | 
			
		||||
 | 
			
		||||
	// TODO: Write update operations to a file for the update installer.
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DownloadUpdateTask::fileDownloadFinished()
 | 
			
		||||
{
 | 
			
		||||
	emitSucceeded();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DownloadUpdateTask::fileDownloadFailed()
 | 
			
		||||
{
 | 
			
		||||
	// TODO: Give more info about the failure.
 | 
			
		||||
	QLOG_ERROR() << "Failed to download update files.";
 | 
			
		||||
	emitFailed(tr("Failed to download update files."));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DownloadUpdateTask::fileDownloadProgressChanged(qint64 current, qint64 total)
 | 
			
		||||
{
 | 
			
		||||
	setProgress((int)(((float)current / (float)total)*100));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										182
									
								
								logic/updater/DownloadUpdateTask.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								logic/updater/DownloadUpdateTask.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,182 @@
 | 
			
		||||
/* 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/tasks/Task.h"
 | 
			
		||||
#include "logic/net/NetJob.h"
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * The DownloadUpdateTask is a task that takes a given version ID and repository URL,
 | 
			
		||||
 * downloads that version's files from the repository, and prepares to install them.
 | 
			
		||||
 */
 | 
			
		||||
class DownloadUpdateTask : public Task
 | 
			
		||||
{
 | 
			
		||||
	Q_OBJECT
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit DownloadUpdateTask(QString repoUrl, int versionId, QObject* parent=0);
 | 
			
		||||
	
 | 
			
		||||
protected:
 | 
			
		||||
	// TODO: We should probably put these data structures into a separate header...
 | 
			
		||||
 | 
			
		||||
	/*!
 | 
			
		||||
	 * Struct that describes an entry in a VersionFileEntry's `Sources` list.
 | 
			
		||||
	 */
 | 
			
		||||
	struct FileSource
 | 
			
		||||
	{
 | 
			
		||||
		FileSource(QString type, QString url, QString compression="")
 | 
			
		||||
		{
 | 
			
		||||
			this->type = type;
 | 
			
		||||
			this->url = url;
 | 
			
		||||
			this->compressionType = compression;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		QString type;
 | 
			
		||||
		QString url;
 | 
			
		||||
		QString compressionType;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	typedef QList<FileSource> FileSourceList;
 | 
			
		||||
 | 
			
		||||
	/*!
 | 
			
		||||
	 * Structure that describes an entry in a GoUpdate version's `Files` list.
 | 
			
		||||
	 */
 | 
			
		||||
	struct VersionFileEntry
 | 
			
		||||
	{
 | 
			
		||||
		QString path;
 | 
			
		||||
		bool isExecutable;
 | 
			
		||||
		FileSourceList sources;
 | 
			
		||||
		QString md5;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	typedef QList<VersionFileEntry> VersionFileList;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	/*!
 | 
			
		||||
	 * Structure that describes an operation to perform when installing updates.
 | 
			
		||||
	 */
 | 
			
		||||
	struct UpdateOperation
 | 
			
		||||
	{
 | 
			
		||||
		static UpdateOperation CopyOp(QString fsource, QString fdest) { return UpdateOperation{OP_COPY, fsource, fdest, 644}; }
 | 
			
		||||
		static UpdateOperation MoveOp(QString fsource, QString fdest) { return UpdateOperation{OP_MOVE, fsource, fdest, 644}; }
 | 
			
		||||
		static UpdateOperation DeleteOp(QString file) { return UpdateOperation{OP_DELETE, file, "", 644}; }
 | 
			
		||||
		static UpdateOperation ChmodOp(QString file, int fmode) { return UpdateOperation{OP_CHMOD, file, "", fmode}; }
 | 
			
		||||
 | 
			
		||||
		//! Specifies the type of operation that this is.
 | 
			
		||||
		enum Type
 | 
			
		||||
		{
 | 
			
		||||
			OP_COPY,
 | 
			
		||||
			OP_DELETE,
 | 
			
		||||
			OP_MOVE,
 | 
			
		||||
			OP_CHMOD,
 | 
			
		||||
		} type;
 | 
			
		||||
 | 
			
		||||
		//! The source file. If this is a DELETE or CHMOD operation, this is the file that will be modified.
 | 
			
		||||
		QString source;
 | 
			
		||||
 | 
			
		||||
		//! The destination file. If this is a DELETE or CHMOD operation, this field will be ignored.
 | 
			
		||||
		QString dest;
 | 
			
		||||
 | 
			
		||||
		//! The mode to change the source file to. Ignored if this isn't a CHMOD operation.
 | 
			
		||||
		int mode;
 | 
			
		||||
 | 
			
		||||
		// Yeah yeah, polymorphism blah blah inheritance, blah blah object oriented. I'm lazy, OK?
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	typedef QList<UpdateOperation> UpdateOperationList;
 | 
			
		||||
 | 
			
		||||
	/*!
 | 
			
		||||
	 * Used for arguments to parseVersionInfo and friends to specify which version info file to parse.
 | 
			
		||||
	 */
 | 
			
		||||
	enum VersionInfoFileEnum { NEW_VERSION, CURRENT_VERSION };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	//! Entry point for tasks.
 | 
			
		||||
	virtual void executeTask();
 | 
			
		||||
 | 
			
		||||
	/*!
 | 
			
		||||
	 * Attempts to find the version ID and repository URL for the current version.
 | 
			
		||||
	 * The function will look up the repository URL in the UpdateChecker's channel list.
 | 
			
		||||
	 * If the repository URL can't be found, this function will return false.
 | 
			
		||||
	 */
 | 
			
		||||
	virtual void findCurrentVersionInfo();
 | 
			
		||||
 | 
			
		||||
	/*!
 | 
			
		||||
	 * 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 
 | 
			
		||||
	 * were removed between versions. It will still replace files that have changed, however.
 | 
			
		||||
	 * Note that although the repository URL for the current version is not given to the update task,
 | 
			
		||||
	 * the task will attempt to look it up in the UpdateChecker's channel list.
 | 
			
		||||
	 * If an error occurs here, the function will call emitFailed and return false.
 | 
			
		||||
	 */
 | 
			
		||||
	virtual void loadVersionInfo();
 | 
			
		||||
 | 
			
		||||
	/*!
 | 
			
		||||
	 * This function is called when version information is finished downloading.
 | 
			
		||||
	 * This handles parsing the JSON downloaded by the version info network job and then calls processFileLists.
 | 
			
		||||
	 * Note that this function will sometimes be called even if the version info download emits failed. If
 | 
			
		||||
	 * we couldn't download the current version's info file, we can still update. This will be called even if the 
 | 
			
		||||
	 * current version's info file fails to download, as long as the new version's info file succeeded.
 | 
			
		||||
	 */
 | 
			
		||||
	virtual void parseDownloadedVersionInfo();
 | 
			
		||||
 | 
			
		||||
	/*!
 | 
			
		||||
	 * Loads the file list from the given version info JSON object into the given list.
 | 
			
		||||
	 */
 | 
			
		||||
	virtual void parseVersionInfo(VersionInfoFileEnum vfile, VersionFileList* list);
 | 
			
		||||
 | 
			
		||||
	/*!
 | 
			
		||||
	 * 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 void processFileLists();
 | 
			
		||||
 | 
			
		||||
	VersionFileList m_downloadList;
 | 
			
		||||
	UpdateOperationList m_operationList;
 | 
			
		||||
 | 
			
		||||
	VersionFileList m_nVersionFileList;
 | 
			
		||||
	VersionFileList m_cVersionFileList;
 | 
			
		||||
 | 
			
		||||
	//! Network job for downloading version info files.
 | 
			
		||||
	NetJobPtr m_vinfoNetJob;
 | 
			
		||||
	
 | 
			
		||||
	//! Network job for downloading update files.
 | 
			
		||||
	NetJobPtr m_filesNetJob;
 | 
			
		||||
 | 
			
		||||
	// Version ID and repo URL for the new version.
 | 
			
		||||
	int m_nVersionId;
 | 
			
		||||
	QString m_nRepoUrl;
 | 
			
		||||
 | 
			
		||||
	// Version ID and repo URL for the currently installed version.
 | 
			
		||||
	int m_cVersionId;
 | 
			
		||||
	QString m_cRepoUrl;
 | 
			
		||||
 | 
			
		||||
	/*!
 | 
			
		||||
	 * Temporary directory to store update files in.
 | 
			
		||||
	 * This will be set to not auto delete. Task will fail if this fails to be created.
 | 
			
		||||
	 */
 | 
			
		||||
	QTemporaryDir m_updateFilesDir;
 | 
			
		||||
 | 
			
		||||
protected slots:
 | 
			
		||||
	void vinfoDownloadFinished();
 | 
			
		||||
	void vinfoDownloadFailed();
 | 
			
		||||
 | 
			
		||||
	void fileDownloadFinished();
 | 
			
		||||
	void fileDownloadFailed();
 | 
			
		||||
	void fileDownloadProgressChanged(qint64 current, qint64 total);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -37,6 +37,16 @@ UpdateChecker::UpdateChecker()
 | 
			
		||||
	m_chanListLoaded = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QList<UpdateChecker::ChannelListEntry> UpdateChecker::getChannelList() const
 | 
			
		||||
{
 | 
			
		||||
	return m_channels;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool UpdateChecker::hasChannels() const
 | 
			
		||||
{
 | 
			
		||||
	return m_channels.isEmpty();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void UpdateChecker::checkForUpdate()
 | 
			
		||||
{
 | 
			
		||||
	QLOG_DEBUG() << "Checking for updates.";
 | 
			
		||||
@@ -224,11 +234,14 @@ void UpdateChecker::chanListDownloadFinished()
 | 
			
		||||
	// If we're waiting to check for updates, do that now.
 | 
			
		||||
	if (m_checkUpdateWaiting)
 | 
			
		||||
		checkForUpdate();
 | 
			
		||||
 | 
			
		||||
	emit channelListLoaded();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void UpdateChecker::chanListDownloadFailed()
 | 
			
		||||
{
 | 
			
		||||
	m_chanListLoading = false;
 | 
			
		||||
	QLOG_ERROR() << "Failed to download channel list.";
 | 
			
		||||
	emit channelListLoaded();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,18 +23,6 @@ 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();
 | 
			
		||||
@@ -56,6 +44,31 @@ public:
 | 
			
		||||
		QString url;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	/*!
 | 
			
		||||
	 * Returns a the current channel list.
 | 
			
		||||
	 * If the channel list hasn't been loaded, this list will be empty.
 | 
			
		||||
	 */
 | 
			
		||||
	QList<ChannelListEntry> getChannelList() const;
 | 
			
		||||
 | 
			
		||||
	/*!
 | 
			
		||||
	 * Returns true if the channel list is empty.
 | 
			
		||||
	 */
 | 
			
		||||
	bool hasChannels() const;
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
 | 
			
		||||
	//! Signal emitted when the channel list finishes loading or fails to load.
 | 
			
		||||
	void channelListLoaded();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void updateCheckFinished();
 | 
			
		||||
	void updateCheckFailed();
 | 
			
		||||
 | 
			
		||||
	void chanListDownloadFinished();
 | 
			
		||||
	void chanListDownloadFailed();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	NetJobPtr indexJob;
 | 
			
		||||
	NetJobPtr chanListJob;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user