Skip to content
This repository was archived by the owner on Dec 30, 2022. It is now read-only.

Commit 3f060e5

Browse files
committed
Rewrote version finding algorithm, added tests for it
Signed-off-by: DeathsGun <deathsgun@protonmail.com>
1 parent b1faead commit 3f060e5

File tree

14 files changed

+3845
-59
lines changed

14 files changed

+3845
-59
lines changed

build.gradle

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ plugins {
2020
id "fabric-loom" version "0.9-SNAPSHOT"
2121
id "com.modrinth.minotaur" version "1.2.1"
2222
id "org.jetbrains.kotlin.jvm" version "1.5.30"
23-
id 'org.jetbrains.kotlin.plugin.serialization' version '1.5.30'
23+
id "org.jetbrains.kotlin.plugin.serialization" version "1.5.30"
2424
}
2525

2626
sourceCompatibility = JavaVersion.VERSION_16
@@ -42,10 +42,13 @@ dependencies {
4242
minecraft "com.mojang:minecraft:${project.minecraft_version}"
4343
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
4444
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
45+
4546
modImplementation "net.fabricmc:fabric-language-kotlin:${project.fabric_kotlin_version}"
4647
modImplementation "com.terraformersmc:modmenu:${project.modmenu_version}"
48+
49+
implementation "com.vdurmont:semver4j:3.1.0"
4750

48-
testImplementation 'org.jetbrains.kotlin:kotlin-test:1.5.21'
51+
testImplementation "org.jetbrains.kotlin:kotlin-test:1.5.21"
4952
}
5053

5154
processResources {

src/main/kotlin/xyz/deathsgun/modmanager/ModManager.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class ModManager : ClientModInitializer {
5656

5757
@JvmStatic
5858
fun getMinecraftVersion(): String {
59-
return MinecraftClient.getInstance()?.game?.version?.releaseTarget ?: "1.17.1"
59+
return MinecraftClient.getInstance()?.game?.version?.name ?: "1.17.1"
6060
}
6161
}
6262

src/main/kotlin/xyz/deathsgun/modmanager/api/mod/Version.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,10 @@ data class Version(
2525
val type: VersionType,
2626
val gameVersions: List<String>,
2727
val assets: List<Asset>
28-
)
28+
) : Comparable<Version> {
29+
override fun compareTo(other: Version): Int {
30+
return Comparator.comparing { v: Version -> v.version }
31+
.thenComparing { v -> v.releaseDate }.compare(this, other)
32+
}
33+
34+
}

src/main/kotlin/xyz/deathsgun/modmanager/config/Config.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,15 @@ data class Config(
6969
fun text(): Text {
7070
return TranslatableText(String.format("modmanager.channel.%s", name.lowercase()))
7171
}
72-
}
7372

74-
fun isReleaseAllowed(type: VersionType): Boolean {
75-
if (updateChannel == UpdateChannel.ALL) {
76-
return true
77-
}
78-
if (updateChannel == UpdateChannel.STABLE && type == VersionType.RELEASE) {
79-
return true
73+
fun isReleaseAllowed(type: VersionType): Boolean {
74+
if (this == ALL) {
75+
return true
76+
}
77+
if (this == STABLE && type == VersionType.RELEASE) {
78+
return true
79+
}
80+
return this == UNSTABLE
8081
}
81-
return updateChannel == UpdateChannel.UNSTABLE
8282
}
8383
}

src/main/kotlin/xyz/deathsgun/modmanager/update/UpdateManager.kt

