Skip to content

Commit

Permalink
Feat+Refactor[MCDL]: rewrite to allow download libraries in parallel
Browse files Browse the repository at this point in the history
  • Loading branch information
khanhduytran0 committed Feb 10, 2024
1 parent 49cff7e commit d730232
Show file tree
Hide file tree
Showing 18 changed files with 556 additions and 478 deletions.
2 changes: 2 additions & 0 deletions Natives/CMakeLists.txt
Expand Up @@ -130,6 +130,7 @@ add_executable(PojavLauncher
AppDelegate.m
CustomControlsViewController.m
CustomControlsViewController+UndoManager.m
DownloadProgressViewController.m
FileListViewController.m
GameSurfaceView.m
JavaGUIViewController.m
Expand All @@ -144,6 +145,7 @@ add_executable(PojavLauncher
LauncherProfileEditorViewController.m
LauncherProfilesViewController.m
LauncherSplitViewController.m
MinecraftResourceDownloadTask.m
MinecraftResourceUtils.m
PickTextField.m
PLLogOutputView.m
Expand Down
9 changes: 9 additions & 0 deletions Natives/DownloadProgressViewController.h
@@ -0,0 +1,9 @@
#import <UIKit/UIKit.h>
#import "MinecraftResourceDownloadTask.h"

@interface DownloadProgressViewController : UITableViewController
@property MinecraftResourceDownloadTask* task;

- (instancetype)initWithTask:(MinecraftResourceDownloadTask *)task;

@end
122 changes: 122 additions & 0 deletions Natives/DownloadProgressViewController.m
@@ -0,0 +1,122 @@
#import <dlfcn.h>
#import <objc/runtime.h>
#import "DownloadProgressViewController.h"
#import "WFWorkflowProgressView.h"

static void *CellProgressObserverContext = &CellProgressObserverContext;
static void *TotalProgressObserverContext = &TotalProgressObserverContext;

@interface DownloadProgressViewController ()
@property BOOL needsReloadData;
@end

@implementation DownloadProgressViewController

- (instancetype)initWithTask:(MinecraftResourceDownloadTask *)task {
self = [super init];
self.task = task;
return self;
}

- (void)loadView {
[super loadView];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemClose target:self action:@selector(actionClose)];
self.tableView.allowsSelection = NO;

// Load WFWorkflowProgressView
dlopen("/System/Library/PrivateFrameworks/WorkflowUIServices.framework/WorkflowUIServices", RTLD_GLOBAL);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];

[self.task.progress addObserver:self
forKeyPath:@"fractionCompleted"
options:NSKeyValueObservingOptionInitial
context:TotalProgressObserverContext];
}

- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];

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

