diff --git a/common/src/main/kotlin/com/github/zly2006/xbackup/BackupDatabaseService.kt b/common/src/main/kotlin/com/github/zly2006/xbackup/BackupDatabaseService.kt index e8ba8b0..b70bb17 100644 --- a/common/src/main/kotlin/com/github/zly2006/xbackup/BackupDatabaseService.kt +++ b/common/src/main/kotlin/com/github/zly2006/xbackup/BackupDatabaseService.kt @@ -18,10 +18,11 @@ import java.io.ByteArrayOutputStream import java.io.File import java.io.IOException import java.io.InputStream -import java.nio.file.FileAlreadyExistsException import java.nio.file.Path +import java.nio.file.StandardCopyOption import java.nio.file.attribute.FileTime import java.security.MessageDigest +import java.util.UUID import java.util.concurrent.ConcurrentHashMap import java.util.zip.* import kotlin.coroutines.CoroutineContext @@ -265,57 +266,83 @@ class BackupDatabaseService( } } } - val md5 = if (sourceFile.isFile) sourceFile.inputStream().digest("MD5") - else "" - dbQuery { - BackupEntryTable.selectAll().where { - BackupEntryTable.path eq path.toString() and - (BackupEntryTable.isDirectory eq sourceFile.isDirectory) and - (BackupEntryTable.hash eq md5) - }.map { it.toBackupEntry() }.firstOrNull { - it.valid(this@BackupDatabaseService) - } - }?.let { return@retry it } - - val blob = getBlobFile(md5) - val gzip = sourceFile.length() > 1024 - val zippedSize: Long + val gzip = sourceFile.isFile && sourceFile.length() > 1024 + var md5 = "" + var zippedSize: Long if (sourceFile.isFile) { - if (!blob.exists()) runCatching { - // fuck u mcos - blob.createParentDirectories().createFile() - } - if (!gzip) { - try { - blob.outputStream().buffered().use { output -> + val beforeSize = sourceFile.length() + val beforeModified = sourceFile.lastModified() + val tempBlob = blobDir.resolve(".tmp").resolve(UUID.randomUUID().toString()) + try { + tempBlob.createParentDirectories() + val digest = MessageDigest.getInstance("MD5") + val buffer = ByteArray(DEFAULT_BUFFER_SIZE) + if (gzip) { + GZIPOutputStream(tempBlob.outputStream().buffered()).use { output -> sourceFile.inputStream().buffered().use { input -> - input.copyTo(output) + var read: Int + while (input.read(buffer).also { read = it } > 0) { + digest.update(buffer, 0, read) + output.write(buffer, 0, read) + } + } + } + } else { + tempBlob.outputStream().buffered().use { output -> + sourceFile.inputStream().buffered().use { input -> + var read: Int + while (input.read(buffer).also { read = it } > 0) { + digest.update(buffer, 0, read) + output.write(buffer, 0, read) + } } } - } catch (_: FileAlreadyExistsException) { - // fuck u macos } - zippedSize = sourceFile.length() - } - else { - GZIPOutputStream(blob.outputStream().buffered()).use { stream -> - sourceFile.inputStream().buffered().use { input -> - input.copyTo(stream) + md5 = digest.digest().joinToString("") { "%02x".format(it) } + zippedSize = tempBlob.fileSize() + + val afterSize = sourceFile.length() + val afterModified = sourceFile.lastModified() + if (beforeSize != afterSize || beforeModified != afterModified) { + tempBlob.deleteIfExists() + error("File changed while creating backup, file: $path") + } + + dbQuery { + BackupEntryTable.selectAll().where { + BackupEntryTable.path eq path.toString() and + (BackupEntryTable.isDirectory eq sourceFile.isDirectory) and + (BackupEntryTable.hash eq md5) + }.map { it.toBackupEntry() }.firstOrNull { + it.valid(this@BackupDatabaseService) + } + }?.let { + tempBlob.deleteIfExists() + return@retry it + } + + val blob = getBlobFile(md5) + if (blob.exists() && blob.fileSize() == zippedSize) { + tempBlob.deleteIfExists() + } else { + blob.createParentDirectories() + try { + tempBlob.moveTo(blob, StandardCopyOption.REPLACE_EXISTING) + } catch (e: IOException) { + if (blob.exists() && blob.fileSize() == zippedSize) { + tempBlob.deleteIfExists() + } else { + throw e + } } } - zippedSize = blob.fileSize() + } catch (e: Throwable) { + tempBlob.deleteIfExists() + throw e } - } - else { + } else { zippedSize = 0 } - if (sourceFile.isFile) { - if (MessageDigest.getInstance("MD5").digest(sourceFile.readBytes()) - .joinToString("") { "%02x".format(it) } != md5 - ) { - error("File hash mismatch when creating backup, file: $path, expected: $md5") - } - } syncDbQuery { val backupEntry = BackupEntryTable.insert { it[this.path] = path.toString() diff --git a/settings.gradle.kts b/settings.gradle.kts index 063d34a..29fb757 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -13,6 +13,7 @@ pluginManagement { plugins { id("dev.kikugie.stonecutter") version "0.7.7-beta.2" + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } rootProject.name = "X Backup" @@ -33,6 +34,7 @@ stonecutter { "1.21.5", "1.21.6", "1.21.9", + "1.21.11", ) } create(rootProject) diff --git a/src/main/kotlin/com/github/zly2006/xbackup/Commands.kt b/src/main/kotlin/com/github/zly2006/xbackup/Commands.kt index 0a857eb..1191432 100644 --- a/src/main/kotlin/com/github/zly2006/xbackup/Commands.kt +++ b/src/main/kotlin/com/github/zly2006/xbackup/Commands.kt @@ -20,6 +20,9 @@ import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.encodeToStream import kotlinx.serialization.json.put import me.lucko.fabric.api.permissions.v0.Permissions +//? if >=1.21.11 { +import net.minecraft.command.DefaultPermissions +//?} import net.minecraft.command.argument.ColumnPosArgumentType import net.minecraft.server.command.ServerCommandSource import net.minecraft.text.ClickEvent @@ -744,7 +747,18 @@ object Commands { Permissions.check(source, perm, defaultLevel) } catch (e: NoClassDefFoundError) { // If the API is not available, just return true + //? if >=1.21.11 { + val permission = when { + defaultLevel <= 0 -> null + defaultLevel <= 1 -> DefaultPermissions.MODERATORS + defaultLevel <= 2 -> DefaultPermissions.GAMEMASTERS + defaultLevel <= 3 -> DefaultPermissions.ADMINS + else -> DefaultPermissions.OWNERS + } + permission == null || source.permissions.hasPermission(permission) + //?} else { source.hasPermissionLevel(defaultLevel) + //?} } } } diff --git a/src/main/kotlin/com/github/zly2006/xbackup/Utils.kt b/src/main/kotlin/com/github/zly2006/xbackup/Utils.kt index 164a9e1..64fbc55 100644 --- a/src/main/kotlin/com/github/zly2006/xbackup/Utils.kt +++ b/src/main/kotlin/com/github/zly2006/xbackup/Utils.kt @@ -29,6 +29,9 @@ object Utils { inline fun MinecraftServer.save() { saveAll(false, false, true) + //? if >=1.21.11 { + syncChunkWrites() + //?} } inline fun MinecraftServer.finishRestore() { diff --git a/src/main/kotlin/com/github/zly2006/xbackup/XBackup.kt b/src/main/kotlin/com/github/zly2006/xbackup/XBackup.kt index 96117db..d024691 100644 --- a/src/main/kotlin/com/github/zly2006/xbackup/XBackup.kt +++ b/src/main/kotlin/com/github/zly2006/xbackup/XBackup.kt @@ -77,7 +77,7 @@ object XBackup : ModInitializer { fun loadConfig() { try { - config = (if (configPath.exists()) Json.decodeFromString(configPath.readText()) + config = (if (configPath.exists()) json.decodeFromString(configPath.readText()) else Config()) config.language = I18n.setLanguage(config.language) } catch (e: Exception) { @@ -92,6 +92,7 @@ object XBackup : ModInitializer { encodeDefaults = true prettyPrint = true allowTrailingComma = true + ignoreUnknownKeys = true } fun saveConfig() { @@ -146,7 +147,11 @@ object XBackup : ModInitializer { ServerLifecycleEvents.SERVER_STARTED.register { server -> this.server = server - server.commandManager.executeWithPrefix(XBackup.server!!.commandSource ,"1") + //? if >= 1.21.11 { + server.commandManager.parseAndExecute(XBackup.server!!.commandSource, "1") + //?} else { + server.commandManager.executeWithPrefix(XBackup.server!!.commandSource, "1") + //?} kotlin.runCatching { // sync client language to the integrated server config.language = I18n.setLanguage(MinecraftClient.getInstance().options.language) @@ -160,7 +165,7 @@ object XBackup : ModInitializer { val database = getDatabaseFromWorld(worldPath) if (config.mirrorMode) { val sourceConfig = kotlin.runCatching { - Json.decodeFromStream( + json.decodeFromStream( Path( config.mirrorFrom!!, "config", diff --git a/stonecutter.gradle.kts b/stonecutter.gradle.kts index d609505..8b2fc10 100644 --- a/stonecutter.gradle.kts +++ b/stonecutter.gradle.kts @@ -1,6 +1,6 @@ plugins { id("dev.kikugie.stonecutter") - id("fabric-loom") version "1.11-SNAPSHOT" apply false + id("fabric-loom") version "1.13.3" apply false kotlin("jvm") version "2.0.21" apply false kotlin("plugin.serialization") version "2.0.0" apply false diff --git a/versions/1.21.11/gradle.properties b/versions/1.21.11/gradle.properties new file mode 100644 index 0000000..7ef0d5c --- /dev/null +++ b/versions/1.21.11/gradle.properties @@ -0,0 +1,8 @@ +# https://fabricmc.net/develop +deps.yarn_build=4 +deps.fabric_api=0.141.1+1.21.11 + +mod.mc_dep=>=1.21.11 <=1.21.11 +mod.mc_title=1.21.11 +mod.mc_targets=1.21.11 +deps.poly_lib=2111.1.1