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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
|
# Network Layer
## Overview
MeshMC's network layer provides a managed download system supporting parallel downloads, caching, integrity validation, and progress tracking. Built on Qt's `QNetworkAccessManager`, it centralizes all HTTP operations through the `NetJob` and `Download` classes.
## Architecture
### Key Classes
| Class | File | Purpose |
|---|---|---|
| `NetAction` | `net/NetAction.h` | Abstract network operation base |
| `Download` | `net/Download.{h,cpp}` | HTTP GET download |
| `NetJob` | `net/NetJob.{h,cpp}` | Parallel download manager |
| `Sink` | `net/Sink.h` | Abstract data receiver |
| `FileSink` | `net/FileSink.{h,cpp}` | Write to file |
| `ByteArraySink` | `net/ByteArraySink.{h,cpp}` | Write to memory |
| `MetaCacheSink` | `net/MetaCacheSink.{h,cpp}` | Write to file with caching |
| `Validator` | `net/Validator.h` | Abstract integrity checker |
| `ChecksumValidator` | `net/ChecksumValidator.{h,cpp}` | Hash-based validation |
| `HttpMetaCache` | `net/HttpMetaCache.{h,cpp}` | HTTP cache metadata |
| `MetaEntry` | `net/HttpMetaCache.h` | Cache entry (ETag, MD5) |
| `PasteUpload` | `net/PasteUpload.{h,cpp}` | Log paste upload |
| `Upload` | `net/Upload.{h,cpp}` | HTTP POST/PUT upload |
## NetAction
Base class for all network operations:
```cpp
class NetAction : public Task
{
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<NetAction>;
virtual void init() = 0;
public slots:
void startAction(shared_qobject_ptr<QNetworkAccessManager> nam);
protected:
virtual void executeTask() override = 0;
// State
QUrl m_url;
QUrl m_redirectUrl;
int m_redirectsRemaining = 6;
shared_qobject_ptr<QNetworkAccessManager> m_network;
std::unique_ptr<QNetworkReply> m_reply;
};
```
`NetAction` extends `Task`, inheriting progress tracking, state management, and signal notifications.
### JobStatus
Network operations report their status through the `Task` state machine:
- `NotStarted` → `Running` → `Succeeded` / `Failed` / `AbortedByUser`
## Download
The primary HTTP download class:
```cpp
class Download : public NetAction
{
Q_OBJECT
public:
// Factory methods
static Download::Ptr makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions);
static Download::Ptr makeByteArray(QUrl url, std::shared_ptr<QByteArray> output);
static Download::Ptr makeFile(QUrl url, QString path);
// Configuration
Download* addValidator(Validator* v);
Download* addHttpRedirectHandler(QUrl url);
private slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void downloadError(QNetworkReply::NetworkError error);
void sslErrors(const QList<QSslError>& errors);
void downloadFinished();
void downloadReadyRead();
protected:
std::unique_ptr<Sink> m_sink;
QList<Validator*> m_validators;
Options m_options;
};
```
### Factory Methods
Three factory methods create downloads with appropriate sinks:
#### `makeCached(url, entry, options)`
Creates a download backed by `MetaCacheSink`:
- Checks `HttpMetaCache` for existing entry
- Sends `If-None-Match` with stored ETag
- On `304 Not Modified`, reuses cached file
- On `200 OK`, writes to file and updates cache
- Supports MD5 and SHA-1 validation
#### `makeByteArray(url, output)`
Creates a download backed by `ByteArraySink`:
- Data written to the provided `QByteArray`
- Used for small API responses, JSON data
- No disk I/O
#### `makeFile(url, path)`
Creates a download backed by `FileSink`:
- Writes directly to the specified file path
- No caching metadata
- Creates parent directories as needed
### Download Options
```cpp
enum class Option {
NoOptions = 0,
AcceptLocalFiles = 1, // Allow file:// URLs
MakeEternal = 2, // Never expire from cache
};
```
### Redirect Handling
Downloads automatically follow HTTP redirects:
- Maximum of 6 redirects (configurable via `m_redirectsRemaining`)
- Both HTTP 301/302 and Qt's redirect attribute are handled
- Custom redirect handlers can be added for complex redirect chains
## NetJob
Manages parallel execution of multiple `NetAction` operations:
```cpp
class NetJob : public Task
{
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<NetJob>;
explicit NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network);
bool addNetAction(NetAction::Ptr action);
// From Task
void executeTask() override;
bool abort() override;
int size() const;
// Progress aggregation
auto totalProgress() const -> qint64;
auto totalSize() const -> qint64;
signals:
void failed(QString reason);
private slots:
void partProgress(int index, qint64 bytesReceived, qint64 bytesTotal);
void partSucceeded(int index);
void partFailed(int index);
void partAborted(int index);
private:
struct Part {
NetAction::Ptr download;
qint64 current_progress = 0;
qint64 total_progress = 1;
int failures = 0;
};
QList<Part> m_parts;
QQueue<int> m_todo;
QSet<int> m_doing;
QSet<int> m_done;
QSet<int> m_failed;
shared_qobject_ptr<QNetworkAccessManager> m_network;
int m_maxConcurrent = 6;
};
```
### Execution Model
1. All `NetAction` items are queued in `m_todo`
2. Up to `m_maxConcurrent` (default 6) downloads run simultaneously
3. As downloads complete, new ones are started from the queue
4. Progress is aggregated across all downloads
5. On failure, items are retried up to 3 times before the job fails
6. The entire job succeeds only when all parts succeed
### Progress Tracking
`NetJob` aggregates progress from all parts:
```cpp
void NetJob::partProgress(int index, qint64 bytesReceived, qint64 bytesTotal)
{
m_parts[index].current_progress = bytesReceived;
m_parts[index].total_progress = bytesTotal;
qint64 current = 0, total = 0;
for (auto& part : m_parts) {
current += part.current_progress;
total += part.total_progress;
}
setProgress(current, total);
}
```
This emits `Task::progress(qint64, qint64)` which the UI connects to for progress bars.
## Sink System
Sinks receive downloaded data and write it to the appropriate destination:
### Sink (Abstract Base)
```cpp
class Sink
{
public:
virtual ~Sink() = default;
virtual auto init(QNetworkRequest& request) -> Task::State = 0;
virtual auto write(const QByteArray& data) -> Task::State = 0;
virtual auto abort() -> Task::State = 0;
virtual auto finalize(QNetworkReply& reply) -> Task::State = 0;
void addValidator(Validator* v);
protected:
bool finalizeAllValidators(QNetworkReply& reply);
QList<Validator*> m_validators;
};
```
### FileSink
```cpp
class FileSink : public Sink
{
public:
explicit FileSink(const QString& filename);
auto init(QNetworkRequest& request) -> Task::State override;
auto write(const QByteArray& data) -> Task::State override;
auto abort() -> Task::State override;
auto finalize(QNetworkReply& reply) -> Task::State override;
private:
QString m_filename;
std::unique_ptr<QSaveFile> m_output; // Atomic write
};
```
Uses `QSaveFile` for atomic writes — the file is written to a temporary location and renamed on `finalize()`, preventing partial files on failure.
### ByteArraySink
```cpp
class ByteArraySink : public Sink
{
public:
explicit ByteArraySink(std::shared_ptr<QByteArray> output);
auto init(QNetworkRequest& request) -> Task::State override;
auto write(const QByteArray& data) -> Task::State override;
auto abort() -> Task::State override;
auto finalize(QNetworkReply& reply) -> Task::State override;
private:
std::shared_ptr<QByteArray> m_output;
};
```
### MetaCacheSink
```cpp
class MetaCacheSink : public FileSink
{
public:
MetaCacheSink(MetaEntryPtr entry, const QString& filename);
auto init(QNetworkRequest& request) -> Task::State override;
auto finalize(QNetworkReply& reply) -> Task::State override;
bool hasLocalData();
private:
MetaEntryPtr m_entry;
};
```
`MetaCacheSink` extends `FileSink` with:
- Setting `If-None-Match` header from cached ETag
- On `304 Not Modified`, short-circuits to success
- On `200 OK`, updates `MetaEntry` with new ETag, MD5, timestamp
## Validation System
### Validator (Abstract)
```cpp
class Validator
{
public:
virtual ~Validator() = default;
virtual bool init(QNetworkRequest& request) = 0;
virtual bool write(const QByteArray& data) = 0;
virtual bool validate(QNetworkReply& reply) = 0;
};
```
### ChecksumValidator
```cpp
class ChecksumValidator : public Validator
{
public:
ChecksumValidator(QCryptographicHash::Algorithm algorithm,
QString expected = QString());
bool init(QNetworkRequest& request) override;
bool write(const QByteArray& data) override;
bool validate(QNetworkReply& reply) override;
private:
QCryptographicHash m_hash;
QString m_expected;
};
```
Usage:
```cpp
auto dl = Download::makeFile(url, path);
dl->addValidator(new ChecksumValidator(QCryptographicHash::Sha1, expectedSha1));
```
The validator incrementally hashes data as it arrives via `write()`, then compares the final hash in `validate()`. Supported algorithms: MD5, SHA-1, SHA-256.
## HttpMetaCache
Persistent HTTP caching metadata:
```cpp
class HttpMetaCache : public QObject
{
Q_OBJECT
public:
HttpMetaCache(const QString& path = QString());
~HttpMetaCache();
MetaEntryPtr resolveEntry(QString base, QString relative_path, QString expected_etag = QString());
bool updateEntry(MetaEntryPtr stale_entry);
bool evictEntry(MetaEntryPtr entry);
void addBase(QString base, QString base_root);
void Load();
void Save();
private:
// Base URL → local root directory mapping
QMap<QString, QString> m_entries;
// base/path → MetaEntry
QMap<QString, QMap<QString, MetaEntryPtr>> m_entry_cache;
QString m_index_file;
};
```
### MetaEntry
```cpp
class MetaEntry
{
public:
QString basePath;
QString relativePath;
QString md5sum;
QString etag;
qint64 local_changed_timestamp = 0;
qint64 remote_changed_timestamp = 0;
bool stale = true;
bool makeEternal = false;
QString getFullPath();
};
```
### Cache Flow
```
1. resolveEntry("mojang", "versions/1.21.json")
│
├── Lookup in m_entry_cache["mojang"]["versions/1.21.json"]
│ ├── Found + !stale → return existing entry
│ └── Found + stale → return for re-download
└── Not found → create new stale entry
│
2. MetaCacheSink.init()
├── Set If-None-Match: <etag> on request
│
3. HTTP Response
├── 304 Not Modified → mark entry not stale, return
└── 200 OK → write file, update etag/md5/timestamps
│
4. updateEntry() → save to cache index
```
### Cache Persistence
The cache index is stored as JSON in `metacache/metacache.json`:
```json
{
"formatVersion": 2,
"entries": {
"mojang": {
"versions/1.21.json": {
"md5sum": "abc123...",
"etag": "\"xyz789\"",
"local_changed_timestamp": 1700000000,
"remote_changed_timestamp": 1699000000
}
}
}
}
```
## Shared Network Manager
`Application` creates a single `QNetworkAccessManager` shared across all operations:
```cpp
// Application.h
shared_qobject_ptr<QNetworkAccessManager> network();
// Application.cpp
m_network = new QNetworkAccessManager();
```
All `NetJob` instances receive this shared manager. Proxy settings from the settings system are applied to the manager at startup and when changed.
### Proxy Configuration
```cpp
// From global settings
switch (proxyType) {
case "None":
QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy);
break;
case "SOCKS5":
proxy.setType(QNetworkProxy::Socks5Proxy);
// ...
break;
case "HTTP":
proxy.setType(QNetworkProxy::HttpProxy);
// ...
break;
}
```
## Upload System
### PasteUpload
Uploads log content to paste services:
```cpp
class PasteUpload : public Task
{
Q_OBJECT
public:
PasteUpload(QWidget* window, QString text, QString url);
QString pasteLink();
protected:
void executeTask() override;
private slots:
void downloadFinished();
void downloadError(QNetworkReply::NetworkError error);
private:
QByteArray m_text;
QString m_pasteLink;
QString m_pasteURL;
shared_qobject_ptr<QNetworkReply> m_reply;
};
```
Used by the log page to share instance logs. The paste URL is configurable in settings.
## Common Download Patterns
### Downloading Minecraft Version Manifest
```cpp
auto job = new NetJob("Version List", APPLICATION->network());
auto entry = APPLICATION->metacache()->resolveEntry("mojang", "version_manifest_v2.json");
auto dl = Download::makeCached(
QUrl("https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"),
entry
);
job->addNetAction(dl);
connect(job, &NetJob::succeeded, this, &VersionList::loadListFromFile);
job->start();
```
### Downloading Game Libraries
```cpp
auto job = new NetJob("Libraries", APPLICATION->network());
for (auto& lib : libraries) {
auto dl = Download::makeFile(lib.url(), lib.storagePath());
dl->addValidator(new ChecksumValidator(QCryptographicHash::Sha1, lib.sha1()));
job->addNetAction(dl);
}
connect(job, &NetJob::progress, this, &LaunchStep::setProgress);
connect(job, &NetJob::succeeded, this, &LaunchStep::emitSucceeded);
connect(job, &NetJob::failed, this, &LaunchStep::emitFailed);
job->start();
```
### Downloading to Memory
```cpp
auto output = std::make_shared<QByteArray>();
auto dl = Download::makeByteArray(
QUrl("https://api.example.com/data.json"),
output
);
auto job = new NetJob("API Request", APPLICATION->network());
job->addNetAction(dl);
connect(job, &NetJob::succeeded, [output]() {
auto doc = QJsonDocument::fromJson(*output);
// Process response
});
job->start();
```
|