aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/kotlin/moe/nea/notenoughupdates/NotEnoughUpdates.kt59
-rw-r--r--src/main/kotlin/moe/nea/notenoughupdates/rei/NEUReiPlugin.kt5
-rw-r--r--src/main/kotlin/moe/nea/notenoughupdates/repo/ItemCache.kt5
-rw-r--r--src/main/kotlin/moe/nea/notenoughupdates/repo/RepoDownloadManager.kt121
-rw-r--r--src/main/kotlin/moe/nea/notenoughupdates/repo/RepoManager.kt31
-rw-r--r--src/main/kotlin/moe/nea/notenoughupdates/util/SequenceUtil.kt9
6 files changed, 202 insertions, 28 deletions
diff --git a/src/main/kotlin/moe/nea/notenoughupdates/NotEnoughUpdates.kt b/src/main/kotlin/moe/nea/notenoughupdates/NotEnoughUpdates.kt
index 27c51e1..c4f3f69 100644
--- a/src/main/kotlin/moe/nea/notenoughupdates/NotEnoughUpdates.kt
+++ b/src/main/kotlin/moe/nea/notenoughupdates/NotEnoughUpdates.kt
@@ -1,44 +1,71 @@
package moe.nea.notenoughupdates
+import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
-import io.github.moulberry.repo.NEURepository
-import moe.nea.notenoughupdates.repo.ItemCache
+import io.ktor.client.*
+import io.ktor.client.plugins.*
+import io.ktor.client.plugins.contentnegotiation.*
+import io.ktor.serialization.kotlinx.json.*
+import kotlinx.coroutines.*
+import kotlinx.serialization.json.Json
+import moe.nea.notenoughupdates.repo.RepoManager
import net.fabricmc.api.ClientModInitializer
import net.fabricmc.api.ModInitializer
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource
-import net.minecraft.client.Minecraft
+import net.fabricmc.loader.api.FabricLoader
import net.minecraft.commands.CommandBuildContext
import net.minecraft.network.chat.Component
-import net.minecraft.network.protocol.game.ClientboundUpdateRecipesPacket
+import org.apache.logging.log4j.LogManager
+import java.nio.file.Files
import java.nio.file.Path
+import kotlin.coroutines.EmptyCoroutineContext
object NotEnoughUpdates : ModInitializer, ClientModInitializer {
- val DATA_DIR = Path.of(".notenoughupdates")
-
const val MOD_ID = "notenoughupdates"
- val neuRepo: NEURepository = NEURepository.of(Path.of("NotEnoughUpdates-REPO")).apply {
- registerReloadListener(ItemCache)
- reload()
- registerReloadListener {
- Minecraft.getInstance().connection?.handleUpdateRecipes(ClientboundUpdateRecipesPacket(mutableListOf()))
+ val DATA_DIR = Path.of(".notenoughupdates").also { Files.createDirectories(it) }
+ val DEBUG = System.getenv("notenoughupdates.debug") == "true"
+ val logger = LogManager.getLogger("NotEnoughUpdates")
+ val metadata by lazy { FabricLoader.getInstance().getModContainer(MOD_ID).orElseThrow().metadata }
+ val version by lazy { metadata.version }
+
+ val json = Json {
+ prettyPrint = DEBUG
+ ignoreUnknownKeys = true
+ }
+
+ val httpClient by lazy {
+ HttpClient {
+ install(ContentNegotiation) {
+ json(json)
+ }
+ install(UserAgent) {
+ agent = "NotEnoughUpdates1.19/$version"
+ }
}
}
- fun registerCommands(
- dispatcher: CommandDispatcher<FabricClientCommandSource>, registryAccess: CommandBuildContext
+ val globalJob = Job()
+ val coroutineScope =
+ CoroutineScope(EmptyCoroutineContext + CoroutineName("NotEnoughUpdates")) + SupervisorJob(globalJob)
+ val coroutineScopeIo = coroutineScope + Dispatchers.IO + SupervisorJob(globalJob)
+
+ private fun registerCommands(
+ dispatcher: CommandDispatcher<FabricClientCommandSource>,
+ @Suppress("UNUSED_PARAMETER")
+ _ctx: CommandBuildContext
) {
dispatcher.register(ClientCommandManager.literal("neureload").executes {
it.source.sendFeedback(Component.literal("Reloading repository from disk. This may lag a bit."))
- neuRepo.reload()
- 0
+ RepoManager.neuRepo.reload()
+ Command.SINGLE_SUCCESS
})
-
}
override fun onInitialize() {
+ RepoManager.launchAsyncUpdate()
ClientCommandRegistrationCallback.EVENT.register(this::registerCommands)
}
diff --git a/src/main/kotlin/moe/nea/notenoughupdates/rei/NEUReiPlugin.kt b/src/main/kotlin/moe/nea/notenoughupdates/rei/NEUReiPlugin.kt
index 69cca41..985743a 100644
--- a/src/main/kotlin/moe/nea/notenoughupdates/rei/NEUReiPlugin.kt
+++ b/src/main/kotlin/moe/nea/notenoughupdates/rei/NEUReiPlugin.kt
@@ -6,8 +6,8 @@ import me.shedaniel.rei.api.client.registry.entry.EntryRegistry
import me.shedaniel.rei.api.common.entry.EntryStack
import me.shedaniel.rei.api.common.entry.type.EntryTypeRegistry
import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes
-import moe.nea.notenoughupdates.NotEnoughUpdates.neuRepo
import moe.nea.notenoughupdates.repo.ItemCache.asItemStack
+import moe.nea.notenoughupdates.repo.RepoManager
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.ItemStack
@@ -22,13 +22,14 @@ class NEUReiPlugin : REIClientPlugin {
val SKYBLOCK_ITEM_TYPE_ID = ResourceLocation("notenoughupdates", "skyblockitems")
}
+
override fun registerEntryTypes(registry: EntryTypeRegistry) {
registry.register(SKYBLOCK_ITEM_TYPE_ID, SBItemEntryDefinition)
}
override fun registerEntries(registry: EntryRegistry) {
- neuRepo.items.items.values.forEach {
+ RepoManager.neuRepo.items.items.values.forEach {
if (!it.isVanilla)
registry.addEntry(EntryStack.of(SBItemEntryDefinition, it))
}
diff --git a/src/main/kotlin/moe/nea/notenoughupdates/repo/ItemCache.kt b/src/main/kotlin/moe/nea/notenoughupdates/repo/ItemCache.kt
index aa93fec..893b1c0 100644
--- a/src/main/kotlin/moe/nea/notenoughupdates/repo/ItemCache.kt
+++ b/src/main/kotlin/moe/nea/notenoughupdates/repo/ItemCache.kt
@@ -4,6 +4,7 @@ import com.mojang.serialization.Dynamic
import io.github.moulberry.repo.IReloadable
import io.github.moulberry.repo.NEURepository
import io.github.moulberry.repo.data.NEUItem
+import moe.nea.notenoughupdates.NotEnoughUpdates
import moe.nea.notenoughupdates.util.LegacyTagParser
import moe.nea.notenoughupdates.util.appendLore
import net.minecraft.nbt.CompoundTag
@@ -37,7 +38,7 @@ object ItemCache : IReloadable {
2975
).value as CompoundTag
} catch (e: Exception) {
- e.printStackTrace()
+ NotEnoughUpdates.logger.error("Failed to datafixer an item", e)
isFlawless = false
null
}
@@ -46,7 +47,7 @@ object ItemCache : IReloadable {
val oldItemTag = get10809CompoundTag()
val modernItemTag = oldItemTag.transformFrom10809ToModern()
?: return ItemStack(Items.PAINTING).apply {
- setHoverName(Component.literal(this@asItemStackNow.displayName))
+ hoverName = Component.literal(this@asItemStackNow.displayName)
appendLore(listOf(Component.literal("Exception rendering item: $skyblockItemId")))
}
val itemInstance = ItemStack.of(modernItemTag)
diff --git a/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoDownloadManager.kt b/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoDownloadManager.kt
index 47b2878..977c035 100644
--- a/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoDownloadManager.kt
+++ b/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoDownloadManager.kt
@@ -1,17 +1,122 @@
package moe.nea.notenoughupdates.repo
+import io.ktor.client.call.*
+import io.ktor.client.request.*
+import io.ktor.client.statement.*
+import io.ktor.utils.io.jvm.nio.*
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.Dispatchers.IO
+import kotlinx.coroutines.withContext
+import kotlinx.serialization.Serializable
import moe.nea.notenoughupdates.NotEnoughUpdates
+import moe.nea.notenoughupdates.NotEnoughUpdates.logger
+import moe.nea.notenoughupdates.util.iterate
+import java.io.IOException
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.StandardOpenOption
+import java.util.zip.ZipInputStream
+import kotlin.io.path.*
+
object RepoDownloadManager {
val repoSavedLocation = NotEnoughUpdates.DATA_DIR.resolve("repo-extracted")
- val repoMetadataLocation = NotEnoughUpdates.DATA_DIR.resolve("loaded-repo.json")
-
- data class RepoMetadata(
- var latestCommit: String,
- var user: String,
- var repository: String,
- var branch: String,
- )
+ val repoMetadataLocation = NotEnoughUpdates.DATA_DIR.resolve("loaded-repo-sha.txt")
+
+ val user = "NotEnoughUpdates"
+ val repo = "NotEnoughUpdates-REPO"
+ val branch = "dangerous"
+
+ private fun loadSavedVersionHash(): String? =
+ if (repoSavedLocation.exists()) {
+ if (repoMetadataLocation.exists()) {
+ try {
+ repoMetadataLocation.readText().trim()
+ } catch (e: IOException) {
+ null
+ }
+ } else {
+ null
+ }
+ } else null
+
+ private fun saveVersionHash(versionHash: String) {
+ latestSavedVersionHash = versionHash
+ repoMetadataLocation.writeText(versionHash)
+ }
+
+ var latestSavedVersionHash: String? = loadSavedVersionHash()
+ private set
+
+ @Serializable
+ private class GithubCommitsResponse(val sha: String)
+
+ private suspend fun requestLatestGithubSha(): String? {
+ val response =
+ NotEnoughUpdates.httpClient.get("https://api.github.com/repos/$user/$repo/commits/$branch")
+ if (response.status.value != 200) {
+ return null
+ }
+ return response.body<GithubCommitsResponse>().sha
+ }
+
+ private suspend fun downloadGithubArchive(url: String): Path = withContext(IO) {
+ val response = NotEnoughUpdates.httpClient.get(url)
+ val targetFile = Files.createTempFile("notenoughupdates-repo", ".zip")
+ val outputChannel = Files.newByteChannel(targetFile, StandardOpenOption.CREATE, StandardOpenOption.WRITE)
+ response.bodyAsChannel().copyTo(outputChannel)
+ targetFile
+ }
+
+ /**
+ * Downloads the latest repository from github, setting [latestSavedVersionHash].
+ * @return true, if an update was performed, false, otherwise (no update needed, or wasn't able to complete update)
+ */
+ suspend fun downloadUpdate(): Boolean = withContext(CoroutineName("Repo Update Check")) {
+ val latestSha = requestLatestGithubSha()
+ if (latestSha == null) {
+ logger.warn("Could not request github API to retrieve latest REPO sha.")
+ return@withContext false
+ }
+ val currentSha = loadSavedVersionHash()
+ if (latestSha != currentSha) {
+ val requestUrl = "https://github.com/$user/$repo/archive/$latestSha.zip"
+ logger.info("Planning to upgrade repository from $currentSha to $latestSha from $requestUrl")
+ val zipFile = downloadGithubArchive(requestUrl)
+ logger.info("Download repository zip file to $zipFile. Deleting old repository")
+ withContext(IO) { repoSavedLocation.deleteIfExists() }
+ logger.info("Extracting new repository")
+ withContext(IO) { extractNewRepository(zipFile) }
+ logger.info("Repository loaded on disk.")
+ saveVersionHash(latestSha)
+ return@withContext true
+ } else {
+ logger.debug("Repository on latest sha $currentSha. Not performing update")
+ return@withContext false
+ }
+ }
+
+ private fun extractNewRepository(zipFile: Path) {
+ repoSavedLocation.createDirectories()
+ ZipInputStream(zipFile.inputStream()).use { cis ->
+ while (true) {
+ val entry = cis.nextEntry ?: break
+ if (entry.isDirectory) continue
+ val extractedLocation =
+ repoSavedLocation.resolve(
+ entry.name.substringAfter('/', missingDelimiterValue = "")
+ )
+ if (repoSavedLocation !in extractedLocation.iterate { it.parent }) {
+ logger.error("Not Enough Updates detected an invalid zip file. This is a potential security risk, please report this in the Moulberry discord.")
+ throw RuntimeException("Not Enough Updates detected an invalid zip file. This is a potential security risk, please report this in the Moulberry discord.")
+ }
+ extractedLocation.parent.createDirectories()
+ cis.copyTo(extractedLocation.outputStream())
+ cis.closeEntry()
+ }
+ }
+ }
+
}
diff --git a/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoManager.kt b/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoManager.kt
new file mode 100644
index 0000000..f3d53e8
--- /dev/null
+++ b/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoManager.kt
@@ -0,0 +1,31 @@
+package moe.nea.notenoughupdates.repo
+
+import io.github.moulberry.repo.NEURepository
+import kotlinx.coroutines.launch
+import moe.nea.notenoughupdates.NotEnoughUpdates
+import moe.nea.notenoughupdates.NotEnoughUpdates.logger
+import net.minecraft.client.Minecraft
+import net.minecraft.network.protocol.game.ClientboundUpdateRecipesPacket
+
+object RepoManager {
+
+
+ val neuRepo: NEURepository = NEURepository.of(RepoDownloadManager.repoSavedLocation).apply {
+ registerReloadListener(ItemCache)
+ registerReloadListener {
+ if (Minecraft.getInstance().connection?.handleUpdateRecipes(ClientboundUpdateRecipesPacket(mutableListOf())) == null) {
+ logger.warn("Failed to issue a ClientboundUpdateRecipesPacket (to reload REI). This may lead to an outdated item list.")
+ }
+ }
+ }
+
+
+ fun launchAsyncUpdate() {
+ NotEnoughUpdates.coroutineScope.launch {
+ if (RepoDownloadManager.downloadUpdate()) {
+ neuRepo.reload()
+ }
+ }
+ }
+
+}
diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/SequenceUtil.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/SequenceUtil.kt
new file mode 100644
index 0000000..3e338bd
--- /dev/null
+++ b/src/main/kotlin/moe/nea/notenoughupdates/util/SequenceUtil.kt
@@ -0,0 +1,9 @@
+package moe.nea.notenoughupdates.util
+
+fun <T : Any> T.iterate(iterator: (T) -> T?): Sequence<T> = sequence {
+ var x: T? = this@iterate
+ while (x != null) {
+ yield(x)
+ x = iterator(x)
+ }
+}