- (void)actionClose {
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSProgress *progress = object;
if (context == CellProgressObserverContext) {
UITableViewCell *cell = objc_getAssociatedObject(progress, @"cell");
if (!cell) return;
dispatch_async(dispatch_get_main_queue(), ^{
cell.detailTextLabel.text = progress.localizedAdditionalDescription;
WFWorkflowProgressView *progressView = (id)cell.accessoryView;
progressView.fractionCompleted = progress.fractionCompleted;
if (progress.finished) {
[progressView transitionCompletedLayerToVisible:YES animated:YES haptic:NO];
}
});
} else if (context == TotalProgressObserverContext) {
dispatch_async(dispatch_get_main_queue(), ^{
self.title = [NSString stringWithFormat:@"(%@) %@", progress.localizedAdditionalDescription, progress.localizedDescription];
static dispatch_once_t once;
if (self.needsReloadData) {
[self.tableView reloadData];
}
});
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
self.needsReloadData = self.task.fileList.count == 0;
return self.task.fileList.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];

if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"];
WFWorkflowProgressView *progressView = [[NSClassFromString(@"WFWorkflowProgressView") alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
progressView.resolvedTintColor = self.view.tintColor;
progressView.stopSize = 0;
cell.accessoryView = progressView;
}

// Unset the last cell displaying the progress
NSProgress *lastProgress = objc_getAssociatedObject(cell, @"progress");
if (lastProgress) {
objc_setAssociatedObject(lastProgress, @"cell", nil, OBJC_ASSOCIATION_ASSIGN);
@try {
[lastProgress removeObserver:self forKeyPath:@"fractionCompleted"];
} @catch(id anException) {}
}

NSProgress *progress = self.task.progressList[indexPath.row];
objc_setAssociatedObject(cell, @"progress", progress, OBJC_ASSOCIATION_ASSIGN);
objc_setAssociatedObject(progress, @"cell", cell, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[progress addObserver:self
forKeyPath:@"fractionCompleted"
options:NSKeyValueObservingOptionInitial
context:CellProgressObserverContext];

WFWorkflowProgressView *progressView = (id)cell.accessoryView;
if (lastProgress.finished) {
[progressView reset];
}
progressView.fractionCompleted = progress.fractionCompleted;
[progressView transitionCompletedLayerToVisible:progress.finished animated:NO haptic:NO];
[progressView transitionRunningLayerToVisible:!progress.finished animated:NO];

cell.textLabel.text = self.task.fileList[indexPath.row];
cell.detailTextLabel.text = progress.localizedAdditionalDescription;
return cell;
}

@end
4 changes: 4 additions & 0 deletions Natives/Info.plist
Expand Up @@ -157,6 +157,10 @@
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
</array>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
Expand Down
2 changes: 1 addition & 1 deletion Natives/LauncherNavigationController.h
Expand Up @@ -10,6 +10,6 @@ NSMutableArray<NSDictionary *> *localVersionList, *remoteVersionList;

- (void)enterModInstallerWithPath:(NSString *)path hitEnterAfterWindowShown:(BOOL)hitEnter;
- (void)fetchLocalVersionList;
- (void)setInteractionEnabled:(BOOL)enable;
- (void)setInteractionEnabled:(BOOL)enable forDownloading:(BOOL)downloading;

@end
102 changes: 68 additions & 34 deletions Natives/LauncherNavigationController.m
Expand Up @@ -3,10 +3,12 @@
#import "AFNetworking.h"
#import "ALTServerConnection.h"
#import "CustomControlsViewController.h"
#import "DownloadProgressViewController.h"
#import "JavaGUIViewController.h"
#import "LauncherMenuViewController.h"
#import "LauncherNavigationController.h"
#import "LauncherPreferences.h"
#import "MinecraftResourceDownloadTask.h"
#import "MinecraftResourceUtils.h"
#import "PickTextField.h"
#import "PLPickerView.h"
Expand All @@ -18,9 +20,13 @@

#define AUTORESIZE_MASKS UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin

static void *ProgressObserverContext = &ProgressObserverContext;

@interface LauncherNavigationController () <UIDocumentPickerDelegate, UIPickerViewDataSource, PLPickerViewDelegate, UIPopoverPresentationControllerDelegate> {
}

@property(nonatomic) MinecraftResourceDownloadTask* task;
@property(nonatomic) DownloadProgressViewController* progressVC;
@property(nonatomic) PLPickerView* versionPickerView;
@property(nonatomic) UITextField* versionTextField;
@property(nonatomic) int profileSelectedAt;
Expand Down Expand Up @@ -61,16 +67,13 @@ - (void)viewDidLoad
self.versionTextField.inputAccessoryView = versionPickToolbar;
self.versionTextField.inputView = self.versionPickerView;

UIView *targetToolbar;
targetToolbar = self.toolbar;
UIView *targetToolbar = self.toolbar;
[targetToolbar addSubview:self.versionTextField];

self.progressViewMain = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 0, self.toolbar.frame.size.width, 4)];
self.progressViewSub = [[UIProgressView alloc] initWithFrame:CGRectMake(0, self.toolbar.frame.size.height - 4, self.toolbar.frame.size.width, 4)];
self.progressViewMain.autoresizingMask = self.progressViewSub.autoresizingMask = AUTORESIZE_MASKS;
self.progressViewMain.hidden = self.progressViewSub.hidden = YES;
self.progressViewMain.autoresizingMask = AUTORESIZE_MASKS;
self.progressViewMain.hidden = YES;
[targetToolbar addSubview:self.progressViewMain];
[targetToolbar addSubview:self.progressViewSub];

