Skip to content

Commit 0c06adf

Browse files
Speed up mod update checks
1 parent 3555614 commit 0c06adf

4 files changed

Lines changed: 112 additions & 17 deletions

File tree

Natives/installer/modpack/CurseForgeAPI.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@
99
- (NSArray *)fileMetadataForFileIDs:(NSArray<NSNumber *> *)fileIDs;
1010
- (NSString *)manualDownloadPageURLForFile:(NSDictionary *)file projectID:(NSNumber *)projectID cache:(NSMutableDictionary *)cache;
1111
- (NSArray *)filesForModID:(id)modID;
12+
- (NSArray *)filesForModID:(id)modID gameVersion:(NSString *)gameVersion modLoaderType:(NSNumber *)modLoaderType;
1213
@end

Natives/installer/modpack/CurseForgeAPI.m

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -254,14 +254,25 @@ - (NSArray *)fileMetadataForFileIDs:(NSArray<NSNumber *> *)fileIDs {
254254
}
255255

256256
- (NSArray *)filesForModID:(id)modID {
257+
return [self filesForModID:modID gameVersion:nil modLoaderType:nil];
258+
}
259+
260+
- (NSArray *)filesForModID:(id)modID gameVersion:(NSString *)gameVersion modLoaderType:(NSNumber *)modLoaderType {
257261
NSMutableArray *result = [NSMutableArray new];
258262
NSUInteger index = 0;
259263
while (true) {
264+
NSMutableDictionary *params = @{
265+
@"pageSize": @(kCurseForgePageSize),
266+
@"index": @(index)
267+
}.mutableCopy;
268+
if ([gameVersion isKindOfClass:NSString.class] && gameVersion.length > 0) {
269+
params[@"gameVersion"] = gameVersion;
270+
}
271+
if ([modLoaderType isKindOfClass:NSNumber.class]) {
272+
params[@"modLoaderType"] = modLoaderType;
273+
}
260274
NSDictionary *response = [self getEndpoint:[NSString stringWithFormat:@"mods/%@/files", modID]
261-
params:@{
262-
@"pageSize": @(kCurseForgePageSize),
263-
@"index": @(index)
264-
}];
275+
params:params];
265276
if (!response) {
266277
return nil;
267278
}

Natives/installer/mods/ModManagerAPI.m

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,16 @@ - (NSDictionary *)normalizedModrinthVersion:(NSDictionary *)version project:(NSD
280280

281281
- (NSArray<NSDictionary *> *)modrinthVersionsForProject:(NSDictionary *)project profileInfo:(NSDictionary *)profileInfo {
282282
NSString *projectID = [project[@"projectId"] description];
283-
NSArray *versions = [self.modrinth getEndpoint:[NSString stringWithFormat:@"project/%@/version", projectID] params:nil];
283+
NSMutableDictionary *params = @{@"include_changelog": @NO}.mutableCopy;
284+
NSString *mcVersion = profileInfo[@"minecraftVersion"];
285+
NSString *loader = profileInfo[@"loader"];
286+
if ([mcVersion isKindOfClass:NSString.class] && mcVersion.length > 0) {
287+
params[@"game_versions"] = [self jsonStringForArray:@[mcVersion]];
288+
}
289+
if ([loader isKindOfClass:NSString.class] && loader.length > 0 && ![loader isEqualToString:@"vanilla"]) {
290+
params[@"loaders"] = [self jsonStringForArray:@[loader.lowercaseString]];
291+
}
292+
NSArray *versions = [self.modrinth getEndpoint:[NSString stringWithFormat:@"project/%@/version", projectID] params:params];
284293
if (![versions isKindOfClass:NSArray.class]) {
285294
self.lastError = self.modrinth.lastError ?: [self errorWithDescription:@"Modrinth returned an invalid version list."];
286295
return nil;
@@ -297,6 +306,15 @@ - (NSDictionary *)normalizedModrinthVersion:(NSDictionary *)version project:(NSD
297306
return result;
298307
}
299308

309+
- (NSNumber *)curseForgeLoaderTypeForProfileInfo:(NSDictionary *)profileInfo {
310+
NSString *loader = [profileInfo[@"loader"] isKindOfClass:NSString.class] ? [profileInfo[@"loader"] lowercaseString] : @"";
311+
if ([loader isEqualToString:@"forge"]) return @1;
312+
if ([loader isEqualToString:@"fabric"]) return @4;
313+
if ([loader isEqualToString:@"quilt"]) return @5;
314+
if ([loader isEqualToString:@"neoforge"]) return @6;
315+
return nil;
316+
}
317+
300318
- (BOOL)curseForgeFile:(NSDictionary *)file matchesProfileInfo:(NSDictionary *)profileInfo {
301319
BOOL isServerPack = [file[@"isServerPack"] respondsToSelector:@selector(boolValue)] && [file[@"isServerPack"] boolValue];
302320
id available = file[@"isAvailable"];
@@ -430,7 +448,12 @@ - (NSDictionary *)curseForgeProjectInfoForID:(NSNumber *)projectID fallback:(NSD
430448
return nil;
431449
}
432450
NSNumber *projectID = [project[@"projectId"] isKindOfClass:NSNumber.class] ? project[@"projectId"] : @([[project[@"projectId"] description] integerValue]);
433-
NSArray *files = [self.curseforge filesForModID:projectID];
451+
NSString *mcVersion = [profileInfo[@"minecraftVersion"] isKindOfClass:NSString.class] ? profileInfo[@"minecraftVersion"] : nil;
452+
NSNumber *loaderType = [self curseForgeLoaderTypeForProfileInfo:profileInfo];
453+
NSArray *files = [self.curseforge filesForModID:projectID gameVersion:mcVersion modLoaderType:loaderType];
454+
if (files.count == 0 && loaderType) {
455+
files = [self.curseforge filesForModID:projectID gameVersion:mcVersion modLoaderType:nil];
456+
}
434457
if (![files isKindOfClass:NSArray.class]) {
435458
self.lastError = self.curseforge.lastError ?: [self errorWithDescription:@"CurseForge returned an invalid file list."];
436459
return nil;

Natives/installer/mods/ModManagerViewController.m

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ @interface ModManagerViewController ()
1919
@property(nonatomic) NSArray<NSMutableDictionary *> *mods;
2020
@property(nonatomic) BOOL checkingUpdates;
2121
@property(nonatomic) BOOL checkingDependencies;
22+
@property(nonatomic) NSInteger updateCheckCompleted;
23+
@property(nonatomic) NSInteger updateCheckTotal;
2224
@end
2325

2426
@implementation ModManagerViewController
@@ -73,6 +75,20 @@ - (void)actionAddMod {
7375
[self.navigationController pushViewController:vc animated:YES];
7476
}
7577

78+
- (void)updateBusyPrompt {
79+
if (self.checkingDependencies) {
80+
self.navigationItem.prompt = @"Checking dependencies...";
81+
} else if (self.checkingUpdates) {
82+
if (self.updateCheckTotal > 0) {
83+
self.navigationItem.prompt = [NSString stringWithFormat:@"Checking updates %ld/%ld...", (long)self.updateCheckCompleted, (long)self.updateCheckTotal];
84+
} else {
85+
self.navigationItem.prompt = @"Checking updates...";
86+
}
87+
} else {
88+
self.navigationItem.prompt = nil;
89+
}
90+
}
91+
7692
- (void)setCheckingUpdates:(BOOL)checkingUpdates {
7793
_checkingUpdates = checkingUpdates;
7894
UIBarButtonItem *updateItem = self.navigationItem.rightBarButtonItems.lastObject;
@@ -85,36 +101,80 @@ - (void)setCheckingUpdates:(BOOL)checkingUpdates {
85101
updateItem.customView = nil;
86102
updateItem.image = [UIImage systemImageNamed:@"arrow.triangle.2.circlepath"];
87103
}
104+
[self updateBusyPrompt];
88105
}
89106

90107
- (void)setCheckingDependencies:(BOOL)checkingDependencies {
91108
_checkingDependencies = checkingDependencies;
92109
self.tableView.userInteractionEnabled = !checkingDependencies;
93-
self.navigationItem.prompt = checkingDependencies ? @"Checking dependencies..." : nil;
110+
[self updateBusyPrompt];
111+
}
112+
113+
- (BOOL)modCanCheckForUpdate:(NSDictionary *)mod {
114+
NSString *source = mod[@"source"];
115+
return [source isKindOfClass:NSString.class] &&
116+
![source isEqualToString:@"manual"] &&
117+
mod[@"projectId"] &&
118+
![mod[@"missing"] boolValue];
94119
}
95120

96121
- (void)actionCheckUpdates {
97122
if (self.checkingUpdates) {
98123
return;
99124
}
100125
NSArray *mods = [self.store installedMods];
126+
NSMutableArray *updatedMods = [NSMutableArray arrayWithCapacity:mods.count];
127+
NSMutableArray<NSNumber *> *updateIndexes = [NSMutableArray new];
128+
for (NSUInteger i = 0; i < mods.count; i++) {
129+
NSDictionary *mod = mods[i];
130+
[updatedMods addObject:mod.mutableCopy];
131+
if ([self modCanCheckForUpdate:mod]) {
132+
[updateIndexes addObject:@(i)];
133+
}
134+
}
135+
self.updateCheckCompleted = 0;
136+
self.updateCheckTotal = updateIndexes.count;
101137
self.checkingUpdates = YES;
102-
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
103-
NSMutableArray *updatedMods = [NSMutableArray new];
104-
for (NSMutableDictionary *mod in mods) {
138+
if (updateIndexes.count == 0) {
139+
self.checkingUpdates = NO;
140+
self.mods = updatedMods;
141+
[self.tableView reloadData];
142+
return;
143+
}
144+
145+
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
146+
dispatch_group_t group = dispatch_group_create();
147+
dispatch_semaphore_t limit = dispatch_semaphore_create(4);
148+
NSDictionary *profileInfo = self.store.profileInfo;
149+
for (NSNumber *indexNumber in updateIndexes) {
150+
dispatch_group_async(group, queue, ^{
151+
dispatch_semaphore_wait(limit, DISPATCH_TIME_FOREVER);
152+
NSUInteger index = indexNumber.unsignedIntegerValue;
153+
NSDictionary *mod = mods[index];
154+
ModManagerAPI *api = [ModManagerAPI new];
105155
NSError *error = nil;
106-
NSDictionary *update = [self.api latestVersionForInstalledMod:mod profileInfo:self.store.profileInfo error:&error];
156+
NSDictionary *update = [api latestVersionForInstalledMod:mod profileInfo:profileInfo error:&error];
107157
NSMutableDictionary *copy = mod.mutableCopy;
108158
if (update) {
109159
copy[@"availableUpdate"] = update;
160+
} else {
161+
[copy removeObjectForKey:@"availableUpdate"];
110162
}
111-
[updatedMods addObject:copy];
112-
}
113-
dispatch_async(dispatch_get_main_queue(), ^{
114-
self.checkingUpdates = NO;
115-
self.mods = updatedMods;
116-
[self.tableView reloadData];
163+
@synchronized (updatedMods) {
164+
updatedMods[index] = copy;
165+
}
166+
dispatch_async(dispatch_get_main_queue(), ^{
167+
self.updateCheckCompleted += 1;
168+
[self updateBusyPrompt];
169+
});
170+
dispatch_semaphore_signal(limit);
117171
});
172+
}
173+
174+
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
175+
self.checkingUpdates = NO;
176+
self.mods = updatedMods;
177+
[self.tableView reloadData];
118178
});
119179
}
120180

0 commit comments

Comments
 (0)