Skip to content

Commit 459234e

Browse files
committed
General Improvements
switch to home-made version parser add a simple coloured logger log more events increment version add more kdoc
1 parent 9820110 commit 459234e

13 files changed

Lines changed: 242 additions & 65 deletions

File tree

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ plugins {
55
}
66

77
group "org.bundleproject"
8-
version "0.0.3"
8+
version "0.1.0"
99
archivesBaseName = "Bundle"
1010

1111
java {
@@ -27,9 +27,9 @@ configurations {
2727

2828
dependencies {
2929
include "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31"
30+
include "org.jetbrains.kotlin:kotlin-reflect:1.5.31"
3031

3132
include "com.google.code.gson:gson:2.8.9"
32-
include "com.github.zafarkhaja:jsemver:0.9.0"
3333

3434
include 'com.formdev:flatlaf:1.6.1'
3535

src/main/kotlin/org/bundleproject/bundle/Bundle.kt

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
package org.bundleproject.bundle
22

33
import com.formdev.flatlaf.FlatLightLaf
4-
import com.github.zafarkhaja.semver.Version
54
import com.google.gson.JsonParser
6-
import io.ktor.client.request.*
75
import kotlinx.coroutines.async
86
import kotlinx.coroutines.awaitAll
7+
import kotlinx.coroutines.coroutineScope
98
import kotlinx.coroutines.runBlocking
109
import org.bundleproject.bundle.entities.Mod
1110
import org.bundleproject.bundle.api.data.Platform
1211
import org.bundleproject.bundle.api.requests.BulkModRequest
13-
import org.bundleproject.bundle.entities.RemoteMod
1412
import org.bundleproject.bundle.gui.LoadingGui
1513
import org.bundleproject.bundle.gui.UpdateOverviewGui
1614
import org.bundleproject.bundle.utils.*
@@ -32,27 +30,31 @@ import kotlin.concurrent.withLock
3230
*
3331
* @since 0.0.1
3432
*/
35-
class Bundle(private val gameDir: File, private val version: Version, modFolderName: String) {
33+
class Bundle(private val gameDir: File, private val version: Version?, modFolderName: String) {
3634
private val modsDir = File(gameDir, modFolderName)
3735

3836
suspend fun start() {
3937
try {
40-
println("Starting Bundle...")
38+
info("Starting Bundle...")
39+
println()
40+
4141
try { UIManager.setLookAndFeel(FlatLightLaf()) }
4242
catch (e: Throwable) { e.printStackTrace() }
4343

4444
val outdated = getOutdatedMods()
4545

4646
if (outdated.isEmpty()) return
4747

48-
val lock = ReentrantLock()
49-
val condition = lock.newCondition()
50-
lock.withLock {
51-
UpdateOverviewGui(this, outdated, condition).apply {
52-
isVisible = true
53-
}
54-
condition.await()
55-
}
48+
// val lock = ReentrantLock()
49+
// val condition = lock.newCondition()
50+
// lock.withLock {
51+
// UpdateOverviewGui(this, outdated, condition).apply {
52+
// isVisible = true
53+
// }
54+
// condition.await()
55+
// }
56+
57+
updateMods(outdated)
5658

5759
} catch (e: Throwable) {
5860
e.printStackTrace()
@@ -70,19 +72,33 @@ class Bundle(private val gameDir: File, private val version: Version, modFolderN
7072
* @since 0.0.1
7173
*/
7274
private suspend fun getOutdatedMods(): MutableList<ModPair> {
75+
info("Getting outdated mods...")
76+
7377
val localMods = mutableListOf<Mod>()
7478
for (mod in modsDir.walkTopDown()) {
7579
if (mod.isDirectory) continue
7680

77-
val localMod = getModInfo(mod) ?: continue
78-
if (localMod.minecraftVersion != version) continue
81+
val localMod = try {
82+
getModInfo(mod)
83+
} catch (e: Exception) {
84+
if (e is Version.VersionParseException) err("Failed to parse version for ${mod.name}")
85+
else e.printStackTrace()
86+
87+
null
88+
} ?: continue
89+
90+
if (localMod.minecraftVersion != (version ?: localMod.minecraftVersion)) continue
7991

8092
localMods.add(localMod)
8193
}
94+
info("Found: ${localMods.map { it.id }.toFormattedString()}")
95+
println()
8296

97+
info("Making bulk request to API.")
8398
val request = BulkModRequest(localMods.map { it.makeRequest() })
8499
val response = request.request()
85100

101+
info("Comparing local to remote mod versions.")
86102
val outdated = mutableListOf<ModPair>()
87103
for (i in localMods.indices) {
88104
val local = localMods[i]
@@ -91,6 +107,9 @@ class Bundle(private val gameDir: File, private val version: Version, modFolderN
91107
if (remote > local)
92108
outdated.add(ModPair(local, remote))
93109
}
110+
info("${outdated.size}/${localMods.size} mods need an update.")
111+
info("Outdated: ${outdated.map { it.remote.id }.toFormattedString()}")
112+
println()
94113

95114
return outdated
96115
}
@@ -101,8 +120,11 @@ class Bundle(private val gameDir: File, private val version: Version, modFolderN
101120
* @since 0.0.1
102121
*/
103122
private fun getModInfo(modFile: File): Mod? {
123+
info("Getting mod info using method: ", false)
104124
JarFile(modFile).use { jar ->
105125
jar.getJarEntry("bundle.project.json")?.let getJarEntry@{ modInfo ->
126+
info("Bundle Info File")
127+
106128
InputStreamReader(jar.getInputStream(modInfo)).use {
107129
val json = JsonParser.parseReader(it).asJsonObject
108130

@@ -111,43 +133,48 @@ class Bundle(private val gameDir: File, private val version: Version, modFolderN
111133
return@getModInfo Mod(
112134
name = json.get("name")?.asString ?: return null,
113135
id = json.get("id")?.asString ?: return@getJarEntry,
114-
version = Version.valueOf(json.get("version")?.asString ?: return@getJarEntry),
115-
minecraftVersion = Version.valueOf(json.get("minecraft_version")?.asString ?: return@getJarEntry),
136+
version = Version.of(json.get("version")?.asString ?: return@getJarEntry),
137+
minecraftVersion = Version.of(json.get("minecraft_version")?.asString ?: return@getJarEntry),
116138
fileName = modFile.name,
117139
platform = Platform.valueOf(json.get("platform")?.asString?.uppercase() ?: return@getJarEntry),
118140
)
119141
}
120142
}
121143

122144
jar.getJarEntry("fabric.mod.json")?.let { modInfo ->
145+
info("Fabric Info File")
146+
123147
InputStreamReader(jar.getInputStream(modInfo)).use {
124148
val json = JsonParser.parseReader(it).asJsonObject
125149
return@getModInfo Mod(
126150
name = json.get("name")?.asString ?: return null,
127151
id = json.get("id")?.asString ?: return null,
128-
version = Version.valueOf(json.get("version")?.asString ?: return null),
129-
minecraftVersion = Version.valueOf(json.get("depends").asJsonObject.get("minecraft")?.asString ?: return null),
152+
version = Version.of(json.get("version")?.asString ?: return null),
153+
minecraftVersion = Version.of(json.get("depends").asJsonObject.get("minecraft")?.asString ?: return null),
130154
fileName = modFile.name,
131155
platform = Platform.Fabric,
132156
)
133157
}
134158
}
135159

136160
jar.getJarEntry("mcmod.info")?.let { modInfo ->
161+
info("Forge Info File")
162+
137163
InputStreamReader(jar.getInputStream(modInfo)).use {
138164
val json = JsonParser.parseReader(it).asJsonArray[0].asJsonObject
139165
return@getModInfo Mod(
140166
name = json.get("name")?.asString ?: return null,
141167
id = json.get("modid")?.asString ?: return null,
142-
version = Version.valueOf(json.get("version")?.asString ?: return null),
143-
minecraftVersion = Version.valueOf(json.get("mcversion")?.asString ?: return null),
168+
version = Version.of(json.get("version")?.asString ?: return null),
169+
minecraftVersion = Version.of(json.get("mcversion")?.asString ?: return null),
144170
fileName = modFile.name,
145171
platform = Platform.Forge,
146172
)
147173
}
148174
}
149175
}
150176

177+
err("None")
151178
return null
152179
}
153180

@@ -158,17 +185,25 @@ class Bundle(private val gameDir: File, private val version: Version, modFolderN
158185
*
159186
* @since 0.0.2
160187
*/
161-
fun updateMods(mods: List<ModPair>) {
162-
launchCoroutine("Mod Updater") {
188+
suspend fun updateMods(mods: List<ModPair>) {
189+
info("Updating Mods...")
190+
191+
coroutineScope {
163192
val loading = LoadingGui(mods.size)
164193
loading.isVisible = true
165194
mods.map { (local, remote) ->
166195
async {
167196
val current = File(modsDir, local.fileName)
197+
val new = File(modsDir, remote.fileName)
198+
199+
info("Downloading: ${new.name}")
200+
if (http.downloadFile(new, remote.downloadEndpoint)) {
201+
info("Deleting: ${current.name}")
202+
current.delete()
203+
} else {
204+
error("Failed to download! Skipping!")
205+
}
168206

169-
Files.delete(current.toPath())
170-
runBlocking { URL(remote.downloadUrl) }
171-
.download(File(modsDir, remote.fileName))
172207
loading.finish()
173208
}
174209
}.awaitAll()

src/main/kotlin/org/bundleproject/bundle/api/data/ModData.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package org.bundleproject.bundle.api.data
22

3-
import com.github.zafarkhaja.semver.Version
3+
import org.bundleproject.bundle.utils.Version
44

55
data class ModData(
66
val url: String,

src/main/kotlin/org/bundleproject/bundle/entities/Mod.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package org.bundleproject.bundle.entities
22

3-
import com.github.zafarkhaja.semver.Version
43
import org.bundleproject.bundle.api.data.ModData
54
import org.bundleproject.bundle.api.data.Platform
65
import org.bundleproject.bundle.api.requests.ModRequest
6+
import org.bundleproject.bundle.utils.Version
7+
import org.bundleproject.bundle.utils.getFileNameFromUrl
78

89
open class Mod(
910
@Transient var enabled: Boolean = true,
@@ -26,7 +27,7 @@ open class Mod(
2627
id,
2728
data.version,
2829
minecraftVersion,
29-
fileName,
30+
getFileNameFromUrl(data.url),
3031
platform,
3132
data.url,
3233
)

src/main/kotlin/org/bundleproject/bundle/entities/RemoteMod.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package org.bundleproject.bundle.entities
22

3-
import com.github.zafarkhaja.semver.Version
43
import org.bundleproject.bundle.api.data.Platform
4+
import org.bundleproject.bundle.utils.Version
55

66
class RemoteMod(
77
enabled: Boolean = true,

src/main/kotlin/org/bundleproject/bundle/gui/LoadingGui.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ class LoadingGui(private val updateCount: Int) : JFrame("Updating Mods") {
3232
pack()
3333
}
3434

35+
/**
36+
* Indicates to gui that another mod has finished downloading
37+
* and the progress bar should progress or complete.
38+
*/
3539
fun finish() {
3640
if (progressBar.value + 1 == updateCount) {
3741
dispose()

src/main/kotlin/org/bundleproject/bundle/gui/UpdateOverviewGui.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.bundleproject.bundle.gui
22

3+
import kotlinx.coroutines.runBlocking
34
import org.bundleproject.bundle.Bundle
45
import org.bundleproject.bundle.entities.Mod
56
import org.bundleproject.bundle.entities.RemoteMod
@@ -61,7 +62,7 @@ class UpdateOverviewGui(private val bundle: Bundle, mods: MutableList<ModPair>,
6162

6263
val downloadButton = JButton("Update")
6364
downloadButton.addActionListener {
64-
bundle.updateMods(mods.filter { it.remote.enabled })
65+
runBlocking { bundle.updateMods(mods.filter { it.remote.enabled }) }
6566
dispose()
6667
condition?.signal()
6768
}
Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
11
package org.bundleproject.bundle.main
22

3-
import com.github.zafarkhaja.semver.Version
43
import org.bundleproject.bundle.Bundle
4+
import org.bundleproject.bundle.utils.Version
5+
import org.bundleproject.bundle.utils.important
6+
import org.bundleproject.bundle.utils.info
57
import java.io.File
68

9+
/**
10+
* List of known entrypoints for mod loaders.
11+
*
12+
* This is probably going to be removed in future
13+
* in favour of the installer providing the version
14+
* installation's mainclass as a program argument
15+
* rather than looking up a list.
16+
*
17+
* @since 0.0.1
18+
*/
719
val entrypoints = arrayOf(
820
"net.fabricmc.loader.launch.knot.KnotClient",
921
"cpw.mods.bootstraplauncher.BootstrapLauncher",
@@ -16,11 +28,11 @@ val entrypoints = arrayOf(
1628
suspend fun main(args: Array<String>) {
1729
findEntrypoint().apply {
1830
try {
19-
val version = (args["version"] ?: "x.x.x").let {
20-
if (it == "MultiMC5") { "x.x.x" } else { it }
21-
}
31+
val version = args["version"]
32+
?.takeIf { it != "MultiMC5" }
33+
?.let(Version::of)
2234

23-
Bundle(File(args["gameDir"] ?: "."), Version.valueOf(version), "mods").start()
35+
Bundle(File(args["gameDir"] ?: "."), version, "mods").start()
2436
} catch (e: Throwable) {
2537
e.printStackTrace()
2638
}
@@ -29,15 +41,28 @@ suspend fun main(args: Array<String>) {
2941
}
3042
}
3143

32-
private operator fun Array<String>.get(arg: String) = getOrNull(indexOf("--$arg") + 1)
44+
private operator fun Array<String>.get(arg: String) = getNullOrNull(indexOf("--$arg").takeIf { it >= 0 }?.plus(1))
45+
private fun Array<String>.getNullOrNull(index: Int?): String? {
46+
if (index == null) return null
47+
return getOrNull(index)
48+
}
3349

50+
/**
51+
* Gets the first entrypoint available and returns it.
52+
*
53+
* If no entrypoints are found, it is presumed Bundle is
54+
* not running in a Minecraft context and returns no entrypoint.
55+
*
56+
* @since 0.0.1
57+
*/
3458
private fun findEntrypoint(): Class<*>? {
3559
entrypoints.forEach {
3660
runCatching { Class.forName(it) }
3761
.getOrNull()
3862
?.let { return it }
3963
}
4064

41-
println("Assuming development environment and returning no entrypoint.")
65+
important("Assuming development environment and returning no entrypoint.")
66+
println()
4267
return null
4368
}

src/main/kotlin/org/bundleproject/bundle/utils/Base64.kt

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.bundleproject.bundle.utils
2+
3+
const val RESET = "\u001b[0m" // Text Reset
4+
5+
const val BLACK = "\u001b[0;30m"
6+
const val RED = "\u001b[0;31m"
7+
const val GREEN = "\u001b[0;32m"
8+
const val YELLOW = "\u001b[0;33m"
9+
const val BLUE = "\u001b[0;34m"
10+
const val PURPLE = "\u001b[0;35m"
11+
const val CYAN = "\u001b[0;36m"
12+
const val WHITE = "\u001b[0;37m"
13+
14+
fun info(any: Any?, newLine: Boolean = true) = print(RESET + (any?.toString() ?: "null") + if (newLine) "\n" else "" + RESET)
15+
fun err(any: Any?, newLine: Boolean = true) = print(RED + (any?.toString() ?: "null") + if (newLine) "\n" else "" + RESET)
16+
fun important(any: Any?, newLine: Boolean = true) = print(PURPLE + (any?.toString() ?: "null") + if (newLine) "\n" else "" + RESET)

0 commit comments

Comments
 (0)