self.buttonInstall = [UIButton buttonWithType:UIButtonTypeSystem];
setButtonPointerInteraction(self.buttonInstall);
Expand All @@ -80,10 +83,10 @@ - (void)viewDidLoad
self.buttonInstall.layer.cornerRadius = 5;
self.buttonInstall.frame = CGRectMake(self.toolbar.frame.size.width * 0.8, 4, self.toolbar.frame.size.width * 0.2, self.toolbar.frame.size.height - 8);
self.buttonInstall.tintColor = UIColor.whiteColor;
[self.buttonInstall addTarget:self action:@selector(launchMinecraft:) forControlEvents:UIControlEventPrimaryActionTriggered];
[self.buttonInstall addTarget:self action:@selector(performInstallOrShowDetails:) forControlEvents:UIControlEventPrimaryActionTriggered];
[targetToolbar addSubview:self.buttonInstall];

self.progressText = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.toolbar.frame.size.width, self.toolbar.frame.size.height)];
self.progressText = [[UILabel alloc] initWithFrame:self.versionTextField.frame];
self.progressText.adjustsFontSizeToFitWidth = YES;
self.progressText.autoresizingMask = AUTORESIZE_MASKS;
self.progressText.font = [self.progressText.font fontWithSize:16];
Expand All @@ -97,11 +100,11 @@ - (void)viewDidLoad

if ([BaseAuthenticator.current isKindOfClass:MicrosoftAuthenticator.class]) {
// Perform token refreshment on startup
[self setInteractionEnabled:NO];
[self setInteractionEnabled:NO forDownloading:NO];
id callback = ^(NSString* status, BOOL success) {
self.progressText.text = status;
if (status == nil) {
[self setInteractionEnabled:YES];
[self setInteractionEnabled:YES forDownloading:NO];
} else if (!success) {
showDialog(localize(@"Error", nil), status);
}
Expand Down Expand Up @@ -213,15 +216,20 @@ - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocum
[self enterModInstallerWithPath:url.path hitEnterAfterWindowShown:NO];
}

- (void)setInteractionEnabled:(BOOL)enabled {
- (void)setInteractionEnabled:(BOOL)enabled forDownloading:(BOOL)downloading {
for (UIControl *view in self.toolbar.subviews) {
if ([view isKindOfClass:UIControl.class]) {
view.alpha = enabled ? 1 : 0.2;
view.enabled = enabled;
}
}

self.progressViewMain.hidden = self.progressViewSub.hidden = enabled;
self.progressViewMain.hidden = enabled;
self.progressText.text = nil;
if (downloading) {
[self.buttonInstall setTitle:localize(enabled ? @"Play" : @"Details", nil) forState:UIControlStateNormal];
self.buttonInstall.alpha = 1;
self.buttonInstall.enabled = YES;
}
}

- (void)launchMinecraft:(UIButton *)sender {
Expand All @@ -238,38 +246,64 @@ - (void)launchMinecraft:(UIButton *)sender {
return;
}

sender.alpha = 0.5;
[self setInteractionEnabled:NO];
[self setInteractionEnabled:NO forDownloading:YES];

NSString *versionId = PLProfiles.current.profiles[self.versionTextField.text][@"lastVersionId"];
NSDictionary *object = [remoteVersionList filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"(id == %@)", versionId]].firstObject;
if (!object) {
object = [localVersionList filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"(id == %@)", versionId]].firstObject;
}

[MinecraftResourceUtils downloadVersion:object callback:^(NSString *stage, NSProgress *mainProgress, NSProgress *progress) {
if (progress == nil && stage != nil) {
NSLog(@"[MCDL] %@", stage);
self.task = [MinecraftResourceDownloadTask new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__weak LauncherNavigationController *weakSelf = self;
self.task.handleError = ^{
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf setInteractionEnabled:YES forDownloading:YES];
weakSelf.task = nil;
weakSelf.progressVC = nil;
});
};
[self.task downloadVersion:object];
dispatch_async(dispatch_get_main_queue(), ^{
self.progressViewMain.observedProgress = self.task.progress;
[self.task.progress addObserver:self
forKeyPath:@"fractionCompleted"
options:NSKeyValueObservingOptionInitial
context:ProgressObserverContext];
});
});
}

