Skip to content

Commit

Permalink
Feat+Fix[MCDL]: show speed and ETA, mitigate timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
khanhduytran0 committed Jun 24, 2024
1 parent 60e04dc commit a9cbdda
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 28 deletions.
6 changes: 3 additions & 3 deletions Natives/DownloadProgressViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ - (void)loadView {
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];

[self.task.progress addObserver:self
[self.task.textProgress addObserver:self
forKeyPath:@"fractionCompleted"
options:NSKeyValueObservingOptionInitial
context:TotalProgressObserverContext];
Expand All @@ -38,7 +38,7 @@ - (void)viewDidAppear:(BOOL)animated {
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];

[self.task.progress removeObserver:self forKeyPath:@"fractionCompleted"];
[self.task.textProgress removeObserver:self forKeyPath:@"fractionCompleted"];
}

- (void)actionClose {
Expand All @@ -60,7 +60,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
});
} else if (context == TotalProgressObserverContext) {
dispatch_async(dispatch_get_main_queue(), ^{
self.title = [NSString stringWithFormat:@"(%@) %@", progress.localizedAdditionalDescription, progress.localizedDescription];
self.title = progress.localizedDescription;
if (self.needsReloadData) {
[self.tableView reloadData];
}
Expand Down
25 changes: 23 additions & 2 deletions Natives/LauncherNavigationController.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#import "ios_uikit_bridge.h"
#import "utils.h"

#include <sys/time.h>

#define AUTORESIZE_MASKS UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin

static void *ProgressObserverContext = &ProgressObserverContext;
Expand Down Expand Up @@ -299,11 +301,30 @@ - (void)performInstallOrShowDetails:(UIButton *)sender {
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context != ProgressObserverContext) {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
return;
}

// Calculate download speed and ETA
static CGFloat lastMsTime;
static NSUInteger lastSecTime, lastCompletedUnitCount;
NSProgress *progress = self.task.textProgress;
struct timeval tv;
gettimeofday(&tv, NULL);
NSInteger completedUnitCount = self.task.progress.totalUnitCount * self.task.progress.fractionCompleted;
progress.completedUnitCount = completedUnitCount;
if (lastSecTime < tv.tv_sec) {
CGFloat currentTime = tv.tv_sec + tv.tv_usec / 1000000.0;
NSInteger throughput = (completedUnitCount - lastCompletedUnitCount) / (currentTime - lastMsTime);
progress.throughput = @(throughput);
progress.estimatedTimeRemaining = @((progress.totalUnitCount - completedUnitCount) / throughput);
lastCompletedUnitCount = completedUnitCount;
lastSecTime = tv.tv_sec;
lastMsTime = currentTime;
}

dispatch_async(dispatch_get_main_queue(), ^{
NSProgress *progress = object;
self.progressText.text = [NSString stringWithFormat:@"(%@) %@", progress.localizedAdditionalDescription, progress.localizedDescription];
self.progressText.text = progress.localizedAdditionalDescription;

if (!progress.finished) return;

self.progressViewMain.observedProgress = nil;
Expand Down
5 changes: 2 additions & 3 deletions Natives/MinecraftResourceDownloadTask.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
@class ModpackAPI;

@interface MinecraftResourceDownloadTask : NSObject
@property NSProgress* progress;
@property NSProgress *progress, *textProgress;
@property NSMutableArray *fileList, *progressList;
@property NSMutableDictionary* metadata;
@property(nonatomic, copy) void(^handleError)(void);

- (NSURLSessionDownloadTask *)createDownloadTask:(NSString *)url sha:(NSString *)sha altName:(NSString *)altName toPath:(NSString *)path;
- (void)addDownloadTaskToProgress:(NSURLSessionDownloadTask *)task;
- (NSURLSessionDownloadTask *)createDownloadTask:(NSString *)url size:(NSUInteger)size sha:(NSString *)sha altName:(NSString *)altName toPath:(NSString *)path;
- (void)finishDownloadWithErrorString:(NSString *)error;

- (void)downloadVersion:(NSDictionary *)version;
Expand Down
52 changes: 38 additions & 14 deletions Natives/MinecraftResourceDownloadTask.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ - (instancetype)init {
self = [super init];
// TODO: implement background download
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.timeoutIntervalForRequest = 86400;
//backgroundSessionConfigurationWithIdentifier:@"net.kdt.pojavlauncher.downloadtask"];
self.manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
self.fileList = [NSMutableArray new];
Expand All @@ -28,7 +29,7 @@ - (instancetype)init {
}

// Add file to the queue
- (NSURLSessionDownloadTask *)createDownloadTask:(NSString *)url sha:(NSString *)sha altName:(NSString *)altName toPath:(NSString *)path success:(void (^)())success {
- (NSURLSessionDownloadTask *)createDownloadTask:(NSString *)url size:(NSUInteger)size sha:(NSString *)sha altName:(NSString *)altName toPath:(NSString *)path success:(void (^)())success {
BOOL fileExists = [NSFileManager.defaultManager fileExistsAtPath:path];
// logSuccess?
if (fileExists && [self checkSHA:sha forFile:path altName:altName]) {
Expand All @@ -43,6 +44,9 @@ - (NSURLSessionDownloadTask *)createDownloadTask:(NSString *)url sha:(NSString *
NSURLSessionDownloadTask *task = [self.manager downloadTaskWithRequest:request progress:nil
destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
NSLog(@"[MCDL] Downloading %@", name);
if (!size && task) {
[self addDownloadTaskToProgress:task size:response.expectedContentLength];
}
[NSFileManager.defaultManager createDirectoryAtPath:path.stringByDeletingLastPathComponent withIntermediateDirectories:YES attributes:nil error:nil];
[NSFileManager.defaultManager removeItemAtPath:path error:nil];
return [NSURL fileURLWithPath:path];
Expand All @@ -57,18 +61,26 @@ - (NSURLSessionDownloadTask *)createDownloadTask:(NSString *)url sha:(NSString *
if (success) success();
}
}];

if (size && task) {
[self addDownloadTaskToProgress:task size:size];
}

return task;
}

- (NSURLSessionDownloadTask *)createDownloadTask:(NSString *)url sha:(NSString *)sha altName:(NSString *)altName toPath:(NSString *)path {
return [self createDownloadTask:url sha:sha altName:altName toPath:path success:nil];
- (NSURLSessionDownloadTask *)createDownloadTask:(NSString *)url size:(NSUInteger)size sha:(NSString *)sha altName:(NSString *)altName toPath:(NSString *)path {
return [self createDownloadTask:url size:size sha:sha altName:altName toPath:path success:nil];
}

- (void)addDownloadTaskToProgress:(NSURLSessionDownloadTask *)task {
- (void)addDownloadTaskToProgress:(NSURLSessionDownloadTask *)task size:(NSUInteger)size {
NSProgress *progress = [self.manager downloadProgressForTask:task];
NSUInteger fileSize = size ?: 1;
progress.kind = NSProgressKindFile;
[self.progressList addObject:progress];
[self.progress addChild:progress withPendingUnitCount:1];
[self.progress addChild:progress withPendingUnitCount:fileSize];
self.progress.totalUnitCount += fileSize;
self.textProgress.totalUnitCount = self.progress.totalUnitCount;
}

- (void)downloadVersionMetadata:(NSDictionary *)version success:(void (^)())success {
Expand Down Expand Up @@ -119,8 +131,9 @@ - (void)downloadVersionMetadata:(NSDictionary *)version success:(void (^)())succ
versionStr = version[@"id"];
NSString *url = version[@"url"];
NSString *sha = url.stringByDeletingLastPathComponent.lastPathComponent;
NSUInteger size = [version[@"size"] unsignedLongLongValue];

NSURLSessionDownloadTask *task = [self createDownloadTask:url sha:sha altName:nil toPath:path success:completionBlock];
NSURLSessionDownloadTask *task = [self createDownloadTask:url size:size sha:sha altName:nil toPath:path success:completionBlock];
[task resume];
}

Expand All @@ -135,7 +148,8 @@ - (void)downloadAssetMetadataWithSuccess:(void (^)())success {
NSString *path = [NSString stringWithFormat:@"%s/assets/indexes/%@.json", getenv("POJAV_GAME_DIR"), assetIndex[@"id"]];
NSString *url = assetIndex[@"url"];
NSString *sha = url.stringByDeletingLastPathComponent.lastPathComponent;
NSURLSessionDownloadTask *task = [self createDownloadTask:url sha:sha altName:nil toPath:path success:^{
NSUInteger size = [assetIndex[@"size"] unsignedLongLongValue];
NSURLSessionDownloadTask *task = [self createDownloadTask:url size:size sha:sha altName:nil toPath:path success:^{
self.metadata[@"assetIndexObj"] = parseJSONFromFile(path);
success();
}];
Expand All @@ -160,16 +174,16 @@ - (NSArray *)downloadClientLibraries {

NSString *path = [NSString stringWithFormat:@"%s/libraries/%@", getenv("POJAV_GAME_DIR"), artifact[@"path"]];
NSString *sha = artifact[@"sha1"];
NSUInteger size = [artifact[@"size"] unsignedLongLongValue];
NSString *url = artifact[@"url"];
if ([library[@"skip"] boolValue]) {
NSLog(@"[MDCL] Skipped library %@", name);
continue;
}

NSURLSessionDownloadTask *task = [self createDownloadTask:url sha:sha altName:nil toPath:path success:nil];
NSURLSessionDownloadTask *task = [self createDownloadTask:url size:size sha:sha altName:nil toPath:path success:nil];
if (task) {
[self.fileList addObject:name];
[self addDownloadTaskToProgress:task];
[tasks addObject:task];
} else if (self.progress.cancelled) {
return nil;
Expand All @@ -185,8 +199,10 @@ - (NSArray *)downloadClientAssets {
return @[];
}
for (NSString *name in assets[@"objects"]) {
NSString *hash = assets[@"objects"][name][@"hash"];
NSDictionary *object = assets[@"objects"][name];
NSString *hash = object[@"hash"];
NSString *pathname = [NSString stringWithFormat:@"%@/%@", [hash substringToIndex:2], hash];
NSUInteger size = [object[@"size"] unsignedLongLongValue];

NSString *path;
if ([assets[@"map_to_resources"] boolValue]) {
Expand All @@ -205,10 +221,9 @@ - (NSArray *)downloadClientAssets {
}

NSString *url = [NSString stringWithFormat:@"https://resources.download.minecraft.net/%@", pathname];
NSURLSessionDownloadTask *task = [self createDownloadTask:url sha:hash altName:name toPath:path success:nil];
NSURLSessionDownloadTask *task = [self createDownloadTask:url size:size sha:hash altName:name toPath:path success:nil];
if (task) {
[self.fileList addObject:name];
[self addDownloadTaskToProgress:task];
[tasks addObject:task];
} else if (self.progress.cancelled) {
return nil;
Expand All @@ -223,11 +238,12 @@ - (void)downloadVersion:(NSDictionary *)version {
[self downloadAssetMetadataWithSuccess:^{
NSArray *libTasks = [self downloadClientLibraries];
NSArray *assetTasks = [self downloadClientAssets];
self.progress.totalUnitCount = libTasks.count + assetTasks.count;
if (self.progress.totalUnitCount == 0) {
// We have nothing to download, invoke completion observer
self.progress.totalUnitCount = 1;
self.progress.completedUnitCount = 1;
self.textProgress.totalUnitCount = 1;
self.textProgress.completedUnitCount = 1;
return;
}
[libTasks makeObjectsPerformSelector:@selector(resume)];
Expand All @@ -243,12 +259,13 @@ - (void)downloadModpackFromAPI:(ModpackAPI *)api detail:(NSDictionary *)modDetai
[self prepareForDownload];

NSString *url = modDetail[@"versionUrls"][selectedVersion];
NSUInteger size = [modDetail[@"versionSizes"][selectedVersion] unsignedLongLongValue];
NSString *sha = modDetail[@"versionHashes"][selectedVersion];
NSString *name = [[modDetail[@"title"] lowercaseString] stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet];
name = [name stringByReplacingOccurrencesOfString:@" " withString:@"_"];
NSString *packagePath = [NSTemporaryDirectory() stringByAppendingFormat:@"/%@.zip", name];

NSURLSessionDownloadTask *task = [self createDownloadTask:url sha:sha altName:nil toPath:packagePath success:^{
NSURLSessionDownloadTask *task = [self createDownloadTask:url size:size sha:sha altName:nil toPath:packagePath success:^{
NSString *path = [NSString stringWithFormat:@"%s/custom_gamedir/%@", getenv("POJAV_GAME_DIR"), name];
[api downloader:self submitDownloadTasksFromPackage:packagePath toPath:path];
}];
Expand All @@ -258,6 +275,13 @@ - (void)downloadModpackFromAPI:(ModpackAPI *)api detail:(NSDictionary *)modDetai
#pragma mark - Utilities

- (void)prepareForDownload {
// Create a fake progress which is used to update completedUnitCount properly
// (completedUnitCount does not update unless subprogress completes)
self.textProgress = [NSProgress new];
self.textProgress.kind = NSProgressKindFile;
self.textProgress.fileOperationKind = NSProgressFileOperationKindDownloading;
self.textProgress.totalUnitCount = -1;

self.progress = [NSProgress new];
[self.fileList removeAllObjects];
[self.progressList removeAllObjects];
Expand Down
15 changes: 9 additions & 6 deletions Natives/installer/modpack/ModrinthAPI.m
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,19 @@ - (void)loadDetailsOfMod:(NSMutableDictionary *)item {
NSMutableArray<NSString *> *mcNames = [NSMutableArray new];
NSMutableArray<NSString *> *urls = [NSMutableArray new];
NSMutableArray<NSString *> *hashes = [NSMutableArray new];
NSMutableArray<NSString *> *sizes = [NSMutableArray new];
[response enumerateObjectsUsingBlock:
^(NSDictionary *version, NSUInteger i, BOOL *stop) {
NSDictionary *file = [version[@"files"] firstObject];
mcNames[i] = [version[@"game_versions"] firstObject];
urls[i] = [version[@"files"] firstObject][@"url"];
NSDictionary *hashesMap = [version[@"files"] firstObject][@"hashes"];
sizes[i] = file[@"size"];
urls[i] = file[@"url"];
NSDictionary *hashesMap = file[@"hashes"];
hashes[i] = hashesMap[@"sha1"] ?: [NSNull null];
}];
item[@"versionNames"] = names;
item[@"mcVersionNames"] = mcNames;
item[@"versionSizes"] = sizes;
item[@"versionUrls"] = urls;
item[@"versionHashes"] = hashes;
item[@"versionDetailsLoaded"] = @(YES);
Expand Down Expand Up @@ -96,10 +100,10 @@ - (void)downloader:(MinecraftResourceDownloadTask *)downloader submitDownloadTas
NSString *url = [indexFile[@"downloads"] firstObject];
NSString *sha = indexFile[@"hashes"][@"sha1"];
NSString *path = [destPath stringByAppendingPathComponent:indexFile[@"path"]];
NSURLSessionDownloadTask *task = [downloader createDownloadTask:url sha:sha altName:nil toPath:path];
NSUInteger size = [indexFile[@"fileSize"] unsignedLongLongValue];
NSURLSessionDownloadTask *task = [downloader createDownloadTask:url size:size sha:sha altName:nil toPath:path];
if (task) {
[downloader.fileList addObject:indexFile[@"path"]];
[downloader addDownloadTaskToProgress:task];
[task resume];
} else if (!downloader.progress.cancelled) {
downloader.progress.completedUnitCount++;
Expand Down Expand Up @@ -127,9 +131,8 @@ - (void)downloader:(MinecraftResourceDownloadTask *)downloader submitDownloadTas
NSDictionary<NSString *, NSString *> *depInfo = [ModpackUtils infoForDependencies:indexDict[@"dependencies"]];
if (depInfo[@"json"]) {
NSString *jsonPath = [NSString stringWithFormat:@"%1$s/versions/%2$@/%2$@.json", getenv("POJAV_GAME_DIR"), depInfo[@"id"]];
NSURLSessionDownloadTask *task = [downloader createDownloadTask:depInfo[@"json"] sha:nil altName:nil toPath:jsonPath];
NSURLSessionDownloadTask *task = [downloader createDownloadTask:depInfo[@"json"] size:0 sha:nil altName:nil toPath:jsonPath];
[task resume];
// FIXME: sometimes progress doesn't report properly, not adding to total progress for now
}
// TODO: automation for Forge

Expand Down

0 comments on commit a9cbdda

Please sign in to comment.