11package org.sinytra.probe.core.service
22
3+ import kotlinx.coroutines.coroutineScope
4+ import kotlinx.io.files.FileNotFoundException
35import org.slf4j.LoggerFactory
46import java.io.FileOutputStream
57import java.net.URI
68import java.nio.channels.Channels
9+ import java.nio.file.FileSystems
710import java.nio.file.Path
11+ import java.util.jar.Attributes
12+ import java.util.jar.Manifest
813import javax.xml.parsers.DocumentBuilderFactory
9- import kotlin.io.path.Path
10- import kotlin.io.path.createParentDirectories
11- import kotlin.io.path.div
12- import kotlin.io.path.exists
14+ import kotlin.io.path.*
1315
14- fun getMavenUrl (repo : String , group : String , name : String , version : String , classifier : String? = null): String {
15- return " $repo /${group.replace(' .' , ' /' )} /$name /$version /$name -$version${classifier?.let { " -$it " }} .jar"
16- }
16+ data class Library (
17+ val group : String , val name : String , val classifier : String? , val repository : String ,
18+ val versionFilter : (gameVersion: String , version: String ) -> Boolean
19+ )
1720
18- data class TransformLib (val path : Path , val version : String )
21+ data class ResolvedLibrary (val path : Path , val version : String )
1922
20- class DownloaderService (private val baseDir : Path , private val initialTransformerVersion : String? , private val useProvided : Boolean = true ) {
23+ class DownloaderService (
24+ private val baseDir : Path ,
25+ private val cache : CacheService ,
26+ private val useCache : Boolean = true
27+ ) {
2128 companion object {
2229 private val LOGGER = LoggerFactory .getLogger(DownloaderService ::class .java)
30+ private const val NEO_MAVEN = " https://maven.neoforged.net/releases"
2331 private const val SINYTRA_MAVEN = " https://maven.sinytra.org"
2432
33+ private val TRANSFORMER_LIB = Library (" org.sinytra.connector" , " transformer" , " all" , SINYTRA_MAVEN )
34+ { gameVersion, version ->
35+ version.contains(" +" ) && version.split(" +" )[1 ] == gameVersion
36+ }
37+ private val NEO_UNIVERSAL_LIB = Library (" net.neoforged" , " neoforge" , " universal" , NEO_MAVEN )
38+ { gameVersion, version ->
39+ val qualifier = gameVersion.split(" ." , limit = 2 )[1 ]
40+ version.startsWith(" $qualifier ." )
41+ }
42+ private val NFRT_LIB = Library (" net.neoforged" , " neoform-runtime" , " all" , NEO_MAVEN ) { _, _ -> true }
43+
2544 fun downloadFile (url : String , dest : Path ) {
2645 if (! dest.exists()) {
2746 LOGGER .info(" Downloading file $url to $dest " )
@@ -35,54 +54,100 @@ class DownloaderService(private val baseDir: Path, private val initialTransforme
3554 }
3655 }
3756
38- var activeTransformerVersion: String? = null
39- private set
57+ suspend fun getTransformerLib (gameVersion : String ): ResolvedLibrary {
58+ return getLibrary(TRANSFORMER_LIB , gameVersion)
59+ ? : throw RuntimeException (" Failed to resolve transformer library" )
60+ }
61+
62+ suspend fun getNeoForgeUniversalLib (gameVersion : String ): ResolvedLibrary {
63+ return getLibrary(NEO_UNIVERSAL_LIB , gameVersion)
64+ ? : throw RuntimeException (" Failed to resolve NeoForge Universal library" )
65+ }
66+
67+ suspend fun getNFRTRuntime (gameVersion : String , version : String ): ResolvedLibrary {
68+ return getLibrary(NFRT_LIB , gameVersion, version)
69+ ? : throw RuntimeException (" Failed to resolve NFRT library" )
70+ }
71+
72+ suspend fun clearCache (gameVersion : String ) {
73+ val libraries = listOf (TRANSFORMER_LIB , NEO_UNIVERSAL_LIB , NFRT_LIB )
74+ for (library in libraries) {
75+ val key = " probe:library:${library.name} :game:$gameVersion "
4076
41- fun getTransformLib (): TransformLib {
42- val provided = System .getProperty(" org.sinytra.transformer.path" )
43- if (useProvided && provided != null ) {
44- val version = initialTransformerVersion ? : throw RuntimeException (" Transformer version must be set" )
45- return TransformLib (Path (provided), version)
77+ cache.del(key)
4678 }
79+ }
4780
48- if (activeTransformerVersion == null ) {
49- activeTransformerVersion = computeTransformerVersion()
81+ private suspend fun getLibrary (library : Library , gameVersion : String , libVersion : String? = null): ResolvedLibrary ? {
82+ val property = gameVersion.replace(" ." , " _" )
83+ val provided = System .getProperty(" org.sinytra.probe.lib.${library.name} .$property .path" )
84+ if (provided != null ) {
85+ val path = Path (provided).takeIf { it.exists() } ? : throw FileNotFoundException (provided)
86+ val version = readImplementationVersion(path) ? : throw RuntimeException (" Library '${library.name} ' version must be set" )
87+ return ResolvedLibrary (path, version)
5088 }
5189
52- val outputFile = baseDir / " transformer-$activeTransformerVersion -all.jar"
90+ if (! useCache) return null
91+
92+ val key = " probe:library:${library.name} :game:$gameVersion "
93+ val version = libVersion
94+ ? : System .getProperty(" org.sinytra.probe.lib.${library.name} .$property .version" )
95+ ? : cache.get(key)
96+ ? : coroutineScope {
97+ val remote = getRemoteLibraryVersion(library, gameVersion)
98+ cache.set(key, remote)
99+ remote
100+ }
101+
102+ val group = library.group.replace(' .' , ' /' )
103+ val classifier = library.classifier?.let { " -$it " } ? : " "
104+ val mavenPath = " ${group} /${library.name} /$version /${library.name} -$version$classifier .jar"
105+
106+ val url = " ${library.repository} /$mavenPath "
107+ val outputFile = baseDir / " maven" / mavenPath
53108 outputFile.createParentDirectories()
54109
55- val transformerUrl = getMavenUrl( SINYTRA_MAVEN , " org.sinytra.connector " , " transformer " , activeTransformerVersion !! , " all " )
56- downloadFile(transformerUrl, outputFile)
110+ downloadFile(url, outputFile )
111+
57112 if (! outputFile.exists()) {
58- throw IllegalStateException (" Failed to download transformer lib " )
113+ throw IllegalStateException (" Failed to download library ${library.name} " )
59114 }
60115
61- return TransformLib (outputFile, activeTransformerVersion !! )
116+ return ResolvedLibrary (outputFile, version )
62117 }
63118
64- private fun computeTransformerVersion (): String {
65- if (initialTransformerVersion != null ) {
66- return initialTransformerVersion
67- }
68-
119+ private fun getRemoteLibraryVersion (library : Library , gameVersion : String ): String {
69120 try {
70121 val builder = DocumentBuilderFactory .newInstance().newDocumentBuilder()
71- val dest = " $SINYTRA_MAVEN /org/sinytra/connector/transformer/maven-metadata.xml"
122+ val path = library.group.replace(' .' , ' /' )
123+
124+ val dest = " ${library.repository} /${path} /${library.name} /maven-metadata.xml"
72125 val doc = URI (dest).toURL().openStream().use(builder::parse)
73126 doc.documentElement.normalize()
74127
75- val latest = doc.getElementsByTagName(" latest" )
76- ?.takeIf { it.length > 0 }
77- ?.item(0 )?.textContent
128+ val versionElements = doc.getElementsByTagName(" version" )
129+ val versions = (0 until versionElements.length)
130+ .map { versionElements.item(it).textContent }
131+ .filter { library.versionFilter(gameVersion, it) }
78132
79- if (latest != null ) {
80- return latest
81- }
133+ versions.lastOrNull()?.let { return it }
82134 } catch (e: Exception ) {
83- LOGGER .error(" Error fetching transformer version" , e)
135+ LOGGER .error(" Error fetching ' ${library.name} ' version" , e)
84136 }
85137
86- throw RuntimeException (" Could not fetch transformer version" )
138+ throw RuntimeException (" Could not fetch library version for '${library.name} '" )
139+ }
140+
141+ private fun readImplementationVersion (path : Path ): String? {
142+ return FileSystems .newFileSystem(path)
143+ .use { fs ->
144+ val mfPath = fs.getPath(" META-INF" , " MANIFEST.MF" )
145+ .takeIf { it.exists() } ? : return null
146+
147+ val manifest = Manifest ()
148+ mfPath.inputStream().use(manifest::read)
149+
150+ manifest.mainAttributes.getValue(Attributes .Name .IMPLEMENTATION_VERSION )
151+ }
87152 }
88153}
0 commit comments