@@ -9,6 +9,7 @@ use reqwest::Client;
9
9
use std:: {
10
10
fs:: { create_dir_all, remove_file, OpenOptions } ,
11
11
io:: Write ,
12
+ path:: PathBuf ,
12
13
} ;
13
14
use util:: {
14
15
cli:: SubCommand ,
@@ -27,7 +28,7 @@ async fn main() -> FResult<()> {
27
28
Ok ( _) => println ! ( "✓" ) ,
28
29
Err ( _) => {
29
30
return Err ( FError :: Quit {
30
- message : "Ferium requires an internet connection to work" . into ( ) ,
31
+ message : "× Ferium requires an internet connection to work" . into ( ) ,
31
32
} )
32
33
}
33
34
}
@@ -80,6 +81,15 @@ async fn main() -> FResult<()> {
80
81
Ok ( ( ) )
81
82
}
82
83
84
+ /// Fetch a mod file's path based on a `name` and `config`uration
85
+ fn get_mod_file_path ( config : & json:: Config , name : & str ) -> PathBuf {
86
+ let mut mod_file_path = config
87
+ . output_dir
88
+ . join ( format ! ( "{}-{}-{}" , name, config. loader, config. version) ) ;
89
+ mod_file_path. set_extension ( "jar" ) ;
90
+ mod_file_path
91
+ }
92
+
83
93
/// Check if `config`'s mods and repos are empty, and if so return an error
84
94
fn check_empty_config ( config : & json:: Config ) -> FResult < ( ) > {
85
95
if config. repos . is_empty ( ) && config. mod_slugs . is_empty ( ) {
@@ -199,8 +209,7 @@ async fn remove(client: &Client, config: &mut json::Config) -> FResult<()> {
199
209
let name = & items[ item_to_remove] ;
200
210
201
211
// Remove the mod from downloaded mods
202
- let mut mod_file_path = config. output_dir . join ( name) ;
203
- mod_file_path. set_extension ( "jar" ) ;
212
+ let mod_file_path = get_mod_file_path ( config, name) ;
204
213
let _ = remove_file ( mod_file_path) ;
205
214
206
215
// Store its name in a string
@@ -212,8 +221,7 @@ async fn remove(client: &Client, config: &mut json::Config) -> FResult<()> {
212
221
let name = & items[ item_to_remove] ;
213
222
214
223
// Remove the mod from downloaded mods
215
- let mut mod_file_path = config. output_dir . join ( name) ;
216
- mod_file_path. set_extension ( "jar" ) ;
224
+ let mod_file_path = get_mod_file_path ( config, name) ;
217
225
let _ = remove_file ( mod_file_path) ;
218
226
219
227
// Store its name in a string
@@ -231,7 +239,7 @@ async fn remove(client: &Client, config: &mut json::Config) -> FResult<()> {
231
239
if !items_removed. is_empty ( ) {
232
240
// Remove trailing ", "
233
241
items_removed. truncate ( items_removed. len ( ) - 2 ) ;
234
- println ! ( "Removed {} from config " , items_removed) ;
242
+ println ! ( "Removed {}" , items_removed) ;
235
243
}
236
244
237
245
Ok ( ( ) )
@@ -244,18 +252,18 @@ async fn add_repo_github(
244
252
repo_name : String ,
245
253
config : & mut json:: Config ,
246
254
) -> FResult < ( ) > {
255
+ eprint ! ( "Adding repo {}/{}... " , owner, repo_name) ;
256
+
247
257
// Check if repo has already been added
248
258
if config. repos . contains ( & json:: Repo {
249
259
owner : owner. clone ( ) ,
250
260
name : repo_name. clone ( ) ,
251
261
} ) {
252
262
return Err ( FError :: Quit {
253
- message : "Repo already added to config!" . into ( ) ,
263
+ message : "× Repsitory already added to config!" . into ( ) ,
254
264
} ) ;
255
265
}
256
266
257
- eprint ! ( "Adding repo {}/{}... " , owner, repo_name) ;
258
-
259
267
// Get repository metadata
260
268
let repo = get_repository ( client, & owner, & repo_name) . await ?;
261
269
@@ -267,7 +275,7 @@ async fn add_repo_github(
267
275
// Check if the releases contain JAR files (a mod file)
268
276
' outer: for release in releases {
269
277
for asset in & release. assets {
270
- if asset. name . contains ( ". jar" ) {
278
+ if asset. name . contains ( "jar" ) {
271
279
// If JAR release is found, set flag to true and break
272
280
contains_jar_asset = true ;
273
281
break ' outer;
@@ -284,7 +292,7 @@ async fn add_repo_github(
284
292
println ! ( "✓" )
285
293
} else {
286
294
return Err ( FError :: Quit {
287
- message : "Repository does not release mods!" . into ( ) ,
295
+ message : "× Repository does not release mods!" . into ( ) ,
288
296
} ) ;
289
297
}
290
298
@@ -297,15 +305,15 @@ async fn add_mod_modrinth(
297
305
mod_id : String ,
298
306
config : & mut json:: Config ,
299
307
) -> FResult < ( ) > {
308
+ eprint ! ( "Adding mod ID {}... " , mod_id) ;
309
+
300
310
// Check if mod has already been added
301
311
if config. mod_slugs . contains ( & mod_id) {
302
312
return Err ( FError :: Quit {
303
- message : "Mod already added to config!" . into ( ) ,
313
+ message : "× Mod already added to config!" . into ( ) ,
304
314
} ) ;
305
315
}
306
316
307
- eprint ! ( "Adding mod {}... " , mod_id) ;
308
-
309
317
// Check if mod exists
310
318
match get_mod ( client, & mod_id) . await {
311
319
Ok ( mod_) => {
@@ -316,7 +324,7 @@ async fn add_mod_modrinth(
316
324
Err ( _) => {
317
325
// Else return an error
318
326
return Err ( FError :: Quit {
319
- message : format ! ( "Mod with ID {} does not exist!" , mod_id) ,
327
+ message : format ! ( "× Mod with ID `{}` does not exist!" , mod_id) ,
320
328
} ) ;
321
329
}
322
330
} ;
@@ -332,14 +340,16 @@ async fn list(client: &Client, config: &json::Config) -> FResult<()> {
332
340
333
341
// Print mod data formatted
334
342
println ! (
335
- "- {}
336
- \r {}
343
+ "- {} (Modrinth)
344
+ \r {}\n
345
+ \r Link: https://modrinth.com/mod/{}
337
346
\r Downloads: {}
338
347
\r Client side: {}
339
348
\r Server side: {}
340
349
\r License: {}\n " ,
341
350
mod_. title,
342
351
mod_. description,
352
+ mod_. slug,
343
353
mod_. downloads,
344
354
mod_. client_side,
345
355
mod_. server_side,
@@ -350,15 +360,30 @@ async fn list(client: &Client, config: &json::Config) -> FResult<()> {
350
360
for repo_name in & config. repos {
351
361
// Get repository metadata
352
362
let repo = get_repository ( client, & repo_name. owner , & repo_name. name ) . await ?;
363
+ let releases = get_releases ( client, & repo) . await ?;
364
+ let mut downloads = 0 ;
365
+
366
+ // Calculate number of downloads
367
+ for release in releases {
368
+ for asset in release. assets {
369
+ downloads += asset. download_count ;
370
+ }
371
+ }
353
372
354
373
// Print repository data formatted
355
374
println ! (
356
- "- {}
357
- \r {}
358
- \r Stars: {}
359
- \r Developer: {}
360
- \r License: {}\n " ,
361
- repo. name, repo. description, repo. stargazers_count, repo. owner. login, repo. license. name,
375
+ "- {} (GitHub)
376
+ \r {}\n
377
+ \r Link: {}
378
+ \r Downloads: {}
379
+ \r Developer: {}
380
+ \r License: {}\n " ,
381
+ repo. name,
382
+ repo. description,
383
+ repo. html_url,
384
+ downloads,
385
+ repo. owner. login,
386
+ repo. license. name,
362
387
)
363
388
}
364
389
@@ -370,44 +395,71 @@ async fn upgrade_github(client: &Client, config: &json::Config) -> FResult<()> {
370
395
for repo_name in & config. repos {
371
396
println ! ( "Downloading {}" , repo_name. name) ;
372
397
eprint ! ( " [1] Getting release information... " ) ;
373
- // Get mod's repository
398
+
374
399
let repository = get_repository ( client, & repo_name. owner , & repo_name. name ) . await ?;
375
- // Get releases
376
400
let releases = get_releases ( client, & repository) . await ?;
377
401
378
- let mut latest_release: Option < & octorok:: structs:: Release > = None ;
402
+ // A vector of assets that are compatible
403
+ let mut asset_candidates: Vec < & octorok:: structs:: Asset > = Vec :: new ( ) ;
404
+ // Whether the mod specifies the mod loader in its Assets' names
405
+ let mut specifies_loader = false ;
379
406
380
- // Try to get the latest compatible release
407
+ // Try to get the latest compatible assets
381
408
for release in & releases {
382
- if release. name . contains ( & config . version ) {
383
- latest_release = Some ( release ) ;
409
+ // If a release with compatible assets has been found, stop searching older releases
410
+ if !asset_candidates . is_empty ( ) {
384
411
break ;
385
412
}
386
- }
387
413
388
- let latest_release = match latest_release {
389
- // If a compatible release was found, install it
390
- Some ( release) => {
391
- println ! ( "✓" ) ;
392
- release
393
- }
394
- // If not, default to the latest one
395
- None => {
396
- println ! (
397
- "✓ Warning! Did not find release with version in name. Defaulting to latest"
398
- ) ;
399
- & releases[ 0 ]
414
+ for asset in & release. assets {
415
+ // If the asset specifies the mod loader, set the `specifies_loader` flag to true
416
+ if asset. name . to_lowercase ( ) . contains ( "fabric" )
417
+ || asset. name . to_lowercase ( ) . contains ( "forge" )
418
+ {
419
+ specifies_loader = true ;
420
+ }
421
+
422
+ if asset
423
+ . name
424
+ // Check that the asset supports the user's specified version
425
+ . contains ( & wrappers:: remove_minor_version ( & config. version ) ?)
426
+ // Check that the asset is a JAR file
427
+ && asset. name . contains ( "jar" )
428
+ // If the asset specifies a mod loader, check for it, if not don't check and return true
429
+ && !( specifies_loader && !asset. name . to_lowercase ( ) . contains ( & config. loader ) )
430
+ {
431
+ // Specify this asset as a compatible asset
432
+ asset_candidates. push ( asset) ;
433
+ }
400
434
}
435
+ }
436
+
437
+ // If 1 compatible asset was found, use it
438
+ let asset_to_download = if asset_candidates. len ( ) == 1 {
439
+ println ! ( "✓" ) ;
440
+ asset_candidates[ 0 ]
441
+ // If none were found, throw an error
442
+ } else if asset_candidates. len ( ) == 0 {
443
+ return Err ( FError :: Quit {
444
+ message : "× Could not find a compatible asset to download" . into ( ) ,
445
+ } ) ;
446
+ // If more than 1 was found, let the user select which one to use
447
+ } else {
448
+ println ! ( "✓" ) ;
449
+ println ! ( "Select the asset to downloaded:" ) ;
450
+ let selection = Select :: with_theme ( & ColorfulTheme :: default ( ) )
451
+ . items ( & asset_candidates)
452
+ . interact ( ) ?;
453
+ asset_candidates[ selection]
401
454
} ;
402
455
403
- eprint ! ( " [2] Downloading {}... " , latest_release . name) ;
456
+ eprint ! ( " [2] Downloading {}... " , asset_to_download . name) ;
404
457
405
- // Compute mod file's output path
406
- let mut mod_file_path = config. output_dir . join ( & repository. name ) ;
407
- mod_file_path. set_extension ( "jar" ) ;
458
+ // Compute output mod file's path
459
+ let mod_file_path = get_mod_file_path ( config, & repository. name ) ;
408
460
409
461
// Get file contents
410
- let contents = download_release ( client, latest_release ) . await ?;
462
+ let contents = download_asset ( client, asset_to_download ) . await ?;
411
463
412
464
// Open the mod JAR file
413
465
let mut mod_file = OpenOptions :: new ( )
@@ -438,9 +490,11 @@ async fn upgrade_modrinth(client: &Client, config: &json::Config) -> FResult<()>
438
490
439
491
let mut latest_version: Option < labrinth:: structs:: Version > = None ;
440
492
441
- // Check if a version compatible with the game version specified in the config is available
493
+ // Check if a version compatible with the game version and mod loader specified in the config is available
442
494
for version in versions {
443
- if version. game_versions . contains ( & config. version ) {
495
+ if version. game_versions . contains ( & config. version )
496
+ && version. loaders . contains ( & config. loader )
497
+ {
444
498
latest_version = Some ( version) ;
445
499
break ;
446
500
}
@@ -452,8 +506,8 @@ async fn upgrade_modrinth(client: &Client, config: &json::Config) -> FResult<()>
452
506
None => {
453
507
return Err ( FError :: Quit {
454
508
message : format ! (
455
- "No version of {} is compatible for Minecraft {}" ,
456
- mod_. title, config. version,
509
+ "× No version of {} is compatible for {} {}" ,
510
+ mod_. title, config. loader , config . version,
457
511
) ,
458
512
} ) ;
459
513
}
@@ -463,11 +517,11 @@ async fn upgrade_modrinth(client: &Client, config: &json::Config) -> FResult<()>
463
517
464
518
eprint ! ( " [2] Downloading {}... " , latest_version. name) ;
465
519
466
- let mut mod_file_path = config . output_dir . join ( mod_ . title ) ;
467
- mod_file_path. set_extension ( "jar" ) ;
520
+ // Compute output mod file's path
521
+ let mod_file_path = get_mod_file_path ( config , & mod_ . title ) ;
468
522
469
523
// Get file contents
470
- let contents = download_version ( client, latest_version) . await ?;
524
+ let contents = download_version_file ( client, & latest_version. files [ 0 ] ) . await ?;
471
525
472
526
// Open mod JAR file
473
527
let mut mod_file = OpenOptions :: new ( )
0 commit comments