From 9796c6c583c5ce166b865ac691083aadef9baf04 Mon Sep 17 00:00:00 2001 From: Jinks Date: Fri, 6 Dec 2024 23:46:34 +0100 Subject: [PATCH] NeoForge rewrite based on Simple Backups by MelanX --- LICENSE | 202 ++++++++++ README.md | 24 +- build.gradle | 373 +++++++++++------- gradle.properties | 36 +- gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 44 ++- gradlew.bat | 37 +- settings.gradle | 13 + .../github/jinks/extbackup/BackupHandler.java | 142 ------- .../github/jinks/extbackup/ConfigHandler.java | 76 ---- .../com/github/jinks/extbackup/ExtBackup.java | 78 ---- .../github/jinks/extbackup/ExtBackupUtil.java | 19 - .../de/melanx/simplebackups/BackupData.java | 63 +++ .../de/melanx/simplebackups/BackupThread.java | 137 +++++++ .../melanx/simplebackups/EventListener.java | 67 ++++ .../melanx/simplebackups/SimpleBackups.java | 48 +++ .../de/melanx/simplebackups/StorageSize.java | 48 +++ .../client/ClientEventHandler.java | 39 ++ .../simplebackups/commands/BackupCommand.java | 37 ++ .../simplebackups/commands/PauseCommand.java | 36 ++ .../simplebackups/config/CommonConfig.java | 51 +++ .../simplebackups/config/ServerConfig.java | 32 ++ .../melanx/simplebackups/network/Pause.java | 32 ++ .../resources/META-INF/accesstransformer.cfg | 4 + src/main/resources/META-INF/mods.toml | 34 -- .../resources/META-INF/neoforge.mods.toml | 30 ++ .../assets/simplebackups/lang/de_de.json | 8 + .../assets/simplebackups/lang/en_us.json | 8 + src/main/resources/pack.mcmeta | 7 - 29 files changed, 1170 insertions(+), 559 deletions(-) create mode 100644 LICENSE create mode 100644 settings.gradle delete mode 100644 src/main/java/com/github/jinks/extbackup/BackupHandler.java delete mode 100644 src/main/java/com/github/jinks/extbackup/ConfigHandler.java delete mode 100644 src/main/java/com/github/jinks/extbackup/ExtBackup.java delete mode 100644 src/main/java/com/github/jinks/extbackup/ExtBackupUtil.java create mode 100644 src/main/java/de/melanx/simplebackups/BackupData.java create mode 100644 src/main/java/de/melanx/simplebackups/BackupThread.java create mode 100644 src/main/java/de/melanx/simplebackups/EventListener.java create mode 100644 src/main/java/de/melanx/simplebackups/SimpleBackups.java create mode 100644 src/main/java/de/melanx/simplebackups/StorageSize.java create mode 100644 src/main/java/de/melanx/simplebackups/client/ClientEventHandler.java create mode 100644 src/main/java/de/melanx/simplebackups/commands/BackupCommand.java create mode 100644 src/main/java/de/melanx/simplebackups/commands/PauseCommand.java create mode 100644 src/main/java/de/melanx/simplebackups/config/CommonConfig.java create mode 100644 src/main/java/de/melanx/simplebackups/config/ServerConfig.java create mode 100644 src/main/java/de/melanx/simplebackups/network/Pause.java create mode 100644 src/main/resources/META-INF/accesstransformer.cfg delete mode 100644 src/main/resources/META-INF/mods.toml create mode 100644 src/main/resources/META-INF/neoforge.mods.toml create mode 100644 src/main/resources/assets/simplebackups/lang/de_de.json create mode 100644 src/main/resources/assets/simplebackups/lang/en_us.json delete mode 100644 src/main/resources/pack.mcmeta diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index b5ac191..a45ad51 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,8 @@ -# ExtBackup -Minimal Minecraft backup mod, relying on external backup tools. +# Simple Backups +A simple mod to create scheduled backups. -## Why? -The regular well know backup mods lack the sophistication of dedicated backup tools. +[![Modrinth](https://badges.moddingx.org/modrinth/versions/fzSKSXVK)](https://modrinth.com/mod/simple-backups) +[![Modrinth](https://badges.moddingx.org/modrinth/downloads/fzSKSXVK)](https://modrinth.com/mod/simple-backups) -ExtBackup does not care about storage, retention, rotating old backups, freeing up space or any of that stuff. -All it does is save the world to disk and then call a script or program on a configurabe schedule. The external -program can be as simple or as complicated as you want it to be. Connect your miecraft to borg, restic, Veeam or -whatever you like and backup your worlds however you like. - -## Target audience -ExtBackup is aimed at server owners. It's so far only tested on Linux and does not offer much configuarion. It just runs -the program with the Minecraft directory as current working directory. It may well work on Windows, or it may not. - -## Acknowledgements -* [LatvianModder](https://github.com/LatvianModder) for his work on [FTB-Backups](https://github.com/FTBTeam/FTB-Backups) - where I stole a lot of the code for this. -* [alexbobp](https://github.com/alexbobp) and the [elytra](https://github.com/elytra) group for - [BTFU](https://github.com/elytra/BTFU), which gave me the idea but didn't go quite far enough. +[![Curseforge](https://badges.moddingx.org/curseforge/versions/583228)](https://www.curseforge.com/minecraft/mc-mods/simple-backups) +[![CurseForge](https://badges.moddingx.org/curseforge/downloads/583228)](https://www.curseforge.com/minecraft/mc-mods/simple-backups) diff --git a/build.gradle b/build.gradle index fa4d3ed..d4c42f9 100644 --- a/build.gradle +++ b/build.gradle @@ -1,174 +1,245 @@ -buildscript { - repositories { - maven { url = 'https://files.minecraftforge.net/maven' } - mavenCentral() - } - dependencies { - classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true - } +import net.darkhax.curseforgegradle.TaskPublishCurseForge +import org.w3c.dom.Document +import org.w3c.dom.NodeList + +import javax.xml.parsers.DocumentBuilderFactory +import java.nio.ByteBuffer +import java.nio.charset.StandardCharsets + +plugins { + id 'java-library' + id 'idea' + id 'maven-publish' + id 'net.darkhax.curseforgegradle' version '1.1.18' + id 'com.modrinth.minotaur' version '2.+' + id 'net.neoforged.moddev' version '[2.0,2.1)' } -apply plugin: 'net.minecraftforge.gradle' -// Only edit below this line, the above code adds and enables the necessary things for Forge to be setup. -apply plugin: 'eclipse' -apply plugin: 'maven-publish' -version = '1.20.1-1.1.1' -group = 'com.github.jinks.extbackup' // http://maven.apache.org/guides/mini/guide-naming-conventions.html -archivesBaseName = 'ExtBackup' +version = getVersion(minecraft_version + "-" + base_version, new URL(remote_maven + "/" + group.replace('.', '/') + "/" + name + "/maven-metadata.xml")) -java.toolchain.languageVersion = JavaLanguageVersion.of(17) - -println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch')) -minecraft { - // The mappings can be changed at any time, and must be in the following format. - // Channel: Version: - // snapshot YYYYMMDD Snapshot are built nightly. - // stable # Stables are built at the discretion of the MCP team. - // official MCVersion Official field/method names from Mojang mapping files - // - // You must be aware of the Mojang license when using the 'official' mappings. - // See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md - // - // Use non-default mappings at your own risk. they may not always work. - // Simply re-run your setup task after changing the mappings to update your workspace. - mappings channel: 'official', version: '1.20.1' - // makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable. - - // accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') - - // Default run configurations. - // These can be tweaked, removed, or duplicated as needed. - runs { - client { - workingDirectory project.file('run') - - // Recommended logging data for a userdev environment - // The markers can be changed as needed. - // "SCAN": For mods scan. - // "REGISTRIES": For firing of registry events. - // "REGISTRYDUMP": For getting the contents of all registries. - property 'forge.logging.markers', 'REGISTRIES' - - // Recommended logging level for the console - // You can set various levels here. - // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels - property 'forge.logging.console.level', 'debug' - - mods { - examplemod { - source sourceSets.main - } - } - } - - server { - workingDirectory project.file('run') - - // Recommended logging data for a userdev environment - // The markers can be changed as needed. - // "SCAN": For mods scan. - // "REGISTRIES": For firing of registry events. - // "REGISTRYDUMP": For getting the contents of all registries. - property 'forge.logging.markers', 'REGISTRIES' - - // Recommended logging level for the console - // You can set various levels here. - // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels - property 'forge.logging.console.level', 'debug' - - mods { - examplemod { - source sourceSets.main - } - } - } - - data { - workingDirectory project.file('run') - - // Recommended logging data for a userdev environment - // The markers can be changed as needed. - // "SCAN": For mods scan. - // "REGISTRIES": For firing of registry events. - // "REGISTRYDUMP": For getting the contents of all registries. - property 'forge.logging.markers', 'REGISTRIES' - - // Recommended logging level for the console - // You can set various levels here. - // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels - property 'forge.logging.console.level', 'debug' - - // Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources. - args '--mod', 'examplemod', '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/') - - mods { - examplemod { - source sourceSets.main - } - } - } - } +repositories { + mavenLocal() } +base { + archivesName = project.name +} + +java.toolchain.languageVersion = JavaLanguageVersion.of(21) + // Include resources generated by data generators. sourceSets.main.resources { srcDir 'src/generated/resources' } - -dependencies { - // Specify the version of Minecraft to use, If this is any group other then 'net.minecraft' it is assumed - // that the dep is a ForgeGradle 'patcher' dependency. And it's patches will be applied. - // The userdev artifact is a special name and will get all sorts of transformations applied to it. - minecraft 'net.minecraftforge:forge:1.20.1-47.2.16' - - // You may put jars on which you depend on in ./libs or you may define them like so.. - // compile "some.group:artifact:version:classifier" - // compile "some.group:artifact:version" - - // Real examples - // compile 'com.mod-buildcraft:buildcraft:6.0.8:dev' // adds buildcraft to the dev env - // compile 'com.googlecode.efficient-java-matrix-library:ejml:0.24' // adds ejml to the dev env - - // The 'provided' configuration is for optional dependencies that exist at compile-time but might not at runtime. - // provided 'com.mod-buildcraft:buildcraft:6.0.8:dev' - - // These dependencies get remapped to your current MCP mappings - // deobf 'com.mod-buildcraft:buildcraft:6.0.8:dev' - - // For more info... - // http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html - // http://www.gradle.org/docs/current/userguide/dependency_management.html - +configurations { + runtimeClasspath.extendsFrom localRuntime } -// Example for how to get properties into the manifest for reading by the runtime.. -jar { - manifest { - attributes([ - "Specification-Title": "extbackup", - "Specification-Vendor": "jinks", - "Specification-Version": "1", // We are version 1 of ourselves - "Implementation-Title": project.name, - "Implementation-Version": "${version}", - "Implementation-Vendor" :"jinks", - "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") - ]) +neoForge { + version = project.neo_version + + validateAccessTransformers = true + + parchment { + minecraftVersion = project.parchment_minecraft_version + mappingsVersion = project.parchment_mappings_version + } + + runs { + client { + client() + + systemProperty 'neoforge.enableGameTestNamespaces', project.modid + } + + server { + server() + programArgument '--nogui' + systemProperty 'neoforge.enabledGameTestNamespaces', project.modid + } + + data { + data() + programArguments.addAll '--mod', project.modid, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() + } + + gameTestServer { + type = "gameTestServer" + systemProperty 'neoforge.enabledGameTestNamespaces', project.modid + } + + configureEach { + // "SCAN": For mods scan. + // "REGISTRIES": For firing of registry events. + // "REGISTRYDUMP": For getting the contents of all registries. + systemProperty 'forge.logging.markers', 'REGISTRIES' + logLevel = org.slf4j.event.Level.DEBUG + gameDirectory = project.file('run' + name.capitalize()) + } + } + + mods { + // define mod <-> source bindings + // these are used to tell the game which sources are for which mod + // mostly optional in a single mod project + // but multi mod projects should define one per mod + "${modid}" { + sourceSet(sourceSets.main) + } } } -// Example configuration to allow publishing using the maven-publish task -// This is the preferred method to reobfuscate your jar file -jar.finalizedBy('reobfJar') -// However if you are in a multi-project build, dev time needs unobfed jar files, so you can delay the obfuscation until publishing by doing -//publish.dependsOn('reobfJar') +tasks.withType(ProcessResources).configureEach { + var replaceProperties = [ + minecraft_version : minecraft_version, + neo_version : neo_version, + loader_version_range: loader_version_range, + modid : modid, + license : license, + mod_version : version + ] + inputs.properties replaceProperties + + filesMatching(['META-INF/neoforge.mods.toml']) { + expand replaceProperties + } +} publishing { publications { mavenJava(MavenPublication) { - artifact jar + groupId = project.group + artifactId = project.name + version = project.version + artifact(jar) + pom { + licenses { + license { + name = license + url = license_url + } + } + } } } repositories { maven { - url "file:///${project.projectDir}/mcmodsrepo" + url "https://maven.melanx.de/release" + credentials { + username = project.findProperty('mavenUsername') ?: System.getenv('MAVEN_USERNAME') + password = project.findProperty('mavenPassword') ?: System.getenv('MAVEN_PASSWORD') + } } } } + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' +} + +idea { + module { + downloadSources = true + downloadJavadoc = true + } +} + +private static String getVersion(String baseVersion, URL url) { + try { + HttpURLConnection connection = (HttpURLConnection) url.openConnection() + connection.setRequestMethod("GET") + + Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(connection.getInputStream()) + NodeList versionNodes = doc.getElementsByTagName("version") + + String latestVersion = null; + for (int i = 0; i < versionNodes.getLength(); i++) { + String version = versionNodes.item(i).getTextContent() + if (version.startsWith(baseVersion)) { + latestVersion = version + } + } + + if (latestVersion == null) { + return baseVersion + ".0" + } + + return baseVersion + "." + (Integer.parseInt(latestVersion.substring(latestVersion.lastIndexOf('.') + 1)) + 1) + } catch (FileNotFoundException ignored) { + // This exception is thrown if the maven-metadata.xml file doesn't exist at the provided URL, + // which would be the case if there were no previous publications to this Maven repository with this project + return baseVersion + ".0" + } catch (Exception e) { + throw new RuntimeException(e) + } +} + +if (!getProperty('project', 'curse').isEmpty()) { + task curseforge(type: TaskPublishCurseForge) { + apiToken = project.findProperty('curse_token') ?: System.getenv('CURSEFORGE_TOKEN') ?: '' + + Closure fileConfig = { file -> + file.releaseType = getProperty('release', 'curse', 'release') + file.changelog = changelog(project) + file.changelogType = 'markdown' + getArray(getProperty('requirements', 'curse')).each { file.addRequirement(it) } + getArray(getProperty('optionals', 'curse')).each { file.addOptional(it) } + getArray(getProperty('versions', 'curse', minecraft_version)).each { file.addGameVersion(it) } + file.addModLoader('neoforge') + } + + def mainFile = upload(getProperty('project', 'curse'), jar) + fileConfig(mainFile) + } + project.tasks.getByName('curseforge').dependsOn('build') +} + +if (!getProperty('project', 'modrinth').isEmpty()) { + + modrinth { + token = project.findProperty('modrinth_token') ?: System.getenv('MODRINTH_TOKEN') ?: '' + projectId = getProperty('project', 'modrinth') + versionNumber = project.version + versionName = jar.getArchiveFileName().get() + uploadFile = jar + + dependencies = [] + changelog = changelog(project) + versionType = getProperty('release', 'modrinth', 'release') + getArray(getProperty('requirements', 'modrinth')).each { dependencies.add(new com.modrinth.minotaur.dependencies.ModDependency(it, 'required')) } + getArray(getProperty('optionals', 'modrinth')).each { dependencies.add(new com.modrinth.minotaur.dependencies.ModDependency(it, 'optional')) } + gameVersions = getArray(getProperty('versions', 'modrinth', minecraft_version)).toList() + loaders = ['neoforge'] + } +} + +String getProperty(String entry, String platform) { + return getProperty(entry, platform, '') +} + +String getProperty(String entry, String platform, String ret) { + return project.findProperty('upload_' + entry) ?: project.findProperty(platform + '_' + entry) ?: ret +} + +static String[] getArray(String deps) { + return deps.split(',')*.strip().toList().findAll { !it.isEmpty() } +} + +String changelog(Project project) { + String logFmt = '--pretty=tformat:"- [%s](' + project.changelog_repository.toString() + ') - *%aN*"' + def stdout = new ByteArrayOutputStream() + def gitHash = System.getenv('GIT_COMMIT') + def gitPrevHash = System.getenv('GIT_PREVIOUS_COMMIT') + if (gitPrevHash == "0000000000000000000000000000000000000000") { + gitPrevHash = "HEAD~1" + } + + if (gitHash != null && gitPrevHash != null) { + exec { + commandLine 'bash', '-c', "git log ${logFmt} ${gitPrevHash}...${gitHash} | grep -v '^- \\[\\[meta\\]'" + standardOutput = stdout + } + } else { + stdout.write('No changelog provided\n'.getBytes(StandardCharsets.UTF_8)) + } + + return StandardCharsets.UTF_8.decode(ByteBuffer.wrap(stdout.toByteArray())).toString() +} diff --git a/gradle.properties b/gradle.properties index 878bf1f..838c570 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,32 @@ -# Sets default memory used for gradle commands. Can be overridden by user or command line properties. -# This is required to provide enough memory for the Minecraft decompilation process. -org.gradle.jvmargs=-Xmx3G -org.gradle.daemon=false \ No newline at end of file +org.gradle.jvmargs=-Xmx6G +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configuration-cache=true + +## Mappings +parchment_minecraft_version=1.21 +parchment_mappings_version=2024.07.28 + +## Loader Properties +minecraft_version=1.21 +neo_version=21.0.167 +loader_version_range=[4,) + +## Mod Properties +modid=simplebackups +mod_name=Simple Backups +group=de.melanx +base_version=2.0 + +## Upload Properties +upload_versions=1.21, 1.21.1 +upload_release=release +modrinth_project=fzSKSXVK +curse_project=583228 + +## Misc +remote_maven=https://maven.melanx.de/release +license=The Apache License, Version 2.0 +license_url=https://www.apache.org/licenses/LICENSE-2.0.txt +changelog_repository=https://github.com/ChaoticTrials/SimpleBackups/commit/%H diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index aa991fc..09523c0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c787..f5feea6 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +82,12 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +134,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +201,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ @@ -205,6 +217,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index 107acd3..9d21a21 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,8 +13,10 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +27,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -75,13 +78,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..b4dac67 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,13 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + maven { url = 'https://maven.neoforged.net/releases' } + } +} + +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' +} + +rootProject.name = 'SimpleBackups' diff --git a/src/main/java/com/github/jinks/extbackup/BackupHandler.java b/src/main/java/com/github/jinks/extbackup/BackupHandler.java deleted file mode 100644 index b875b74..0000000 --- a/src/main/java/com/github/jinks/extbackup/BackupHandler.java +++ /dev/null @@ -1,142 +0,0 @@ -package com.github.jinks.extbackup; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.text.SimpleDateFormat; -import java.util.Date; - -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ServerLevel; - -public enum BackupHandler { - INSTANCE; - - public long nextBackup = -1L; - public int doingBackup = 0; - public boolean hadPlayersOnline = false; - private boolean youHaveBeenWarned = false; - public void init() { - doingBackup = 0; - nextBackup = System.currentTimeMillis() + ConfigHandler.COMMON.time(); - File script = ConfigHandler.COMMON.getScript(); - - if (!script.exists()) { - script.getParentFile().mkdirs(); - try { - Files.write(script.toPath(), "#!/bin/bash\n# Put your backup script here!\n\nexit 0".getBytes(StandardCharsets.UTF_8)); - script.setExecutable(true); - ExtBackup.logger.info("No backup script was found, a script has been created at " + script.getAbsolutePath() + ", please modify it to your liking."); - } catch (IOException e) { - ExtBackup.logger.error("Backup script does not exist and cannot be created!"); - ExtBackup.logger.error("Disabling ExtBackup!"); - ConfigHandler.COMMON.enabled.set(false); - } - } - - ExtBackup.logger.info("Active script: " + script.getAbsolutePath()); - ExtBackup.logger.info("Next Backup at: " + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(new Date(nextBackup))); - } - - public boolean run(MinecraftServer server) { - if (doingBackup != 0 || !ConfigHandler.COMMON.enabled.get()) { - return false; - } - if (doingBackup != 0) { - ExtBackup.logger.warn("Tried to start backup while one is already running!"); - return false; - } - - File script = ConfigHandler.COMMON.getScript(); - if (!script.exists() || !script.canExecute()) { - ExtBackup.logger.error("Cannot access or execute backup script. Bailing out!"); - return false; - } - - doingBackup = 1; - - try { - if (server.getPlayerList() != null) { - server.getPlayerList().saveAll(); - } - - for (ServerLevel level : server.getAllLevels()) { - if (level != null) { - //world.save(null, true, false); - level.noSave = true; - } - } - } catch (Exception ex) { - ExtBackup.logger.error("Saving the world failed!"); - enableSaving(server); - ex.printStackTrace(); - return false; - } - - new Thread(() -> { - try { - doBackup(server, script); - } catch (Exception ex) { - ex.printStackTrace(); - } - - doingBackup = 2; - }).start(); - - nextBackup = System.currentTimeMillis() + ConfigHandler.COMMON.time(); - return true; - } - - private int doBackup(MinecraftServer server, File script) { - if (ConfigHandler.COMMON.silent.get()) {ExtBackup.logger.info("Starting backup.");} - ExtBackupUtil.broadcast(server, "Starting Backup!"); - - ProcessBuilder pb = new ProcessBuilder(script.getAbsolutePath()); - int returnValue = -1; - //Map env = pb.environment(); - pb.redirectErrorStream(true); - try { - Process backup = pb.start(); - returnValue = backup.waitFor(); - } catch (Exception ex) { - enableSaving(server); - ExtBackup.logger.error("Something went wrong with the Backup script!"); - ExtBackup.logger.error("Check your Backups."); - ex.printStackTrace(); - } - enableSaving(server); - youHaveBeenWarned = false; - if (ConfigHandler.COMMON.silent.get()) {ExtBackup.logger.info("Backup done.");} - ExtBackupUtil.broadcast(server, "Backup done!"); - ExtBackup.logger.info("Next Backup at: " + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(new Date(nextBackup))); - return returnValue; - } - - private void enableSaving(MinecraftServer server) { - for (ServerLevel level : server.getAllLevels()) { - if (level != null) { - level.noSave = false; - } - } - } - - public void tick(MinecraftServer server, long now) { - if (nextBackup > 0L && nextBackup <= now) { - //ExtBackup.logger.info("Backup time!"); - if (!ConfigHandler.COMMON.only_if_players_online.get() || hadPlayersOnline || !server.getPlayerList().getPlayers().isEmpty()) { - hadPlayersOnline = false; - run(server); - } - } - if (doingBackup > 1) { - doingBackup = 0; - } else if (doingBackup > 0) { - if (now - nextBackup > 1200000 && !youHaveBeenWarned) { - ExtBackup.logger.warn("There has been a running backup for more than 20 minutes."); - ExtBackup.logger.warn("Something seems to be wrong."); - youHaveBeenWarned = true; - } - } - } -} \ No newline at end of file diff --git a/src/main/java/com/github/jinks/extbackup/ConfigHandler.java b/src/main/java/com/github/jinks/extbackup/ConfigHandler.java deleted file mode 100644 index 01e6791..0000000 --- a/src/main/java/com/github/jinks/extbackup/ConfigHandler.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.github.jinks.extbackup; - -import java.io.File; - -import org.apache.commons.lang3.tuple.Pair; - -import net.minecraftforge.common.ForgeConfigSpec; -import net.minecraftforge.fml.loading.FMLPaths; - -public final class ConfigHandler { - - public static class Common { - public final ForgeConfigSpec.BooleanValue enabled; - private final ForgeConfigSpec.IntValue backup_timer; - public final ForgeConfigSpec.BooleanValue silent; - public final ForgeConfigSpec.BooleanValue only_if_players_online; - public final ForgeConfigSpec.BooleanValue force_on_shutdown; - public final ForgeConfigSpec.ConfigValue script; - - public Common(ForgeConfigSpec.Builder builder) { - enabled = builder - .comment("backups are enabled") - .define("enabled", true); - - backup_timer = builder - .comment("interval in minutes to run the backup script") - .defineInRange("backup_timer", 10, 1 , 3600); - - silent = builder - .comment("be silent, do not post backups in chat") - .define("silent", false); - - only_if_players_online = builder - .comment("run only if players are online") - .define("only_if_players_online", true); - - force_on_shutdown = builder - .comment("run backup on server shutdown") - .define("force_on_shutdown", true); - - script = builder - .comment("script location - this script is run on each interval") - .define("script", "local/extbackup/runbackup.sh"); - } - - public long time() { - return (long) (ConfigHandler.COMMON.backup_timer.get() * 60000L); - } - - private static File cachedScript; - public File getScript() { - String scr = ConfigHandler.COMMON.script.get(); - if (scr == null) { - ExtBackup.logger.warn("Script is NULL!"); - return null; - } - if (cachedScript == null) { - cachedScript = scr.trim().isEmpty() ? new File(FMLPaths.GAMEDIR.get() + "local/extbackup/runbackup.sh") : new File(scr.trim()); - } - return cachedScript; - } - } - - public static final Common COMMON; - public static final ForgeConfigSpec COMMON_SPEC; - static { - final Pair specPair = new ForgeConfigSpec.Builder().configure(Common::new); - COMMON_SPEC = specPair.getRight(); - COMMON = specPair.getLeft(); - } - - public static void onConfigLoad() { - //BackupHandler.INSTANCE.nextBackup = System.currentTimeMillis() + ConfigHandler.COMMON.time(); - //ExtBackup.logger.info("Config Changed, next backup at: " + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(new Date(BackupHandler.INSTANCE.nextBackup))); - } -} \ No newline at end of file diff --git a/src/main/java/com/github/jinks/extbackup/ExtBackup.java b/src/main/java/com/github/jinks/extbackup/ExtBackup.java deleted file mode 100644 index 80dbebf..0000000 --- a/src/main/java/com/github/jinks/extbackup/ExtBackup.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.github.jinks.extbackup; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import net.minecraft.server.MinecraftServer; -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.event.TickEvent; -import net.minecraftforge.event.entity.player.PlayerEvent; -import net.minecraftforge.eventbus.api.IEventBus; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.ModLoadingContext; -import net.minecraftforge.fml.config.ModConfig; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; -import net.minecraftforge.event.server.ServerStartedEvent; -import net.minecraftforge.event.server.ServerStoppingEvent; -import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; - -@Mod(ExtBackup.MOD_ID) -@Mod.EventBusSubscriber(modid = ExtBackup.MOD_ID) -public class ExtBackup { - - public static final String MOD_ID = "extbackup"; - - public static final Logger logger = LogManager.getLogger("ExtBackup"); - - public ExtBackup() { - ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ConfigHandler.COMMON_SPEC); - IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus(); - modBus.addListener(this::setup); - //modBus.addListener((ModConfig.Loading e) -> ConfigHandler.onConfigLoad()); - //modBus.addListener((ModConfig.Reloading e) -> ConfigHandler.onConfigLoad()); - - // Register ourselves for server and other game events we are interested in - IEventBus forgeBus = MinecraftForge.EVENT_BUS; - forgeBus.register(this); - forgeBus.addListener(this::serverAboutToStart); - forgeBus.addListener(this::serverStopping); - } - - private void setup(final FMLCommonSetupEvent event) { - ConfigHandler.onConfigLoad(); - } - - public void serverAboutToStart(ServerStartedEvent event) { - BackupHandler.INSTANCE.init(); - } - - @SubscribeEvent - public void serverStopping(ServerStoppingEvent event) { - if (ConfigHandler.COMMON.force_on_shutdown.get()) { - MinecraftServer server = event.getServer(); - - if (server != null) { - BackupHandler.INSTANCE.run(server); - } - } - } - - @SubscribeEvent - public static void serverTick(TickEvent.ServerTickEvent event) { - if (event.phase != TickEvent.Phase.START) { - //MinecraftServer server = LogicalSidedProvider.INSTANCE.get(LogicalSide.SERVER); - MinecraftServer server = event.getServer(); - - if (server != null) { - //logger.debug("Server Tick! " + event.phase); - BackupHandler.INSTANCE.tick(server, System.currentTimeMillis()); - } - } - } - - @SubscribeEvent - public static void playerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) { - BackupHandler.INSTANCE.hadPlayersOnline = true; - } -} diff --git a/src/main/java/com/github/jinks/extbackup/ExtBackupUtil.java b/src/main/java/com/github/jinks/extbackup/ExtBackupUtil.java deleted file mode 100644 index 74b9aa3..0000000 --- a/src/main/java/com/github/jinks/extbackup/ExtBackupUtil.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.github.jinks.extbackup; - -import net.minecraft.server.MinecraftServer; -import net.minecraft.Util; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.ChatType; - -public class ExtBackupUtil { - - public static void broadcast(MinecraftServer server, String message) { - if (!ConfigHandler.COMMON.silent.get()) { - //Component bcMessage = new TextComponent("[§1ExtBackup§r] " + message); - Component bcMessage = Component.literal("[§1ExtBackup§r] " + message); - //server.getPlayerList().broadcastMessage(bcMessage, ChatType.SYSTEM, Util.NIL_UUID); - server.getPlayerList().broadcastSystemMessage(bcMessage, true); - } - } - -} diff --git a/src/main/java/de/melanx/simplebackups/BackupData.java b/src/main/java/de/melanx/simplebackups/BackupData.java new file mode 100644 index 0000000..331731c --- /dev/null +++ b/src/main/java/de/melanx/simplebackups/BackupData.java @@ -0,0 +1,63 @@ +package de.melanx.simplebackups; + +import net.minecraft.core.HolderLookup; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.saveddata.SavedData; + +import javax.annotation.Nonnull; + +public class BackupData extends SavedData { + + private long lastSaved; + private boolean paused; + + private BackupData() { + // use BackupData.get + } + + @Nonnull + @Override + public CompoundTag save(@Nonnull CompoundTag nbt, @Nonnull HolderLookup.Provider provider) { + nbt.putLong("lastSaved", this.lastSaved); + nbt.putBoolean("paused", this.paused); + return nbt; + } + + public static BackupData get(ServerLevel level) { + return BackupData.get(level.getServer()); + } + + public static BackupData get(MinecraftServer server) { + return server.overworld().getDataStorage().computeIfAbsent(BackupData.factory(), "simplebackups"); + } + + public BackupData load(@Nonnull CompoundTag nbt, @Nonnull HolderLookup.Provider provider) { + this.lastSaved = nbt.getLong("lastSaved"); + this.paused = nbt.getBoolean("paused"); + return this; + } + + public void setPaused(boolean paused) { + this.paused = paused; + this.setDirty(); + } + + public boolean isPaused() { + return this.paused; + } + + public long getLastSaved() { + return this.lastSaved; + } + + public void updateSaveTime(long time) { + this.lastSaved = time; + this.setDirty(); + } + + private static SavedData.Factory factory() { + return new SavedData.Factory<>(BackupData::new, (nbt, provider) -> new BackupData().load(nbt, provider)); + } +} diff --git a/src/main/java/de/melanx/simplebackups/BackupThread.java b/src/main/java/de/melanx/simplebackups/BackupThread.java new file mode 100644 index 0000000..1bf0f22 --- /dev/null +++ b/src/main/java/de/melanx/simplebackups/BackupThread.java @@ -0,0 +1,137 @@ +package de.melanx.simplebackups; + +import de.melanx.simplebackups.config.CommonConfig; +import de.melanx.simplebackups.config.ServerConfig; +import de.melanx.simplebackups.network.Pause; +import net.minecraft.ChatFormatting; +import net.minecraft.DefaultUncaughtExceptionHandler; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.Style; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +//import net.minecraft.world.level.storage.LevelStorageSource; +import net.neoforged.fml.i18n.I18nManager; +import net.neoforged.neoforge.network.registration.NetworkRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.SignStyle; +import java.time.temporal.ChronoField; +import java.util.*; + +public class BackupThread extends Thread { + + private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder() + .appendValue(ChronoField.YEAR, 4, 10, SignStyle.EXCEEDS_PAD).appendLiteral('-') + .appendValue(ChronoField.MONTH_OF_YEAR, 2).appendLiteral('-') + .appendValue(ChronoField.DAY_OF_MONTH, 2).appendLiteral('_') + .appendValue(ChronoField.HOUR_OF_DAY, 2).appendLiteral('-') + .appendValue(ChronoField.MINUTE_OF_HOUR, 2).appendLiteral('-') + .appendValue(ChronoField.SECOND_OF_MINUTE, 2) + .toFormatter(); + public static final Logger LOGGER = LoggerFactory.getLogger(BackupThread.class); + private final MinecraftServer server; + private final boolean quiet; + private BackupThread(@Nonnull MinecraftServer server, boolean quiet, BackupData backupData) { + this.server = server; + this.quiet = quiet; + this.setName("SimpleBackups"); + this.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)); + } + + public static boolean tryCreateBackup(MinecraftServer server) { + BackupData backupData = BackupData.get(server); + if (BackupThread.shouldRunBackup(server)) { + BackupThread thread = new BackupThread(server, false, backupData); + thread.start(); + long currentTime = System.currentTimeMillis(); + backupData.updateSaveTime(currentTime); + + return true; + } + + return false; + } + + public static boolean shouldRunBackup(MinecraftServer server) { + BackupData backupData = BackupData.get(server); + if (!CommonConfig.isEnabled() || backupData.isPaused()) { + return false; + } + + return System.currentTimeMillis() - CommonConfig.getTimer() > backupData.getLastSaved(); + } + + public static void createBackup(MinecraftServer server, boolean quiet) { + BackupThread thread = new BackupThread(server, quiet, null); + thread.start(); + } + + @Override + public void run() { + try { + long start = System.currentTimeMillis(); + this.broadcast("simplebackups.backup_started", Style.EMPTY.withColor(ChatFormatting.GOLD)); + long end = System.currentTimeMillis(); + String time = Timer.getTimer(end - start); + this.broadcast("simplebackups.backup_finished", Style.EMPTY.withColor(ChatFormatting.GOLD), time); + } catch (Exception e) { + SimpleBackups.LOGGER.error("Error backing up", e); + } + } + + private void broadcast(String message, Style style, Object... parameters) { + if (CommonConfig.sendMessages() && !this.quiet) { + this.server.execute(() -> { + this.server.getPlayerList().getPlayers().forEach(player -> { + if (ServerConfig.messagesForEveryone() || player.hasPermissions(2)) { + player.sendSystemMessage(BackupThread.component(player, message, parameters).withStyle(style)); + } + }); + }); + } + } + + @SuppressWarnings("null") + public static MutableComponent component(@Nullable ServerPlayer player, String key, Object... parameters) { + if (player != null) { + //noinspection UnstableApiUsage + if (NetworkRegistry.hasChannel(player.connection.connection, null, Pause.ID)) { + return Component.translatable(key, parameters); + } + } + + return Component.literal(String.format(Optional.ofNullable(I18nManager.loadTranslations("en_us").get(key)).orElse(key), parameters)); + } + + // vanilla copy with modifications + private long makeWorldBackup() throws IOException { + return 0; + } + + private static class Timer { + + private static final SimpleDateFormat SECONDS = new SimpleDateFormat("s.SSS"); + private static final SimpleDateFormat MINUTES = new SimpleDateFormat("mm:ss"); + private static final SimpleDateFormat HOURS = new SimpleDateFormat("HH:mm"); + + public static String getTimer(long milliseconds) { + Date date = new Date(milliseconds); + double seconds = milliseconds / 1000d; + if (seconds < 60) { + return SECONDS.format(date) + "s"; + } else if (seconds < 3600) { + return MINUTES.format(date) + "min"; + } else { + return HOURS.format(date) + "h"; + } + } + } +} diff --git a/src/main/java/de/melanx/simplebackups/EventListener.java b/src/main/java/de/melanx/simplebackups/EventListener.java new file mode 100644 index 0000000..b6bdc65 --- /dev/null +++ b/src/main/java/de/melanx/simplebackups/EventListener.java @@ -0,0 +1,67 @@ +package de.melanx.simplebackups; + +import de.melanx.simplebackups.commands.BackupCommand; +import de.melanx.simplebackups.commands.PauseCommand; +import de.melanx.simplebackups.config.CommonConfig; +import de.melanx.simplebackups.config.ServerConfig; +import de.melanx.simplebackups.network.Pause; +import net.minecraft.commands.Commands; +//import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.event.RegisterCommandsEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import net.neoforged.neoforge.event.tick.LevelTickEvent; +import net.neoforged.neoforge.network.PacketDistributor; +import net.neoforged.neoforge.network.registration.NetworkRegistry; + +public class EventListener { + + private boolean doBackup; + + @SubscribeEvent + public void registerCommands(RegisterCommandsEvent event) { + event.getDispatcher().register(Commands.literal(SimpleBackups.MODID) + .requires(stack -> ServerConfig.commandsCheatsDisabled() || stack.hasPermission(2)) + .then(BackupCommand.register()) + .then(PauseCommand.register())); + } + + @SubscribeEvent + public void onServerTick(LevelTickEvent.Post event) { + if (event.getLevel() instanceof ServerLevel level && !level.isClientSide + && level.getGameTime() % 20 == 0 && level == level.getServer().overworld()) { + + if (!level.getServer().getPlayerList().getPlayers().isEmpty() || this.doBackup || CommonConfig.doNoPlayerBackups()) { + this.doBackup = false; + + boolean done = BackupThread.tryCreateBackup(level.getServer()); + if (done) { + SimpleBackups.LOGGER.info("Backup done."); + } + } + } + } + + @SuppressWarnings("null") + @SubscribeEvent + public void onPlayerConnect(PlayerEvent.PlayerLoggedInEvent event) { + ServerPlayer player = (ServerPlayer) event.getEntity(); + //noinspection UnstableApiUsage + if (CommonConfig.isEnabled() && event.getEntity().getServer() != null && NetworkRegistry.hasChannel(player.connection.connection, null, Pause.ID)) { + PacketDistributor.sendToPlayer(player, new Pause(BackupData.get(event.getEntity().getServer()).isPaused())); + } + } + + @SuppressWarnings("null") + @SubscribeEvent + public void onPlayerDisconnect(PlayerEvent.PlayerLoggedOutEvent event) { + if (event.getEntity() instanceof ServerPlayer player) { + //noinspection ConstantConditions + if (player.getServer().getPlayerList().getPlayers().isEmpty()) { + this.doBackup = true; + } + } + } +} diff --git a/src/main/java/de/melanx/simplebackups/SimpleBackups.java b/src/main/java/de/melanx/simplebackups/SimpleBackups.java new file mode 100644 index 0000000..9e896de --- /dev/null +++ b/src/main/java/de/melanx/simplebackups/SimpleBackups.java @@ -0,0 +1,48 @@ +package de.melanx.simplebackups; + +import de.melanx.simplebackups.client.ClientEventHandler; +import de.melanx.simplebackups.config.CommonConfig; +import de.melanx.simplebackups.config.ServerConfig; +import de.melanx.simplebackups.network.Pause; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.config.ModConfig; +import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; +import net.neoforged.neoforge.network.registration.PayloadRegistrar; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Mod(SimpleBackups.MODID) +public class SimpleBackups { + + public static final Logger LOGGER = LoggerFactory.getLogger(SimpleBackups.class); + public static final String MODID = "simplebackups"; + + public SimpleBackups(IEventBus modEventBus, ModContainer modContainer, Dist dist) { + modContainer.registerConfig(ModConfig.Type.COMMON, CommonConfig.CONFIG); + modContainer.registerConfig(ModConfig.Type.SERVER, ServerConfig.CONFIG); + NeoForge.EVENT_BUS.register(new EventListener()); + modEventBus.addListener(this::setup); + modEventBus.addListener(this::onRegisterPayloadHandler); + + if (dist.isClient()) { + NeoForge.EVENT_BUS.register(new ClientEventHandler()); + } + } + + private void onRegisterPayloadHandler(RegisterPayloadHandlersEvent event) { + PayloadRegistrar registrar = event.registrar(SimpleBackups.MODID) + .versioned("1.0") + .optional(); + + registrar.playToClient(Pause.TYPE, Pause.CODEC, Pause::handle); + } + + private void setup(FMLCommonSetupEvent event) { + // NO-OP + } +} diff --git a/src/main/java/de/melanx/simplebackups/StorageSize.java b/src/main/java/de/melanx/simplebackups/StorageSize.java new file mode 100644 index 0000000..59890e2 --- /dev/null +++ b/src/main/java/de/melanx/simplebackups/StorageSize.java @@ -0,0 +1,48 @@ +package de.melanx.simplebackups; + +import java.util.Locale; + +public enum StorageSize { + B(0), + KB(1), + MB(2), + GB(3), + TB(4); + + private final long sizeInBytes; + private final String postfix; + + StorageSize(int factor) { + this.sizeInBytes = (long) Math.pow(1024, factor); + this.postfix = this.name().toUpperCase(Locale.ROOT); + } + + public static StorageSize getSizeFor(double bytes) { + for (StorageSize value : StorageSize.values()) { + if (bytes < value.sizeInBytes) { + return value.getLower(); + } else if (value == TB) { + return value; + } + } + + return B; + } + + public static long getBytes(String s) { + String[] splits = s.split(" "); + int amount = Integer.parseInt(splits[0]); + StorageSize size = StorageSize.valueOf(splits[1].toUpperCase(Locale.ROOT)); + return amount * size.sizeInBytes; + } + + public static String getFormattedSize(double bytes) { + StorageSize size = StorageSize.getSizeFor(bytes); + double small = bytes / size.sizeInBytes; + return String.format("%.1f %s", small, size.postfix); + } + + public StorageSize getLower() { + return this.ordinal() == 0 ? this : StorageSize.values()[this.ordinal() - 1]; + } +} diff --git a/src/main/java/de/melanx/simplebackups/client/ClientEventHandler.java b/src/main/java/de/melanx/simplebackups/client/ClientEventHandler.java new file mode 100644 index 0000000..b5d575e --- /dev/null +++ b/src/main/java/de/melanx/simplebackups/client/ClientEventHandler.java @@ -0,0 +1,39 @@ +package de.melanx.simplebackups.client; + +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.client.event.CustomizeGuiOverlayEvent; + +public class ClientEventHandler { + + private static final MutableComponent COMPONENT = Component.translatable("simplebackups.backups_paused").withStyle(ChatFormatting.RED); + private static boolean isPaused = false; + + public static void setPaused(boolean paused) { + isPaused = paused; + } + + public static boolean isPaused() { + return isPaused; + } + + @SuppressWarnings("resource") + @SubscribeEvent + public void onRenderText(CustomizeGuiOverlayEvent.DebugText event) { + if (!isPaused) { + return; + } + + GuiGraphics guiGraphics = event.getGuiGraphics(); + guiGraphics.fill(3, 3, 20, 20, 0); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + guiGraphics.drawString(Minecraft.getInstance().font, COMPONENT, 3, 3, 0); + RenderSystem.disableBlend(); + } +} diff --git a/src/main/java/de/melanx/simplebackups/commands/BackupCommand.java b/src/main/java/de/melanx/simplebackups/commands/BackupCommand.java new file mode 100644 index 0000000..6ed2f06 --- /dev/null +++ b/src/main/java/de/melanx/simplebackups/commands/BackupCommand.java @@ -0,0 +1,37 @@ +package de.melanx.simplebackups.commands; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.BoolArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import de.melanx.simplebackups.BackupThread; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.server.MinecraftServer; + +public class BackupCommand implements Command { + + public static ArgumentBuilder register() { + return Commands.literal("backup") + .then(Commands.literal("start") + .executes(new BackupCommand()) + .then(Commands.argument("quiet", BoolArgumentType.bool()) + .executes(new BackupCommand()) + ) + ); + } + + @Override + public int run(CommandContext context) { + boolean quiet = false; + try { + quiet = BoolArgumentType.getBool(context, "quiet"); + } catch (IllegalArgumentException e) { + // do nothing + } + + MinecraftServer server = context.getSource().getServer(); + BackupThread.createBackup(server, quiet); + return 1; + } +} diff --git a/src/main/java/de/melanx/simplebackups/commands/PauseCommand.java b/src/main/java/de/melanx/simplebackups/commands/PauseCommand.java new file mode 100644 index 0000000..75f5544 --- /dev/null +++ b/src/main/java/de/melanx/simplebackups/commands/PauseCommand.java @@ -0,0 +1,36 @@ +package de.melanx.simplebackups.commands; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import de.melanx.simplebackups.BackupData; +import de.melanx.simplebackups.network.Pause; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.neoforged.neoforge.network.PacketDistributor; + +public class PauseCommand implements Command { + + private final boolean paused; + + private PauseCommand(boolean paused) { + this.paused = paused; + } + + public static ArgumentBuilder register() { + return Commands.literal("backup") + .then(Commands.literal("pause") + .executes(new PauseCommand(true))) + .then(Commands.literal("unpause") + .executes(new PauseCommand(false))); + } + + + @Override + public int run(CommandContext context) { + BackupData data = BackupData.get(context.getSource().getServer()); + data.setPaused(this.paused); + PacketDistributor.sendToAllPlayers(new Pause(this.paused)); + return this.paused ? 1 : 0; + } +} diff --git a/src/main/java/de/melanx/simplebackups/config/CommonConfig.java b/src/main/java/de/melanx/simplebackups/config/CommonConfig.java new file mode 100644 index 0000000..7e305bd --- /dev/null +++ b/src/main/java/de/melanx/simplebackups/config/CommonConfig.java @@ -0,0 +1,51 @@ +package de.melanx.simplebackups.config; + +import net.neoforged.neoforge.common.ModConfigSpec; + + +public class CommonConfig { + + public static final ModConfigSpec CONFIG; + private static final ModConfigSpec.Builder BUILDER = new ModConfigSpec.Builder(); + + static { + init(BUILDER); + CONFIG = BUILDER.build(); + } + + private static ModConfigSpec.BooleanValue enabled; + private static ModConfigSpec.IntValue timer; + private static ModConfigSpec.BooleanValue sendMessages; + private static ModConfigSpec.BooleanValue noPlayerBackups; + + public static void init(ModConfigSpec.Builder builder) { + enabled = builder.comment("If set false, no backups are being made.") + .define("enabled", true); + timer = builder.comment("The time between two backups in minutes", "5 = each 5 minutes", "60 = each hour", "1440 = each day") + .defineInRange("timer", 120, 1, Short.MAX_VALUE); + sendMessages = builder.comment("Should message be sent when backup is in the making?") + .define("sendMessages", true); + noPlayerBackups = builder.comment("Create backups, even if nobody is online") + .define("noPlayerBackups", false); + + builder.push("mod_compat"); + builder.pop(); + } + + public static boolean isEnabled() { + return enabled.get(); + } + + // converts config value from milliseconds to minutes + public static int getTimer() { + return timer.get() * 60 * 1000; + } + + public static boolean doNoPlayerBackups() { + return noPlayerBackups.get(); + } + + public static boolean sendMessages() { + return sendMessages.get(); + } +} diff --git a/src/main/java/de/melanx/simplebackups/config/ServerConfig.java b/src/main/java/de/melanx/simplebackups/config/ServerConfig.java new file mode 100644 index 0000000..13ed863 --- /dev/null +++ b/src/main/java/de/melanx/simplebackups/config/ServerConfig.java @@ -0,0 +1,32 @@ +package de.melanx.simplebackups.config; + +import net.neoforged.neoforge.common.ModConfigSpec; + +public class ServerConfig { + + public static final ModConfigSpec CONFIG; + private static final ModConfigSpec.Builder BUILDER = new ModConfigSpec.Builder(); + + static { + init(BUILDER); + CONFIG = BUILDER.build(); + } + + private static ModConfigSpec.BooleanValue commandsCheatsDisabled; + private static ModConfigSpec.BooleanValue messagesForEveryone; + + public static void init(ModConfigSpec.Builder builder) { + commandsCheatsDisabled = builder.comment("Should commands work without cheats enabled? Mainly recommended for single player, otherwise all users on servers can trigger commands.") + .define("commandsCheatsDisabled", false); + messagesForEveryone = builder.comment("Should all users receive the backup message? Disable to only have users with permission level 2 and higher receive them.") + .define("messagesForEveryone", true); + } + + public static boolean commandsCheatsDisabled() { + return commandsCheatsDisabled.get(); + } + + public static boolean messagesForEveryone() { + return messagesForEveryone.get(); + } +} diff --git a/src/main/java/de/melanx/simplebackups/network/Pause.java b/src/main/java/de/melanx/simplebackups/network/Pause.java new file mode 100644 index 0000000..32b3abb --- /dev/null +++ b/src/main/java/de/melanx/simplebackups/network/Pause.java @@ -0,0 +1,32 @@ +package de.melanx.simplebackups.network; + +import de.melanx.simplebackups.SimpleBackups; +import de.melanx.simplebackups.client.ClientEventHandler; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.network.handling.IPayloadContext; + +import javax.annotation.Nonnull; + +public record Pause(boolean pause) implements CustomPacketPayload { + + public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath(SimpleBackups.MODID, "pause"); + public static final CustomPacketPayload.Type TYPE = new Type<>(ID); + + public static final StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.BOOL, Pause::pause, Pause::new + ); + + @Nonnull + @Override + public Type type() { + return Pause.TYPE; + } + + public void handle(IPayloadContext context) { + ClientEventHandler.setPaused(this.pause); + } +} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 0000000..0b49329 --- /dev/null +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1,4 @@ +public net.minecraft.server.MinecraftServer storageSource +public net.minecraft.server.network.ServerCommonPacketListenerImpl connection +public net.minecraft.world.level.storage.LevelStorageSource$LevelStorageAccess checkLock()V +public net.minecraft.world.level.storage.LevelStorageSource$LevelStorageAccess levelId diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml deleted file mode 100644 index eb57444..0000000 --- a/src/main/resources/META-INF/mods.toml +++ /dev/null @@ -1,34 +0,0 @@ -# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml - modLoader="javafml" - # A version range to match for said mod loader - for regular FML @Mod it will be the forge version - # Forge for 1.16.3 is version 34 - loaderVersion="[40,)" - # A URL to refer people to when problems occur with this mod - issueTrackerURL="https://github.com/jinks/extbackup/issues" - license="GPL" - - [[mods]] - modId="extbackup" - version="${file.jarVersion}" - displayName="ExtBackup" - displayURL="https://github.com/jinks/extbackup" - #logoFile="assets/examplemod/textures/logo.png" - credits="LatVianModder, alexbobp, vazkii" - authors="jinks" - description=''' - Minimal Minecraft backup mod, relying on external backup tools. - ''' - - [[dependencies.extbackup]] - modId="forge" - mandatory=true - versionRange="[47,)" - ordering="NONE" - side="BOTH" - - [[dependencies.extbackup]] - modId="minecraft" - mandatory=true - versionRange="[1.20.1]" - ordering="NONE" - side="BOTH" diff --git a/src/main/resources/META-INF/neoforge.mods.toml b/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 0000000..caed0ad --- /dev/null +++ b/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,30 @@ +modLoader="javafml" +loaderVersion="${loader_version_range}" +license="${license}" +issueTrackerURL = "https://github.com/ChaoticTrials/SimpleBackups/issues" + +[[mods]] +modId = "${modid}" +version = "${mod_version}" +displayName = "Simple Backups" +updateJSONURL = "https://assets.melanx.de/updates/simple-backups.json" +displayURL = "https://modrinth.com/user/MelanX" +authors = "MelanX" +displayTest = "IGNORE_ALL_VERSION" +description = ''' +A simple mod to create scheduled backups. +''' + +[[dependencies.${ modid }]] + modId = "neoforge" + type = "required" + versionRange = "[${neo_version},)" + ordering="NONE" + side="BOTH" + +[[dependencies.${modid}]] + modId="minecraft" + type="required" + versionRange="[${minecraft_version},)" + ordering="NONE" + side="BOTH" diff --git a/src/main/resources/assets/simplebackups/lang/de_de.json b/src/main/resources/assets/simplebackups/lang/de_de.json new file mode 100644 index 0000000..bf5c21e --- /dev/null +++ b/src/main/resources/assets/simplebackups/lang/de_de.json @@ -0,0 +1,8 @@ +{ + "simplebackups.backup_started": "Backup gestartet...", + "simplebackups.backup_finished": "Backup fertiggestellt in %s", + "simplebackups.backups_paused": "Backups pausiert", + "simplebackups.commands.only_modified": "Es werden nicht nur veränderte Dateien gesichert, prüfe deine Konfigurationsdatei", + "simplebackups.commands.is_merging": "Ein Zusammenführungsvorgang ist bereits im Gange", + "simplebackups.commands.finished": "Zusammenführen der Backups erfolgreich abgeschlossen" +} diff --git a/src/main/resources/assets/simplebackups/lang/en_us.json b/src/main/resources/assets/simplebackups/lang/en_us.json new file mode 100644 index 0000000..bad3f60 --- /dev/null +++ b/src/main/resources/assets/simplebackups/lang/en_us.json @@ -0,0 +1,8 @@ +{ + "simplebackups.backup_started": "Backup started...", + "simplebackups.backup_finished": "Backup completed in %s", + "simplebackups.backups_paused": "Backups paused", + "simplebackups.commands.only_modified": "Not only modified files are being backed up, please check your configuration file", + "simplebackups.commands.is_merging": "A merge operation is already in progress", + "simplebackups.commands.finished": "Merging backups completed successfully" +} diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta deleted file mode 100644 index 33eb037..0000000 --- a/src/main/resources/pack.mcmeta +++ /dev/null @@ -1,7 +0,0 @@ -{ - "pack": { - "description": "ExtBackup resources", - "pack_format": 8, - "_comment": "A pack_format of 6 requires json lang files and some texture changes from 1.16.2. Note: we require v6 pack meta for all mods." - } -}