Lines changed: 24 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ import kotlinx.serialization.ExperimentalSerializationApi
2323
import kotlinx.serialization.decodeFromString
2424
import kotlinx.serialization.json.Json
2525
import net.fabricmc.loader.api.FabricLoader
26-
import net.fabricmc.loader.api.SemanticVersion
2726
import net.fabricmc.loader.api.metadata.ModMetadata
28-
import net.fabricmc.loader.util.version.VersionDeserializer
2927
import net.minecraft.text.TranslatableText
3028
import org.apache.commons.io.FileUtils
3129
import org.apache.logging.log4j.LogManager
@@ -99,7 +97,12 @@ class UpdateManager {
9997
}
10098
var result = updateProvider.getVersionsForMod(metadata.id)
10199
if (result is VersionResult.Success) {
102-
val version = findLatestCompatible(metadata.version.friendlyString, result.versions)
100+
val version = VersionFinder.findUpdate(
101+
metadata.version.friendlyString,
102+
ModManager.getMinecraftVersion(),
103+
ModManager.modManager.config.updateChannel,
104+
result.versions
105+
)
103106
if (version == null) {
104107
logger.info("No update for {} found!", metadata.id)
105108
ModManager.modManager.setModState(metadata.id, metadata.id, ModState.INSTALLED)
@@ -142,7 +145,12 @@ class UpdateManager {
142145
}
143146
is VersionResult.Success -> result.versions
144147
}
145-
val version = findLatestCompatible(metadata.version.friendlyString, versions)
148+
val version = VersionFinder.findUpdate(
149+
metadata.version.friendlyString,
150+
ModManager.getMinecraftVersion(),
151+
ModManager.modManager.config.updateChannel,
152+
versions
153+
)
146154
if (version == null) {
147155
logger.info("No update for {} found!", metadata.id)
148156
ModManager.modManager.setModState(metadata.id, mod.id, ModState.INSTALLED)
@@ -183,7 +191,12 @@ class UpdateManager {
183191
}
184192
is VersionResult.Success -> result.versions
185193
}
186-
val version = findLatestCompatible(metadata.version.friendlyString, versions)
194+
val version = VersionFinder.findUpdate(
195+
metadata.version.friendlyString,
196+
ModManager.getMinecraftVersion(),
197+
ModManager.modManager.config.updateChannel,
198+
versions
199+
)
187200
if (version == null) {
188201
logger.info("No update for {} found!", metadata.id)
189202
ModManager.modManager.setModState(metadata.id, id, ModState.INSTALLED)
@@ -212,7 +225,12 @@ class UpdateManager {
212225
is VersionResult.Error -> return ModInstallResult.Error(result.text, result.cause)
213226
is VersionResult.Success -> result.versions
214227
}
215-
val version = findLatestCompatible("0.0.0.0", versions)
228+
val version = VersionFinder.findUpdate(
229+
"0.0.0.0",
230+
ModManager.getMinecraftVersion(),
231+
ModManager.modManager.config.updateChannel,
232+
versions
233+
)
216234
?: return ModInstallResult.Error(TranslatableText("modmanager.error.noCompatibleModVersionFound"))
217235

218236
val dir = FabricLoader.getInstance().gameDir.resolve("mods")
@@ -258,47 +276,6 @@ class UpdateManager {
258276
}
259277
}
260278

261-
private fun findLatestCompatible(installedVersion: String, versions: List<Version>): Version? {
262-
var latest: Version? = null
263-
var latestVersion: SemanticVersion? = null
264-
var installed: Version? = null
265-
val installVersion =
266-
VersionDeserializer.deserializeSemantic(installedVersion)
267-
for (version in versions) {
268-
if (version.version == installedVersion) {
269-
installed = version
270-
}
271-
if (!version.gameVersions.contains(ModManager.getMinecraftVersion()) ||
272-
!ModManager.modManager.config.isReleaseAllowed(version.type)
273-
) {
274-
continue
275-
}
276-
val ver = try {
277-
VersionDeserializer.deserializeSemantic(version.version) // Remove additional info from version
278-
} catch (e: Exception) {
279-
if (latestVersion == null || version.releaseDate > latest?.releaseDate) {
280-
logger.info("Setting version {} via release date", version.version)
281-
latest = version
282-
latestVersion = null
283-
continue
284-
}
285-
logger.warn("Skipping error producing version {}", version.version)
286-
continue
287-
}
288-
if (latestVersion == null || ver > latestVersion || version.releaseDate > latest?.releaseDate) {
289-
latest = version
290-
latestVersion = ver
291-
}
292-
}
293-
if (installed != null && installed.releaseDate > latest?.releaseDate) {
294-
return null
295-
}
296-
if (latestVersion?.compareTo(installVersion) == 0) {
297-
return null
298-
}
299-
return latest
300-
}
301-
302279
fun updateMod(update: Update): ModUpdateResult {
303280
val oldUpdate = FabricLoader.getInstance().allMods.find { it.metadata.id == update.fabricId }
304281
?: return ModUpdateResult.Error(TranslatableText("modmanager.error.container.notFound"))
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright 2021 DeathsGun
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package xyz.deathsgun.modmanager.update
18+
19+
import net.fabricmc.loader.api.SemanticVersion
20+
import net.fabricmc.loader.api.VersionParsingException
21+
import net.fabricmc.loader.util.version.VersionDeserializer
22+
import xyz.deathsgun.modmanager.api.mod.Version
23+
import xyz.deathsgun.modmanager.config.Config
24+
25+
object VersionFinder {
26+
27+
fun findUpdateFallback(
28+
installedVersion: String,
29+
mcVersion: String,
30+
updateChannel: Config.UpdateChannel,
31+
modVersions: List<Version>
32+
): Version? {
33+
val versions =
34+
modVersions.filter { updateChannel.isReleaseAllowed(it.type) }
35+
.filter { it.gameVersions.any { it1 -> it1.startsWith(mcVersion) } }
36+
.sortedByDescending { it.releaseDate }
37+
38+
val version = versions.firstOrNull()
39+
if (version?.version == installedVersion) {
40+
return null
41+
}
42+
return version
43+
}
44+
45+
internal fun findUpdateByVersion(
46+
installedVersion: String,
47+
mcVersion: String,
48+
channel: Config.UpdateChannel,
49+
modVersions: List<Version>
50+
): Version? {
51+
val versions = modVersions.filter { channel.isReleaseAllowed(it.type) }
52+
.filter { it.gameVersions.any { it1 -> it1.startsWith(mcVersion) } }
53+
var latestVersion: Version? = null
54+
var latestVer: SemanticVersion? = null
55+
val installedVer = VersionDeserializer.deserializeSemantic(installedVersion)
56+
for (version in versions) {
57+
val parsedVersion = try {
58+
VersionDeserializer.deserializeSemantic(version.version)
59+
} catch (e: VersionParsingException) {
60+
continue
61+
}
62+
if (latestVersion == null) {
63+
latestVersion = version
64+
latestVer = parsedVersion
65+
continue
66+
}
67+
if (latestVer != null && parsedVersion > latestVer) {
68+
latestVersion = version
69+
latestVer = parsedVersion
70+
}
71+
}
72+
if (installedVersion == latestVersion?.version || installedVer >= latestVer) {
73+
return null
74+
}
75+
return latestVersion
76+
}
77+
78+
fun findUpdate(
79+
installedVersion: String,
80+
mcVersion: String,
81+
channel: Config.UpdateChannel,
82+
modVersions: List<Version>
83+
): Version? {
84+
return try {
85+
findUpdateByVersion(installedVersion, mcVersion, channel, modVersions)
86+
} catch (e: VersionParsingException) {
87+
findUpdateFallback(installedVersion, mcVersion, channel, modVersions)
88+
}
89+
}
90+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2021 DeathsGun
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package xyz.deathsgun.modmanager.dummy
18+
19+
import kotlinx.serialization.ExperimentalSerializationApi
20+
import kotlinx.serialization.decodeFromString
21+
import kotlinx.serialization.json.Json
22+
import net.minecraft.text.TranslatableText
23+
import xyz.deathsgun.modmanager.api.http.VersionResult
24+
import xyz.deathsgun.modmanager.api.mod.Asset
25+
import xyz.deathsgun.modmanager.api.mod.Version
26+
import xyz.deathsgun.modmanager.api.mod.VersionType
27+
import xyz.deathsgun.modmanager.api.provider.IModUpdateProvider
28+
import xyz.deathsgun.modmanager.providers.modrinth.models.ModrinthVersion
29+
import java.io.BufferedReader
30+
import java.io.InputStreamReader
31+
import java.time.Instant
32+
import java.time.ZoneOffset
33+
34+
internal class DummyModrinthVersionProvider : IModUpdateProvider {
35+
36+
private val json = Json {
37+
ignoreUnknownKeys = true
38+
}
39+
40+
override fun getName(): String {
41+
return "Modrinth"
42+
}
43+
44+
@OptIn(ExperimentalSerializationApi::class)
45+
override fun getVersionsForMod(id: String): VersionResult {
46+
return try {
47+
val stream = DummyModrinthVersionProvider::class.java.getResourceAsStream("/version/$id.json")
48+
?: return VersionResult.Error(TranslatableText("reading.failed"))
49+
val reader = BufferedReader(InputStreamReader(stream))
50+
val modrinthVersions = json.decodeFromString<List<ModrinthVersion>>(reader.readText())
51+
val versions = ArrayList<Version>()
52+
for (modVersion in modrinthVersions) {
53+
if (!modVersion.loaders.contains("fabric")) {
54+
continue
55+
}
56+
val assets = ArrayList<Asset>()
57+
for (file in modVersion.files) {
58+
assets.add(Asset(file.url, file.filename, file.hashes, file.primary))
59+
}
60+
versions.add(
61+
Version(
62+
modVersion.version,
63+
modVersion.changelog,
64+
// 2021-09-03T10:56:59.402790Z
65+
Instant.parse(modVersion.releaseDate).atOffset(
66+
ZoneOffset.UTC
67+
).toLocalDate(),
68+
getVersionType(modVersion.type),
69+
modVersion.gameVersions,
70+
assets
71+
)
72+
)
73+
}
74+
VersionResult.Success(versions)
75+
} catch (e: Exception) {
76+
VersionResult.Error(TranslatableText("modmanager.error.failedToParse", e.message), e)
77+
}
78+
}
79+
80+
private fun getVersionType(id: String): VersionType {
81+
return when (id) {
82+
"release" -> VersionType.RELEASE
83+
"alpha" -> VersionType.ALPHA
84+
"beta" -> VersionType.BETA
85+
else -> VersionType.UNKNOWN
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)