summaryrefslogtreecommitdiffstats
path: root/logic/updater/DownloadUpdateTask.h
blob: 4feab871772d5f7ca648781656516ff74d7c5b1c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
/* 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);

	/*!
	 * Gets the directory that contains the update files.
	 */
	QString updateFilesDir();
	
public:

	// 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;
		int mode;
		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, int fmode=0644) { return UpdateOperation{OP_COPY, fsource, fdest, fmode}; }
		static UpdateOperation MoveOp(QString fsource, QString fdest, int fmode=0644) { return UpdateOperation{OP_MOVE, fsource, fdest, fmode}; }
		static UpdateOperation DeleteOp(QString file) { return UpdateOperation{OP_DELETE, file, "", 0644}; }
		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 file to operate on. If this is a DELETE or CHMOD operation, this is the file that will be modified.
		QString file;

		//! 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;

protected:
	friend class DownloadUpdateTaskTest;


	/*!
	 * 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();

	/*!
	 * 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 
	 * 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 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 bool writeInstallScript(UpdateOperationList& opsList, QString scriptFile);

	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;

	/*!
	 * 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
	 * 
	 * Returns false if the path couldn't be fixed (is invalid)
	 */
	static bool fixPathForOSX(QString &path);

protected slots:
	void vinfoDownloadFinished();
	void vinfoDownloadFailed();

	void fileDownloadFinished();
	void fileDownloadFailed();
	void fileDownloadProgressChanged(qint64 current, qint64 total);
};