- (void)performInstallOrShowDetails:(UIButton *)sender {
if (self.task) {
if (!self.progressVC) {
self.progressVC = [[DownloadProgressViewController alloc] initWithTask:self.task];
}
self.progressViewMain.observedProgress = mainProgress;
self.progressViewSub.observedProgress = progress;
if (stage == nil) {
sender.alpha = 1;
self.progressText.text = nil;
[self setInteractionEnabled:YES];
if (mainProgress != nil) {
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:self.progressVC];
nav.modalPresentationStyle = UIModalPresentationPopover;
nav.popoverPresentationController.sourceView = sender;
[self presentViewController:nav animated:YES completion:nil];
} else {
[self launchMinecraft:sender];
}
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == ProgressObserverContext) {
dispatch_async(dispatch_get_main_queue(), ^{
NSProgress *progress = object;
self.progressText.text = [NSString stringWithFormat:@"(%@) %@", progress.localizedAdditionalDescription, progress.localizedDescription];
if (progress.finished) {
self.progressViewMain.observedProgress = nil;
[self invokeAfterJITEnabled:^{
UIKit_launchMinecraftSurfaceVC();
UIKit_launchMinecraftSurfaceVC(self.task.verMetadata);
}];
}
return;
}
NSString *completed = [NSByteCountFormatter stringFromByteCount:progress.completedUnitCount countStyle:NSByteCountFormatterCountStyleMemory];
NSString *total = [NSByteCountFormatter stringFromByteCount:progress.totalUnitCount countStyle:NSByteCountFormatterCountStyleMemory];
self.progressText.text = [NSString stringWithFormat:@"%@ (%@ / %@)", stage, completed, total];
}];

//callback_LauncherViewController_installMinecraft("1.12.2");
});
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

- (void)invokeAfterJITEnabled:(void(^)(void))handler {
Expand Down
4 changes: 2 additions & 2 deletions Natives/LauncherPrefManageJREViewController.m
Expand Up @@ -94,7 +94,7 @@ - (void)actionImportRuntime {

- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url {
LauncherNavigationController *nav = (id)self.navigationController;
[nav setInteractionEnabled:NO];
[nav setInteractionEnabled:NO forDownloading:NO];

[url startAccessingSecurityScopedResource];
NSUInteger xzSize = [NSFileManager.defaultManager attributesOfItemAtPath:url.path error:nil].fileSize;
Expand Down Expand Up @@ -136,7 +136,7 @@ - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocum
vc.installingIndexPath = nil;
[vc.tableView reloadData];

[nav setInteractionEnabled:YES];
[nav setInteractionEnabled:YES forDownloading:NO];
nav.progressViewMain.observedProgress = nil;
nav.progressViewSub.observedProgress = nil;
nav.progressText.text = @"";
Expand Down
11 changes: 11 additions & 0 deletions Natives/MinecraftResourceDownloadTask.h
@@ -0,0 +1,11 @@
#import <UIKit/UIKit.h>

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

- (void)downloadVersion:(NSDictionary *)version;

@end

0 comments on commit d730232

Please sign in to comment.