Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for downloading and installing informal modrinth packs #272

Merged
merged 24 commits into from
Aug 14, 2023

Conversation

kMaiSmith
Copy link
Contributor

@kMaiSmith kMaiSmith commented Jul 29, 2023

Backs #271

Purpose

Decouple the Modrinth .mrpack fetching mechanism from the pack installation mechanism to enable installing packs beyond the Modrinth API

Strategy

  • Extract all business logic captured in the InstallModrinthModpackCommand class into its own package, leaving the InstallModrinthModpackCommand a thin wrapper for the new package interface
  • Separate modpack fetching logic into the ModrinthAPIPackFetcher class backed by the IModrinthPackFetcher interface
  • Add ModrinthHttpPackFetcher to fetch non-Modrinth-API packs also implementing the IModrinthPackFetcher
  • Add ModrinthPackInstallerclass to capture.mrpack` extraction logic
  • Add new ModrinthModpack base class which is responsible for coordinating the fetch and install actions
  • Lots of tests

Checklist

  • Modrinth API modpack fetcher class fetches .mrpack from Modrinth API
  • Modrinth modpack installation/extraction class
  • Refactor InstallModrinthModpackCommand to use new logic
  • Add Http modpack fetcher
  • Migrate modpack code back to modrinth package

@itzg
Copy link
Owner

itzg commented Jul 29, 2023

I'm sorry, these changes are way too disruptive. You'll need to be extremely minimal in your refactoring.

Specifically,

  • Do not create another package
  • Do not name things with "I" prefix. I dislike that convention and it is not a modern naming convention

@kMaiSmith
Copy link
Contributor Author

I felt the need for restructure to get my head around the code for tests, but now with the tests i can comfortably undo many of code-wise refactors.

Do not create another package

I will re-merge these changes into the original package and see how i can minimize restructuring.

I will propose a class structure to minimize structural changes:

  • Utilize the InstallModrinthModpackCommand to act as the primary logic facilitator (in stead of the new ModrinthModpack)
  • Utilize the existing data fields on the InstallModrinthModpackCommand to pass around configurations to the fetcher and installer class
  • Move the ModrinthApiPackFetcher (+ Interface) and ModrinthPackInstaller into the modrinth package

Do not name things with "I" prefix

Noted. What are the modern practices for this? Would I just name the interface without the I?


I can tell I'm not quite getting the Reactive Streams just yet; I'm in the middle of giving the docs a read-through, and i would appreciate feedback on the way the ModrinthApiPackFetcher was put together especially in regards to the streams processing and Java best practices.

I expect I will be leaving the ModrinthPackInstaller logic mostly untouched as it should apply regardless of the source of the pack, but i do feel compelled to pursue extracting it so i can write some tests around it.


I appreciate and welcome any additional feedback, i do not aim to be disruptive. 😄

 - Remove ModrinthModpack class and config
 - Use InstallModrinthModpackCommand as source of config
@itzg
Copy link
Owner

itzg commented Jul 29, 2023

Sorry for my less-than-constructive initial feedback. It didn't help that I only had a few minutes at the time to reply via the mobile app. I also apologize for the total lack of existing unit tests for the Modrinth commands. I was going to suggest I write those before you proceed, but you beat me to it 😀. Please refer to src/test/java/me/itzg/helpers/fabric/FabricLauncherInstallerTest.java to see how I have been doing command/integration level unit tests without imposing extra design layers. Keep in mind, this is only a helper tool and over-design is a code smell in this case.

I prefer the sound of your revised strategy. In fact, I am expecting a majority or all of the changes to reside in InstallModrinthModpackCommand. Your goal is to introduce as few changes as possible to get from the picocli option for the mrpack file/URL and call the existing me.itzg.helpers.modrinth.InstallModrinthModpackCommand#processModpackZip.

As for avoiding "I" prefix. Yes, just name interfaces sensibly and intuitively. It shouldn't take a prefix to identify it's an interface...and in most cases the object oriented design should make interface/class non-important to the user/caller.

For the reactive calls, I recommend glancing through https://projectreactor.io/docs/core/release/reference/ and/or https://projectreactor.io/learn.

Copy link
Owner

@itzg itzg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wanted to pass along a few initial code comments.

 - No longer passing InstallModrinthModpackCommand as config
 - Expect ModrinthApiClient to be passed in, vs constructed
 - no longer consume InstallModrinthModpackCommand as config
 - add initial unit test demonstrating minimum assembly and extraction of modrinth pack
private final boolean forceModloaderReinstall;
private final SharedFetchArgs sharedFetchArgs;

public ModrinthPackInstaller(ModrinthApiClient apiClient, SharedFetchArgs sharedFetchArgs, Path zipFile, Path outputDirectory, Path resultsFile, boolean forceModloaderReinstall) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just thinking, rather pass in SharedFetchArgs it would be cleaner to pass in SharedFetch.Options.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part confuses me: the only consumer of this in the whole class is the Quilt loader installer; why does the Quilt loader need it while Forge and Fabric don't? (I acknowledge this question is for my curiosity and desire for architectural consistency; having spent so much time in this code now, i want to come back to this class and clean it up but that falls beyond the scope of what i am trying to achieve here)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair question 🙂. I have been evolving the design over time, so Quilt is the newer of the three implementations with cleaner shared fetch usage. If you're interested in a little extra cleanup, the SharedFetch.Options should really be passed in rather than the default used here from the Options.builder().build() call.

try (SharedFetch sharedFetch = sharedFetch("fabric", Options.builder().build())) {

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...and Forge installer is "pre shared fetch", so it's calling me.itzg.helpers.http.Fetch#fetch to create one-off instances.

@itzg
Copy link
Owner

itzg commented Jul 29, 2023

The latest changes are looking really good. Thanks for jumping into this feature BTW.

 - private functions in ModrinthModpackIndex use apiClient field
 - ModrinthPackInstaller returns both Index and list of installed files
    (Required for generating manifest)
 - ModrinthTestHelpers.createModrinthPack returns byte array
    (Enables gluing whole test arch without writing tempfiles)
Copy link
Owner

@itzg itzg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just taking a quick look at a recent change

src/test/java/me/itzg/helpers/modrinth/TestProjectRef.java Outdated Show resolved Hide resolved
void testProjectRefHasProjectSlug() {
projectRefUT = new ProjectRef(this.expectedSlug, null);

assertEquals(expectedSlug, projectRefUT.getIdOrSlug());
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please switch to https://assertj.github.io/doc/ rather than using the junit assertions. It'll be consistent with the rest of the project and they are much more expressive.

@kMaiSmith kMaiSmith changed the title WIP: Add support for downloading and installing informal modrinth packs Add support for downloading and installing informal modrinth packs Aug 11, 2023
@kMaiSmith kMaiSmith marked this pull request as ready for review August 11, 2023 22:12
@kMaiSmith
Copy link
Contributor Author

Just got it tested out and i was able to use it to download my self-hosted modpack. Due to the nature of my pack i would prefer not to share its URL for validation testing; if necessary I can assemble another one and share its URL with you.

What are the next steps for getting this merged?

@itzg
Copy link
Owner

itzg commented Aug 12, 2023

It seems like unit tests will be sufficient, so don't worry about providing an additional mrpack URL for further manual testing.

From here the only remaining step is code review and iterating on changes from that.

@itzg
Copy link
Owner

itzg commented Aug 12, 2023

Sorry, I needed to put a fix in for me.itzg.helpers.modrinth.InstallModrinthModpackCommand#extractOverrides. I can work on resolving the conflict in your branch if needed.

@itzg itzg self-requested a review August 12, 2023 17:48
Copy link
Owner

@itzg itzg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great overall! Very nice refactorings. Please switch to AssertJ -- I provided a specific comment where it would have helped even more so.

@kMaiSmith
Copy link
Contributor Author

I just took the body of the mainline extractOverrides method, everything is still functioning as i expected. AssertJ now proveds the assertions. ModrinthPackInstaller now consumes the Shared Fetch Options. I look forward to closing this out 😄

@kMaiSmith kMaiSmith requested a review from itzg August 14, 2023 16:46
Copy link
Owner

@itzg itzg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All looks great. I'll get this merged and cut a release. Don't worry if you find any follow up issues during final integration with the docker image repo -- can iterate on a follow up PR as needed.

@itzg itzg added the enhancement New feature or request label Aug 14, 2023
@itzg itzg merged commit e5e6d46 into itzg:master Aug 14, 2023
1 check passed
@itzg
Copy link
Owner

itzg commented Aug 14, 2023

)
.doOnNext(version -> log.debug("Resolved version={} from projectRef={}", version.getVersionNumber(), projectRef))
.publishOn(Schedulers.boundedElastic()) // since next item does I/O
.filter(version -> needsInstall(prevManifest, project, version))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kMaiSmith I just realized you seem to have removed this "force synchronize" feature. Can you create a follow up PR to put it back?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created #290 to track this

@itzg
Copy link
Owner

itzg commented Aug 18, 2023

@kMaiSmith I needed to revert this PR in #292. It has caused a couple of regressions one of which is critical since it prevents users from restart their container with a previously installed modpack.

@itzg
Copy link
Owner

itzg commented Aug 18, 2023

...I will revisit this over the weekend to see if I can fix the versionId being null in the resulting manifest.

@drewgingerich
Copy link

drewgingerich commented Nov 25, 2023

Hi there! I'm curious about the status of this PR. I'm looking to share an informal modrinth pack with friends by storing the .mrpack file in a GitHub repo, and then giving the URL to the minecraft docker container via env var to fetch and install. Going through a whole formal modpack release process on modrinth feels like overkill since I don't want to share it publicly.

As an aside, the existing feature to auto-fetch modpacks from modrinth is awesome. I'm coming back to minecraft after a few years and I'm amazed at how slick self-hosting a modded minecraft server is nowadays!

@itzg
Copy link
Owner

itzg commented Nov 25, 2023

This MR is merged and ready to use in the image.

The only thing left to implement is handling local mrpack files, but your use case is covered with hosting in GitHub repo.

@itzg
Copy link
Owner

itzg commented Nov 25, 2023

@itzg
Copy link
Owner

itzg commented Nov 25, 2023

...and I misspoke, the final integration using mrpack URLs may not have been completed.

@drewgingerich
Copy link

Thanks for taking a look! I was wondering if it had slipped through the cracks since it was merged and then reverted, and GitHub doesn't really make that state visible.

@itzg
Copy link
Owner

itzg commented Nov 27, 2023

FYI changes in this PR were restored in #293

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants