From 23da19494a3ebde437b08f240372ae25e925bfbe Mon Sep 17 00:00:00 2001 From: Nathan JANCZEWSKI Date: Thu, 28 Oct 2021 21:26:21 +0200 Subject: [PATCH 01/42] Updating Gradle + Updating for MC1.17.1 --- build.gradle | 14 +- gradle.properties | 4 +- gradle/wrapper/gradle-wrapper.jar | Bin 52271 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 294 +++++++++++------- gradlew.bat | 179 ++++++----- .../mods/ftbbackups/BackupCommands.java | 38 +-- .../mods/ftbbackups/Backups.java | 64 ++-- .../mods/ftbbackups/FTBBackups.java | 27 +- .../mods/ftbbackups/FTBBackupsClient.java | 8 +- .../mods/ftbbackups/FTBBackupsConfig.java | 3 +- .../ftbbackups/net/BackupProgressPacket.java | 8 +- .../ftbbackups/net/FTBBackupsNetHandler.java | 6 +- .../mods/ftbbackups/net/package-info.java | 1 - src/main/resources/META-INF/mods.toml | 8 +- 15 files changed, 362 insertions(+), 295 deletions(-) mode change 100644 => 100755 gradlew diff --git a/build.gradle b/build.gradle index 43792afd..34c1881e 100644 --- a/build.gradle +++ b/build.gradle @@ -6,14 +6,13 @@ buildscript { mavenCentral() } dependencies { - classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '3.+', changing: true + classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true classpath 'gradle.plugin.com.matthewprenger:CurseGradle:1.1.0' } } apply plugin: 'net.minecraftforge.gradle' apply plugin: 'eclipse' apply plugin: 'com.matthewprenger.cursegradle' -apply plugin: 'maven' apply from: 'https://files.latmod.com/public/markdown-git-changelog.gradle' @@ -22,15 +21,16 @@ version = ENV.GITHUB_RUN_NUMBER ? "${mod_version}.${ENV.GITHUB_RUN_NUMBER}" : "$ group = project.mod_package archivesBaseName = project.mod_name -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' +java.toolchain.languageVersion = JavaLanguageVersion.of(16) minecraft { - mappings channel: 'snapshot', version: project.mapping_version + mappings channel: 'official', version: project.mapping_version // accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') runs { client { workingDirectory project.file('run') //property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP' + property 'forge.logging.markers', 'REGISTRIES' property 'forge.logging.console.level', 'debug' if (project.hasProperty('mc_dev_uuid')) args '--uuid', project.mc_dev_uuid @@ -48,6 +48,7 @@ minecraft { server { workingDirectory project.file('run') //property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP' + property 'forge.logging.markers', 'REGISTRIES' property 'forge.logging.console.level', 'debug' args 'nogui' @@ -62,6 +63,7 @@ minecraft { data { workingDirectory project.file('run') //property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP' + property 'forge.logging.markers', 'REGISTRIES' property 'forge.logging.console.level', 'debug' args '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/') @@ -75,6 +77,8 @@ minecraft { } } +sourceSets.main.resources { srcDir 'src/generated/resources' } + dependencies { minecraft "net.minecraftforge:forge:${forge_version}" } @@ -105,7 +109,7 @@ if (ENV.CURSEFORGE_KEY) { project { id = project.curseforge_id releaseType = project.curseforge_type - addGameVersion '1.16.5' + addGameVersion '1.17.1' changelog = getGitChangelog changelogType = 'markdown' } diff --git a/gradle.properties b/gradle.properties index 0cee06b8..3f1554bd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ mod_name=ftb-backups mod_package=dev.ftb.mods mod_version=2.1.2 mod_author=LatvianModder -forge_version=1.16.5-36.2.2 -mapping_version=20200723-1.16.1 +forge_version=1.17.1-37.0.103 +mapping_version=1.17.1 curseforge_id=314904 curseforge_type=release \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 30d399d8d2bf522ff5de94bf434a7cc43a9a74b5..7454180f2ae8848c63b8b4dea2cb829da983f2fa 100644 GIT binary patch literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 52271 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 p '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +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"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum -warn ( ) { +warn () { echo "$*" -} +} >&2 -die ( ) { +die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -82,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + 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. Please set the JAVA_HOME variable in your environment to match the @@ -90,75 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# 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. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 86209688..ac1b06f9 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,90 +1,89 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS=-Xmx3g - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -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. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -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. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="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 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "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. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +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. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="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 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/BackupCommands.java b/src/main/java/com/feed_the_beast/mods/ftbbackups/BackupCommands.java index b3e67f1d..414c8f2f 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/BackupCommands.java +++ b/src/main/java/com/feed_the_beast/mods/ftbbackups/BackupCommands.java @@ -2,62 +2,62 @@ import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.StringArgumentType; -import net.minecraft.command.CommandSource; -import net.minecraft.command.Commands; -import net.minecraft.entity.player.ServerPlayerEntity; -import net.minecraft.util.Util; -import net.minecraft.util.text.TranslationTextComponent; -import net.minecraft.world.storage.FolderName; +import net.minecraft.Util; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.storage.LevelResource; /** * @author LatvianModder */ public class BackupCommands { - public static void register(CommandDispatcher dispatcher) + public static void register(CommandDispatcher dispatcher) { dispatcher.register(Commands.literal("ftbbackups") .then(Commands.literal("time") .executes(ctx -> time(ctx.getSource())) ) .then(Commands.literal("start") - .requires(cs -> cs.getServer().isSinglePlayer() || cs.hasPermissionLevel(3)) + .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) .then(Commands.argument("name", StringArgumentType.word()) .executes(ctx -> start(ctx.getSource(), StringArgumentType.getString(ctx, "name"))) ) .executes(ctx -> start(ctx.getSource(), "")) ) .then(Commands.literal("size") - .requires(cs -> cs.getServer().isSinglePlayer() || cs.hasPermissionLevel(3)) + .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) .executes(ctx -> size(ctx.getSource())) ) ); } - private static int time(CommandSource source) + private static int time(CommandSourceStack source) { - source.sendFeedback(new TranslationTextComponent("ftbbackups.lang.timer", BackupUtils.getTimeString(Backups.INSTANCE.nextBackup - System.currentTimeMillis())), true); + source.sendSuccess(new TranslatableComponent("ftbbackups.lang.timer", BackupUtils.getTimeString(Backups.INSTANCE.nextBackup - System.currentTimeMillis())), true); return 1; } - private static int start(CommandSource source, String customName) + private static int start(CommandSourceStack source, String customName) { if (Backups.INSTANCE.run(source.getServer(), false, source.getDisplayName(), customName)) { - for (ServerPlayerEntity player : source.getServer().getPlayerList().getPlayers()) + for (ServerPlayer player : source.getServer().getPlayerList().getPlayers()) { - player.sendMessage(new TranslationTextComponent("ftbbackups.lang.manual_launch", source.getDisplayName()), Util.DUMMY_UUID); + player.sendMessage(new TranslatableComponent("ftbbackups.lang.manual_launch", source.getDisplayName()), Util.NIL_UUID); } } else { - source.sendFeedback(new TranslationTextComponent("ftbbackups.lang.already_running"), true); + source.sendSuccess(new TranslatableComponent("ftbbackups.lang.already_running"), true); } return 1; } - private static int size(CommandSource source) + private static int size(CommandSourceStack source) { long totalSize = 0L; @@ -66,9 +66,9 @@ private static int size(CommandSource source) totalSize += backup.size; } - source.sendFeedback(new TranslationTextComponent("ftbbackups.lang.size.current", BackupUtils.getSizeString(source.getServer().func_240776_a_(FolderName.field_237253_i_).toFile())), true); - source.sendFeedback(new TranslationTextComponent("ftbbackups.lang.size.total", BackupUtils.getSizeString(totalSize)), true); - source.sendFeedback(new TranslationTextComponent("ftbbackups.lang.size.available", BackupUtils.getSizeString(Math.min(FTBBackupsConfig.maxTotalSize, Backups.INSTANCE.backupsFolder.getFreeSpace()))), true); + source.sendSuccess(new TranslatableComponent("ftbbackups.lang.size.current", BackupUtils.getSizeString(source.getServer().getWorldPath(LevelResource.ROOT).toFile())), true); + source.sendSuccess(new TranslatableComponent("ftbbackups.lang.size.total", BackupUtils.getSizeString(totalSize)), true); + source.sendSuccess(new TranslatableComponent("ftbbackups.lang.size.available", BackupUtils.getSizeString(Math.min(FTBBackupsConfig.maxTotalSize, Backups.INSTANCE.backupsFolder.getFreeSpace()))), true); return 1; } diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/Backups.java b/src/main/java/com/feed_the_beast/mods/ftbbackups/Backups.java index db37e1a9..c3cc9ffd 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/Backups.java +++ b/src/main/java/com/feed_the_beast/mods/ftbbackups/Backups.java @@ -5,19 +5,15 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import net.minecraft.ChatFormatting; +import net.minecraft.Util; +import net.minecraft.network.chat.*; import net.minecraft.server.MinecraftServer; -import net.minecraft.util.Util; -import net.minecraft.util.text.ChatType; -import net.minecraft.util.text.Color; -import net.minecraft.util.text.ITextComponent; -import net.minecraft.util.text.StringTextComponent; -import net.minecraft.util.text.TextFormatting; -import net.minecraft.util.text.TranslationTextComponent; -import net.minecraft.world.server.ServerWorld; -import net.minecraft.world.storage.FolderName; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.storage.LevelResource; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.loading.FMLPaths; -import net.minecraftforge.fml.network.PacketDistributor; +import net.minecraftforge.fmllegacy.network.PacketDistributor; import java.io.File; import java.io.FileInputStream; @@ -48,7 +44,7 @@ public enum Backups public void init(MinecraftServer server) { - File dataDir = server.getDataDirectory(); + File dataDir = server.getServerDirectory(); backupsFolder = FTBBackupsConfig.folder.trim().isEmpty() ? FMLPaths.GAMEDIR.get().resolve("backups").toFile() : new File(FTBBackupsConfig.folder); try @@ -109,7 +105,7 @@ public void tick(MinecraftServer server, long now) if (!FTBBackupsConfig.onlyIfPlayersOnline || hadPlayersOnline || !server.getPlayerList().getPlayers().isEmpty()) { hadPlayersOnline = false; - run(server, true, new StringTextComponent("Server"), ""); + run(server, true, new TextComponent("Server"), ""); } } @@ -117,11 +113,11 @@ public void tick(MinecraftServer server, long now) { doingBackup = BackupStatus.NONE; - for (ServerWorld world : server.getWorlds()) + for (ServerLevel world : server.getAllLevels()) { if (world != null) { - world.disableLevelSaving = false; + world.noSave = false; } } @@ -144,15 +140,15 @@ else if (doingBackup.isRunning() && printFiles) } } - public void notifyAll(MinecraftServer server, ITextComponent component, boolean error) + public void notifyAll(MinecraftServer server, Component component, boolean error) { - component = component.deepCopy(); - component.getStyle().setColor(Color.func_240744_a_(error ? TextFormatting.DARK_RED : TextFormatting.LIGHT_PURPLE)); + component = component.plainCopy(); + component.getStyle().withColor(TextColor.fromLegacyFormat(error ? ChatFormatting.DARK_RED : ChatFormatting.LIGHT_PURPLE)); FTBBackups.LOGGER.info(component.getString()); - server.getPlayerList().func_232641_a_(component, ChatType.GAME_INFO, Util.DUMMY_UUID); + server.getPlayerList().broadcastMessage(component, ChatType.GAME_INFO, Util.NIL_UUID); } - public boolean run(MinecraftServer server, boolean auto, ITextComponent name, String customName) + public boolean run(MinecraftServer server, boolean auto, Component name, String customName) { if (doingBackup.isRunningOrDone()) { @@ -164,19 +160,19 @@ public boolean run(MinecraftServer server, boolean auto, ITextComponent name, St return false; } - notifyAll(server, new TranslationTextComponent("ftbbackups.lang.start", name), false); + notifyAll(server, new TranslatableComponent("ftbbackups.lang.start", name), false); nextBackup = System.currentTimeMillis() + FTBBackupsConfig.backupTimer; - for (ServerWorld world : server.getWorlds()) + for (ServerLevel world : server.getAllLevels()) { if (world != null) { - world.disableLevelSaving = true; + world.noSave = true; } } doingBackup = BackupStatus.RUNNING; - server.getPlayerList().saveAllPlayerData(); + server.getPlayerList().saveAll(); new Thread(() -> { try @@ -188,12 +184,12 @@ public boolean run(MinecraftServer server, boolean auto, ITextComponent name, St catch (Exception ex) { ex.printStackTrace(); - notifyAll(server, new TranslationTextComponent("ftbbackups.lang.saving_failed"), true); + notifyAll(server, new TranslatableComponent("ftbbackups.lang.saving_failed"), true); } } catch (Exception ex) { - notifyAll(server, new TranslationTextComponent("ftbbackups.lang.saving_failed"), true); + notifyAll(server, new TranslatableComponent("ftbbackups.lang.saving_failed"), true); ex.printStackTrace(); } @@ -205,7 +201,7 @@ public boolean run(MinecraftServer server, boolean auto, ITextComponent name, St private void createBackup(MinecraftServer server, String customName) { - File src = server.func_240776_a_(FolderName.field_237253_i_).toFile(); + File src = server.getWorldPath(LevelResource.ROOT).toFile(); try { @@ -240,7 +236,7 @@ private void createBackup(MinecraftServer server, String customName) try { LinkedHashMap fileMap = new LinkedHashMap<>(); - String mcdir = server.getDataDirectory().getCanonicalFile().getAbsolutePath(); + String mcdir = server.getServerDirectory().getCanonicalFile().getAbsolutePath(); Consumer consumer = file0 -> { for (File file : BackupUtils.listTree(file0)) @@ -397,7 +393,7 @@ private void createBackup(MinecraftServer server, String customName) if (!FTBBackupsConfig.silent) { String errorName = ex.getClass().getName(); - notifyAll(server, new TranslationTextComponent("ftbbackups.lang.fail", errorName), true); + notifyAll(server, new TranslatableComponent("ftbbackups.lang.fail", errorName), true); } ex.printStackTrace(); @@ -417,12 +413,12 @@ private void createBackup(MinecraftServer server, String customName) array.add(backup1.toJsonObject()); } - BackupUtils.toJson(new File(server.getDataDirectory(), "local/ftbutilities/backups.json"), array, true); + BackupUtils.toJson(new File(server.getServerDirectory(), "local/ftbutilities/backups.json"), array, true); if (error == null && !FTBBackupsConfig.silent) { String timeString = BackupUtils.getTimeString(System.currentTimeMillis() - time.getTimeInMillis()); - ITextComponent component; + Component component; if (FTBBackupsConfig.displayFileSize) { @@ -436,15 +432,15 @@ private void createBackup(MinecraftServer server, String customName) String sizeB = BackupUtils.getSizeString(fileSize); String sizeT = BackupUtils.getSizeString(totalSize); String sizeString = sizeB.equals(sizeT) ? sizeB : (sizeB + " | " + sizeT); - component = new TranslationTextComponent("ftbbackups.lang.end_2", timeString, sizeString); + component = new TranslatableComponent("ftbbackups.lang.end_2", timeString, sizeString); } else { - component = new TranslationTextComponent("ftbbackups.lang.end_1", timeString); + component = new TranslatableComponent("ftbbackups.lang.end_1", timeString); } - component.getStyle().setColor(Color.func_240744_a_(TextFormatting.LIGHT_PURPLE)); - server.getPlayerList().func_232641_a_(component, ChatType.CHAT, Util.DUMMY_UUID); + component.getStyle().withColor(TextColor.fromLegacyFormat(ChatFormatting.LIGHT_PURPLE)); + server.getPlayerList().broadcastMessage(component, ChatType.CHAT, Util.NIL_UUID); } } diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackups.java b/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackups.java index 953b1d2a..6899072f 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackups.java +++ b/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackups.java @@ -2,25 +2,24 @@ import com.feed_the_beast.mods.ftbbackups.net.BackupProgressPacket; import com.feed_the_beast.mods.ftbbackups.net.FTBBackupsNetHandler; -import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.network.chat.TextComponent; import net.minecraft.server.MinecraftServer; -import net.minecraft.util.text.StringTextComponent; +import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.event.RegisterCommandsEvent; import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.ExtensionPoint; +import net.minecraftforge.fml.IExtensionPoint; import net.minecraftforge.fml.LogicalSide; -import net.minecraftforge.fml.LogicalSidedProvider; import net.minecraftforge.fml.ModLoadingContext; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; -import net.minecraftforge.fml.event.server.FMLServerStartedEvent; -import net.minecraftforge.fml.event.server.FMLServerStoppingEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; -import net.minecraftforge.fml.network.FMLNetworkConstants; -import net.minecraftforge.fml.network.PacketDistributor; -import org.apache.commons.lang3.tuple.Pair; +import net.minecraftforge.fmllegacy.LogicalSidedProvider; +import net.minecraftforge.fmllegacy.network.FMLNetworkConstants; +import net.minecraftforge.fmllegacy.network.PacketDistributor; +import net.minecraftforge.fmlserverevents.FMLServerStartedEvent; +import net.minecraftforge.fmlserverevents.FMLServerStoppingEvent; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -36,7 +35,7 @@ public FTBBackups() FMLJavaModLoadingContext.get().getModEventBus().addListener(this::clientSetup); FTBBackupsNetHandler.init(); FTBBackupsConfig.register(); - ModLoadingContext.get().registerExtensionPoint(ExtensionPoint.DISPLAYTEST, () -> Pair.of(() -> FMLNetworkConstants.IGNORESERVERONLY, (a, b) -> true)); + ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> FMLNetworkConstants.IGNORESERVERONLY, (a, b) -> true)); } private void clientSetup(FMLClientSetupEvent event) @@ -61,23 +60,23 @@ public static void serverStopping(FMLServerStoppingEvent event) { if (FTBBackupsConfig.forceOnShutdown) { - Backups.INSTANCE.run(event.getServer(), true, new StringTextComponent("Server"), ""); + Backups.INSTANCE.run(event.getServer(), true, new TextComponent("Server"), ""); } } @SubscribeEvent public static void playerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { - if (event.getPlayer() instanceof ServerPlayerEntity) + if (event.getPlayer() instanceof ServerPlayer) { - FTBBackupsNetHandler.MAIN.send(PacketDistributor.PLAYER.with(() -> (ServerPlayerEntity) event.getPlayer()), new BackupProgressPacket(Backups.INSTANCE.currentFile, Backups.INSTANCE.totalFiles)); + FTBBackupsNetHandler.MAIN.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) event.getPlayer()), new BackupProgressPacket(Backups.INSTANCE.currentFile, Backups.INSTANCE.totalFiles)); } } @SubscribeEvent public static void playerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) { - if (event.getPlayer() instanceof ServerPlayerEntity) + if (event.getPlayer() instanceof ServerPlayer) { Backups.INSTANCE.hadPlayersOnline = true; } diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackupsClient.java b/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackupsClient.java index d7106a19..5b57cf7d 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackupsClient.java +++ b/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackupsClient.java @@ -1,8 +1,8 @@ package com.feed_the_beast.mods.ftbbackups; +import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; -import net.minecraft.client.resources.I18n; -import net.minecraft.util.text.TextFormatting; +import net.minecraft.client.resources.language.I18n; import net.minecraftforge.client.event.RenderGameOverlayEvent; import net.minecraftforge.common.MinecraftForge; @@ -31,14 +31,14 @@ public static void setFiles(int current, int total) private static void onDebugInfoEvent(RenderGameOverlayEvent.Text event) { - if (Minecraft.getInstance().gameSettings.showDebugInfo) + if (Minecraft.getInstance().options.renderDebug) { return; } if (totalBackupFiles > 0 && totalBackupFiles > currentBackupFile) { - event.getLeft().add(TextFormatting.LIGHT_PURPLE + I18n.format("ftbbackups.lang.timer_progress", currentBackupFile * 100 / totalBackupFiles, currentBackupFile, totalBackupFiles)); + event.getLeft().add(ChatFormatting.LIGHT_PURPLE + I18n.get("ftbbackups.lang.timer_progress", currentBackupFile * 100 / totalBackupFiles, currentBackupFile, totalBackupFiles)); } } } \ No newline at end of file diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackupsConfig.java b/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackupsConfig.java index 63aa7de5..49ad67c0 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackupsConfig.java +++ b/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackupsConfig.java @@ -3,6 +3,7 @@ import net.minecraftforge.common.ForgeConfigSpec; import net.minecraftforge.fml.ModLoadingContext; import net.minecraftforge.fml.config.ModConfig; +import net.minecraftforge.fml.event.config.ModConfigEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import org.apache.commons.lang3.tuple.Pair; @@ -39,7 +40,7 @@ public static void register() modLoadingContext.registerConfig(ModConfig.Type.COMMON, common.getRight()); } - public static void reload(ModConfig.ModConfigEvent event) + public static void reload(ModConfigEvent event) { ModConfig config = event.getConfig(); diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/net/BackupProgressPacket.java b/src/main/java/com/feed_the_beast/mods/ftbbackups/net/BackupProgressPacket.java index 4c4de59b..ef387e29 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/net/BackupProgressPacket.java +++ b/src/main/java/com/feed_the_beast/mods/ftbbackups/net/BackupProgressPacket.java @@ -1,8 +1,8 @@ package com.feed_the_beast.mods.ftbbackups.net; import com.feed_the_beast.mods.ftbbackups.FTBBackupsClient; -import net.minecraft.network.PacketBuffer; -import net.minecraftforge.fml.network.NetworkEvent; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.fmllegacy.network.NetworkEvent; import java.util.function.Supplier; @@ -19,13 +19,13 @@ public BackupProgressPacket(int c, int t) total = t; } - public BackupProgressPacket(PacketBuffer buf) + public BackupProgressPacket(FriendlyByteBuf buf) { total = buf.readVarInt(); current = buf.readVarInt(); } - public void write(PacketBuffer buf) + public void write(FriendlyByteBuf buf) { buf.writeVarInt(total); buf.writeVarInt(current); diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/net/FTBBackupsNetHandler.java b/src/main/java/com/feed_the_beast/mods/ftbbackups/net/FTBBackupsNetHandler.java index 3380acd9..5c5debd5 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/net/FTBBackupsNetHandler.java +++ b/src/main/java/com/feed_the_beast/mods/ftbbackups/net/FTBBackupsNetHandler.java @@ -1,9 +1,9 @@ package com.feed_the_beast.mods.ftbbackups.net; import com.feed_the_beast.mods.ftbbackups.FTBBackups; -import net.minecraft.util.ResourceLocation; -import net.minecraftforge.fml.network.NetworkRegistry; -import net.minecraftforge.fml.network.simple.SimpleChannel; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.fmllegacy.network.NetworkRegistry; +import net.minecraftforge.fmllegacy.network.simple.SimpleChannel; import java.util.function.Predicate; diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/net/package-info.java b/src/main/java/com/feed_the_beast/mods/ftbbackups/net/package-info.java index 3f5e74f1..a2c67119 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/net/package-info.java +++ b/src/main/java/com/feed_the_beast/mods/ftbbackups/net/package-info.java @@ -1,3 +1,2 @@ @javax.annotation.ParametersAreNonnullByDefault -@mcp.MethodsReturnNonnullByDefault package com.feed_the_beast.mods.ftbbackups.net; \ No newline at end of file diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index f4c37a11..cf0adf82 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -1,5 +1,5 @@ modLoader = "javafml" -loaderVersion = "[32,)" +loaderVersion = "[37,)" license = "All Rights Reserved" [[mods]] @@ -16,13 +16,13 @@ Creates backups of your world [[dependencies.ftbbackups]] modId="forge" mandatory = true -versionRange = "[32,)" +versionRange = "[37,)" ordering = "NONE" side="BOTH" [[dependencies.ftbbackups]] modId="minecraft" mandatory = true -versionRange = "[1.16,)" +versionRange = "[1.17,)" ordering = "NONE" -side="BOTH" \ No newline at end of file +side="BOTH" From 2bab8aee9bdc080fbc0e389b519a242ed7e455a7 Mon Sep 17 00:00:00 2001 From: Nathan JANCZEWSKI Date: Thu, 6 Jan 2022 12:42:50 +0100 Subject: [PATCH 02/42] Upgrade 1.18.1 --- build.gradle | 6 +++--- gradle.properties | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 34c1881e..962c6efb 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ version = ENV.GITHUB_RUN_NUMBER ? "${mod_version}.${ENV.GITHUB_RUN_NUMBER}" : "$ group = project.mod_package archivesBaseName = project.mod_name -java.toolchain.languageVersion = JavaLanguageVersion.of(16) +java.toolchain.languageVersion = JavaLanguageVersion.of(17) minecraft { mappings channel: 'official', version: project.mapping_version @@ -109,9 +109,9 @@ if (ENV.CURSEFORGE_KEY) { project { id = project.curseforge_id releaseType = project.curseforge_type - addGameVersion '1.17.1' + addGameVersion '1.18.1' changelog = getGitChangelog changelogType = 'markdown' } } -} \ No newline at end of file +} diff --git a/gradle.properties b/gradle.properties index 3f1554bd..038e187b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ mod_name=ftb-backups mod_package=dev.ftb.mods mod_version=2.1.2 mod_author=LatvianModder -forge_version=1.17.1-37.0.103 -mapping_version=1.17.1 +forge_version=1.18.1-39.0.10 +mapping_version=1.18.1 curseforge_id=314904 -curseforge_type=release \ No newline at end of file +curseforge_type=release From 16a7306b9b1f7afe0f4c3dc1861eeb265456d168 Mon Sep 17 00:00:00 2001 From: Nathan JANCZEWSKI Date: Sat, 8 Jan 2022 11:33:49 +0100 Subject: [PATCH 03/42] Upgrading to 1.18.1 --- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 10 +++---- .../mods/ftbbackups/Backups.java | 2 +- .../mods/ftbbackups/FTBBackups.java | 28 +++++++++---------- .../ftbbackups/net/BackupProgressPacket.java | 2 +- .../ftbbackups/net/FTBBackupsNetHandler.java | 4 +-- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ffed3a25..2e6e5897 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index c53aefaa..1b6c7873 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -32,10 +32,10 @@ # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/Backups.java b/src/main/java/com/feed_the_beast/mods/ftbbackups/Backups.java index c3cc9ffd..a52d49bd 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/Backups.java +++ b/src/main/java/com/feed_the_beast/mods/ftbbackups/Backups.java @@ -12,8 +12,8 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.storage.LevelResource; import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.network.PacketDistributor; import net.minecraftforge.fml.loading.FMLPaths; -import net.minecraftforge.fmllegacy.network.PacketDistributor; import java.io.File; import java.io.FileInputStream; diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackups.java b/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackups.java index 6899072f..cec88075 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackups.java +++ b/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackups.java @@ -5,6 +5,8 @@ import net.minecraft.network.chat.TextComponent; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.Level; +import net.minecraftforge.common.util.LogicalSidedProvider; import net.minecraftforge.event.RegisterCommandsEvent; import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.entity.player.PlayerEvent; @@ -15,14 +17,15 @@ import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; -import net.minecraftforge.fmllegacy.LogicalSidedProvider; -import net.minecraftforge.fmllegacy.network.FMLNetworkConstants; -import net.minecraftforge.fmllegacy.network.PacketDistributor; -import net.minecraftforge.fmlserverevents.FMLServerStartedEvent; -import net.minecraftforge.fmlserverevents.FMLServerStoppingEvent; +import net.minecraftforge.network.NetworkConstants; +import net.minecraftforge.network.PacketDistributor; +import net.minecraftforge.event.server.ServerStartedEvent; +import net.minecraftforge.event.server.ServerStoppingEvent; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.util.Optional; + @Mod(FTBBackups.MOD_ID) @Mod.EventBusSubscriber(modid = FTBBackups.MOD_ID) public class FTBBackups @@ -35,7 +38,7 @@ public FTBBackups() FMLJavaModLoadingContext.get().getModEventBus().addListener(this::clientSetup); FTBBackupsNetHandler.init(); FTBBackupsConfig.register(); - ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> FMLNetworkConstants.IGNORESERVERONLY, (a, b) -> true)); + ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true)); } private void clientSetup(FMLClientSetupEvent event) @@ -44,7 +47,7 @@ private void clientSetup(FMLClientSetupEvent event) } @SubscribeEvent - public static void serverAboutToStart(FMLServerStartedEvent event) + public static void serverAboutToStart(ServerStartedEvent event) { Backups.INSTANCE.init(event.getServer()); } @@ -56,7 +59,7 @@ public static void registerCommands(RegisterCommandsEvent event) } @SubscribeEvent - public static void serverStopping(FMLServerStoppingEvent event) + public static void serverStopping(ServerStoppingEvent event) { if (FTBBackupsConfig.forceOnShutdown) { @@ -87,12 +90,9 @@ public static void serverTick(TickEvent.ServerTickEvent event) { if (event.phase != TickEvent.Phase.START) { - MinecraftServer server = LogicalSidedProvider.INSTANCE.get(LogicalSide.SERVER); - - if (server != null) - { - Backups.INSTANCE.tick(server, System.currentTimeMillis()); - } + LogicalSidedProvider.CLIENTWORLD.get(LogicalSide.SERVER).ifPresent( + level -> Backups.INSTANCE.tick(level.getServer(), System.currentTimeMillis()) + ); } } } \ No newline at end of file diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/net/BackupProgressPacket.java b/src/main/java/com/feed_the_beast/mods/ftbbackups/net/BackupProgressPacket.java index ef387e29..3cf6963e 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/net/BackupProgressPacket.java +++ b/src/main/java/com/feed_the_beast/mods/ftbbackups/net/BackupProgressPacket.java @@ -2,7 +2,7 @@ import com.feed_the_beast.mods.ftbbackups.FTBBackupsClient; import net.minecraft.network.FriendlyByteBuf; -import net.minecraftforge.fmllegacy.network.NetworkEvent; +import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/net/FTBBackupsNetHandler.java b/src/main/java/com/feed_the_beast/mods/ftbbackups/net/FTBBackupsNetHandler.java index 5c5debd5..ebdc43a7 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/net/FTBBackupsNetHandler.java +++ b/src/main/java/com/feed_the_beast/mods/ftbbackups/net/FTBBackupsNetHandler.java @@ -2,8 +2,8 @@ import com.feed_the_beast.mods.ftbbackups.FTBBackups; import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.fmllegacy.network.NetworkRegistry; -import net.minecraftforge.fmllegacy.network.simple.SimpleChannel; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.simple.SimpleChannel; import java.util.function.Predicate; From b7d1e5d69ec0021c5761bcfd729d1ba445a46f56 Mon Sep 17 00:00:00 2001 From: Nathan JANCZEWSKI Date: Sun, 9 Jan 2022 22:06:40 +0100 Subject: [PATCH 04/42] Updating forge version in the TOML --- src/main/resources/META-INF/mods.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index cf0adf82..54f1a19e 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -1,5 +1,5 @@ modLoader = "javafml" -loaderVersion = "[37,)" +loaderVersion = "[39,)" license = "All Rights Reserved" [[mods]] @@ -16,13 +16,13 @@ Creates backups of your world [[dependencies.ftbbackups]] modId="forge" mandatory = true -versionRange = "[37,)" +versionRange = "[39,)" ordering = "NONE" side="BOTH" [[dependencies.ftbbackups]] modId="minecraft" mandatory = true -versionRange = "[1.17,)" +versionRange = "[1.18,)" ordering = "NONE" side="BOTH" From d1d9b673c3cf852224b23ae84170effdcc4e7581 Mon Sep 17 00:00:00 2001 From: Nathan JANCZEWSKI Date: Sat, 19 Mar 2022 02:11:18 +0100 Subject: [PATCH 05/42] Fixing ServerTick event not "firing" --- .../mods/ftbbackups/BackupCommands.java | 35 +++++++++++++++++++ .../mods/ftbbackups/FTBBackups.java | 20 +++-------- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/BackupCommands.java b/src/main/java/com/feed_the_beast/mods/ftbbackups/BackupCommands.java index 414c8f2f..18ca20a9 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/BackupCommands.java +++ b/src/main/java/com/feed_the_beast/mods/ftbbackups/BackupCommands.java @@ -31,6 +31,14 @@ public static void register(CommandDispatcher dispatcher) .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) .executes(ctx -> size(ctx.getSource())) ) + .then(Commands.literal("status") + .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) + .executes(ctx -> status(ctx.getSource())) + ) + .then(Commands.literal("reset") + .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) + .executes(ctx -> resetState(ctx.getSource())) + ) ); } @@ -72,4 +80,31 @@ private static int size(CommandSourceStack source) return 1; } + + private static int status(CommandSourceStack source) + { + source.getServer().getAllLevels().forEach(level -> { + if (level != null) { + source.sendSuccess(new TranslatableComponent("Current status for " + level.toString() + ": " + (!level.noSave ? "NO SAVE !!" : "Autosave")), true); + } + }); + + return 1; + } + + private static int resetState(CommandSourceStack source) + { + source.getServer().getAllLevels().forEach(level -> { + if (level != null) { + source.sendSuccess(new TranslatableComponent("Reseting state + saving for " + level.toString()), true); + level.noSave = false; + level.save(null, true, true); + } + }); + + source.sendSuccess(new TranslatableComponent("Reseting state + saving for players"), true); + source.getServer().getPlayerList().saveAll(); + + return 1; + } } \ No newline at end of file diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackups.java b/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackups.java index cec88075..3200403c 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackups.java +++ b/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackups.java @@ -3,29 +3,23 @@ import com.feed_the_beast.mods.ftbbackups.net.BackupProgressPacket; import com.feed_the_beast.mods.ftbbackups.net.FTBBackupsNetHandler; import net.minecraft.network.chat.TextComponent; -import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.level.Level; -import net.minecraftforge.common.util.LogicalSidedProvider; import net.minecraftforge.event.RegisterCommandsEvent; import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.entity.player.PlayerEvent; +import net.minecraftforge.event.server.ServerStartedEvent; +import net.minecraftforge.event.server.ServerStoppingEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.IExtensionPoint; -import net.minecraftforge.fml.LogicalSide; import net.minecraftforge.fml.ModLoadingContext; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.network.NetworkConstants; import net.minecraftforge.network.PacketDistributor; -import net.minecraftforge.event.server.ServerStartedEvent; -import net.minecraftforge.event.server.ServerStoppingEvent; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.util.Optional; - @Mod(FTBBackups.MOD_ID) @Mod.EventBusSubscriber(modid = FTBBackups.MOD_ID) public class FTBBackups @@ -86,13 +80,9 @@ public static void playerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) } @SubscribeEvent - public static void serverTick(TickEvent.ServerTickEvent event) - { - if (event.phase != TickEvent.Phase.START) - { - LogicalSidedProvider.CLIENTWORLD.get(LogicalSide.SERVER).ifPresent( - level -> Backups.INSTANCE.tick(level.getServer(), System.currentTimeMillis()) - ); + public static void worldTick(TickEvent.WorldTickEvent event) { + if (event.phase != TickEvent.Phase.START && !event.world.isClientSide()) { + Backups.INSTANCE.tick(event.world.getServer(), System.currentTimeMillis()); } } } \ No newline at end of file From da54f8aa4ce91ccc14168fe723f2160e11a6c29e Mon Sep 17 00:00:00 2001 From: manmaed Date: Fri, 9 Sep 2022 22:15:55 +0100 Subject: [PATCH 06/42] 1.19.2?? --- build.gradle | 28 ++++++++----------- gradle.properties | 14 ++++++++-- settings.gradle | 7 +++++ .../mods/ftbbackups/BackupCommands.java | 20 ++++++------- .../mods/ftbbackups/Backups.java | 18 ++++++------ .../mods/ftbbackups/FTBBackups.java | 16 +++++------ .../mods/ftbbackups/FTBBackupsClient.java | 8 +++--- src/main/resources/META-INF/mods.toml | 11 ++++---- 8 files changed, 65 insertions(+), 57 deletions(-) create mode 100644 settings.gradle diff --git a/build.gradle b/build.gradle index 962c6efb..b56c5ef2 100644 --- a/build.gradle +++ b/build.gradle @@ -1,18 +1,9 @@ -buildscript { - repositories { - jcenter() - maven { url = 'https://files.minecraftforge.net/maven' } - maven { url "https://plugins.gradle.org/m2/" } - mavenCentral() - } - dependencies { - classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true - classpath 'gradle.plugin.com.matthewprenger:CurseGradle:1.1.0' - } +plugins { + id 'eclipse' + id 'maven-publish' + id 'net.minecraftforge.gradle' version '5.1.+' + id 'com.matthewprenger.cursegradle' version '1.4.0' } -apply plugin: 'net.minecraftforge.gradle' -apply plugin: 'eclipse' -apply plugin: 'com.matthewprenger.cursegradle' apply from: 'https://files.latmod.com/public/markdown-git-changelog.gradle' @@ -24,7 +15,7 @@ archivesBaseName = project.mod_name java.toolchain.languageVersion = JavaLanguageVersion.of(17) minecraft { - mappings channel: 'official', version: project.mapping_version + mappings channel: 'official', version: project.minecraft_version // accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') runs { client { @@ -80,7 +71,7 @@ minecraft { sourceSets.main.resources { srcDir 'src/generated/resources' } dependencies { - minecraft "net.minecraftforge:forge:${forge_version}" + minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" } jar { @@ -109,9 +100,12 @@ if (ENV.CURSEFORGE_KEY) { project { id = project.curseforge_id releaseType = project.curseforge_type - addGameVersion '1.18.1' + addGameVersion "${minecraft_version}" changelog = getGitChangelog changelogType = 'markdown' } } } +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 038e187b..6ab4f78b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,11 +1,19 @@ +# 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 + +#Forge Stuff +minecraft_version=1.19.2 +forge_version=43.1.1 + +#Mod Stuff mod_id=ftbackups mod_name=ftb-backups mod_package=dev.ftb.mods mod_version=2.1.2 -mod_author=LatvianModder -forge_version=1.18.1-39.0.10 -mapping_version=1.18.1 +mod_author=FTB Team + +#CurseForge curseforge_id=314904 curseforge_type=release diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..bdcf2cdc --- /dev/null +++ b/settings.gradle @@ -0,0 +1,7 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven { url = 'https://forge.modpacks.ch/maven' } + maven { url = 'https://maven.minecraftforge.net/' } + } +} \ No newline at end of file diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/BackupCommands.java b/src/main/java/com/feed_the_beast/mods/ftbbackups/BackupCommands.java index 18ca20a9..ffa5eae7 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/BackupCommands.java +++ b/src/main/java/com/feed_the_beast/mods/ftbbackups/BackupCommands.java @@ -5,7 +5,7 @@ import net.minecraft.Util; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; -import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.level.storage.LevelResource; @@ -44,7 +44,7 @@ public static void register(CommandDispatcher dispatcher) private static int time(CommandSourceStack source) { - source.sendSuccess(new TranslatableComponent("ftbbackups.lang.timer", BackupUtils.getTimeString(Backups.INSTANCE.nextBackup - System.currentTimeMillis())), true); + source.sendSuccess(Component.translatable("ftbbackups.lang.timer", BackupUtils.getTimeString(Backups.INSTANCE.nextBackup - System.currentTimeMillis())), true); return 1; } @@ -54,12 +54,12 @@ private static int start(CommandSourceStack source, String customName) { for (ServerPlayer player : source.getServer().getPlayerList().getPlayers()) { - player.sendMessage(new TranslatableComponent("ftbbackups.lang.manual_launch", source.getDisplayName()), Util.NIL_UUID); + player.sendSystemMessage(Component.translatable("ftbbackups.lang.manual_launch", source.getDisplayName())); } } else { - source.sendSuccess(new TranslatableComponent("ftbbackups.lang.already_running"), true); + source.sendSuccess(Component.translatable("ftbbackups.lang.already_running"), true); } return 1; @@ -74,9 +74,9 @@ private static int size(CommandSourceStack source) totalSize += backup.size; } - source.sendSuccess(new TranslatableComponent("ftbbackups.lang.size.current", BackupUtils.getSizeString(source.getServer().getWorldPath(LevelResource.ROOT).toFile())), true); - source.sendSuccess(new TranslatableComponent("ftbbackups.lang.size.total", BackupUtils.getSizeString(totalSize)), true); - source.sendSuccess(new TranslatableComponent("ftbbackups.lang.size.available", BackupUtils.getSizeString(Math.min(FTBBackupsConfig.maxTotalSize, Backups.INSTANCE.backupsFolder.getFreeSpace()))), true); + source.sendSuccess(Component.translatable("ftbbackups.lang.size.current", BackupUtils.getSizeString(source.getServer().getWorldPath(LevelResource.ROOT).toFile())), true); + source.sendSuccess(Component.translatable("ftbbackups.lang.size.total", BackupUtils.getSizeString(totalSize)), true); + source.sendSuccess(Component.translatable("ftbbackups.lang.size.available", BackupUtils.getSizeString(Math.min(FTBBackupsConfig.maxTotalSize, Backups.INSTANCE.backupsFolder.getFreeSpace()))), true); return 1; } @@ -85,7 +85,7 @@ private static int status(CommandSourceStack source) { source.getServer().getAllLevels().forEach(level -> { if (level != null) { - source.sendSuccess(new TranslatableComponent("Current status for " + level.toString() + ": " + (!level.noSave ? "NO SAVE !!" : "Autosave")), true); + source.sendSuccess(Component.translatable("Current status for " + level.toString() + ": " + (!level.noSave ? "NO SAVE !!" : "Autosave")), true); } }); @@ -96,13 +96,13 @@ private static int resetState(CommandSourceStack source) { source.getServer().getAllLevels().forEach(level -> { if (level != null) { - source.sendSuccess(new TranslatableComponent("Reseting state + saving for " + level.toString()), true); + source.sendSuccess(Component.translatable("Reseting state + saving for " + level.toString()), true); level.noSave = false; level.save(null, true, true); } }); - source.sendSuccess(new TranslatableComponent("Reseting state + saving for players"), true); + source.sendSuccess(Component.translatable("Reseting state + saving for players"), true); source.getServer().getPlayerList().saveAll(); return 1; diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/Backups.java b/src/main/java/com/feed_the_beast/mods/ftbbackups/Backups.java index a52d49bd..54e7ca15 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/Backups.java +++ b/src/main/java/com/feed_the_beast/mods/ftbbackups/Backups.java @@ -105,7 +105,7 @@ public void tick(MinecraftServer server, long now) if (!FTBBackupsConfig.onlyIfPlayersOnline || hadPlayersOnline || !server.getPlayerList().getPlayers().isEmpty()) { hadPlayersOnline = false; - run(server, true, new TextComponent("Server"), ""); + run(server, true, Component.translatable("Server"), ""); } } @@ -145,7 +145,7 @@ public void notifyAll(MinecraftServer server, Component component, boolean error component = component.plainCopy(); component.getStyle().withColor(TextColor.fromLegacyFormat(error ? ChatFormatting.DARK_RED : ChatFormatting.LIGHT_PURPLE)); FTBBackups.LOGGER.info(component.getString()); - server.getPlayerList().broadcastMessage(component, ChatType.GAME_INFO, Util.NIL_UUID); + server.getPlayerList().broadcastSystemMessage(component, true); } public boolean run(MinecraftServer server, boolean auto, Component name, String customName) @@ -160,7 +160,7 @@ public boolean run(MinecraftServer server, boolean auto, Component name, String return false; } - notifyAll(server, new TranslatableComponent("ftbbackups.lang.start", name), false); + notifyAll(server, Component.translatable("ftbbackups.lang.start", name), false); nextBackup = System.currentTimeMillis() + FTBBackupsConfig.backupTimer; for (ServerLevel world : server.getAllLevels()) @@ -184,12 +184,12 @@ public boolean run(MinecraftServer server, boolean auto, Component name, String catch (Exception ex) { ex.printStackTrace(); - notifyAll(server, new TranslatableComponent("ftbbackups.lang.saving_failed"), true); + notifyAll(server, Component.translatable("ftbbackups.lang.saving_failed"), true); } } catch (Exception ex) { - notifyAll(server, new TranslatableComponent("ftbbackups.lang.saving_failed"), true); + notifyAll(server, Component.translatable("ftbbackups.lang.saving_failed"), true); ex.printStackTrace(); } @@ -393,7 +393,7 @@ private void createBackup(MinecraftServer server, String customName) if (!FTBBackupsConfig.silent) { String errorName = ex.getClass().getName(); - notifyAll(server, new TranslatableComponent("ftbbackups.lang.fail", errorName), true); + notifyAll(server, Component.translatable("ftbbackups.lang.fail", errorName), true); } ex.printStackTrace(); @@ -432,15 +432,15 @@ private void createBackup(MinecraftServer server, String customName) String sizeB = BackupUtils.getSizeString(fileSize); String sizeT = BackupUtils.getSizeString(totalSize); String sizeString = sizeB.equals(sizeT) ? sizeB : (sizeB + " | " + sizeT); - component = new TranslatableComponent("ftbbackups.lang.end_2", timeString, sizeString); + component = Component.translatable("ftbbackups.lang.end_2", timeString, sizeString); } else { - component = new TranslatableComponent("ftbbackups.lang.end_1", timeString); + component = Component.translatable("ftbbackups.lang.end_1", timeString); } component.getStyle().withColor(TextColor.fromLegacyFormat(ChatFormatting.LIGHT_PURPLE)); - server.getPlayerList().broadcastMessage(component, ChatType.CHAT, Util.NIL_UUID); + server.getPlayerList().broadcastSystemMessage(component, true); } } diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackups.java b/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackups.java index 3200403c..70879ffb 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackups.java +++ b/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackups.java @@ -2,7 +2,7 @@ import com.feed_the_beast.mods.ftbbackups.net.BackupProgressPacket; import com.feed_the_beast.mods.ftbbackups.net.FTBBackupsNetHandler; -import net.minecraft.network.chat.TextComponent; +import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.event.RegisterCommandsEvent; import net.minecraftforge.event.TickEvent; @@ -57,32 +57,32 @@ public static void serverStopping(ServerStoppingEvent event) { if (FTBBackupsConfig.forceOnShutdown) { - Backups.INSTANCE.run(event.getServer(), true, new TextComponent("Server"), ""); + Backups.INSTANCE.run(event.getServer(), true, Component.literal("Server"), ""); } } @SubscribeEvent public static void playerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { - if (event.getPlayer() instanceof ServerPlayer) + if (event.getEntity() instanceof ServerPlayer) { - FTBBackupsNetHandler.MAIN.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) event.getPlayer()), new BackupProgressPacket(Backups.INSTANCE.currentFile, Backups.INSTANCE.totalFiles)); + FTBBackupsNetHandler.MAIN.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) event.getEntity()), new BackupProgressPacket(Backups.INSTANCE.currentFile, Backups.INSTANCE.totalFiles)); } } @SubscribeEvent public static void playerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) { - if (event.getPlayer() instanceof ServerPlayer) + if (event.getEntity() instanceof ServerPlayer) { Backups.INSTANCE.hadPlayersOnline = true; } } @SubscribeEvent - public static void worldTick(TickEvent.WorldTickEvent event) { - if (event.phase != TickEvent.Phase.START && !event.world.isClientSide()) { - Backups.INSTANCE.tick(event.world.getServer(), System.currentTimeMillis()); + public static void worldTick(TickEvent.ServerTickEvent event) { + if (event.phase != TickEvent.Phase.START && !event.side.isClient()) { + Backups.INSTANCE.tick(event.getServer(), System.currentTimeMillis()); } } } \ No newline at end of file diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackupsClient.java b/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackupsClient.java index 5b57cf7d..37e0bec3 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackupsClient.java +++ b/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackupsClient.java @@ -3,7 +3,7 @@ import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; import net.minecraft.client.resources.language.I18n; -import net.minecraftforge.client.event.RenderGameOverlayEvent; +import net.minecraftforge.client.event.RenderGuiOverlayEvent; import net.minecraftforge.common.MinecraftForge; public class FTBBackupsClient @@ -29,8 +29,8 @@ public static void setFiles(int current, int total) totalBackupFiles = 0; }*/ - private static void onDebugInfoEvent(RenderGameOverlayEvent.Text event) - { + private static void onDebugInfoEvent(RenderGuiOverlayEvent.Post event) + { //RenderGameOverlayEvent if (Minecraft.getInstance().options.renderDebug) { return; @@ -38,7 +38,7 @@ private static void onDebugInfoEvent(RenderGameOverlayEvent.Text event) if (totalBackupFiles > 0 && totalBackupFiles > currentBackupFile) { - event.getLeft().add(ChatFormatting.LIGHT_PURPLE + I18n.get("ftbbackups.lang.timer_progress", currentBackupFile * 100 / totalBackupFiles, currentBackupFile, totalBackupFiles)); + // event.getLeft().add(ChatFormatting.LIGHT_PURPLE + I18n.get("ftbbackups.lang.timer_progress", currentBackupFile * 100 / totalBackupFiles, currentBackupFile, totalBackupFiles)); } } } \ No newline at end of file diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 54f1a19e..a982f14d 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -1,5 +1,5 @@ modLoader = "javafml" -loaderVersion = "[39,)" +loaderVersion = "[43,)" license = "All Rights Reserved" [[mods]] @@ -8,21 +8,20 @@ version = "${file.jarVersion}" displayName = "FTB Backups" displayURL = "https://www.curseforge.com/minecraft/mc-mods/ftb-backups" logoFile = "logo.png" -authors = "LatvianModder" +authors = "FTB Team" description = ''' -Creates backups of your world -''' +Creates backups of your world''' [[dependencies.ftbbackups]] modId="forge" mandatory = true -versionRange = "[39,)" +versionRange = "[43,)" ordering = "NONE" side="BOTH" [[dependencies.ftbbackups]] modId="minecraft" mandatory = true -versionRange = "[1.18,)" +versionRange = "[1.19.2,)" ordering = "NONE" side="BOTH" From 4d99b47f7b6510311b22efab7e9059e9bcf38a33 Mon Sep 17 00:00:00 2001 From: manmaed Date: Fri, 6 Sep 2024 10:55:23 +0100 Subject: [PATCH 07/42] refactor to dev.ftb.mods + Init Port to 1.21 --- build.gradle | 139 +++++++----------- gradle.properties | 13 +- gradle/wrapper/gradle-wrapper.properties | 6 +- settings.gradle | 8 +- .../mods/ftbbackups/FTBBackups.java | 88 ----------- .../ftb}/mods/ftbbackups/Backup.java | 2 +- .../ftb}/mods/ftbbackups/BackupCommands.java | 26 ++-- .../ftb}/mods/ftbbackups/BackupEvent.java | 5 +- .../ftb}/mods/ftbbackups/BackupStatus.java | 2 +- .../ftb}/mods/ftbbackups/BackupUtils.java | 2 +- .../ftb}/mods/ftbbackups/Backups.java | 29 ++-- .../dev/ftb/mods/ftbbackups/FTBBackups.java | 88 +++++++++++ .../mods/ftbbackups/FTBBackupsClient.java | 18 ++- .../mods/ftbbackups/FTBBackupsConfig.java | 63 ++++---- .../ftbbackups/net/BackupProgressPacket.java | 12 +- .../ftbbackups/net/FTBBackupsNetHandler.java | 12 +- .../mods/ftbbackups/net/package-info.java | 2 +- 17 files changed, 242 insertions(+), 273 deletions(-) delete mode 100644 src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackups.java rename src/main/java/{com/feed_the_beast => dev/ftb}/mods/ftbbackups/Backup.java (96%) rename src/main/java/{com/feed_the_beast => dev/ftb}/mods/ftbbackups/BackupCommands.java (66%) rename src/main/java/{com/feed_the_beast => dev/ftb}/mods/ftbbackups/BackupEvent.java (88%) rename src/main/java/{com/feed_the_beast => dev/ftb}/mods/ftbbackups/BackupStatus.java (86%) rename src/main/java/{com/feed_the_beast => dev/ftb}/mods/ftbbackups/BackupUtils.java (99%) rename src/main/java/{com/feed_the_beast => dev/ftb}/mods/ftbbackups/Backups.java (91%) create mode 100644 src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java rename src/main/java/{com/feed_the_beast => dev/ftb}/mods/ftbbackups/FTBBackupsClient.java (61%) rename src/main/java/{com/feed_the_beast => dev/ftb}/mods/ftbbackups/FTBBackupsConfig.java (71%) rename src/main/java/{com/feed_the_beast => dev/ftb}/mods/ftbbackups/net/BackupProgressPacket.java (68%) rename src/main/java/{com/feed_the_beast => dev/ftb}/mods/ftbbackups/net/FTBBackupsNetHandler.java (74%) rename src/main/java/{com/feed_the_beast => dev/ftb}/mods/ftbbackups/net/package-info.java (50%) diff --git a/build.gradle b/build.gradle index b56c5ef2..abeed774 100644 --- a/build.gradle +++ b/build.gradle @@ -1,111 +1,76 @@ plugins { - id 'eclipse' - id 'maven-publish' - id 'net.minecraftforge.gradle' version '5.1.+' - id 'com.matthewprenger.cursegradle' version '1.4.0' + id 'java-library' + id 'eclipse' + id 'idea' + id 'maven-publish' + id 'net.neoforged.gradle.userdev' version "${neo_gradle}" + id "me.modmuss50.mod-publish-plugin" version "0.4.5" } -apply from: 'https://files.latmod.com/public/markdown-git-changelog.gradle' - def ENV = System.getenv() -version = ENV.GITHUB_RUN_NUMBER ? "${mod_version}.${ENV.GITHUB_RUN_NUMBER}" : "${mod_version}.9999" -group = project.mod_package -archivesBaseName = project.mod_name - -java.toolchain.languageVersion = JavaLanguageVersion.of(17) - -minecraft { - mappings channel: 'official', version: project.minecraft_version - // accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') - runs { - client { - workingDirectory project.file('run') - //property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP' - property 'forge.logging.markers', 'REGISTRIES' - property 'forge.logging.console.level', 'debug' +version = "${ENV.GITHUB_RUN_NUMBER}" ? "${mod_version}.${ENV.GITHUB_RUN_NUMBER}" : "${mod_version}.9999" +group = project.maven_group - if (project.hasProperty('mc_dev_uuid')) args '--uuid', project.mc_dev_uuid - if (project.hasProperty('mc_dev_username')) args '--username', project.mc_dev_username - if (project.hasProperty('mc_dev_width')) args '--width', project.mc_dev_width - if (project.hasProperty('mc_dev_height')) args '--height', project.mc_dev_height - - mods { - ftbbackups { - source sourceSets.main - } - } - } +repositories { + mavenLocal() + maven { + url "https://cursemaven.com" + } +} - server { - workingDirectory project.file('run') - //property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP' - property 'forge.logging.markers', 'REGISTRIES' - property 'forge.logging.console.level', 'debug' +base { + archivesName = project.archives_base_name +} - args 'nogui' +java.toolchain.languageVersion = JavaLanguageVersion.of(21) - mods { - ftbbackups { - source sourceSets.main - } - } - } +runs { + configureEach { + systemProperty 'forge.logging.markers', 'REGISTRIES' + systemProperty 'forge.logging.console.level', 'debug' + modSource project.sourceSets.main + } - data { - workingDirectory project.file('run') - //property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP' - property 'forge.logging.markers', 'REGISTRIES' - property 'forge.logging.console.level', 'debug' + client { + systemProperty 'forge.enabledGameTestNamespaces', project.archives_base_name + } - args '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/') + server { + systemProperty 'forge.enabledGameTestNamespaces', project.archives_base_name + programArgument '--nogui' + } - mods { - ftbbackups { - source sourceSets.main - } - } - } + data { + programArguments.addAll '--mod', project.archives_base_name, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() } } -sourceSets.main.resources { srcDir 'src/generated/resources' } +sourceSets.main.resources { + srcDir 'src/generated/resources' +} dependencies { - minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" + implementation "net.neoforged:neoforge:${neo_version}" } - -jar { - manifest { - attributes([ - "Specification-Title" : project.mod_id, - "Specification-Vendor" : project.mod_author, - "Specification-Version" : "1", - "Implementation-Title" : project.name, - "Implementation-Version" : version, - "Implementation-Vendor" : project.mod_author, - "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") - ]) +tasks.withType(ProcessResources).configureEach { + var replaceProperties = [ + version: version + ] + inputs.properties replaceProperties + + filesMatching(['META-INF/neoforge.mods.toml']) { + expand replaceProperties + [project: project] } } -def reobfFile = file("$buildDir/reobfJar/output.jar") -def reobfArtifact = artifacts.add('default', reobfFile) { - type 'jar' - builtBy 'reobfJar' -} -if (ENV.CURSEFORGE_KEY) { - curseforge { - apiKey = ENV.CURSEFORGE_KEY - project { - id = project.curseforge_id - releaseType = project.curseforge_type - addGameVersion "${minecraft_version}" - changelog = getGitChangelog - changelogType = 'markdown' - } - } -} tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation +} + +idea { + module { + downloadSources = true + downloadJavadoc = true + } } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 6ab4f78b..bd0ec7ec 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,16 +3,15 @@ org.gradle.jvmargs=-Xmx3G org.gradle.daemon=false -#Forge Stuff -minecraft_version=1.19.2 -forge_version=43.1.1 +# Neo Stuff +minecraft_version=1.21 +neo_version=21.0.160 +neo_gradle=7.0.142 #Mod Stuff -mod_id=ftbackups -mod_name=ftb-backups -mod_package=dev.ftb.mods +archives_base_name=ftbackups +maven_group=dev.ftb.mods mod_version=2.1.2 -mod_author=FTB Team #CurseForge curseforge_id=314904 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2e6e5897..e4a5f610 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.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index bdcf2cdc..fd1434c1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,7 +1,11 @@ pluginManagement { repositories { + mavenLocal() gradlePluginPortal() - maven { url = 'https://forge.modpacks.ch/maven' } - maven { url = 'https://maven.minecraftforge.net/' } + maven { url = 'https://maven.neoforged.net/releases' } } +} + +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' } \ No newline at end of file diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackups.java b/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackups.java deleted file mode 100644 index 70879ffb..00000000 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackups.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.feed_the_beast.mods.ftbbackups; - -import com.feed_the_beast.mods.ftbbackups.net.BackupProgressPacket; -import com.feed_the_beast.mods.ftbbackups.net.FTBBackupsNetHandler; -import net.minecraft.network.chat.Component; -import net.minecraft.server.level.ServerPlayer; -import net.minecraftforge.event.RegisterCommandsEvent; -import net.minecraftforge.event.TickEvent; -import net.minecraftforge.event.entity.player.PlayerEvent; -import net.minecraftforge.event.server.ServerStartedEvent; -import net.minecraftforge.event.server.ServerStoppingEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.IExtensionPoint; -import net.minecraftforge.fml.ModLoadingContext; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; -import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; -import net.minecraftforge.network.NetworkConstants; -import net.minecraftforge.network.PacketDistributor; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@Mod(FTBBackups.MOD_ID) -@Mod.EventBusSubscriber(modid = FTBBackups.MOD_ID) -public class FTBBackups -{ - public static final String MOD_ID = "ftbbackups"; - public static final Logger LOGGER = LogManager.getLogger("FTB Utilities Backups"); - - public FTBBackups() - { - FMLJavaModLoadingContext.get().getModEventBus().addListener(this::clientSetup); - FTBBackupsNetHandler.init(); - FTBBackupsConfig.register(); - ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true)); - } - - private void clientSetup(FMLClientSetupEvent event) - { - FTBBackupsClient.init(); - } - - @SubscribeEvent - public static void serverAboutToStart(ServerStartedEvent event) - { - Backups.INSTANCE.init(event.getServer()); - } - - @SubscribeEvent - public static void registerCommands(RegisterCommandsEvent event) - { - BackupCommands.register(event.getDispatcher()); - } - - @SubscribeEvent - public static void serverStopping(ServerStoppingEvent event) - { - if (FTBBackupsConfig.forceOnShutdown) - { - Backups.INSTANCE.run(event.getServer(), true, Component.literal("Server"), ""); - } - } - - @SubscribeEvent - public static void playerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) - { - if (event.getEntity() instanceof ServerPlayer) - { - FTBBackupsNetHandler.MAIN.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) event.getEntity()), new BackupProgressPacket(Backups.INSTANCE.currentFile, Backups.INSTANCE.totalFiles)); - } - } - - @SubscribeEvent - public static void playerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) - { - if (event.getEntity() instanceof ServerPlayer) - { - Backups.INSTANCE.hadPlayersOnline = true; - } - } - - @SubscribeEvent - public static void worldTick(TickEvent.ServerTickEvent event) { - if (event.phase != TickEvent.Phase.START && !event.side.isClient()) { - Backups.INSTANCE.tick(event.getServer(), System.currentTimeMillis()); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/Backup.java b/src/main/java/dev/ftb/mods/ftbbackups/Backup.java similarity index 96% rename from src/main/java/com/feed_the_beast/mods/ftbbackups/Backup.java rename to src/main/java/dev/ftb/mods/ftbbackups/Backup.java index 90d09736..f2380500 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/Backup.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/Backup.java @@ -1,4 +1,4 @@ -package com.feed_the_beast.mods.ftbbackups; +package dev.ftb.mods.ftbbackups; import com.google.gson.JsonObject; diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/BackupCommands.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java similarity index 66% rename from src/main/java/com/feed_the_beast/mods/ftbbackups/BackupCommands.java rename to src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java index ffa5eae7..0538d1ef 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/BackupCommands.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java @@ -1,8 +1,7 @@ -package com.feed_the_beast.mods.ftbbackups; +package dev.ftb.mods.ftbbackups; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.StringArgumentType; -import net.minecraft.Util; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.network.chat.Component; @@ -44,7 +43,7 @@ public static void register(CommandDispatcher dispatcher) private static int time(CommandSourceStack source) { - source.sendSuccess(Component.translatable("ftbbackups.lang.timer", BackupUtils.getTimeString(Backups.INSTANCE.nextBackup - System.currentTimeMillis())), true); + source.sendSuccess(() -> Component.translatable("ftbbackups.lang.timer", BackupUtils.getTimeString(Backups.INSTANCE.nextBackup - System.currentTimeMillis())),true); return 1; } @@ -59,7 +58,7 @@ private static int start(CommandSourceStack source, String customName) } else { - source.sendSuccess(Component.translatable("ftbbackups.lang.already_running"), true); + source.sendSuccess(() -> Component.translatable("ftbbackups.lang.already_running"), true); } return 1; @@ -67,16 +66,11 @@ private static int start(CommandSourceStack source, String customName) private static int size(CommandSourceStack source) { - long totalSize = 0L; + long totalSize = Backups.INSTANCE.backups.stream().mapToLong(backup -> backup.size).sum(); - for (Backup backup : Backups.INSTANCE.backups) - { - totalSize += backup.size; - } - - source.sendSuccess(Component.translatable("ftbbackups.lang.size.current", BackupUtils.getSizeString(source.getServer().getWorldPath(LevelResource.ROOT).toFile())), true); - source.sendSuccess(Component.translatable("ftbbackups.lang.size.total", BackupUtils.getSizeString(totalSize)), true); - source.sendSuccess(Component.translatable("ftbbackups.lang.size.available", BackupUtils.getSizeString(Math.min(FTBBackupsConfig.maxTotalSize, Backups.INSTANCE.backupsFolder.getFreeSpace()))), true); + source.sendSuccess(() -> Component.translatable("ftbbackups.lang.size.current", BackupUtils.getSizeString(source.getServer().getWorldPath(LevelResource.ROOT).toFile())), true); + source.sendSuccess(() -> Component.translatable("ftbbackups.lang.size.total", BackupUtils.getSizeString(totalSize)), true); + source.sendSuccess(() -> Component.translatable("ftbbackups.lang.size.available", BackupUtils.getSizeString(Math.min(FTBBackupsConfig.maxTotalSize, Backups.INSTANCE.backupsFolder.getFreeSpace()))), true); return 1; } @@ -85,7 +79,7 @@ private static int status(CommandSourceStack source) { source.getServer().getAllLevels().forEach(level -> { if (level != null) { - source.sendSuccess(Component.translatable("Current status for " + level.toString() + ": " + (!level.noSave ? "NO SAVE !!" : "Autosave")), true); + source.sendSuccess(() -> Component.literal("Current status for " + level.toString() + ": " + (!level.noSave ? "NO SAVE !!" : "Autosave")), true); } }); @@ -96,13 +90,13 @@ private static int resetState(CommandSourceStack source) { source.getServer().getAllLevels().forEach(level -> { if (level != null) { - source.sendSuccess(Component.translatable("Reseting state + saving for " + level.toString()), true); + source.sendSuccess(() -> Component.literal("Reseting state + saving for " + level.toString()), true); level.noSave = false; level.save(null, true, true); } }); - source.sendSuccess(Component.translatable("Reseting state + saving for players"), true); + source.sendSuccess(() -> Component.literal("Reseting state + saving for players"), true); source.getServer().getPlayerList().saveAll(); return 1; diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/BackupEvent.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupEvent.java similarity index 88% rename from src/main/java/com/feed_the_beast/mods/ftbbackups/BackupEvent.java rename to src/main/java/dev/ftb/mods/ftbbackups/BackupEvent.java index 969cd5ab..d7b14455 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/BackupEvent.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupEvent.java @@ -1,6 +1,7 @@ -package com.feed_the_beast.mods.ftbbackups; +package dev.ftb.mods.ftbbackups; -import net.minecraftforge.eventbus.api.Event; + +import net.neoforged.bus.api.Event; import javax.annotation.Nullable; import java.io.File; diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/BackupStatus.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupStatus.java similarity index 86% rename from src/main/java/com/feed_the_beast/mods/ftbbackups/BackupStatus.java rename to src/main/java/dev/ftb/mods/ftbbackups/BackupStatus.java index fef432bf..59fbdfbb 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/BackupStatus.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupStatus.java @@ -1,4 +1,4 @@ -package com.feed_the_beast.mods.ftbbackups; +package dev.ftb.mods.ftbbackups; /** * @author LatvianModder diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/BackupUtils.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java similarity index 99% rename from src/main/java/com/feed_the_beast/mods/ftbbackups/BackupUtils.java rename to src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java index 525afa5b..61867970 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/BackupUtils.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java @@ -1,4 +1,4 @@ -package com.feed_the_beast.mods.ftbbackups; +package dev.ftb.mods.ftbbackups; import com.google.gson.JsonElement; import com.google.gson.JsonIOException; diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/Backups.java b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java similarity index 91% rename from src/main/java/com/feed_the_beast/mods/ftbbackups/Backups.java rename to src/main/java/dev/ftb/mods/ftbbackups/Backups.java index 54e7ca15..40296bc9 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/Backups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java @@ -1,23 +1,22 @@ -package com.feed_the_beast.mods.ftbbackups; +package dev.ftb.mods.ftbbackups; -import com.feed_the_beast.mods.ftbbackups.net.BackupProgressPacket; -import com.feed_the_beast.mods.ftbbackups.net.FTBBackupsNetHandler; +import dev.ftb.mods.ftbbackups.net.BackupProgressPacket; +import dev.ftb.mods.ftbbackups.net.FTBBackupsNetHandler; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import net.minecraft.ChatFormatting; -import net.minecraft.Util; import net.minecraft.network.chat.*; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.storage.LevelResource; -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.network.PacketDistributor; -import net.minecraftforge.fml.loading.FMLPaths; +import net.neoforged.fml.loading.FMLPaths; +import net.neoforged.neoforge.common.NeoForge; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Calendar; import java.util.LinkedHashMap; @@ -44,7 +43,7 @@ public enum Backups public void init(MinecraftServer server) { - File dataDir = server.getServerDirectory(); + Path dataDir = server.getServerDirectory(); backupsFolder = FTBBackupsConfig.folder.trim().isEmpty() ? FMLPaths.GAMEDIR.get().resolve("backups").toFile() : new File(FTBBackupsConfig.folder); try @@ -58,7 +57,7 @@ public void init(MinecraftServer server) doingBackup = BackupStatus.NONE; backups.clear(); - File file = new File(dataDir, "local/ftbutilities/backups.json"); + File file = new File(dataDir.toFile(), "local/ftbutilities/backups.json"); if (!file.exists()) { @@ -123,7 +122,7 @@ public void tick(MinecraftServer server, long now) if (!FTBBackupsConfig.silent) { - FTBBackupsNetHandler.MAIN.send(PacketDistributor.ALL.noArg(), new BackupProgressPacket(0, 0)); + //TODO: Fix FTBBackupsNetHandler.MAIN.send(PacketDistributor.ALL.noArg(), new BackupProgressPacket(0, 0)); } } else if (doingBackup.isRunning() && printFiles) @@ -135,7 +134,7 @@ else if (doingBackup.isRunning() && printFiles) if (!FTBBackupsConfig.silent) { - FTBBackupsNetHandler.MAIN.send(PacketDistributor.ALL.noArg(), new BackupProgressPacket(currentFile, totalFiles)); + //TODO: Fix FTBBackupsNetHandler.MAIN.send(PacketDistributor.ALL.noArg(), new BackupProgressPacket(currentFile, totalFiles)); } } } @@ -236,7 +235,7 @@ private void createBackup(MinecraftServer server, String customName) try { LinkedHashMap fileMap = new LinkedHashMap<>(); - String mcdir = server.getServerDirectory().getCanonicalFile().getAbsolutePath(); + String mcdir = server.getServerDirectory().getFileSystem().toString(); Consumer consumer = file0 -> { for (File file : BackupUtils.listTree(file0)) @@ -257,7 +256,7 @@ private void createBackup(MinecraftServer server, String customName) consumer.accept(new File(s)); } - MinecraftForge.EVENT_BUS.post(new BackupEvent.Pre(consumer)); + NeoForge.EVENT_BUS.post(new BackupEvent.Pre(consumer)); for (File file : BackupUtils.listTree(src)) { @@ -404,7 +403,7 @@ private void createBackup(MinecraftServer server, String customName) Backup backup = new Backup(time.getTimeInMillis(), out.toString().replace('\\', '/'), getLastIndex() + 1, success, fileSize); backups.add(backup); - MinecraftForge.EVENT_BUS.post(new BackupEvent.Post(backup, error)); + NeoForge.EVENT_BUS.post(new BackupEvent.Post(backup, error)); JsonArray array = new JsonArray(); @@ -413,7 +412,7 @@ private void createBackup(MinecraftServer server, String customName) array.add(backup1.toJsonObject()); } - BackupUtils.toJson(new File(server.getServerDirectory(), "local/ftbutilities/backups.json"), array, true); + BackupUtils.toJson(new File(server.getServerDirectory().toFile(), "local/ftbutilities/backups.json"), array, true); if (error == null && !FTBBackupsConfig.silent) { diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java new file mode 100644 index 00000000..de98f4c2 --- /dev/null +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java @@ -0,0 +1,88 @@ +package dev.ftb.mods.ftbbackups; + + +import dev.ftb.mods.ftbbackups.net.BackupProgressPacket; +import dev.ftb.mods.ftbbackups.net.FTBBackupsNetHandler; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.ModLoadingContext; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; +import net.neoforged.neoforge.event.RegisterCommandsEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import net.neoforged.neoforge.event.server.ServerStartedEvent; +import net.neoforged.neoforge.event.server.ServerStoppingEvent; +import net.neoforged.neoforge.event.tick.ServerTickEvent; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@Mod(FTBBackups.MOD_ID) +public class FTBBackups +{ + public static final String MOD_ID = "ftbbackups"; + public static final Logger LOGGER = LogManager.getLogger("FTB Utilities Backups"); + + public FTBBackups(IEventBus eventBus) + { + eventBus.addListener(this::clientSetup); +// FTBBackupsNetHandler.init(); + FTBBackupsConfig.register(eventBus); + //ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true)); + } + + private void clientSetup(FMLClientSetupEvent event) + { + FTBBackupsClient.init(); + } + + @SubscribeEvent + public static void serverAboutToStart(ServerStartedEvent event) + { + Backups.INSTANCE.init(event.getServer()); + } + + @SubscribeEvent + public static void registerCommands(RegisterCommandsEvent event) + { + BackupCommands.register(event.getDispatcher()); + } + + @SubscribeEvent + public static void serverStopping(ServerStoppingEvent event) + { + if (FTBBackupsConfig.forceOnShutdown) + { + Backups.INSTANCE.run(event.getServer(), true, Component.literal("Server"), ""); + } + } + + @SubscribeEvent + public static void playerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) + { + if (event.getEntity() instanceof ServerPlayer) + { + //TODO: Send Packets + //FTBBackupsNetHandler.MAIN.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) event.getEntity()), new BackupProgressPacket(Backups.INSTANCE.currentFile, Backups.INSTANCE.totalFiles)); + } + } + + @SubscribeEvent + public static void playerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) + { + if (event.getEntity() instanceof ServerPlayer) + { + Backups.INSTANCE.hadPlayersOnline = true; + } + } + + @SubscribeEvent + public static void worldTick(ServerTickEvent event) { + /*if (event.phase != ServerTickEvent.PostTickEvent.Phase.START && !event.side.isClient()) { + Backups.INSTANCE.tick(event.getServer(), System.currentTimeMillis()); + }*/ + //I Guess this is just called server side now?? + Backups.INSTANCE.tick(event.getServer(), System.currentTimeMillis()); + } +} \ No newline at end of file diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackupsClient.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java similarity index 61% rename from src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackupsClient.java rename to src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java index 37e0bec3..68148b91 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackupsClient.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java @@ -1,10 +1,11 @@ -package com.feed_the_beast.mods.ftbbackups; +package dev.ftb.mods.ftbbackups; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; +import net.minecraft.client.OptionInstance; import net.minecraft.client.resources.language.I18n; -import net.minecraftforge.client.event.RenderGuiOverlayEvent; -import net.minecraftforge.common.MinecraftForge; +import net.neoforged.neoforge.client.event.RenderGuiLayerEvent; +import net.neoforged.neoforge.common.NeoForge; public class FTBBackupsClient { @@ -14,7 +15,7 @@ public class FTBBackupsClient public static void init() { //MinecraftForge.EVENT_BUS.addListener(FTBBackupsClient::onClientDisconnected); - MinecraftForge.EVENT_BUS.addListener(FTBBackupsClient::onDebugInfoEvent); + NeoForge.EVENT_BUS.addListener(FTBBackupsClient::onDebugInfoEvent); } public static void setFiles(int current, int total) @@ -29,16 +30,17 @@ public static void setFiles(int current, int total) totalBackupFiles = 0; }*/ - private static void onDebugInfoEvent(RenderGuiOverlayEvent.Post event) - { //RenderGameOverlayEvent - if (Minecraft.getInstance().options.renderDebug) + private static void onDebugInfoEvent(RenderGuiLayerEvent.Post event) + { + OptionInstance booleanOptionInstance = Minecraft.getInstance().options.reducedDebugInfo(); + if (booleanOptionInstance.get()) { return; } if (totalBackupFiles > 0 && totalBackupFiles > currentBackupFile) { - // event.getLeft().add(ChatFormatting.LIGHT_PURPLE + I18n.get("ftbbackups.lang.timer_progress", currentBackupFile * 100 / totalBackupFiles, currentBackupFile, totalBackupFiles)); + //event.getLeft().add(ChatFormatting.LIGHT_PURPLE + I18n.get("ftbbackups.lang.timer_progress", currentBackupFile * 100 / totalBackupFiles, currentBackupFile, totalBackupFiles)); } } } \ No newline at end of file diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackupsConfig.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java similarity index 71% rename from src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackupsConfig.java rename to src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java index 49ad67c0..5e491def 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/FTBBackupsConfig.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java @@ -1,11 +1,13 @@ -package com.feed_the_beast.mods.ftbbackups; +package dev.ftb.mods.ftbbackups; + + -import net.minecraftforge.common.ForgeConfigSpec; -import net.minecraftforge.fml.ModLoadingContext; -import net.minecraftforge.fml.config.ModConfig; -import net.minecraftforge.fml.event.config.ModConfigEvent; -import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import org.apache.commons.lang3.tuple.Pair; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.ModLoadingContext; +import net.neoforged.fml.config.ModConfig; +import net.neoforged.fml.event.config.ModConfigEvent; +import net.neoforged.neoforge.common.ModConfigSpec; import java.util.ArrayList; import java.util.List; @@ -28,16 +30,17 @@ public class FTBBackupsConfig public static boolean forceOnShutdown; public static int bufferSize; - private static Pair common; + private static Pair common; - public static void register() + public static void register(IEventBus eventBus) { - FMLJavaModLoadingContext.get().getModEventBus().addListener(FTBBackupsConfig::reload); + eventBus.addListener(FTBBackupsConfig::reload); - common = new ForgeConfigSpec.Builder().configure(CommonConfig::new); + common = new ModConfigSpec.Builder().configure(CommonConfig::new); ModLoadingContext modLoadingContext = ModLoadingContext.get(); - modLoadingContext.registerConfig(ModConfig.Type.COMMON, common.getRight()); + //TODO: Register Configs?? + /*modLoadingContext.registerConfig(ModConfig.Type.COMMON, common.getRight());*/ } public static void reload(ModConfigEvent event) @@ -64,21 +67,22 @@ public static void reloadCommon() String mts = cfg.maxTotalSize.get(); + long l = Long.parseLong(mts.substring(0, mts.length() - 2).trim()); if (mts.endsWith("TB")) { - maxTotalSize = Long.parseLong(mts.substring(0, mts.length() - 2).trim()) * BackupUtils.TB; + maxTotalSize = l * BackupUtils.TB; } else if (mts.endsWith("GB")) { - maxTotalSize = Long.parseLong(mts.substring(0, mts.length() - 2).trim()) * BackupUtils.GB; + maxTotalSize = l * BackupUtils.GB; } else if (mts.endsWith("MB")) { - maxTotalSize = Long.parseLong(mts.substring(0, mts.length() - 2).trim()) * BackupUtils.MB; + maxTotalSize = l * BackupUtils.MB; } else if (mts.endsWith("KB")) { - maxTotalSize = Long.parseLong(mts.substring(0, mts.length() - 2).trim()) * BackupUtils.KB; + maxTotalSize = l * BackupUtils.KB; } else { @@ -92,20 +96,21 @@ else if (mts.endsWith("KB")) private static class CommonConfig { - private final ForgeConfigSpec.BooleanValue auto; - private final ForgeConfigSpec.BooleanValue silent; - private final ForgeConfigSpec.IntValue backupsToKeep; - private final ForgeConfigSpec.IntValue backupTimer; - private final ForgeConfigSpec.IntValue compressionLevel; - private final ForgeConfigSpec.ConfigValue folder; - private final ForgeConfigSpec.BooleanValue displayFileSize; - private final ForgeConfigSpec.ConfigValue> extraFiles; - private final ForgeConfigSpec.ConfigValue maxTotalSize; - private final ForgeConfigSpec.BooleanValue onlyIfPlayersOnline; - private final ForgeConfigSpec.BooleanValue forceOnShutdown; - private final ForgeConfigSpec.IntValue bufferSize; - - private CommonConfig(ForgeConfigSpec.Builder builder) + //ModConfigSpec or NeoForgeConfig + private final ModConfigSpec.BooleanValue auto; + private final ModConfigSpec.BooleanValue silent; + private final ModConfigSpec.IntValue backupsToKeep; + private final ModConfigSpec.IntValue backupTimer; + private final ModConfigSpec.IntValue compressionLevel; + private final ModConfigSpec.ConfigValue folder; + private final ModConfigSpec.BooleanValue displayFileSize; + private final ModConfigSpec.ConfigValue> extraFiles; + private final ModConfigSpec.ConfigValue maxTotalSize; + private final ModConfigSpec.BooleanValue onlyIfPlayersOnline; + private final ModConfigSpec.BooleanValue forceOnShutdown; + private final ModConfigSpec.IntValue bufferSize; + + private CommonConfig(ModConfigSpec.Builder builder) { auto = builder .comment("Enables backups to run automatically.") diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/net/BackupProgressPacket.java b/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java similarity index 68% rename from src/main/java/com/feed_the_beast/mods/ftbbackups/net/BackupProgressPacket.java rename to src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java index 3cf6963e..95ab08f7 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/net/BackupProgressPacket.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java @@ -1,10 +1,7 @@ -package com.feed_the_beast.mods.ftbbackups.net; +package dev.ftb.mods.ftbbackups.net; -import com.feed_the_beast.mods.ftbbackups.FTBBackupsClient; +import dev.ftb.mods.ftbbackups.FTBBackupsClient; import net.minecraft.network.FriendlyByteBuf; -import net.minecraftforge.network.NetworkEvent; - -import java.util.function.Supplier; /** * @author LatvianModder @@ -31,9 +28,10 @@ public void write(FriendlyByteBuf buf) buf.writeVarInt(current); } - public void handle(Supplier context) + //TODO This + /*public void handle(Supplier context) { context.get().enqueueWork(() -> FTBBackupsClient.setFiles(current, total)); context.get().setPacketHandled(true); - } + }*/ } \ No newline at end of file diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/net/FTBBackupsNetHandler.java b/src/main/java/dev/ftb/mods/ftbbackups/net/FTBBackupsNetHandler.java similarity index 74% rename from src/main/java/com/feed_the_beast/mods/ftbbackups/net/FTBBackupsNetHandler.java rename to src/main/java/dev/ftb/mods/ftbbackups/net/FTBBackupsNetHandler.java index ebdc43a7..14fccabf 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/net/FTBBackupsNetHandler.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/net/FTBBackupsNetHandler.java @@ -1,15 +1,15 @@ -package com.feed_the_beast.mods.ftbbackups.net; +package dev.ftb.mods.ftbbackups.net; -import com.feed_the_beast.mods.ftbbackups.FTBBackups; +import dev.ftb.mods.ftbbackups.FTBBackups; import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.network.NetworkRegistry; -import net.minecraftforge.network.simple.SimpleChannel; import java.util.function.Predicate; public class FTBBackupsNetHandler { - public static SimpleChannel MAIN; + + //TODO NetHandler + /*public static SimpleChannel MAIN; private static final String MAIN_VERSION = "1"; public static void init() @@ -24,5 +24,5 @@ public static void init() .simpleChannel(); MAIN.registerMessage(1, BackupProgressPacket.class, BackupProgressPacket::write, BackupProgressPacket::new, BackupProgressPacket::handle); - } + }*/ } \ No newline at end of file diff --git a/src/main/java/com/feed_the_beast/mods/ftbbackups/net/package-info.java b/src/main/java/dev/ftb/mods/ftbbackups/net/package-info.java similarity index 50% rename from src/main/java/com/feed_the_beast/mods/ftbbackups/net/package-info.java rename to src/main/java/dev/ftb/mods/ftbbackups/net/package-info.java index a2c67119..5332d546 100644 --- a/src/main/java/com/feed_the_beast/mods/ftbbackups/net/package-info.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/net/package-info.java @@ -1,2 +1,2 @@ @javax.annotation.ParametersAreNonnullByDefault -package com.feed_the_beast.mods.ftbbackups.net; \ No newline at end of file +package dev.ftb.mods.ftbbackups.net; \ No newline at end of file From 372101d8b1a4b1a7a662648e3f84540d1968b694 Mon Sep 17 00:00:00 2001 From: manmaed Date: Fri, 6 Sep 2024 12:04:53 +0100 Subject: [PATCH 08/42] Seems to Work? It Seems to Backup --- .gitignore | 3 +- .../ftb/mods/ftbbackups/BackupCommands.java | 3 ++ .../dev/ftb/mods/ftbbackups/FTBBackups.java | 36 +++++++++++-------- .../ftb/mods/ftbbackups/FTBBackupsConfig.java | 7 ++-- .../{mods.toml => neoforge.mods.toml} | 20 +++++------ 5 files changed, 39 insertions(+), 30 deletions(-) rename src/main/resources/META-INF/{mods.toml => neoforge.mods.toml} (62%) diff --git a/.gitignore b/.gitignore index ca672db3..d1ab1f60 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,5 @@ logs # other eclipse run -.vscode \ No newline at end of file +.vscode +runs diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java index 0538d1ef..7f679dfb 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java @@ -79,6 +79,9 @@ private static int status(CommandSourceStack source) { source.getServer().getAllLevels().forEach(level -> { if (level != null) { + //[Dev: Current status for ServerLevel[world]: NO SAVE !!] x3 + //level.dimension().toString() - ResourceKey[minecraft:dimension / minecraft:overworld] + //level.toSting() - ServerLevel[New World] source.sendSuccess(() -> Component.literal("Current status for " + level.toString() + ": " + (!level.noSave ? "NO SAVE !!" : "Autosave")), true); } }); diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java index de98f4c2..bfb77055 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java @@ -5,14 +5,21 @@ import dev.ftb.mods.ftbbackups.net.FTBBackupsNetHandler; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; +import net.neoforged.bus.api.EventPriority; import net.neoforged.bus.api.IEventBus; import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.ModContainer; import net.neoforged.fml.ModLoadingContext; import net.neoforged.fml.common.Mod; +import net.neoforged.fml.config.IConfigSpec; +import net.neoforged.fml.config.ModConfig; import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; +import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.event.RegisterCommandsEvent; import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import net.neoforged.neoforge.event.server.ServerAboutToStartEvent; import net.neoforged.neoforge.event.server.ServerStartedEvent; +import net.neoforged.neoforge.event.server.ServerStartingEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; import net.neoforged.neoforge.event.tick.ServerTickEvent; import org.apache.logging.log4j.LogManager; @@ -24,33 +31,35 @@ public class FTBBackups public static final String MOD_ID = "ftbbackups"; public static final Logger LOGGER = LogManager.getLogger("FTB Utilities Backups"); - public FTBBackups(IEventBus eventBus) + public FTBBackups(IEventBus eventBus, ModContainer container) { eventBus.addListener(this::clientSetup); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverAboutToStart); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::registerCommands); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverStopping); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedIn); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedOut); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::worldTick); // FTBBackupsNetHandler.init(); - FTBBackupsConfig.register(eventBus); + FTBBackupsConfig.register(eventBus, container); //ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true)); } - private void clientSetup(FMLClientSetupEvent event) { FTBBackupsClient.init(); } - @SubscribeEvent - public static void serverAboutToStart(ServerStartedEvent event) + public void serverAboutToStart(ServerAboutToStartEvent event) { Backups.INSTANCE.init(event.getServer()); } - @SubscribeEvent - public static void registerCommands(RegisterCommandsEvent event) + public void registerCommands(RegisterCommandsEvent event) { BackupCommands.register(event.getDispatcher()); } - @SubscribeEvent - public static void serverStopping(ServerStoppingEvent event) + public void serverStopping(ServerStoppingEvent event) { if (FTBBackupsConfig.forceOnShutdown) { @@ -58,8 +67,7 @@ public static void serverStopping(ServerStoppingEvent event) } } - @SubscribeEvent - public static void playerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) + public void playerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { if (event.getEntity() instanceof ServerPlayer) { @@ -68,8 +76,7 @@ public static void playerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) } } - @SubscribeEvent - public static void playerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) + public void playerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) { if (event.getEntity() instanceof ServerPlayer) { @@ -77,8 +84,7 @@ public static void playerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) } } - @SubscribeEvent - public static void worldTick(ServerTickEvent event) { + public void worldTick(ServerTickEvent.Post event) { /*if (event.phase != ServerTickEvent.PostTickEvent.Phase.START && !event.side.isClient()) { Backups.INSTANCE.tick(event.getServer(), System.currentTimeMillis()); }*/ diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java index 5e491def..b4ad4142 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java @@ -2,6 +2,7 @@ +import net.neoforged.fml.ModContainer; import org.apache.commons.lang3.tuple.Pair; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.ModLoadingContext; @@ -32,15 +33,13 @@ public class FTBBackupsConfig private static Pair common; - public static void register(IEventBus eventBus) + public static void register(IEventBus eventBus, ModContainer container) { eventBus.addListener(FTBBackupsConfig::reload); common = new ModConfigSpec.Builder().configure(CommonConfig::new); - ModLoadingContext modLoadingContext = ModLoadingContext.get(); - //TODO: Register Configs?? - /*modLoadingContext.registerConfig(ModConfig.Type.COMMON, common.getRight());*/ + container.registerConfig(ModConfig.Type.COMMON, common.getRight()); } public static void reload(ModConfigEvent event) diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/neoforge.mods.toml similarity index 62% rename from src/main/resources/META-INF/mods.toml rename to src/main/resources/META-INF/neoforge.mods.toml index a982f14d..95348a6f 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/neoforge.mods.toml @@ -1,10 +1,10 @@ modLoader = "javafml" -loaderVersion = "[43,)" +loaderVersion = "[1,)" license = "All Rights Reserved" [[mods]] modId = "ftbbackups" -version = "${file.jarVersion}" +version = "${version}" displayName = "FTB Backups" displayURL = "https://www.curseforge.com/minecraft/mc-mods/ftb-backups" logoFile = "logo.png" @@ -13,15 +13,15 @@ description = ''' Creates backups of your world''' [[dependencies.ftbbackups]] -modId="forge" +modId="neoforge" mandatory = true -versionRange = "[43,)" +versionRange = "[21,)" ordering = "NONE" side="BOTH" -[[dependencies.ftbbackups]] -modId="minecraft" -mandatory = true -versionRange = "[1.19.2,)" -ordering = "NONE" -side="BOTH" +#[[dependencies.ftbbackups]] +#modId="minecraft" +#mandatory = true +#versionRange = "[1.21,)" +#ordering = "NONE" +#side="BOTH" From fc61e1f7480662ee9378575ef4d98a54b24e4019 Mon Sep 17 00:00:00 2001 From: manmaed Date: Fri, 6 Sep 2024 12:05:58 +0100 Subject: [PATCH 09/42] Work flow and remove funding --- .github/FUNDING.yml | 1 - .github/workflows/build.yml | 44 ------------------------------------- 2 files changed, 45 deletions(-) delete mode 100644 .github/FUNDING.yml delete mode 100644 .github/workflows/build.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 0c6aa437..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -github: LatvianModder diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 7ab381a6..00000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Java CI - -on: - push: - branches: [ main, dev ] - workflow_dispatch: - inputs: - norelease: - description: 'Do not publish' - required: true - default: 'false' - -jobs: - build: - runs-on: ubuntu-latest - if: | - !contains(github.event.head_commit.message, '[ciskip]') - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 30 # Gets the last 30 commits so the changelog might work - - name: Set up JDK 8 - uses: actions/setup-java@v2 - with: - distribution: 'adopt' - java-version: '8' - cache: 'gradle' - - name: Build with Gradle - env: - FTB_MAVEN_TOKEN: ${{ secrets.FTB_MAVEN_TOKEN}} - SAPS_TOKEN: ${{ secrets.SAPS_TOKEN }} - run: | - chmod +x ./gradlew - ./gradlew build --stacktrace --no-daemon - - name: Release to CurseForge - if: | - github.ref == 'refs/heads/main' && !contains(github.event.head_commit.message, '[norelease]') && github.event.inputs.norelease != 'true' - env: - GIT_COMMIT: ${{ github.event.after }} - GIT_PREVIOUS_COMMIT: ${{ github.event.before }} - CURSEFORGE_KEY: ${{ secrets.CURSEFORGE_KEY }} - run: | - chmod +x ./gradlew - ./gradlew build curseforge --stacktrace --no-daemon From de09577e4d3b23c68b07389f6e153fd19b36b9de Mon Sep 17 00:00:00 2001 From: manmaed Date: Fri, 6 Sep 2024 12:11:26 +0100 Subject: [PATCH 10/42] use project id --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4ef6396e..a35ea7e2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# FTB Backups [![](http://cf.way2muchnoise.eu/ftb-backups-forge.svg) ![](https://cf.way2muchnoise.eu/packs/ftb-backups-forge.svg) ![](http://cf.way2muchnoise.eu/versions/ftb-backups-forge.svg)](https://www.curseforge.com/minecraft/mc-mods/ftb-backups-forge) +# FTB Backups [![](http://cf.way2muchnoise.eu/314904.svg) ![](http://cf.way2muchnoise.eu/versions/314904.svg)](https://www.curseforge.com/minecraft/mc-mods/ftb-backups-forge) Use https://github.com/FTBTeam/FTB-Mods-Issues for any mod issues \ No newline at end of file From dc115475aa5d85f2a4ad3130cd7241e6440aeca2 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Tue, 18 Mar 2025 14:11:47 +0000 Subject: [PATCH 11/42] chore: updated networking to 1.21 --- .../dev/ftb/mods/ftbbackups/FTBBackups.java | 27 ++++++------ .../ftbbackups/net/BackupProgressPacket.java | 43 ++++++++----------- .../ftbbackups/net/FTBBackupsNetHandler.java | 30 ++++--------- 3 files changed, 41 insertions(+), 59 deletions(-) diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java index bfb77055..998a6152 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java @@ -1,25 +1,20 @@ package dev.ftb.mods.ftbbackups; - -import dev.ftb.mods.ftbbackups.net.BackupProgressPacket; import dev.ftb.mods.ftbbackups.net.FTBBackupsNetHandler; import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; +import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.EventPriority; import net.neoforged.bus.api.IEventBus; -import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.ModContainer; -import net.neoforged.fml.ModLoadingContext; import net.neoforged.fml.common.Mod; -import net.neoforged.fml.config.IConfigSpec; -import net.neoforged.fml.config.ModConfig; import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; +import net.neoforged.fml.loading.FMLEnvironment; import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.event.RegisterCommandsEvent; import net.neoforged.neoforge.event.entity.player.PlayerEvent; import net.neoforged.neoforge.event.server.ServerAboutToStartEvent; -import net.neoforged.neoforge.event.server.ServerStartedEvent; -import net.neoforged.neoforge.event.server.ServerStartingEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; import net.neoforged.neoforge.event.tick.ServerTickEvent; import org.apache.logging.log4j.LogManager; @@ -29,21 +24,29 @@ public class FTBBackups { public static final String MOD_ID = "ftbbackups"; - public static final Logger LOGGER = LogManager.getLogger("FTB Utilities Backups"); + public static final Logger LOGGER = LogManager.getLogger("FTB Backups 3"); public FTBBackups(IEventBus eventBus, ModContainer container) { - eventBus.addListener(this::clientSetup); + if (FMLEnvironment.dist == Dist.CLIENT) { + eventBus.addListener(this::clientSetup); + } + eventBus.addListener(FTBBackupsNetHandler::init); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverAboutToStart); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::registerCommands); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverStopping); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedIn); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedOut); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::worldTick); -// FTBBackupsNetHandler.init(); + FTBBackupsConfig.register(eventBus, container); - //ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true)); } + + public static ResourceLocation id(String path) { + return ResourceLocation.fromNamespaceAndPath(MOD_ID, path); + } + private void clientSetup(FMLClientSetupEvent event) { FTBBackupsClient.init(); diff --git a/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java b/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java index 95ab08f7..fc5b78af 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java @@ -1,37 +1,28 @@ package dev.ftb.mods.ftbbackups.net; +import dev.ftb.mods.ftbbackups.FTBBackups; import dev.ftb.mods.ftbbackups.FTBBackupsClient; 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.neoforged.neoforge.network.handling.IPayloadContext; -/** - * @author LatvianModder - */ -public class BackupProgressPacket -{ - private int current, total; +public record BackupProgressPacket(int current, int total) implements CustomPacketPayload { + public static final Type TYPE = new Type<>(FTBBackups.id("backup_progress")); - public BackupProgressPacket(int c, int t) - { - current = c; - total = t; - } + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, BackupProgressPacket::current, + ByteBufCodecs.VAR_INT, BackupProgressPacket::total, + BackupProgressPacket::new + ); - public BackupProgressPacket(FriendlyByteBuf buf) - { - total = buf.readVarInt(); - current = buf.readVarInt(); + @Override + public Type type() { + return TYPE; } - public void write(FriendlyByteBuf buf) - { - buf.writeVarInt(total); - buf.writeVarInt(current); + public static void handler(BackupProgressPacket message, IPayloadContext context) { + FTBBackupsClient.setFiles(message.current, message.total); } - - //TODO This - /*public void handle(Supplier context) - { - context.get().enqueueWork(() -> FTBBackupsClient.setFiles(current, total)); - context.get().setPacketHandled(true); - }*/ } \ No newline at end of file diff --git a/src/main/java/dev/ftb/mods/ftbbackups/net/FTBBackupsNetHandler.java b/src/main/java/dev/ftb/mods/ftbbackups/net/FTBBackupsNetHandler.java index 14fccabf..af12b964 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/net/FTBBackupsNetHandler.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/net/FTBBackupsNetHandler.java @@ -1,28 +1,16 @@ package dev.ftb.mods.ftbbackups.net; import dev.ftb.mods.ftbbackups.FTBBackups; -import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; +import net.neoforged.neoforge.network.registration.PayloadRegistrar; -import java.util.function.Predicate; +public class FTBBackupsNetHandler { + private static final String NETWORK_VERSION = "1.0"; -public class FTBBackupsNetHandler -{ + public static void init(final RegisterPayloadHandlersEvent event) { + final PayloadRegistrar registrar = event.registrar(FTBBackups.MOD_ID) + .versioned(NETWORK_VERSION); - //TODO NetHandler - /*public static SimpleChannel MAIN; - private static final String MAIN_VERSION = "1"; - - public static void init() - { - Predicate validator = v -> MAIN_VERSION.equals(v) || NetworkRegistry.ABSENT.equals(v) || NetworkRegistry.ACCEPTVANILLA.equals(v); - - MAIN = NetworkRegistry.ChannelBuilder - .named(new ResourceLocation(FTBBackups.MOD_ID, "main")) - .clientAcceptedVersions(validator) - .serverAcceptedVersions(validator) - .networkProtocolVersion(() -> MAIN_VERSION) - .simpleChannel(); - - MAIN.registerMessage(1, BackupProgressPacket.class, BackupProgressPacket::write, BackupProgressPacket::new, BackupProgressPacket::handle); - }*/ + registrar.playToClient(BackupProgressPacket.TYPE, BackupProgressPacket.STREAM_CODEC, BackupProgressPacket::handler); + } } \ No newline at end of file From c49cee31264795226333304360a3d006a39811ab Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Tue, 18 Mar 2025 14:21:32 +0000 Subject: [PATCH 12/42] chore: code reformat, removed author headers --- .../java/dev/ftb/mods/ftbbackups/Backup.java | 110 ++- .../ftb/mods/ftbbackups/BackupCommands.java | 154 ++-- .../dev/ftb/mods/ftbbackups/BackupEvent.java | 73 +- .../dev/ftb/mods/ftbbackups/BackupStatus.java | 33 +- .../dev/ftb/mods/ftbbackups/BackupUtils.java | 592 ++++++------- .../java/dev/ftb/mods/ftbbackups/Backups.java | 811 ++++++++---------- .../dev/ftb/mods/ftbbackups/FTBBackups.java | 110 ++- .../ftb/mods/ftbbackups/FTBBackupsClient.java | 57 +- .../ftb/mods/ftbbackups/FTBBackupsConfig.java | 342 ++++---- .../ftbbackups/net/BackupProgressPacket.java | 31 +- .../resources/META-INF/neoforge.mods.toml | 4 +- .../assets/ftbbackups/lang/de_de.json | 64 +- .../assets/ftbbackups/lang/en_us.json | 68 +- .../assets/ftbbackups/lang/pl_pl.json | 68 +- .../assets/ftbbackups/lang/ru_ru.json | 68 +- .../assets/ftbbackups/lang/zh_cn.json | 46 +- src/main/resources/pack.mcmeta | 8 +- 17 files changed, 1192 insertions(+), 1447 deletions(-) diff --git a/src/main/java/dev/ftb/mods/ftbbackups/Backup.java b/src/main/java/dev/ftb/mods/ftbbackups/Backup.java index f2380500..24410a36 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/Backup.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/Backup.java @@ -4,65 +4,53 @@ import java.io.File; -/** - * @author LatvianModder - */ -public class Backup implements Comparable -{ - public final long time; - public final String fileId; - public final int index; - public final boolean success; - public final long size; - - public Backup(long t, String f, int i, boolean s, long fs) - { - time = t; - fileId = f; - index = i; - success = s; - size = fs; - } - - public Backup(JsonObject o) - { - this(o.get("time").getAsLong(), o.get("file").getAsString(), o.get("index").getAsInt(), o.get("success").getAsBoolean(), o.get("size").getAsLong()); - } - - public JsonObject toJsonObject() - { - JsonObject o = new JsonObject(); - o.addProperty("time", time); - o.addProperty("file", fileId); - o.addProperty("index", index); - o.addProperty("success", success); - o.addProperty("size", size); - return o; - } - - public int hashCode() - { - return Long.hashCode(time); - } - - public String toString() - { - return fileId; - } - - public boolean equals(Object o) - { - return o == this || (o instanceof Backup && ((Backup) o).time == time); - } - - public File getFile() - { - return new File(Backups.INSTANCE.backupsFolder, fileId); - } - - @Override - public int compareTo(Backup o) - { - return Long.compare(time, o.time); - } +public class Backup implements Comparable { + public final long time; + public final String fileId; + public final int index; + public final boolean success; + public final long size; + + public Backup(long t, String f, int i, boolean s, long fs) { + time = t; + fileId = f; + index = i; + success = s; + size = fs; + } + + public Backup(JsonObject o) { + this(o.get("time").getAsLong(), o.get("file").getAsString(), o.get("index").getAsInt(), o.get("success").getAsBoolean(), o.get("size").getAsLong()); + } + + public JsonObject toJsonObject() { + JsonObject o = new JsonObject(); + o.addProperty("time", time); + o.addProperty("file", fileId); + o.addProperty("index", index); + o.addProperty("success", success); + o.addProperty("size", size); + return o; + } + + public int hashCode() { + return Long.hashCode(time); + } + + public String toString() { + return fileId; + } + + public boolean equals(Object o) { + return o == this || (o instanceof Backup && ((Backup) o).time == time); + } + + public File getFile() { + return new File(Backups.INSTANCE.backupsFolder, fileId); + } + + @Override + public int compareTo(Backup o) { + return Long.compare(time, o.time); + } } \ No newline at end of file diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java index 7f679dfb..3d73429b 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java @@ -8,100 +8,86 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.level.storage.LevelResource; -/** - * @author LatvianModder - */ -public class BackupCommands -{ - public static void register(CommandDispatcher dispatcher) - { - dispatcher.register(Commands.literal("ftbbackups") - .then(Commands.literal("time") - .executes(ctx -> time(ctx.getSource())) - ) - .then(Commands.literal("start") - .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) - .then(Commands.argument("name", StringArgumentType.word()) - .executes(ctx -> start(ctx.getSource(), StringArgumentType.getString(ctx, "name"))) - ) - .executes(ctx -> start(ctx.getSource(), "")) - ) - .then(Commands.literal("size") - .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) - .executes(ctx -> size(ctx.getSource())) - ) - .then(Commands.literal("status") - .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) - .executes(ctx -> status(ctx.getSource())) - ) - .then(Commands.literal("reset") - .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) - .executes(ctx -> resetState(ctx.getSource())) - ) - ); - } +public class BackupCommands { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("ftbbackups") + .then(Commands.literal("time") + .executes(ctx -> time(ctx.getSource())) + ) + .then(Commands.literal("start") + .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) + .then(Commands.argument("name", StringArgumentType.word()) + .executes(ctx -> start(ctx.getSource(), StringArgumentType.getString(ctx, "name"))) + ) + .executes(ctx -> start(ctx.getSource(), "")) + ) + .then(Commands.literal("size") + .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) + .executes(ctx -> size(ctx.getSource())) + ) + .then(Commands.literal("status") + .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) + .executes(ctx -> status(ctx.getSource())) + ) + .then(Commands.literal("reset") + .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) + .executes(ctx -> resetState(ctx.getSource())) + ) + ); + } - private static int time(CommandSourceStack source) - { - source.sendSuccess(() -> Component.translatable("ftbbackups.lang.timer", BackupUtils.getTimeString(Backups.INSTANCE.nextBackup - System.currentTimeMillis())),true); - return 1; - } + private static int time(CommandSourceStack source) { + source.sendSuccess(() -> Component.translatable("ftbbackups.lang.timer", BackupUtils.getTimeString(Backups.INSTANCE.nextBackup - System.currentTimeMillis())), true); + return 1; + } - private static int start(CommandSourceStack source, String customName) - { - if (Backups.INSTANCE.run(source.getServer(), false, source.getDisplayName(), customName)) - { - for (ServerPlayer player : source.getServer().getPlayerList().getPlayers()) - { - player.sendSystemMessage(Component.translatable("ftbbackups.lang.manual_launch", source.getDisplayName())); - } - } - else - { - source.sendSuccess(() -> Component.translatable("ftbbackups.lang.already_running"), true); - } + private static int start(CommandSourceStack source, String customName) { + if (Backups.INSTANCE.run(source.getServer(), false, source.getDisplayName(), customName)) { + for (ServerPlayer player : source.getServer().getPlayerList().getPlayers()) { + player.sendSystemMessage(Component.translatable("ftbbackups.lang.manual_launch", source.getDisplayName())); + } + } else { + source.sendSuccess(() -> Component.translatable("ftbbackups.lang.already_running"), true); + } - return 1; - } + return 1; + } - private static int size(CommandSourceStack source) - { - long totalSize = Backups.INSTANCE.backups.stream().mapToLong(backup -> backup.size).sum(); + private static int size(CommandSourceStack source) { + long totalSize = Backups.INSTANCE.backups.stream().mapToLong(backup -> backup.size).sum(); source.sendSuccess(() -> Component.translatable("ftbbackups.lang.size.current", BackupUtils.getSizeString(source.getServer().getWorldPath(LevelResource.ROOT).toFile())), true); - source.sendSuccess(() -> Component.translatable("ftbbackups.lang.size.total", BackupUtils.getSizeString(totalSize)), true); - source.sendSuccess(() -> Component.translatable("ftbbackups.lang.size.available", BackupUtils.getSizeString(Math.min(FTBBackupsConfig.maxTotalSize, Backups.INSTANCE.backupsFolder.getFreeSpace()))), true); + source.sendSuccess(() -> Component.translatable("ftbbackups.lang.size.total", BackupUtils.getSizeString(totalSize)), true); + source.sendSuccess(() -> Component.translatable("ftbbackups.lang.size.available", BackupUtils.getSizeString(Math.min(FTBBackupsConfig.maxTotalSize, Backups.INSTANCE.backupsFolder.getFreeSpace()))), true); - return 1; - } + return 1; + } - private static int status(CommandSourceStack source) - { - source.getServer().getAllLevels().forEach(level -> { - if (level != null) { - //[Dev: Current status for ServerLevel[world]: NO SAVE !!] x3 - //level.dimension().toString() - ResourceKey[minecraft:dimension / minecraft:overworld] - //level.toSting() - ServerLevel[New World] - source.sendSuccess(() -> Component.literal("Current status for " + level.toString() + ": " + (!level.noSave ? "NO SAVE !!" : "Autosave")), true); - } - }); + private static int status(CommandSourceStack source) { + source.getServer().getAllLevels().forEach(level -> { + if (level != null) { + //[Dev: Current status for ServerLevel[world]: NO SAVE !!] x3 + //level.dimension().toString() - ResourceKey[minecraft:dimension / minecraft:overworld] + //level.toSting() - ServerLevel[New World] + source.sendSuccess(() -> Component.literal("Current status for " + level.toString() + ": " + (!level.noSave ? "NO SAVE !!" : "Autosave")), true); + } + }); - return 1; - } + return 1; + } - private static int resetState(CommandSourceStack source) - { - source.getServer().getAllLevels().forEach(level -> { - if (level != null) { - source.sendSuccess(() -> Component.literal("Reseting state + saving for " + level.toString()), true); - level.noSave = false; - level.save(null, true, true); - } - }); + private static int resetState(CommandSourceStack source) { + source.getServer().getAllLevels().forEach(level -> { + if (level != null) { + source.sendSuccess(() -> Component.literal("Reseting state + saving for " + level.toString()), true); + level.noSave = false; + level.save(null, true, true); + } + }); - source.sendSuccess(() -> Component.literal("Reseting state + saving for players"), true); - source.getServer().getPlayerList().saveAll(); + source.sendSuccess(() -> Component.literal("Reseting state + saving for players"), true); + source.getServer().getPlayerList().saveAll(); - return 1; - } + return 1; + } } \ No newline at end of file diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupEvent.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupEvent.java index d7b14455..867edb51 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupEvent.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupEvent.java @@ -7,46 +7,35 @@ import java.io.File; import java.util.function.Consumer; -/** - * @author LatvianModder - */ -public class BackupEvent extends Event -{ - public static class Pre extends BackupEvent - { - private final Consumer callback; - - public Pre(Consumer c) - { - callback = c; - } - - public void add(File file) - { - callback.accept(file); - } - } - - public static class Post extends BackupEvent - { - private final Backup backup; - private final Exception error; - - public Post(Backup b, @Nullable Exception ex) - { - backup = b; - error = ex; - } - - public Backup getBackup() - { - return backup; - } - - @Nullable - public Exception getError() - { - return error; - } - } +public class BackupEvent extends Event { + public static class Pre extends BackupEvent { + private final Consumer callback; + + public Pre(Consumer c) { + callback = c; + } + + public void add(File file) { + callback.accept(file); + } + } + + public static class Post extends BackupEvent { + private final Backup backup; + private final Exception error; + + public Post(Backup b, @Nullable Exception ex) { + backup = b; + error = ex; + } + + public Backup getBackup() { + return backup; + } + + @Nullable + public Exception getError() { + return error; + } + } } \ No newline at end of file diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupStatus.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupStatus.java index 59fbdfbb..7bbe0bf3 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupStatus.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupStatus.java @@ -1,26 +1,19 @@ package dev.ftb.mods.ftbbackups; -/** - * @author LatvianModder - */ -public enum BackupStatus -{ - NONE, - RUNNING, - DONE; +public enum BackupStatus { + NONE, + RUNNING, + DONE; - public boolean isRunning() - { - return this == RUNNING; - } + public boolean isRunning() { + return this == RUNNING; + } - public boolean isDone() - { - return this == DONE; - } + public boolean isDone() { + return this == DONE; + } - public boolean isRunningOrDone() - { - return isRunning() || isDone(); - } + public boolean isRunningOrDone() { + return isRunning() || isDone(); + } } \ No newline at end of file diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java index 61867970..de6798a3 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java @@ -10,348 +10,262 @@ import com.google.gson.stream.JsonWriter; import javax.annotation.Nullable; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.Writer; +import java.io.*; import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -/** - * @author LatvianModder - */ -public class BackupUtils -{ - public static final long KB = 1024L; - public static final long MB = KB * 1024L; - public static final long GB = MB * 1024L; - public static final long TB = GB * 1024L; - - public static final double KB_D = 1024D; - public static final double MB_D = KB_D * 1024D; - public static final double GB_D = MB_D * 1024D; - public static final double TB_D = GB_D * 1024D; - - public static String getTimeString(long millis) - { - boolean neg = false; - if (millis < 0L) - { - neg = true; - millis = -millis; - } - - StringBuilder sb = new StringBuilder(); - - if (millis < 1000L) - { - if (neg) - { - sb.append('-'); - } - - sb.append(millis); - sb.append('m'); - sb.append('s'); - return sb.toString(); - } - - long secs = millis / 1000L; - - if (neg) - { - sb.append('-'); - } - - long h = (secs / 3600L) % 24; - long m = (secs / 60L) % 60L; - long s = secs % 60L; - - if (secs >= 86400L) - { - sb.append(secs / 86400L); - sb.append('d'); - sb.append(' '); - } - - if (h > 0 || secs >= 86400L) - { - if (h < 10) - { - sb.append('0'); - } - sb.append(h); - //sb.append("h "); - sb.append(':'); - } - - if (m < 10) - { - sb.append('0'); - } - sb.append(m); - //sb.append("m "); - sb.append(':'); - if (s < 10) - { - sb.append('0'); - } - sb.append(s); - //sb.append('s'); - - return sb.toString(); - } - - public static long getSize(File file) - { - if (!file.exists()) - { - return 0L; - } - else if (file.isFile()) - { - return file.length(); - } - else if (file.isDirectory()) - { - long length = 0L; - File[] f1 = file.listFiles(); - if (f1 != null && f1.length > 0) - { - for (File aF1 : f1) - { - length += getSize(aF1); - } - } - return length; - } - return 0L; - } - - public static String getSizeString(double b) - { - if (b >= TB_D) - { - return String.format("%.1fTB", b / TB_D); - } - else if (b >= GB_D) - { - return String.format("%.1fGB", b / GB_D); - } - else if (b >= MB_D) - { - return String.format("%.1fMB", b / MB_D); - } - else if (b >= KB_D) - { - return String.format("%.1fKB", b / KB_D); - } - - return ((long) b) + "B"; - } - - public static String getSizeString(File file) - { - return getSizeString(getSize(file)); - } - - public static File newFile(File file) - { - if (!file.exists()) - { - try - { - File parent = file.getParentFile(); - if (!parent.exists()) - { - parent.mkdirs(); - } - file.createNewFile(); - } - catch (Exception e) - { - e.printStackTrace(); - } - } - - return file; - } - - public static List listTree(File file) - { - List l = new ArrayList<>(); - listTree0(l, file); - return l; - } - - public static void listTree0(List list, File file) - { - if (file.isDirectory()) - { - File[] fl = file.listFiles(); - - if (fl != null && fl.length > 0) - { - for (File aFl : fl) - { - listTree0(list, aFl); - } - } - } - else if (file.isFile()) - { - list.add(file); - } - } - - public static void copyFile(File src, File dst) throws Exception - { - if (src.exists() && !src.equals(dst)) - { - if (src.isDirectory() && dst.isDirectory()) - { - for (File f : listTree(src)) - { - File dst1 = new File(dst.getAbsolutePath() + File.separatorChar + (f.getAbsolutePath().replace(src.getAbsolutePath(), ""))); - copyFile(f, dst1); - } - } - else - { - dst = newFile(dst); - - try (FileInputStream fis = new FileInputStream(src); - FileOutputStream fos = new FileOutputStream(dst); - FileChannel srcC = fis.getChannel(); - FileChannel dstC = fos.getChannel()) - { - dstC.transferFrom(srcC, 0L, srcC.size()); - } - } - } - } - - public static boolean delete(File file) - { - if (!file.exists()) - { - return false; - } - else if (file.isFile()) - { - return file.delete(); - } - - String[] files = file.list(); - - if (files != null) - { - for (String s : files) - { - delete(new File(file, s)); - } - } - - return file.delete(); - } - - public static void toJson(Writer writer, @Nullable JsonElement element, boolean prettyPrinting) - { - if (element == null || element.isJsonNull()) - { - try - { - writer.write("null"); - } - catch (Exception ex) - { - ex.printStackTrace(); - } - - return; - } - - JsonWriter jsonWriter = new JsonWriter(writer); - jsonWriter.setLenient(true); - jsonWriter.setHtmlSafe(false); - jsonWriter.setSerializeNulls(true); - - if (prettyPrinting) - { - jsonWriter.setIndent("\t"); - } - - try - { - Streams.write(element, jsonWriter); - } - catch (Exception ex) - { - throw new JsonIOException(ex); - } - } - - public static void toJson(File file, @Nullable JsonElement element, boolean prettyPrinting) - { - try (OutputStreamWriter output = new OutputStreamWriter(new FileOutputStream(newFile(file)), StandardCharsets.UTF_8); - BufferedWriter writer = new BufferedWriter(output)) - { - toJson(writer, element, prettyPrinting); - } - catch (Exception ex) - { - ex.printStackTrace(); - } - } - - public static JsonElement readJson(File file) - { - if (!file.exists()) - { - return JsonNull.INSTANCE; - } - - try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) - { - JsonReader jsonReader = new JsonReader(reader); - jsonReader.setLenient(true); - JsonElement element = Streams.parse(jsonReader); - - if (!element.isJsonNull() && jsonReader.peek() != JsonToken.END_DOCUMENT) - { - throw new JsonSyntaxException("Did not consume the entire document."); - } - - return element; - } - catch (Exception ex) - { - return null; - } - } - - public static String removeAllWhitespace(String s) - { - char[] chars = new char[s.length()]; - int j = 0; - - for (int i = 0; i < chars.length; i++) - { - char c = s.charAt(i); - - if (c > ' ') - { - chars[j] = c; - j++; - } - } - - return new String(chars, 0, j); - } +public class BackupUtils { + public static final long KB = 1024L; + public static final long MB = KB * 1024L; + public static final long GB = MB * 1024L; + public static final long TB = GB * 1024L; + + public static final double KB_D = 1024D; + public static final double MB_D = KB_D * 1024D; + public static final double GB_D = MB_D * 1024D; + public static final double TB_D = GB_D * 1024D; + + public static String getTimeString(long millis) { + boolean neg = false; + if (millis < 0L) { + neg = true; + millis = -millis; + } + + StringBuilder sb = new StringBuilder(); + + if (millis < 1000L) { + if (neg) { + sb.append('-'); + } + + sb.append(millis); + sb.append('m'); + sb.append('s'); + return sb.toString(); + } + + long secs = millis / 1000L; + + if (neg) { + sb.append('-'); + } + + long h = (secs / 3600L) % 24; + long m = (secs / 60L) % 60L; + long s = secs % 60L; + + if (secs >= 86400L) { + sb.append(secs / 86400L); + sb.append('d'); + sb.append(' '); + } + + if (h > 0 || secs >= 86400L) { + if (h < 10) { + sb.append('0'); + } + sb.append(h); + //sb.append("h "); + sb.append(':'); + } + + if (m < 10) { + sb.append('0'); + } + sb.append(m); + //sb.append("m "); + sb.append(':'); + if (s < 10) { + sb.append('0'); + } + sb.append(s); + //sb.append('s'); + + return sb.toString(); + } + + public static long getSize(File file) { + if (!file.exists()) { + return 0L; + } else if (file.isFile()) { + return file.length(); + } else if (file.isDirectory()) { + long length = 0L; + File[] f1 = file.listFiles(); + if (f1 != null && f1.length > 0) { + for (File aF1 : f1) { + length += getSize(aF1); + } + } + return length; + } + return 0L; + } + + public static String getSizeString(double b) { + if (b >= TB_D) { + return String.format("%.1fTB", b / TB_D); + } else if (b >= GB_D) { + return String.format("%.1fGB", b / GB_D); + } else if (b >= MB_D) { + return String.format("%.1fMB", b / MB_D); + } else if (b >= KB_D) { + return String.format("%.1fKB", b / KB_D); + } + + return ((long) b) + "B"; + } + + public static String getSizeString(File file) { + return getSizeString(getSize(file)); + } + + public static File newFile(File file) { + if (!file.exists()) { + try { + File parent = file.getParentFile(); + if (!parent.exists()) { + parent.mkdirs(); + } + file.createNewFile(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + return file; + } + + public static List listTree(File file) { + List l = new ArrayList<>(); + listTree0(l, file); + return l; + } + + public static void listTree0(List list, File file) { + if (file.isDirectory()) { + File[] fl = file.listFiles(); + + if (fl != null && fl.length > 0) { + for (File aFl : fl) { + listTree0(list, aFl); + } + } + } else if (file.isFile()) { + list.add(file); + } + } + + public static void copyFile(File src, File dst) throws Exception { + if (src.exists() && !src.equals(dst)) { + if (src.isDirectory() && dst.isDirectory()) { + for (File f : listTree(src)) { + File dst1 = new File(dst.getAbsolutePath() + File.separatorChar + (f.getAbsolutePath().replace(src.getAbsolutePath(), ""))); + copyFile(f, dst1); + } + } else { + dst = newFile(dst); + + try (FileInputStream fis = new FileInputStream(src); + FileOutputStream fos = new FileOutputStream(dst); + FileChannel srcC = fis.getChannel(); + FileChannel dstC = fos.getChannel()) { + dstC.transferFrom(srcC, 0L, srcC.size()); + } + } + } + } + + public static boolean delete(File file) { + if (!file.exists()) { + return false; + } else if (file.isFile()) { + return file.delete(); + } + + String[] files = file.list(); + + if (files != null) { + for (String s : files) { + delete(new File(file, s)); + } + } + + return file.delete(); + } + + public static void toJson(Writer writer, @Nullable JsonElement element, boolean prettyPrinting) { + if (element == null || element.isJsonNull()) { + try { + writer.write("null"); + } catch (Exception ex) { + ex.printStackTrace(); + } + + return; + } + + JsonWriter jsonWriter = new JsonWriter(writer); + jsonWriter.setLenient(true); + jsonWriter.setHtmlSafe(false); + jsonWriter.setSerializeNulls(true); + + if (prettyPrinting) { + jsonWriter.setIndent("\t"); + } + + try { + Streams.write(element, jsonWriter); + } catch (Exception ex) { + throw new JsonIOException(ex); + } + } + + public static void toJson(File file, @Nullable JsonElement element, boolean prettyPrinting) { + try (OutputStreamWriter output = new OutputStreamWriter(new FileOutputStream(newFile(file)), StandardCharsets.UTF_8); + BufferedWriter writer = new BufferedWriter(output)) { + toJson(writer, element, prettyPrinting); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public static JsonElement readJson(File file) { + if (!file.exists()) { + return JsonNull.INSTANCE; + } + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) { + JsonReader jsonReader = new JsonReader(reader); + jsonReader.setLenient(true); + JsonElement element = Streams.parse(jsonReader); + + if (!element.isJsonNull() && jsonReader.peek() != JsonToken.END_DOCUMENT) { + throw new JsonSyntaxException("Did not consume the entire document."); + } + + return element; + } catch (Exception ex) { + return null; + } + } + + public static String removeAllWhitespace(String s) { + char[] chars = new char[s.length()]; + int j = 0; + + for (int i = 0; i < chars.length; i++) { + char c = s.charAt(i); + + if (c > ' ') { + chars[j] = c; + j++; + } + } + + return new String(chars, 0, j); + } } \ No newline at end of file diff --git a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java index 40296bc9..15081048 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java @@ -1,12 +1,11 @@ package dev.ftb.mods.ftbbackups; -import dev.ftb.mods.ftbbackups.net.BackupProgressPacket; -import dev.ftb.mods.ftbbackups.net.FTBBackupsNetHandler; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import net.minecraft.ChatFormatting; -import net.minecraft.network.chat.*; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TextColor; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.storage.LevelResource; @@ -17,454 +16,368 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.Consumer; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -public enum Backups -{ - INSTANCE; - - public final List backups = new ArrayList<>(); - public File backupsFolder; - public long nextBackup = -1L; - public BackupStatus doingBackup = BackupStatus.NONE; - public boolean printFiles = false; - - public int currentFile = 0; - public int totalFiles = 0; - private String currentFileName = ""; - public boolean hadPlayersOnline = false; - - public void init(MinecraftServer server) - { - Path dataDir = server.getServerDirectory(); - backupsFolder = FTBBackupsConfig.folder.trim().isEmpty() ? FMLPaths.GAMEDIR.get().resolve("backups").toFile() : new File(FTBBackupsConfig.folder); - - try - { - backupsFolder = backupsFolder.getCanonicalFile(); - } - catch (Exception ex) - { - } - - doingBackup = BackupStatus.NONE; - backups.clear(); - - File file = new File(dataDir.toFile(), "local/ftbutilities/backups.json"); - - if (!file.exists()) - { - File oldFile = new File(backupsFolder, "backups.json"); - - if (oldFile.exists()) - { - oldFile.renameTo(file); - } - } - - JsonElement element = BackupUtils.readJson(file); - - if (element != null && element.isJsonArray()) - { - try - { - for (JsonElement e : element.getAsJsonArray()) - { - JsonObject json = e.getAsJsonObject(); - - if (!json.has("size")) - { - json.addProperty("size", BackupUtils.getSize(new File(backupsFolder, json.get("file").getAsString()))); - } - - backups.add(new Backup(json)); - } - } - catch (Throwable ex) - { - ex.printStackTrace(); - } - } - - FTBBackups.LOGGER.info("Backups folder - " + backupsFolder.getAbsolutePath()); - nextBackup = System.currentTimeMillis() + FTBBackupsConfig.backupTimer; - } - - public void tick(MinecraftServer server, long now) - { - if (nextBackup > 0L && nextBackup <= now) - { - if (!FTBBackupsConfig.onlyIfPlayersOnline || hadPlayersOnline || !server.getPlayerList().getPlayers().isEmpty()) - { - hadPlayersOnline = false; - run(server, true, Component.translatable("Server"), ""); - } - } - - if (doingBackup.isDone()) - { - doingBackup = BackupStatus.NONE; - - for (ServerLevel world : server.getAllLevels()) - { - if (world != null) - { - world.noSave = false; - } - } - - if (!FTBBackupsConfig.silent) - { - //TODO: Fix FTBBackupsNetHandler.MAIN.send(PacketDistributor.ALL.noArg(), new BackupProgressPacket(0, 0)); - } - } - else if (doingBackup.isRunning() && printFiles) - { - if (currentFile == 0 || currentFile == totalFiles - 1) - { - FTBBackups.LOGGER.info("[" + currentFile + " | " + (int) ((currentFile / (double) totalFiles) * 100D) + "%]: " + currentFileName); - } - - if (!FTBBackupsConfig.silent) - { - //TODO: Fix FTBBackupsNetHandler.MAIN.send(PacketDistributor.ALL.noArg(), new BackupProgressPacket(currentFile, totalFiles)); - } - } - } - - public void notifyAll(MinecraftServer server, Component component, boolean error) - { - component = component.plainCopy(); - component.getStyle().withColor(TextColor.fromLegacyFormat(error ? ChatFormatting.DARK_RED : ChatFormatting.LIGHT_PURPLE)); - FTBBackups.LOGGER.info(component.getString()); - server.getPlayerList().broadcastSystemMessage(component, true); - } - - public boolean run(MinecraftServer server, boolean auto, Component name, String customName) - { - if (doingBackup.isRunningOrDone()) - { - return false; - } - - if (auto && !FTBBackupsConfig.auto) - { - return false; - } - - notifyAll(server, Component.translatable("ftbbackups.lang.start", name), false); - nextBackup = System.currentTimeMillis() + FTBBackupsConfig.backupTimer; - - for (ServerLevel world : server.getAllLevels()) - { - if (world != null) - { - world.noSave = true; - } - } - - doingBackup = BackupStatus.RUNNING; - server.getPlayerList().saveAll(); - - new Thread(() -> { - try - { - try - { - createBackup(server, customName); - } - catch (Exception ex) - { - ex.printStackTrace(); - notifyAll(server, Component.translatable("ftbbackups.lang.saving_failed"), true); - } - } - catch (Exception ex) - { - notifyAll(server, Component.translatable("ftbbackups.lang.saving_failed"), true); - ex.printStackTrace(); - } - - doingBackup = BackupStatus.DONE; - }).start(); - - return true; - } - - private void createBackup(MinecraftServer server, String customName) - { - File src = server.getWorldPath(LevelResource.ROOT).toFile(); - - try - { - src = src.getCanonicalFile(); - } - catch (Exception ex) - { - } - - Calendar time = Calendar.getInstance(); - File dstFile; - boolean success = false; - StringBuilder out = new StringBuilder(); - - if (customName.isEmpty()) - { - appendNum(out, time.get(Calendar.YEAR), '-'); - appendNum(out, time.get(Calendar.MONTH) + 1, '-'); - appendNum(out, time.get(Calendar.DAY_OF_MONTH), '-'); - appendNum(out, time.get(Calendar.HOUR_OF_DAY), '-'); - appendNum(out, time.get(Calendar.MINUTE), '-'); - appendNum(out, time.get(Calendar.SECOND), '\0'); - } - else - { - out.append(customName); - } - - Exception error = null; - long fileSize = 0L; - - try - { - LinkedHashMap fileMap = new LinkedHashMap<>(); - String mcdir = server.getServerDirectory().getFileSystem().toString(); - - Consumer consumer = file0 -> { - for (File file : BackupUtils.listTree(file0)) - { - String s1 = file.getAbsolutePath().replace(mcdir, ""); - - if (s1.startsWith(File.separator)) - { - s1 = s1.substring(File.separator.length()); - } - - fileMap.put(file, "_extra_" + File.separator + s1); - } - }; - - for (String s : FTBBackupsConfig.extraFiles) - { - consumer.accept(new File(s)); - } - - NeoForge.EVENT_BUS.post(new BackupEvent.Pre(consumer)); - - for (File file : BackupUtils.listTree(src)) - { - if (file.getName().equals("session.lock") || !file.canRead()) - { - continue; - } - - String filePath = file.getAbsolutePath(); - fileMap.put(file, src.getName() + File.separator + filePath.substring(src.getAbsolutePath().length() + 1)); - } - - for (Map.Entry entry : fileMap.entrySet()) - { - fileSize += BackupUtils.getSize(entry.getKey()); - } - - long totalSize = 0L; - - if (!backups.isEmpty()) - { - backups.sort(null); - - int backupsToKeep = FTBBackupsConfig.backupsToKeep - 1; - - if (backupsToKeep > 0 && backups.size() > backupsToKeep) - { - while (backups.size() > backupsToKeep) - { - Backup backup = backups.remove(0); - FTBBackups.LOGGER.info("Deleting old backup: " + backup.fileId); - BackupUtils.delete(backup.getFile()); - } - } - - for (Backup backup : backups) - { - totalSize += backup.size; - } - - if (fileSize > 0L) - { - long freeSpace = Math.min(FTBBackupsConfig.maxTotalSize, backupsFolder.getFreeSpace()); - - while (totalSize + fileSize > freeSpace && !backups.isEmpty()) - { - Backup backup = backups.remove(0); - totalSize -= backup.size; - FTBBackups.LOGGER.info("Deleting backup to free space: " + backup.fileId); - BackupUtils.delete(backup.getFile()); - } - } - } - - totalFiles = fileMap.size(); - FTBBackups.LOGGER.info("Backing up " + totalFiles + " files..."); - printFiles = true; - - if (FTBBackupsConfig.compressionLevel > 0) - { - out.append(".zip"); - dstFile = BackupUtils.newFile(new File(backupsFolder, out.toString())); - - long start = System.currentTimeMillis(); - - ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dstFile)); - zos.setLevel(FTBBackupsConfig.compressionLevel); - - byte[] buffer = new byte[FTBBackupsConfig.bufferSize]; - - FTBBackups.LOGGER.info("Compressing " + totalFiles + " files..."); - - currentFile = 0; - for (Map.Entry entry : fileMap.entrySet()) - { - try - { - ZipEntry ze = new ZipEntry(entry.getValue()); - currentFileName = entry.getValue(); - - zos.putNextEntry(ze); - FileInputStream fis = new FileInputStream(entry.getKey()); - - int len; - while ((len = fis.read(buffer)) > 0) - { - zos.write(buffer, 0, len); - } - zos.closeEntry(); - fis.close(); - } - catch (Exception ex) - { - FTBBackups.LOGGER.error("Failed to read file " + entry.getValue() + ": " + ex); - } - - currentFile++; - } - - zos.close(); - fileSize = BackupUtils.getSize(dstFile); - FTBBackups.LOGGER.info("Done compressing in " + BackupUtils.getTimeString(System.currentTimeMillis() - start) + " seconds (" + BackupUtils.getSizeString(fileSize) + ")!"); - } - else - { - dstFile = new File(backupsFolder, out.toString()); - dstFile.mkdirs(); - - currentFile = 0; - for (Map.Entry entry : fileMap.entrySet()) - { - try - { - File file = entry.getKey(); - currentFileName = entry.getValue(); - File dst1 = new File(dstFile, entry.getValue()); - BackupUtils.copyFile(file, dst1); - } - catch (Exception ex) - { - FTBBackups.LOGGER.error("Failed to copy file " + entry.getValue() + ": " + ex); - } - - currentFile++; - } - } - - FTBBackups.LOGGER.info("Created " + dstFile.getAbsolutePath() + " from " + src.getAbsolutePath()); - success = true; - } - catch (Exception ex) - { - if (!FTBBackupsConfig.silent) - { - String errorName = ex.getClass().getName(); - notifyAll(server, Component.translatable("ftbbackups.lang.fail", errorName), true); - } - - ex.printStackTrace(); - error = ex; - } - - printFiles = false; - - Backup backup = new Backup(time.getTimeInMillis(), out.toString().replace('\\', '/'), getLastIndex() + 1, success, fileSize); - backups.add(backup); - NeoForge.EVENT_BUS.post(new BackupEvent.Post(backup, error)); - - JsonArray array = new JsonArray(); - - for (Backup backup1 : backups) - { - array.add(backup1.toJsonObject()); - } - - BackupUtils.toJson(new File(server.getServerDirectory().toFile(), "local/ftbutilities/backups.json"), array, true); - - if (error == null && !FTBBackupsConfig.silent) - { - String timeString = BackupUtils.getTimeString(System.currentTimeMillis() - time.getTimeInMillis()); - Component component; - - if (FTBBackupsConfig.displayFileSize) - { - long totalSize = 0L; - - for (Backup backup1 : backups) - { - totalSize += backup1.size; - } - - String sizeB = BackupUtils.getSizeString(fileSize); - String sizeT = BackupUtils.getSizeString(totalSize); - String sizeString = sizeB.equals(sizeT) ? sizeB : (sizeB + " | " + sizeT); - component = Component.translatable("ftbbackups.lang.end_2", timeString, sizeString); - } - else - { - component = Component.translatable("ftbbackups.lang.end_1", timeString); - } - - component.getStyle().withColor(TextColor.fromLegacyFormat(ChatFormatting.LIGHT_PURPLE)); - server.getPlayerList().broadcastSystemMessage(component, true); - } - } - - private void appendNum(StringBuilder sb, int num, char c) - { - if (num < 10) - { - sb.append('0'); - } - sb.append(num); - if (c != '\0') - { - sb.append(c); - } - } - - private int getLastIndex() - { - int i = 0; - - for (Backup b : backups) - { - i = Math.max(i, b.index); - } - - return i; - } +public enum Backups { + INSTANCE; + + public final List backups = new ArrayList<>(); + public File backupsFolder; + public long nextBackup = -1L; + public BackupStatus doingBackup = BackupStatus.NONE; + public boolean printFiles = false; + + public int currentFile = 0; + public int totalFiles = 0; + public boolean hadPlayersOnline = false; + private String currentFileName = ""; + + public void init(MinecraftServer server) { + Path dataDir = server.getServerDirectory(); + backupsFolder = FTBBackupsConfig.folder.trim().isEmpty() ? FMLPaths.GAMEDIR.get().resolve("backups").toFile() : new File(FTBBackupsConfig.folder); + + try { + backupsFolder = backupsFolder.getCanonicalFile(); + } catch (Exception ex) { + } + + doingBackup = BackupStatus.NONE; + backups.clear(); + + File file = new File(dataDir.toFile(), "local/ftbutilities/backups.json"); + + if (!file.exists()) { + File oldFile = new File(backupsFolder, "backups.json"); + + if (oldFile.exists()) { + oldFile.renameTo(file); + } + } + + JsonElement element = BackupUtils.readJson(file); + + if (element != null && element.isJsonArray()) { + try { + for (JsonElement e : element.getAsJsonArray()) { + JsonObject json = e.getAsJsonObject(); + + if (!json.has("size")) { + json.addProperty("size", BackupUtils.getSize(new File(backupsFolder, json.get("file").getAsString()))); + } + + backups.add(new Backup(json)); + } + } catch (Throwable ex) { + ex.printStackTrace(); + } + } + + FTBBackups.LOGGER.info("Backups folder - " + backupsFolder.getAbsolutePath()); + nextBackup = System.currentTimeMillis() + FTBBackupsConfig.backupTimer; + } + + public void tick(MinecraftServer server, long now) { + if (nextBackup > 0L && nextBackup <= now) { + if (!FTBBackupsConfig.onlyIfPlayersOnline || hadPlayersOnline || !server.getPlayerList().getPlayers().isEmpty()) { + hadPlayersOnline = false; + run(server, true, Component.translatable("Server"), ""); + } + } + + if (doingBackup.isDone()) { + doingBackup = BackupStatus.NONE; + + for (ServerLevel world : server.getAllLevels()) { + if (world != null) { + world.noSave = false; + } + } + + if (!FTBBackupsConfig.silent) { + //TODO: Fix FTBBackupsNetHandler.MAIN.send(PacketDistributor.ALL.noArg(), new BackupProgressPacket(0, 0)); + } + } else if (doingBackup.isRunning() && printFiles) { + if (currentFile == 0 || currentFile == totalFiles - 1) { + FTBBackups.LOGGER.info("[" + currentFile + " | " + (int) ((currentFile / (double) totalFiles) * 100D) + "%]: " + currentFileName); + } + + if (!FTBBackupsConfig.silent) { + //TODO: Fix FTBBackupsNetHandler.MAIN.send(PacketDistributor.ALL.noArg(), new BackupProgressPacket(currentFile, totalFiles)); + } + } + } + + public void notifyAll(MinecraftServer server, Component component, boolean error) { + component = component.plainCopy(); + component.getStyle().withColor(TextColor.fromLegacyFormat(error ? ChatFormatting.DARK_RED : ChatFormatting.LIGHT_PURPLE)); + FTBBackups.LOGGER.info(component.getString()); + server.getPlayerList().broadcastSystemMessage(component, true); + } + + public boolean run(MinecraftServer server, boolean auto, Component name, String customName) { + if (doingBackup.isRunningOrDone()) { + return false; + } + + if (auto && !FTBBackupsConfig.auto) { + return false; + } + + notifyAll(server, Component.translatable("ftbbackups.lang.start", name), false); + nextBackup = System.currentTimeMillis() + FTBBackupsConfig.backupTimer; + + for (ServerLevel world : server.getAllLevels()) { + if (world != null) { + world.noSave = true; + } + } + + doingBackup = BackupStatus.RUNNING; + server.getPlayerList().saveAll(); + + new Thread(() -> { + try { + try { + createBackup(server, customName); + } catch (Exception ex) { + ex.printStackTrace(); + notifyAll(server, Component.translatable("ftbbackups.lang.saving_failed"), true); + } + } catch (Exception ex) { + notifyAll(server, Component.translatable("ftbbackups.lang.saving_failed"), true); + ex.printStackTrace(); + } + + doingBackup = BackupStatus.DONE; + }).start(); + + return true; + } + + private void createBackup(MinecraftServer server, String customName) { + File src = server.getWorldPath(LevelResource.ROOT).toFile(); + + try { + src = src.getCanonicalFile(); + } catch (Exception ex) { + } + + Calendar time = Calendar.getInstance(); + File dstFile; + boolean success = false; + StringBuilder out = new StringBuilder(); + + if (customName.isEmpty()) { + appendNum(out, time.get(Calendar.YEAR), '-'); + appendNum(out, time.get(Calendar.MONTH) + 1, '-'); + appendNum(out, time.get(Calendar.DAY_OF_MONTH), '-'); + appendNum(out, time.get(Calendar.HOUR_OF_DAY), '-'); + appendNum(out, time.get(Calendar.MINUTE), '-'); + appendNum(out, time.get(Calendar.SECOND), '\0'); + } else { + out.append(customName); + } + + Exception error = null; + long fileSize = 0L; + + try { + LinkedHashMap fileMap = new LinkedHashMap<>(); + String mcdir = server.getServerDirectory().getFileSystem().toString(); + + Consumer consumer = file0 -> { + for (File file : BackupUtils.listTree(file0)) { + String s1 = file.getAbsolutePath().replace(mcdir, ""); + + if (s1.startsWith(File.separator)) { + s1 = s1.substring(File.separator.length()); + } + + fileMap.put(file, "_extra_" + File.separator + s1); + } + }; + + for (String s : FTBBackupsConfig.extraFiles) { + consumer.accept(new File(s)); + } + + NeoForge.EVENT_BUS.post(new BackupEvent.Pre(consumer)); + + for (File file : BackupUtils.listTree(src)) { + if (file.getName().equals("session.lock") || !file.canRead()) { + continue; + } + + String filePath = file.getAbsolutePath(); + fileMap.put(file, src.getName() + File.separator + filePath.substring(src.getAbsolutePath().length() + 1)); + } + + for (Map.Entry entry : fileMap.entrySet()) { + fileSize += BackupUtils.getSize(entry.getKey()); + } + + long totalSize = 0L; + + if (!backups.isEmpty()) { + backups.sort(null); + + int backupsToKeep = FTBBackupsConfig.backupsToKeep - 1; + + if (backupsToKeep > 0 && backups.size() > backupsToKeep) { + while (backups.size() > backupsToKeep) { + Backup backup = backups.remove(0); + FTBBackups.LOGGER.info("Deleting old backup: " + backup.fileId); + BackupUtils.delete(backup.getFile()); + } + } + + for (Backup backup : backups) { + totalSize += backup.size; + } + + if (fileSize > 0L) { + long freeSpace = Math.min(FTBBackupsConfig.maxTotalSize, backupsFolder.getFreeSpace()); + + while (totalSize + fileSize > freeSpace && !backups.isEmpty()) { + Backup backup = backups.remove(0); + totalSize -= backup.size; + FTBBackups.LOGGER.info("Deleting backup to free space: " + backup.fileId); + BackupUtils.delete(backup.getFile()); + } + } + } + + totalFiles = fileMap.size(); + FTBBackups.LOGGER.info("Backing up " + totalFiles + " files..."); + printFiles = true; + + if (FTBBackupsConfig.compressionLevel > 0) { + out.append(".zip"); + dstFile = BackupUtils.newFile(new File(backupsFolder, out.toString())); + + long start = System.currentTimeMillis(); + + ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dstFile)); + zos.setLevel(FTBBackupsConfig.compressionLevel); + + byte[] buffer = new byte[FTBBackupsConfig.bufferSize]; + + FTBBackups.LOGGER.info("Compressing " + totalFiles + " files..."); + + currentFile = 0; + for (Map.Entry entry : fileMap.entrySet()) { + try { + ZipEntry ze = new ZipEntry(entry.getValue()); + currentFileName = entry.getValue(); + + zos.putNextEntry(ze); + FileInputStream fis = new FileInputStream(entry.getKey()); + + int len; + while ((len = fis.read(buffer)) > 0) { + zos.write(buffer, 0, len); + } + zos.closeEntry(); + fis.close(); + } catch (Exception ex) { + FTBBackups.LOGGER.error("Failed to read file " + entry.getValue() + ": " + ex); + } + + currentFile++; + } + + zos.close(); + fileSize = BackupUtils.getSize(dstFile); + FTBBackups.LOGGER.info("Done compressing in " + BackupUtils.getTimeString(System.currentTimeMillis() - start) + " seconds (" + BackupUtils.getSizeString(fileSize) + ")!"); + } else { + dstFile = new File(backupsFolder, out.toString()); + dstFile.mkdirs(); + + currentFile = 0; + for (Map.Entry entry : fileMap.entrySet()) { + try { + File file = entry.getKey(); + currentFileName = entry.getValue(); + File dst1 = new File(dstFile, entry.getValue()); + BackupUtils.copyFile(file, dst1); + } catch (Exception ex) { + FTBBackups.LOGGER.error("Failed to copy file " + entry.getValue() + ": " + ex); + } + + currentFile++; + } + } + + FTBBackups.LOGGER.info("Created " + dstFile.getAbsolutePath() + " from " + src.getAbsolutePath()); + success = true; + } catch (Exception ex) { + if (!FTBBackupsConfig.silent) { + String errorName = ex.getClass().getName(); + notifyAll(server, Component.translatable("ftbbackups.lang.fail", errorName), true); + } + + ex.printStackTrace(); + error = ex; + } + + printFiles = false; + + Backup backup = new Backup(time.getTimeInMillis(), out.toString().replace('\\', '/'), getLastIndex() + 1, success, fileSize); + backups.add(backup); + NeoForge.EVENT_BUS.post(new BackupEvent.Post(backup, error)); + + JsonArray array = new JsonArray(); + + for (Backup backup1 : backups) { + array.add(backup1.toJsonObject()); + } + + BackupUtils.toJson(new File(server.getServerDirectory().toFile(), "local/ftbutilities/backups.json"), array, true); + + if (error == null && !FTBBackupsConfig.silent) { + String timeString = BackupUtils.getTimeString(System.currentTimeMillis() - time.getTimeInMillis()); + Component component; + + if (FTBBackupsConfig.displayFileSize) { + long totalSize = 0L; + + for (Backup backup1 : backups) { + totalSize += backup1.size; + } + + String sizeB = BackupUtils.getSizeString(fileSize); + String sizeT = BackupUtils.getSizeString(totalSize); + String sizeString = sizeB.equals(sizeT) ? sizeB : (sizeB + " | " + sizeT); + component = Component.translatable("ftbbackups.lang.end_2", timeString, sizeString); + } else { + component = Component.translatable("ftbbackups.lang.end_1", timeString); + } + + component.getStyle().withColor(TextColor.fromLegacyFormat(ChatFormatting.LIGHT_PURPLE)); + server.getPlayerList().broadcastSystemMessage(component, true); + } + } + + private void appendNum(StringBuilder sb, int num, char c) { + if (num < 10) { + sb.append('0'); + } + sb.append(num); + if (c != '\0') { + sb.append(c); + } + } + + private int getLastIndex() { + int i = 0; + + for (Backup b : backups) { + i = Math.max(i, b.index); + } + + return i; + } } \ No newline at end of file diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java index 998a6152..c7b7b5b3 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java @@ -1,5 +1,6 @@ package dev.ftb.mods.ftbbackups; +import dev.ftb.mods.ftbbackups.net.BackupProgressPacket; import dev.ftb.mods.ftbbackups.net.FTBBackupsNetHandler; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; @@ -17,81 +18,66 @@ import net.neoforged.neoforge.event.server.ServerAboutToStartEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; import net.neoforged.neoforge.event.tick.ServerTickEvent; +import net.neoforged.neoforge.network.PacketDistributor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @Mod(FTBBackups.MOD_ID) -public class FTBBackups -{ - public static final String MOD_ID = "ftbbackups"; - public static final Logger LOGGER = LogManager.getLogger("FTB Backups 3"); +public class FTBBackups { + public static final String MOD_ID = "ftbbackups"; + public static final Logger LOGGER = LogManager.getLogger("FTB Backups 3"); - public FTBBackups(IEventBus eventBus, ModContainer container) - { - if (FMLEnvironment.dist == Dist.CLIENT) { - eventBus.addListener(this::clientSetup); - } - eventBus.addListener(FTBBackupsNetHandler::init); + public FTBBackups(IEventBus eventBus, ModContainer container) { + if (FMLEnvironment.dist == Dist.CLIENT) { + eventBus.addListener(this::clientSetup); + } + eventBus.addListener(FTBBackupsNetHandler::init); - NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverAboutToStart); - NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::registerCommands); - NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverStopping); - NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedIn); - NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedOut); - NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::worldTick); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverAboutToStart); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::registerCommands); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverStopping); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedIn); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedOut); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::worldTick); - FTBBackupsConfig.register(eventBus, container); - } + FTBBackupsConfig.register(eventBus, container); + } - public static ResourceLocation id(String path) { - return ResourceLocation.fromNamespaceAndPath(MOD_ID, path); - } + public static ResourceLocation id(String path) { + return ResourceLocation.fromNamespaceAndPath(MOD_ID, path); + } - private void clientSetup(FMLClientSetupEvent event) - { - FTBBackupsClient.init(); - } + private void clientSetup(FMLClientSetupEvent event) { + FTBBackupsClient.init(); + } - public void serverAboutToStart(ServerAboutToStartEvent event) - { - Backups.INSTANCE.init(event.getServer()); - } + public void serverAboutToStart(ServerAboutToStartEvent event) { + Backups.INSTANCE.init(event.getServer()); + } - public void registerCommands(RegisterCommandsEvent event) - { - BackupCommands.register(event.getDispatcher()); - } + public void registerCommands(RegisterCommandsEvent event) { + BackupCommands.register(event.getDispatcher()); + } - public void serverStopping(ServerStoppingEvent event) - { - if (FTBBackupsConfig.forceOnShutdown) - { - Backups.INSTANCE.run(event.getServer(), true, Component.literal("Server"), ""); - } - } + public void serverStopping(ServerStoppingEvent event) { + if (FTBBackupsConfig.forceOnShutdown) { + Backups.INSTANCE.run(event.getServer(), true, Component.literal("Server"), ""); + } + } - public void playerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) - { - if (event.getEntity() instanceof ServerPlayer) - { - //TODO: Send Packets - //FTBBackupsNetHandler.MAIN.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) event.getEntity()), new BackupProgressPacket(Backups.INSTANCE.currentFile, Backups.INSTANCE.totalFiles)); - } - } + public void playerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { + if (event.getEntity() instanceof ServerPlayer serverPlayer) { + PacketDistributor.sendToPlayer(serverPlayer, BackupProgressPacket.create()); + } + } - public void playerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) - { - if (event.getEntity() instanceof ServerPlayer) - { - Backups.INSTANCE.hadPlayersOnline = true; - } - } + public void playerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) { + if (event.getEntity() instanceof ServerPlayer) { + Backups.INSTANCE.hadPlayersOnline = true; + } + } - public void worldTick(ServerTickEvent.Post event) { - /*if (event.phase != ServerTickEvent.PostTickEvent.Phase.START && !event.side.isClient()) { - Backups.INSTANCE.tick(event.getServer(), System.currentTimeMillis()); - }*/ - //I Guess this is just called server side now?? - Backups.INSTANCE.tick(event.getServer(), System.currentTimeMillis()); - } + public void worldTick(ServerTickEvent.Post event) { + Backups.INSTANCE.tick(event.getServer(), System.currentTimeMillis()); + } } \ No newline at end of file diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java index 68148b91..b48165cb 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java @@ -2,45 +2,38 @@ import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; -import net.minecraft.client.OptionInstance; import net.minecraft.client.resources.language.I18n; -import net.neoforged.neoforge.client.event.RenderGuiLayerEvent; +import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent; +import net.neoforged.neoforge.client.event.CustomizeGuiOverlayEvent; import net.neoforged.neoforge.common.NeoForge; -public class FTBBackupsClient -{ - private static int currentBackupFile = 0; - private static int totalBackupFiles = 0; +public class FTBBackupsClient { + private static int currentBackupFile = 0; + private static int totalBackupFiles = 0; - public static void init() - { - //MinecraftForge.EVENT_BUS.addListener(FTBBackupsClient::onClientDisconnected); - NeoForge.EVENT_BUS.addListener(FTBBackupsClient::onDebugInfoEvent); - } + public static void init() { + NeoForge.EVENT_BUS.addListener(FTBBackupsClient::onClientDisconnected); + NeoForge.EVENT_BUS.addListener(FTBBackupsClient::onDebugInfoEvent); + } - public static void setFiles(int current, int total) - { - currentBackupFile = current; - totalBackupFiles = total; - } + public static void setBackupProgress(int current, int total) { + currentBackupFile = current; + totalBackupFiles = total; + } - /*private static void onClientDisconnected(FMLNetworkEvent.ClientDisconnectionFromServerEvent event) - { + private static void onClientDisconnected(ClientPlayerNetworkEvent.LoggingOut ignoredEvent) { currentBackupFile = 0; totalBackupFiles = 0; - }*/ - - private static void onDebugInfoEvent(RenderGuiLayerEvent.Post event) - { - OptionInstance booleanOptionInstance = Minecraft.getInstance().options.reducedDebugInfo(); - if (booleanOptionInstance.get()) - { - return; - } - - if (totalBackupFiles > 0 && totalBackupFiles > currentBackupFile) - { - //event.getLeft().add(ChatFormatting.LIGHT_PURPLE + I18n.get("ftbbackups.lang.timer_progress", currentBackupFile * 100 / totalBackupFiles, currentBackupFile, totalBackupFiles)); - } } + + private static void onDebugInfoEvent(CustomizeGuiOverlayEvent.DebugText event) { + if (!Minecraft.getInstance().options.reducedDebugInfo().get() + && totalBackupFiles > 0 + && totalBackupFiles > currentBackupFile) + { + event.getLeft().add(ChatFormatting.LIGHT_PURPLE + I18n.get("ftbbackups.lang.timer_progress", + currentBackupFile * 100 / totalBackupFiles, currentBackupFile, totalBackupFiles) + ); + } + } } \ No newline at end of file diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java index b4ad4142..fc04e434 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java @@ -1,193 +1,171 @@ package dev.ftb.mods.ftbbackups; - - -import net.neoforged.fml.ModContainer; -import org.apache.commons.lang3.tuple.Pair; import net.neoforged.bus.api.IEventBus; -import net.neoforged.fml.ModLoadingContext; +import net.neoforged.fml.ModContainer; import net.neoforged.fml.config.ModConfig; import net.neoforged.fml.event.config.ModConfigEvent; import net.neoforged.neoforge.common.ModConfigSpec; +import org.apache.commons.lang3.tuple.Pair; import java.util.ArrayList; import java.util.List; -/** - * @author LatvianModder - */ -public class FTBBackupsConfig -{ - public static boolean auto; - public static boolean silent; - public static int backupsToKeep; - public static long backupTimer; - public static int compressionLevel; - public static String folder; - public static boolean displayFileSize; - public static List extraFiles; - public static long maxTotalSize; - public static boolean onlyIfPlayersOnline; - public static boolean forceOnShutdown; - public static int bufferSize; - - private static Pair common; - - public static void register(IEventBus eventBus, ModContainer container) - { - eventBus.addListener(FTBBackupsConfig::reload); - - common = new ModConfigSpec.Builder().configure(CommonConfig::new); - - container.registerConfig(ModConfig.Type.COMMON, common.getRight()); - } - - public static void reload(ModConfigEvent event) - { - ModConfig config = event.getConfig(); - - if (config.getSpec() == common.getRight()) - { - reloadCommon(); - } - } - - public static void reloadCommon() - { - CommonConfig cfg = common.getLeft(); - auto = cfg.auto.get(); - silent = cfg.silent.get(); - backupsToKeep = cfg.backupsToKeep.get(); - backupTimer = cfg.backupTimer.get() * 60000L; - compressionLevel = cfg.compressionLevel.get(); - folder = cfg.folder.get(); - displayFileSize = cfg.displayFileSize.get(); - extraFiles = new ArrayList<>(cfg.extraFiles.get()); - - String mts = cfg.maxTotalSize.get(); - - long l = Long.parseLong(mts.substring(0, mts.length() - 2).trim()); - if (mts.endsWith("TB")) - { - maxTotalSize = l * BackupUtils.TB; - } - else if (mts.endsWith("GB")) - { - maxTotalSize = l * BackupUtils.GB; - } - else if (mts.endsWith("MB")) - { - maxTotalSize = l * BackupUtils.MB; - } - else if (mts.endsWith("KB")) - { - maxTotalSize = l * BackupUtils.KB; - } - else - { - maxTotalSize = Long.parseLong(mts.trim()); - } - - onlyIfPlayersOnline = cfg.onlyIfPlayersOnline.get(); - forceOnShutdown = cfg.forceOnShutdown.get(); - bufferSize = cfg.bufferSize.get(); - } - - private static class CommonConfig - { - //ModConfigSpec or NeoForgeConfig - private final ModConfigSpec.BooleanValue auto; - private final ModConfigSpec.BooleanValue silent; - private final ModConfigSpec.IntValue backupsToKeep; - private final ModConfigSpec.IntValue backupTimer; - private final ModConfigSpec.IntValue compressionLevel; - private final ModConfigSpec.ConfigValue folder; - private final ModConfigSpec.BooleanValue displayFileSize; - private final ModConfigSpec.ConfigValue> extraFiles; - private final ModConfigSpec.ConfigValue maxTotalSize; - private final ModConfigSpec.BooleanValue onlyIfPlayersOnline; - private final ModConfigSpec.BooleanValue forceOnShutdown; - private final ModConfigSpec.IntValue bufferSize; - - private CommonConfig(ModConfigSpec.Builder builder) - { - auto = builder - .comment("Enables backups to run automatically.") - .translation("ftbbackups.general.auto") - .define("auto", true); - - silent = builder - .comment("If set to true, no messages will be displayed in chat/status bar.") - .translation("ftbbackups.general.silent") - .define("silent", false); - - backupsToKeep = builder - .comment( - "The number of backup files to keep.", - "More backups = more space used", - "0 - Infinite" - ) - .translation("ftbbackups.general.backups_to_keep") - .defineInRange("backups_to_keep", 12, 0, 32000); - - backupTimer = builder - .comment( - "Timer in minutes.", - "5 - backups every 5 minutes", - "60 - backups every hour", - "360 - backups every 6 hours", - "1440 - backups once every day" - ) - .translation("ftbbackups.general.backup_timer") - .defineInRange("backup_timer", 120, 1, 43800); - - compressionLevel = builder - .comment( - "0 - Disabled (output = folders)", - "1 - Best speed", - "9 - Smallest file size" - ) - .translation("ftbbackups.general.compression_level") - .defineInRange("compression_level", 1, 0, 9); - - folder = builder - .comment("Absolute path to backups folder.") - .translation("ftbbackups.general.folder") - .define("folder", ""); - - displayFileSize = builder - .comment("Prints (current size | total size) when backup is done.") - .translation("ftbbackups.general.display_file_size") - .define("display_file_size", true); - - extraFiles = builder - .comment("Add extra files that will be placed in backup _extra_/ folder.") - .translation("ftbbackups.general.extra_files") - .defineList("extra_files", new ArrayList<>(), o -> true); - - maxTotalSize = builder - .comment("Maximum total size that is allowed in backups folder. Older backups will be deleted to free space for newer ones.") - .translation("ftbbackups.general.max_total_size") - .define("max_total_size", "50 GB"); - - onlyIfPlayersOnline = builder - .comment("Only create backups when players have been online.") - .translation("ftbbackups.general.only_if_players_online") - .define("only_if_players_online", true); - - forceOnShutdown = builder - .comment("Create a backup when server is stopped.") - .translation("ftbbackups.general.force_on_shutdown") - .define("force_on_shutdown", false); - - builder.comment("Advanced features that shouldn't be changed unless you know what you are doing.").push("advanced"); - - bufferSize = builder - .comment("Buffer size for writing files.") - .translation("ftbbackups.general.buffer_size") - .defineInRange("buffer_size", 4096, 256, 65536); - - builder.pop(); - } - } +public class FTBBackupsConfig { + public static boolean auto; + public static boolean silent; + public static int backupsToKeep; + public static long backupTimer; + public static int compressionLevel; + public static String folder; + public static boolean displayFileSize; + public static List extraFiles; + public static long maxTotalSize; + public static boolean onlyIfPlayersOnline; + public static boolean forceOnShutdown; + public static int bufferSize; + + private static Pair common; + + public static void register(IEventBus eventBus, ModContainer container) { + eventBus.addListener(FTBBackupsConfig::reload); + + common = new ModConfigSpec.Builder().configure(CommonConfig::new); + + container.registerConfig(ModConfig.Type.COMMON, common.getRight()); + } + + public static void reload(ModConfigEvent event) { + ModConfig config = event.getConfig(); + + if (config.getSpec() == common.getRight()) { + reloadCommon(); + } + } + + public static void reloadCommon() { + CommonConfig cfg = common.getLeft(); + auto = cfg.auto.get(); + silent = cfg.silent.get(); + backupsToKeep = cfg.backupsToKeep.get(); + backupTimer = cfg.backupTimer.get() * 60000L; + compressionLevel = cfg.compressionLevel.get(); + folder = cfg.folder.get(); + displayFileSize = cfg.displayFileSize.get(); + extraFiles = new ArrayList<>(cfg.extraFiles.get()); + + String mts = cfg.maxTotalSize.get(); + + long l = Long.parseLong(mts.substring(0, mts.length() - 2).trim()); + if (mts.endsWith("TB")) { + maxTotalSize = l * BackupUtils.TB; + } else if (mts.endsWith("GB")) { + maxTotalSize = l * BackupUtils.GB; + } else if (mts.endsWith("MB")) { + maxTotalSize = l * BackupUtils.MB; + } else if (mts.endsWith("KB")) { + maxTotalSize = l * BackupUtils.KB; + } else { + maxTotalSize = Long.parseLong(mts.trim()); + } + + onlyIfPlayersOnline = cfg.onlyIfPlayersOnline.get(); + forceOnShutdown = cfg.forceOnShutdown.get(); + bufferSize = cfg.bufferSize.get(); + } + + private static class CommonConfig { + //ModConfigSpec or NeoForgeConfig + private final ModConfigSpec.BooleanValue auto; + private final ModConfigSpec.BooleanValue silent; + private final ModConfigSpec.IntValue backupsToKeep; + private final ModConfigSpec.IntValue backupTimer; + private final ModConfigSpec.IntValue compressionLevel; + private final ModConfigSpec.ConfigValue folder; + private final ModConfigSpec.BooleanValue displayFileSize; + private final ModConfigSpec.ConfigValue> extraFiles; + private final ModConfigSpec.ConfigValue maxTotalSize; + private final ModConfigSpec.BooleanValue onlyIfPlayersOnline; + private final ModConfigSpec.BooleanValue forceOnShutdown; + private final ModConfigSpec.IntValue bufferSize; + + private CommonConfig(ModConfigSpec.Builder builder) { + auto = builder + .comment("Enables backups to run automatically.") + .translation("ftbbackups.general.auto") + .define("auto", true); + + silent = builder + .comment("If set to true, no messages will be displayed in chat/status bar.") + .translation("ftbbackups.general.silent") + .define("silent", false); + + backupsToKeep = builder + .comment( + "The number of backup files to keep.", + "More backups = more space used", + "0 - Infinite" + ) + .translation("ftbbackups.general.backups_to_keep") + .defineInRange("backups_to_keep", 12, 0, 32000); + + backupTimer = builder + .comment( + "Timer in minutes.", + "5 - backups every 5 minutes", + "60 - backups every hour", + "360 - backups every 6 hours", + "1440 - backups once every day" + ) + .translation("ftbbackups.general.backup_timer") + .defineInRange("backup_timer", 120, 1, 43800); + + compressionLevel = builder + .comment( + "0 - Disabled (output = folders)", + "1 - Best speed", + "9 - Smallest file size" + ) + .translation("ftbbackups.general.compression_level") + .defineInRange("compression_level", 1, 0, 9); + + folder = builder + .comment("Absolute path to backups folder.") + .translation("ftbbackups.general.folder") + .define("folder", ""); + + displayFileSize = builder + .comment("Prints (current size | total size) when backup is done.") + .translation("ftbbackups.general.display_file_size") + .define("display_file_size", true); + + extraFiles = builder + .comment("Add extra files that will be placed in backup _extra_/ folder.") + .translation("ftbbackups.general.extra_files") + .defineList("extra_files", new ArrayList<>(), o -> true); + + maxTotalSize = builder + .comment("Maximum total size that is allowed in backups folder. Older backups will be deleted to free space for newer ones.") + .translation("ftbbackups.general.max_total_size") + .define("max_total_size", "50 GB"); + + onlyIfPlayersOnline = builder + .comment("Only create backups when players have been online.") + .translation("ftbbackups.general.only_if_players_online") + .define("only_if_players_online", true); + + forceOnShutdown = builder + .comment("Create a backup when server is stopped.") + .translation("ftbbackups.general.force_on_shutdown") + .define("force_on_shutdown", false); + + builder.comment("Advanced features that shouldn't be changed unless you know what you are doing.").push("advanced"); + + bufferSize = builder + .comment("Buffer size for writing files.") + .translation("ftbbackups.general.buffer_size") + .defineInRange("buffer_size", 4096, 256, 65536); + + builder.pop(); + } + } } \ No newline at end of file diff --git a/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java b/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java index fc5b78af..07711cae 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java @@ -1,5 +1,6 @@ package dev.ftb.mods.ftbbackups.net; +import dev.ftb.mods.ftbbackups.Backups; import dev.ftb.mods.ftbbackups.FTBBackups; import dev.ftb.mods.ftbbackups.FTBBackupsClient; import net.minecraft.network.FriendlyByteBuf; @@ -9,20 +10,24 @@ import net.neoforged.neoforge.network.handling.IPayloadContext; public record BackupProgressPacket(int current, int total) implements CustomPacketPayload { - public static final Type TYPE = new Type<>(FTBBackups.id("backup_progress")); + public static final Type TYPE = new Type<>(FTBBackups.id("backup_progress")); - public static final StreamCodec STREAM_CODEC = StreamCodec.composite( - ByteBufCodecs.VAR_INT, BackupProgressPacket::current, - ByteBufCodecs.VAR_INT, BackupProgressPacket::total, - BackupProgressPacket::new - ); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, BackupProgressPacket::current, + ByteBufCodecs.VAR_INT, BackupProgressPacket::total, + BackupProgressPacket::new + ); - @Override - public Type type() { - return TYPE; - } + public static void handler(BackupProgressPacket message, IPayloadContext context) { + FTBBackupsClient.setBackupProgress(message.current, message.total); + } - public static void handler(BackupProgressPacket message, IPayloadContext context) { - FTBBackupsClient.setFiles(message.current, message.total); - } + public static CustomPacketPayload create() { + return new BackupProgressPacket(Backups.INSTANCE.currentFile, Backups.INSTANCE.totalFiles); + } + + @Override + public Type type() { + return TYPE; + } } \ No newline at end of file diff --git a/src/main/resources/META-INF/neoforge.mods.toml b/src/main/resources/META-INF/neoforge.mods.toml index 95348a6f..f9c51e96 100644 --- a/src/main/resources/META-INF/neoforge.mods.toml +++ b/src/main/resources/META-INF/neoforge.mods.toml @@ -13,11 +13,11 @@ description = ''' Creates backups of your world''' [[dependencies.ftbbackups]] -modId="neoforge" +modId = "neoforge" mandatory = true versionRange = "[21,)" ordering = "NONE" -side="BOTH" +side = "BOTH" #[[dependencies.ftbbackups]] #modId="minecraft" diff --git a/src/main/resources/assets/ftbbackups/lang/de_de.json b/src/main/resources/assets/ftbbackups/lang/de_de.json index 1be98af8..d2016433 100644 --- a/src/main/resources/assets/ftbbackups/lang/de_de.json +++ b/src/main/resources/assets/ftbbackups/lang/de_de.json @@ -1,34 +1,34 @@ { - "_comment": "Config", - "ftbbackups.general": "Allgemein", - "ftbbackups.general.silent": "Still", - "ftbbackups.general.backups_to_keep": "Sicherungen zum Behalten", - "ftbbackups.general.backup_timer": "Sicherungszeitgeber", - "ftbbackups.general.compression_level": "Komprimierungsstufe", - "ftbbackups.general.folder": "Ordner", - "ftbbackups.general.display_file_size": "Dateigröße Anzeigen", - "ftbbackups.general.use_separate_thread": "Separaten Aktivitätsträger Nutzen", - "ftbbackups.general.extra_files": "Zusätzliche Dateien", - "ftbbackups.general.max_total_size": "Maximale Gesamtgröße", - "ftbbackups.general.disable_level_saving": "Stufenspeicherung Deaktivieren", - "ftbbackups.general.only_if_players_online": "Sicherung machen, nur wenn Spieler online sind.", - "ftbbackups.general.force_on_shutdown": "Das Erstellen einer Sicherung erzwingen beim Herunterfahren.", - "ftbbackups.general.buffer_size": "Puffergröße", - "_comment": "Misc", - "ftbbackups.lang.timer": "Serversicherung in: %s", - "ftbbackups.lang.timer_progress": "[%d%% | %d / %d]", - "ftbbackups.lang.start": "Serversicherung startete!", - "ftbbackups.lang.end_1": "Serversicherung ist in %s fertig!", - "ftbbackups.lang.end_2": "Serversicherung ist in %s fertig! (%s)", - "ftbbackups.lang.fail": "Die Speicherung der Welt ist fehlgeschlagen! (%s)", - "ftbbackups.lang.manual_launch": "%s startete eine manuelle Sicherung!", - "ftbbackups.lang.already_running": "Sicherung in Arbeit!", - "ftbbackups.lang.size.current": "Aktuelle Weltgröße: %s", - "ftbbackups.lang.size.total": "Die Größe des Sicherungsordners insgesamt: %s", - "ftbbackups.lang.saving_failed": "Die Speicherung der Welt ist fehlgeschlagen!", - "_comment": "Commands", - "commands.backup.usage": "/backup", - "commands.backup.start.usage": "/backup start [benutzerdefinierter Name]", - "commands.backup.time.usage": "/backup time", - "commands.backup.size.usage": "/backup size" + "_comment": "Config", + "ftbbackups.general": "Allgemein", + "ftbbackups.general.silent": "Still", + "ftbbackups.general.backups_to_keep": "Sicherungen zum Behalten", + "ftbbackups.general.backup_timer": "Sicherungszeitgeber", + "ftbbackups.general.compression_level": "Komprimierungsstufe", + "ftbbackups.general.folder": "Ordner", + "ftbbackups.general.display_file_size": "Dateigröße Anzeigen", + "ftbbackups.general.use_separate_thread": "Separaten Aktivitätsträger Nutzen", + "ftbbackups.general.extra_files": "Zusätzliche Dateien", + "ftbbackups.general.max_total_size": "Maximale Gesamtgröße", + "ftbbackups.general.disable_level_saving": "Stufenspeicherung Deaktivieren", + "ftbbackups.general.only_if_players_online": "Sicherung machen, nur wenn Spieler online sind.", + "ftbbackups.general.force_on_shutdown": "Das Erstellen einer Sicherung erzwingen beim Herunterfahren.", + "ftbbackups.general.buffer_size": "Puffergröße", + "_comment": "Misc", + "ftbbackups.lang.timer": "Serversicherung in: %s", + "ftbbackups.lang.timer_progress": "[%d%% | %d / %d]", + "ftbbackups.lang.start": "Serversicherung startete!", + "ftbbackups.lang.end_1": "Serversicherung ist in %s fertig!", + "ftbbackups.lang.end_2": "Serversicherung ist in %s fertig! (%s)", + "ftbbackups.lang.fail": "Die Speicherung der Welt ist fehlgeschlagen! (%s)", + "ftbbackups.lang.manual_launch": "%s startete eine manuelle Sicherung!", + "ftbbackups.lang.already_running": "Sicherung in Arbeit!", + "ftbbackups.lang.size.current": "Aktuelle Weltgröße: %s", + "ftbbackups.lang.size.total": "Die Größe des Sicherungsordners insgesamt: %s", + "ftbbackups.lang.saving_failed": "Die Speicherung der Welt ist fehlgeschlagen!", + "_comment": "Commands", + "commands.backup.usage": "/backup", + "commands.backup.start.usage": "/backup start [benutzerdefinierter Name]", + "commands.backup.time.usage": "/backup time", + "commands.backup.size.usage": "/backup size" } \ No newline at end of file diff --git a/src/main/resources/assets/ftbbackups/lang/en_us.json b/src/main/resources/assets/ftbbackups/lang/en_us.json index e05778b5..b6542f54 100644 --- a/src/main/resources/assets/ftbbackups/lang/en_us.json +++ b/src/main/resources/assets/ftbbackups/lang/en_us.json @@ -1,36 +1,36 @@ { - "_comment": "Config", - "ftbbackups.general": "General", - "ftbbackups.general.auto": "Auto", - "ftbbackups.general.silent": "Silent", - "ftbbackups.general.backups_to_keep": "Backups to Keep", - "ftbbackups.general.backup_timer": "Backup Timer", - "ftbbackups.general.compression_level": "Compression Level", - "ftbbackups.general.folder": "Folder", - "ftbbackups.general.display_file_size": "Display File Size", - "ftbbackups.general.use_separate_thread": "Use Separate Thread", - "ftbbackups.general.extra_files": "Extra Files", - "ftbbackups.general.max_total_size": "Maximum Total Size", - "ftbbackups.general.disable_level_saving": "Disable Level Saving", - "ftbbackups.general.only_if_players_online": "Backup Only If Players are Online", - "ftbbackups.general.force_on_shutdown": "Force Backup on Shutdown", - "ftbbackups.general.buffer_size": "Buffer Size", - "_comment": "Misc", - "ftbbackups.lang.timer": "Server backup in: %s", - "ftbbackups.lang.timer_progress": "[%d%% | %d / %d]", - "ftbbackups.lang.start": "Server Backup started!", - "ftbbackups.lang.end_1": "Server backup done in %s!", - "ftbbackups.lang.end_2": "Server backup done in %s! (%s)", - "ftbbackups.lang.fail": "Failed to save world! (%s)", - "ftbbackups.lang.manual_launch": "%s launched a manual backup!", - "ftbbackups.lang.already_running": "Backup in progress!", - "ftbbackups.lang.size.current": "Current world size: %s", - "ftbbackups.lang.size.total": "Total backups folder size: %s", - "ftbbackups.lang.size.available": "Available: %s", - "ftbbackups.lang.saving_failed": "World saving failed!", - "_comment": "Commands", - "commands.backup.usage": "/backup", - "commands.backup.start.usage": "/backup start [custom name]", - "commands.backup.time.usage": "/backup time", - "commands.backup.size.usage": "/backup size" + "_comment": "Config", + "ftbbackups.general": "General", + "ftbbackups.general.auto": "Auto", + "ftbbackups.general.silent": "Silent", + "ftbbackups.general.backups_to_keep": "Backups to Keep", + "ftbbackups.general.backup_timer": "Backup Timer", + "ftbbackups.general.compression_level": "Compression Level", + "ftbbackups.general.folder": "Folder", + "ftbbackups.general.display_file_size": "Display File Size", + "ftbbackups.general.use_separate_thread": "Use Separate Thread", + "ftbbackups.general.extra_files": "Extra Files", + "ftbbackups.general.max_total_size": "Maximum Total Size", + "ftbbackups.general.disable_level_saving": "Disable Level Saving", + "ftbbackups.general.only_if_players_online": "Backup Only If Players are Online", + "ftbbackups.general.force_on_shutdown": "Force Backup on Shutdown", + "ftbbackups.general.buffer_size": "Buffer Size", + "_comment": "Misc", + "ftbbackups.lang.timer": "Server backup in: %s", + "ftbbackups.lang.timer_progress": "[%d%% | %d / %d]", + "ftbbackups.lang.start": "Server Backup started!", + "ftbbackups.lang.end_1": "Server backup done in %s!", + "ftbbackups.lang.end_2": "Server backup done in %s! (%s)", + "ftbbackups.lang.fail": "Failed to save world! (%s)", + "ftbbackups.lang.manual_launch": "%s launched a manual backup!", + "ftbbackups.lang.already_running": "Backup in progress!", + "ftbbackups.lang.size.current": "Current world size: %s", + "ftbbackups.lang.size.total": "Total backups folder size: %s", + "ftbbackups.lang.size.available": "Available: %s", + "ftbbackups.lang.saving_failed": "World saving failed!", + "_comment": "Commands", + "commands.backup.usage": "/backup", + "commands.backup.start.usage": "/backup start [custom name]", + "commands.backup.time.usage": "/backup time", + "commands.backup.size.usage": "/backup size" } \ No newline at end of file diff --git a/src/main/resources/assets/ftbbackups/lang/pl_pl.json b/src/main/resources/assets/ftbbackups/lang/pl_pl.json index 593c311d..03ca7eb5 100644 --- a/src/main/resources/assets/ftbbackups/lang/pl_pl.json +++ b/src/main/resources/assets/ftbbackups/lang/pl_pl.json @@ -1,36 +1,36 @@ { - "_comment": "Konfiguracja", - "ftbbackups.general": "Ogólny", - "ftbbackups.general.auto": "Automatyczny", - "ftbbackups.general.silent": "Cichy", - "ftbbackups.general.backups_to_keep": "Kopia zapasowa do zachowania", - "ftbbackups.general.backup_timer": "Czas kopii zapasowej", - "ftbbackups.general.compression_level": "Poziom kompresji", - "ftbbackups.general.folder": "Folder", - "ftbbackups.general.display_file_size": "WyÅ›wietl rozmiar pliku", - "ftbbackups.general.use_separate_thread": "Użyj oddzielnego wÄ…tku", - "ftbbackups.general.extra_files": "Dodatkowe pliki", - "ftbbackups.general.max_total_size": "Maksymalny caÅ‚kowity rozmiar", - "ftbbackups.general.disable_level_saving": "Wyłącz zapisywanie poziomu", - "ftbbackups.general.only_if_players_online": "Rób tylko kopie zapasowe kiedy gracze sÄ… w grze", - "ftbbackups.general.force_on_shutdown": "WymuÅ› kopiÄ™ zapasowÄ… podczas zamykania", - "ftbbackups.general.buffer_size": "Rozmiar bufora", - "_comment": "Misc", - "ftbbackups.lang.timer": "Serwerowa kopia zapasowa zapisze siÄ™ w: %s", - "ftbbackups.lang.timer_progress": "[%d%% | %d / %d]", - "ftbbackups.lang.start": "Robienie kopii zapasowej serwera zaczęło siÄ™!", - "ftbbackups.lang.end_1": "Robienie kopii zapasowej skoÅ„czyÅ‚o siÄ™ w %s!", - "ftbbackups.lang.end_2": "Robienie kopii zapasowej skoÅ„czyÅ‚o siÄ™ w %s! (%s)", - "ftbbackups.lang.fail": "Niepowodzenie w zapisaniu Å›wiata! (%s)", - "ftbbackups.lang.manual_launch": "%s odpaliÅ‚ rÄ™cznÄ… kopiÄ™ zapasowÄ…!", - "ftbbackups.lang.already_running": "Kopia zapasowa siÄ™ robi!", - "ftbbackups.lang.size.current": "Aktualny rozmiar Å›wiata: %s", - "ftbbackups.lang.size.total": "CaÅ‚kowity rozmiar folderu kopii zapasowych: %s", - "ftbbackups.lang.size.available": "DostÄ™pne: %s", - "ftbbackups.lang.saving_failed": "Niepowodzenie w zapisywaniu Å›wiata!", - "_comment": "Commands", - "commands.backup.usage": "/backup", - "commands.backup.start.usage": "/backup start [custom name]", - "commands.backup.time.usage": "/backup time", - "commands.backup.size.usage": "/backup size" + "_comment": "Konfiguracja", + "ftbbackups.general": "Ogólny", + "ftbbackups.general.auto": "Automatyczny", + "ftbbackups.general.silent": "Cichy", + "ftbbackups.general.backups_to_keep": "Kopia zapasowa do zachowania", + "ftbbackups.general.backup_timer": "Czas kopii zapasowej", + "ftbbackups.general.compression_level": "Poziom kompresji", + "ftbbackups.general.folder": "Folder", + "ftbbackups.general.display_file_size": "WyÅ›wietl rozmiar pliku", + "ftbbackups.general.use_separate_thread": "Użyj oddzielnego wÄ…tku", + "ftbbackups.general.extra_files": "Dodatkowe pliki", + "ftbbackups.general.max_total_size": "Maksymalny caÅ‚kowity rozmiar", + "ftbbackups.general.disable_level_saving": "Wyłącz zapisywanie poziomu", + "ftbbackups.general.only_if_players_online": "Rób tylko kopie zapasowe kiedy gracze sÄ… w grze", + "ftbbackups.general.force_on_shutdown": "WymuÅ› kopiÄ™ zapasowÄ… podczas zamykania", + "ftbbackups.general.buffer_size": "Rozmiar bufora", + "_comment": "Misc", + "ftbbackups.lang.timer": "Serwerowa kopia zapasowa zapisze siÄ™ w: %s", + "ftbbackups.lang.timer_progress": "[%d%% | %d / %d]", + "ftbbackups.lang.start": "Robienie kopii zapasowej serwera zaczęło siÄ™!", + "ftbbackups.lang.end_1": "Robienie kopii zapasowej skoÅ„czyÅ‚o siÄ™ w %s!", + "ftbbackups.lang.end_2": "Robienie kopii zapasowej skoÅ„czyÅ‚o siÄ™ w %s! (%s)", + "ftbbackups.lang.fail": "Niepowodzenie w zapisaniu Å›wiata! (%s)", + "ftbbackups.lang.manual_launch": "%s odpaliÅ‚ rÄ™cznÄ… kopiÄ™ zapasowÄ…!", + "ftbbackups.lang.already_running": "Kopia zapasowa siÄ™ robi!", + "ftbbackups.lang.size.current": "Aktualny rozmiar Å›wiata: %s", + "ftbbackups.lang.size.total": "CaÅ‚kowity rozmiar folderu kopii zapasowych: %s", + "ftbbackups.lang.size.available": "DostÄ™pne: %s", + "ftbbackups.lang.saving_failed": "Niepowodzenie w zapisywaniu Å›wiata!", + "_comment": "Commands", + "commands.backup.usage": "/backup", + "commands.backup.start.usage": "/backup start [custom name]", + "commands.backup.time.usage": "/backup time", + "commands.backup.size.usage": "/backup size" } diff --git a/src/main/resources/assets/ftbbackups/lang/ru_ru.json b/src/main/resources/assets/ftbbackups/lang/ru_ru.json index 1ce7bb59..48b2d1ec 100644 --- a/src/main/resources/assets/ftbbackups/lang/ru_ru.json +++ b/src/main/resources/assets/ftbbackups/lang/ru_ru.json @@ -1,36 +1,36 @@ { - "_comment": "Config", - "ftbbackups.general": "Общее", - "ftbbackups.general.auto": "ÐвтоматичеÑкий режим", - "ftbbackups.general.silent": "Тихий режим", - "ftbbackups.general.backups_to_keep": "КоличеÑтво резервных копий", - "ftbbackups.general.backup_timer": "ЧаÑтота ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñ€ÐµÐ·ÐµÑ€Ð²Ð½Ñ‹Ñ… копий", - "ftbbackups.general.compression_level": "Уровень ÑжатиÑ", - "ftbbackups.general.folder": "Папка", - "ftbbackups.general.display_file_size": "Отображать размер файла", - "ftbbackups.general.use_separate_thread": "ИÑпользовать отдельный поток", - "ftbbackups.general.extra_files": "Дополнительные файлы", - "ftbbackups.general.max_total_size": "МакÑимальный общий размер", - "ftbbackups.general.disable_level_saving": "Отключить Ñохранение уровнÑ", - "ftbbackups.general.only_if_players_online": "Резервное копирование только еÑли игроки находÑÑ‚ÑÑ Ð² Ñети", - "ftbbackups.general.force_on_shutdown": "Принудительное резервное копирование при завершении работы", - "ftbbackups.general.buffer_size": "Размер буфера", - "_comment": "Misc", - "ftbbackups.lang.timer": "Резервное копирование Ñервера в: %s", - "ftbbackups.lang.timer_progress": "[%d%% | %d / %d]", - "ftbbackups.lang.start": "Резервное копирование Ñервера началоÑÑŒ!", - "ftbbackups.lang.end_1": "Резервное копирование Ñервера выполнено в %s!", - "ftbbackups.lang.end_2": "Резервное копирование Ñервера выполнено в %s! (%s)", - "ftbbackups.lang.fail": "Ðе удалоÑÑŒ Ñохранить мир! (%s)", - "ftbbackups.lang.manual_launch": "%s запуÑтил ручное резервное копирование!", - "ftbbackups.lang.already_running": "Резервное копирование выполнÑетÑÑ!", - "ftbbackups.lang.size.current": "Текущий размер мира: %s", - "ftbbackups.lang.size.total": "Общий размер папки резервных копий: %s", - "ftbbackups.lang.size.available": "ДоÑтупно: %s", - "ftbbackups.lang.saving_failed": "Ðе удалоÑÑŒ Ñохранить мир!", - "_comment": "Commands", - "commands.backup.usage": "/backup", - "commands.backup.start.usage": "/backup start [пользовательÑкое имÑ]", - "commands.backup.time.usage": "/backup time", - "commands.backup.size.usage": "/backup size" + "_comment": "Config", + "ftbbackups.general": "Общее", + "ftbbackups.general.auto": "ÐвтоматичеÑкий режим", + "ftbbackups.general.silent": "Тихий режим", + "ftbbackups.general.backups_to_keep": "КоличеÑтво резервных копий", + "ftbbackups.general.backup_timer": "ЧаÑтота ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñ€ÐµÐ·ÐµÑ€Ð²Ð½Ñ‹Ñ… копий", + "ftbbackups.general.compression_level": "Уровень ÑжатиÑ", + "ftbbackups.general.folder": "Папка", + "ftbbackups.general.display_file_size": "Отображать размер файла", + "ftbbackups.general.use_separate_thread": "ИÑпользовать отдельный поток", + "ftbbackups.general.extra_files": "Дополнительные файлы", + "ftbbackups.general.max_total_size": "МакÑимальный общий размер", + "ftbbackups.general.disable_level_saving": "Отключить Ñохранение уровнÑ", + "ftbbackups.general.only_if_players_online": "Резервное копирование только еÑли игроки находÑÑ‚ÑÑ Ð² Ñети", + "ftbbackups.general.force_on_shutdown": "Принудительное резервное копирование при завершении работы", + "ftbbackups.general.buffer_size": "Размер буфера", + "_comment": "Misc", + "ftbbackups.lang.timer": "Резервное копирование Ñервера в: %s", + "ftbbackups.lang.timer_progress": "[%d%% | %d / %d]", + "ftbbackups.lang.start": "Резервное копирование Ñервера началоÑÑŒ!", + "ftbbackups.lang.end_1": "Резервное копирование Ñервера выполнено в %s!", + "ftbbackups.lang.end_2": "Резервное копирование Ñервера выполнено в %s! (%s)", + "ftbbackups.lang.fail": "Ðе удалоÑÑŒ Ñохранить мир! (%s)", + "ftbbackups.lang.manual_launch": "%s запуÑтил ручное резервное копирование!", + "ftbbackups.lang.already_running": "Резервное копирование выполнÑетÑÑ!", + "ftbbackups.lang.size.current": "Текущий размер мира: %s", + "ftbbackups.lang.size.total": "Общий размер папки резервных копий: %s", + "ftbbackups.lang.size.available": "ДоÑтупно: %s", + "ftbbackups.lang.saving_failed": "Ðе удалоÑÑŒ Ñохранить мир!", + "_comment": "Commands", + "commands.backup.usage": "/backup", + "commands.backup.start.usage": "/backup start [пользовательÑкое имÑ]", + "commands.backup.time.usage": "/backup time", + "commands.backup.size.usage": "/backup size" } \ No newline at end of file diff --git a/src/main/resources/assets/ftbbackups/lang/zh_cn.json b/src/main/resources/assets/ftbbackups/lang/zh_cn.json index a63f2505..b185973e 100644 --- a/src/main/resources/assets/ftbbackups/lang/zh_cn.json +++ b/src/main/resources/assets/ftbbackups/lang/zh_cn.json @@ -1,25 +1,25 @@ { - "_comment": "Config", - "ftbbackups.general.silent": "é™é»˜æ¨¡å¼", - "ftbbackups.general.backups_to_keep": "备份ä¿ç•™æ•°", - "ftbbackups.general.backup_timer": "备份间隔", - "ftbbackups.general.compression_level": "压缩等级", - "ftbbackups.general.folder": "文件夹", - "ftbbackups.general.display_file_size": "显示文件大å°", - "ftbbackups.general.use_separate_thread": "使用独立线程", - "ftbbackups.general.extra_files": "é¢å¤–文件", - "ftbbackups.general.max_total_size": "最大总大å°", - "ftbbackups.general.disable_level_saving": "ç¦ç”¨ç­‰çº§ä¿å­˜", - "_comment": "Misc", - "ftbbackups.lang.timer": "æœåŠ¡å™¨å¤‡ä»½ï¼š%s", - "ftbbackups.lang.start": "æœåС噍备份开始ï¼", - "ftbbackups.lang.end_1": "æœåŠ¡å™¨å¤‡ä»½å®Œæˆï¼Œè€—æ—¶ %sï¼", - "ftbbackups.lang.end_2": "æœåŠ¡å™¨å¤‡ä»½å®Œæˆï¼Œè€—æ—¶ %sï¼ï¼ˆ%s)", - "ftbbackups.lang.fail": "ä¿å­˜ä¸–界失败ï¼ï¼ˆ%s)", - "ftbbackups.lang.manual_launch": "%s å¯åŠ¨äº†ä¸€ä¸ªæ‰‹åŠ¨å¤‡ä»½ï¼", - "ftbbackups.lang.already_running": "正在备份中ï¼", - "ftbbackups.lang.size.current": "当å‰ä¸–界大å°ï¼š%s", - "ftbbackups.lang.size.total": "总备份文件夹大å°ï¼š%s", - "ftbbackups.lang.saving_failed": "世界存储失败ï¼", - "commands.backup.start.usage": "/backup start [自定义åç§°]" + "_comment": "Config", + "ftbbackups.general.silent": "é™é»˜æ¨¡å¼", + "ftbbackups.general.backups_to_keep": "备份ä¿ç•™æ•°", + "ftbbackups.general.backup_timer": "备份间隔", + "ftbbackups.general.compression_level": "压缩等级", + "ftbbackups.general.folder": "文件夹", + "ftbbackups.general.display_file_size": "显示文件大å°", + "ftbbackups.general.use_separate_thread": "使用独立线程", + "ftbbackups.general.extra_files": "é¢å¤–文件", + "ftbbackups.general.max_total_size": "最大总大å°", + "ftbbackups.general.disable_level_saving": "ç¦ç”¨ç­‰çº§ä¿å­˜", + "_comment": "Misc", + "ftbbackups.lang.timer": "æœåŠ¡å™¨å¤‡ä»½ï¼š%s", + "ftbbackups.lang.start": "æœåС噍备份开始ï¼", + "ftbbackups.lang.end_1": "æœåŠ¡å™¨å¤‡ä»½å®Œæˆï¼Œè€—æ—¶ %sï¼", + "ftbbackups.lang.end_2": "æœåŠ¡å™¨å¤‡ä»½å®Œæˆï¼Œè€—æ—¶ %sï¼ï¼ˆ%s)", + "ftbbackups.lang.fail": "ä¿å­˜ä¸–界失败ï¼ï¼ˆ%s)", + "ftbbackups.lang.manual_launch": "%s å¯åŠ¨äº†ä¸€ä¸ªæ‰‹åŠ¨å¤‡ä»½ï¼", + "ftbbackups.lang.already_running": "正在备份中ï¼", + "ftbbackups.lang.size.current": "当å‰ä¸–界大å°ï¼š%s", + "ftbbackups.lang.size.total": "总备份文件夹大å°ï¼š%s", + "ftbbackups.lang.saving_failed": "世界存储失败ï¼", + "commands.backup.start.usage": "/backup start [自定义åç§°]" } \ No newline at end of file diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index db13abda..5bfff908 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,6 +1,6 @@ { - "pack": { - "description": "Mod Resources", - "pack_format": 4 - } + "pack": { + "description": "Mod Resources", + "pack_format": 4 + } } From 07a941251d884e295657dc6496e9021935f30d60 Mon Sep 17 00:00:00 2001 From: Michael Hillcox Date: Tue, 18 Mar 2025 22:42:33 +0000 Subject: [PATCH 13/42] feat: code cleanup --- .../java/dev/ftb/mods/ftbbackups/Backup.java | 112 ++- .../ftb/mods/ftbbackups/BackupCommands.java | 156 ++-- .../dev/ftb/mods/ftbbackups/BackupEvent.java | 75 +- .../dev/ftb/mods/ftbbackups/BackupStatus.java | 35 +- .../dev/ftb/mods/ftbbackups/BackupUtils.java | 585 ++++++------- .../java/dev/ftb/mods/ftbbackups/Backups.java | 802 ++++++++---------- .../dev/ftb/mods/ftbbackups/FTBBackups.java | 100 +-- .../ftb/mods/ftbbackups/FTBBackupsClient.java | 48 +- .../ftb/mods/ftbbackups/FTBBackupsConfig.java | 338 ++++---- .../ftbbackups/net/BackupProgressPacket.java | 39 +- .../ftbbackups/net/FTBBackupsNetHandler.java | 7 +- 11 files changed, 1024 insertions(+), 1273 deletions(-) diff --git a/src/main/java/dev/ftb/mods/ftbbackups/Backup.java b/src/main/java/dev/ftb/mods/ftbbackups/Backup.java index f2380500..5bbc25ef 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/Backup.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/Backup.java @@ -4,65 +4,53 @@ import java.io.File; -/** - * @author LatvianModder - */ -public class Backup implements Comparable -{ - public final long time; - public final String fileId; - public final int index; - public final boolean success; - public final long size; - - public Backup(long t, String f, int i, boolean s, long fs) - { - time = t; - fileId = f; - index = i; - success = s; - size = fs; - } - - public Backup(JsonObject o) - { - this(o.get("time").getAsLong(), o.get("file").getAsString(), o.get("index").getAsInt(), o.get("success").getAsBoolean(), o.get("size").getAsLong()); - } - - public JsonObject toJsonObject() - { - JsonObject o = new JsonObject(); - o.addProperty("time", time); - o.addProperty("file", fileId); - o.addProperty("index", index); - o.addProperty("success", success); - o.addProperty("size", size); - return o; - } - - public int hashCode() - { - return Long.hashCode(time); - } - - public String toString() - { - return fileId; - } - - public boolean equals(Object o) - { - return o == this || (o instanceof Backup && ((Backup) o).time == time); - } - - public File getFile() - { - return new File(Backups.INSTANCE.backupsFolder, fileId); - } - - @Override - public int compareTo(Backup o) - { - return Long.compare(time, o.time); - } -} \ No newline at end of file +public class Backup implements Comparable { + public final long time; + public final String fileId; + public final int index; + public final boolean success; + public final long size; + + public Backup(long t, String f, int i, boolean s, long fs) { + time = t; + fileId = f; + index = i; + success = s; + size = fs; + } + + public Backup(JsonObject o) { + this(o.get("time").getAsLong(), o.get("file").getAsString(), o.get("index").getAsInt(), o.get("success").getAsBoolean(), o.get("size").getAsLong()); + } + + public JsonObject toJsonObject() { + JsonObject o = new JsonObject(); + o.addProperty("time", time); + o.addProperty("file", fileId); + o.addProperty("index", index); + o.addProperty("success", success); + o.addProperty("size", size); + return o; + } + + public int hashCode() { + return Long.hashCode(time); + } + + public String toString() { + return fileId; + } + + public boolean equals(Object o) { + return o == this || (o instanceof Backup && ((Backup) o).time == time); + } + + public File getFile() { + return new File(Backups.INSTANCE.backupsFolder, fileId); + } + + @Override + public int compareTo(Backup o) { + return Long.compare(time, o.time); + } +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java index 7f679dfb..002338c5 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java @@ -8,100 +8,86 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.level.storage.LevelResource; -/** - * @author LatvianModder - */ -public class BackupCommands -{ - public static void register(CommandDispatcher dispatcher) - { - dispatcher.register(Commands.literal("ftbbackups") - .then(Commands.literal("time") - .executes(ctx -> time(ctx.getSource())) - ) - .then(Commands.literal("start") - .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) - .then(Commands.argument("name", StringArgumentType.word()) - .executes(ctx -> start(ctx.getSource(), StringArgumentType.getString(ctx, "name"))) - ) - .executes(ctx -> start(ctx.getSource(), "")) - ) - .then(Commands.literal("size") - .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) - .executes(ctx -> size(ctx.getSource())) - ) - .then(Commands.literal("status") - .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) - .executes(ctx -> status(ctx.getSource())) - ) - .then(Commands.literal("reset") - .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) - .executes(ctx -> resetState(ctx.getSource())) - ) - ); - } +public class BackupCommands { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("ftbbackups") + .then(Commands.literal("time") + .executes(ctx -> time(ctx.getSource())) + ) + .then(Commands.literal("start") + .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) + .then(Commands.argument("name", StringArgumentType.word()) + .executes(ctx -> start(ctx.getSource(), StringArgumentType.getString(ctx, "name"))) + ) + .executes(ctx -> start(ctx.getSource(), "")) + ) + .then(Commands.literal("size") + .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) + .executes(ctx -> size(ctx.getSource())) + ) + .then(Commands.literal("status") + .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) + .executes(ctx -> status(ctx.getSource())) + ) + .then(Commands.literal("reset") + .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) + .executes(ctx -> resetState(ctx.getSource())) + ) + ); + } - private static int time(CommandSourceStack source) - { - source.sendSuccess(() -> Component.translatable("ftbbackups.lang.timer", BackupUtils.getTimeString(Backups.INSTANCE.nextBackup - System.currentTimeMillis())),true); - return 1; - } + private static int time(CommandSourceStack source) { + source.sendSuccess(() -> Component.translatable("ftbbackups.lang.timer", BackupUtils.getTimeString(Backups.INSTANCE.nextBackup - System.currentTimeMillis())), true); + return 1; + } - private static int start(CommandSourceStack source, String customName) - { - if (Backups.INSTANCE.run(source.getServer(), false, source.getDisplayName(), customName)) - { - for (ServerPlayer player : source.getServer().getPlayerList().getPlayers()) - { - player.sendSystemMessage(Component.translatable("ftbbackups.lang.manual_launch", source.getDisplayName())); - } - } - else - { - source.sendSuccess(() -> Component.translatable("ftbbackups.lang.already_running"), true); - } + private static int start(CommandSourceStack source, String customName) { + if (Backups.INSTANCE.run(source.getServer(), false, source.getDisplayName(), customName)) { + for (ServerPlayer player : source.getServer().getPlayerList().getPlayers()) { + player.sendSystemMessage(Component.translatable("ftbbackups.lang.manual_launch", source.getDisplayName())); + } + } else { + source.sendSuccess(() -> Component.translatable("ftbbackups.lang.already_running"), true); + } - return 1; - } + return 1; + } - private static int size(CommandSourceStack source) - { - long totalSize = Backups.INSTANCE.backups.stream().mapToLong(backup -> backup.size).sum(); + private static int size(CommandSourceStack source) { + long totalSize = Backups.INSTANCE.backups.stream().mapToLong(backup -> backup.size).sum(); source.sendSuccess(() -> Component.translatable("ftbbackups.lang.size.current", BackupUtils.getSizeString(source.getServer().getWorldPath(LevelResource.ROOT).toFile())), true); - source.sendSuccess(() -> Component.translatable("ftbbackups.lang.size.total", BackupUtils.getSizeString(totalSize)), true); - source.sendSuccess(() -> Component.translatable("ftbbackups.lang.size.available", BackupUtils.getSizeString(Math.min(FTBBackupsConfig.maxTotalSize, Backups.INSTANCE.backupsFolder.getFreeSpace()))), true); + source.sendSuccess(() -> Component.translatable("ftbbackups.lang.size.total", BackupUtils.getSizeString(totalSize)), true); + source.sendSuccess(() -> Component.translatable("ftbbackups.lang.size.available", BackupUtils.getSizeString(Math.min(FTBBackupsConfig.maxTotalSize, Backups.INSTANCE.backupsFolder.getFreeSpace()))), true); - return 1; - } + return 1; + } - private static int status(CommandSourceStack source) - { - source.getServer().getAllLevels().forEach(level -> { - if (level != null) { - //[Dev: Current status for ServerLevel[world]: NO SAVE !!] x3 - //level.dimension().toString() - ResourceKey[minecraft:dimension / minecraft:overworld] - //level.toSting() - ServerLevel[New World] - source.sendSuccess(() -> Component.literal("Current status for " + level.toString() + ": " + (!level.noSave ? "NO SAVE !!" : "Autosave")), true); - } - }); + private static int status(CommandSourceStack source) { + source.getServer().getAllLevels().forEach(level -> { + if (level != null) { + //[Dev: Current status for ServerLevel[world]: NO SAVE !!] x3 + //level.dimension().toString() - ResourceKey[minecraft:dimension / minecraft:overworld] + //level.toSting() - ServerLevel[New World] + source.sendSuccess(() -> Component.literal("Current status for " + level.toString() + ": " + (!level.noSave ? "NO SAVE !!" : "Autosave")), true); + } + }); - return 1; - } + return 1; + } - private static int resetState(CommandSourceStack source) - { - source.getServer().getAllLevels().forEach(level -> { - if (level != null) { - source.sendSuccess(() -> Component.literal("Reseting state + saving for " + level.toString()), true); - level.noSave = false; - level.save(null, true, true); - } - }); + private static int resetState(CommandSourceStack source) { + source.getServer().getAllLevels().forEach(level -> { + if (level != null) { + source.sendSuccess(() -> Component.literal("Reseting state + saving for " + level.toString()), true); + level.noSave = false; + level.save(null, true, true); + } + }); - source.sendSuccess(() -> Component.literal("Reseting state + saving for players"), true); - source.getServer().getPlayerList().saveAll(); + source.sendSuccess(() -> Component.literal("Reseting state + saving for players"), true); + source.getServer().getPlayerList().saveAll(); - return 1; - } -} \ No newline at end of file + return 1; + } +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupEvent.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupEvent.java index d7b14455..11b07f35 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupEvent.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupEvent.java @@ -7,46 +7,35 @@ import java.io.File; import java.util.function.Consumer; -/** - * @author LatvianModder - */ -public class BackupEvent extends Event -{ - public static class Pre extends BackupEvent - { - private final Consumer callback; - - public Pre(Consumer c) - { - callback = c; - } - - public void add(File file) - { - callback.accept(file); - } - } - - public static class Post extends BackupEvent - { - private final Backup backup; - private final Exception error; - - public Post(Backup b, @Nullable Exception ex) - { - backup = b; - error = ex; - } - - public Backup getBackup() - { - return backup; - } - - @Nullable - public Exception getError() - { - return error; - } - } -} \ No newline at end of file +public class BackupEvent extends Event { + public static class Pre extends BackupEvent { + private final Consumer callback; + + public Pre(Consumer c) { + callback = c; + } + + public void add(File file) { + callback.accept(file); + } + } + + public static class Post extends BackupEvent { + private final Backup backup; + private final Exception error; + + public Post(Backup b, @Nullable Exception ex) { + backup = b; + error = ex; + } + + public Backup getBackup() { + return backup; + } + + @Nullable + public Exception getError() { + return error; + } + } +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupStatus.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupStatus.java index 59fbdfbb..40e78e35 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupStatus.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupStatus.java @@ -1,26 +1,19 @@ package dev.ftb.mods.ftbbackups; -/** - * @author LatvianModder - */ -public enum BackupStatus -{ - NONE, - RUNNING, - DONE; +public enum BackupStatus { + NONE, + RUNNING, + DONE; - public boolean isRunning() - { - return this == RUNNING; - } + public boolean isRunning() { + return this == RUNNING; + } - public boolean isDone() - { - return this == DONE; - } + public boolean isDone() { + return this == DONE; + } - public boolean isRunningOrDone() - { - return isRunning() || isDone(); - } -} \ No newline at end of file + public boolean isRunningOrDone() { + return isRunning() || isDone(); + } +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java index 61867970..9a202fd7 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java @@ -23,335 +23,256 @@ import java.util.ArrayList; import java.util.List; -/** - * @author LatvianModder - */ -public class BackupUtils -{ - public static final long KB = 1024L; - public static final long MB = KB * 1024L; - public static final long GB = MB * 1024L; - public static final long TB = GB * 1024L; - - public static final double KB_D = 1024D; - public static final double MB_D = KB_D * 1024D; - public static final double GB_D = MB_D * 1024D; - public static final double TB_D = GB_D * 1024D; - - public static String getTimeString(long millis) - { - boolean neg = false; - if (millis < 0L) - { - neg = true; - millis = -millis; - } - - StringBuilder sb = new StringBuilder(); - - if (millis < 1000L) - { - if (neg) - { - sb.append('-'); - } - - sb.append(millis); - sb.append('m'); - sb.append('s'); - return sb.toString(); - } - - long secs = millis / 1000L; - - if (neg) - { - sb.append('-'); - } - - long h = (secs / 3600L) % 24; - long m = (secs / 60L) % 60L; - long s = secs % 60L; - - if (secs >= 86400L) - { - sb.append(secs / 86400L); - sb.append('d'); - sb.append(' '); - } - - if (h > 0 || secs >= 86400L) - { - if (h < 10) - { - sb.append('0'); - } - sb.append(h); - //sb.append("h "); - sb.append(':'); - } - - if (m < 10) - { - sb.append('0'); - } - sb.append(m); - //sb.append("m "); - sb.append(':'); - if (s < 10) - { - sb.append('0'); - } - sb.append(s); - //sb.append('s'); - - return sb.toString(); - } - - public static long getSize(File file) - { - if (!file.exists()) - { - return 0L; - } - else if (file.isFile()) - { - return file.length(); - } - else if (file.isDirectory()) - { - long length = 0L; - File[] f1 = file.listFiles(); - if (f1 != null && f1.length > 0) - { - for (File aF1 : f1) - { - length += getSize(aF1); - } - } - return length; - } - return 0L; - } - - public static String getSizeString(double b) - { - if (b >= TB_D) - { - return String.format("%.1fTB", b / TB_D); - } - else if (b >= GB_D) - { - return String.format("%.1fGB", b / GB_D); - } - else if (b >= MB_D) - { - return String.format("%.1fMB", b / MB_D); - } - else if (b >= KB_D) - { - return String.format("%.1fKB", b / KB_D); - } - - return ((long) b) + "B"; - } - - public static String getSizeString(File file) - { - return getSizeString(getSize(file)); - } - - public static File newFile(File file) - { - if (!file.exists()) - { - try - { - File parent = file.getParentFile(); - if (!parent.exists()) - { - parent.mkdirs(); - } - file.createNewFile(); - } - catch (Exception e) - { - e.printStackTrace(); - } - } - - return file; - } - - public static List listTree(File file) - { - List l = new ArrayList<>(); - listTree0(l, file); - return l; - } - - public static void listTree0(List list, File file) - { - if (file.isDirectory()) - { - File[] fl = file.listFiles(); - - if (fl != null && fl.length > 0) - { - for (File aFl : fl) - { - listTree0(list, aFl); - } - } - } - else if (file.isFile()) - { - list.add(file); - } - } - - public static void copyFile(File src, File dst) throws Exception - { - if (src.exists() && !src.equals(dst)) - { - if (src.isDirectory() && dst.isDirectory()) - { - for (File f : listTree(src)) - { - File dst1 = new File(dst.getAbsolutePath() + File.separatorChar + (f.getAbsolutePath().replace(src.getAbsolutePath(), ""))); - copyFile(f, dst1); - } - } - else - { - dst = newFile(dst); - - try (FileInputStream fis = new FileInputStream(src); - FileOutputStream fos = new FileOutputStream(dst); - FileChannel srcC = fis.getChannel(); - FileChannel dstC = fos.getChannel()) - { - dstC.transferFrom(srcC, 0L, srcC.size()); - } - } - } - } - - public static boolean delete(File file) - { - if (!file.exists()) - { - return false; - } - else if (file.isFile()) - { - return file.delete(); - } - - String[] files = file.list(); - - if (files != null) - { - for (String s : files) - { - delete(new File(file, s)); - } - } - - return file.delete(); - } - - public static void toJson(Writer writer, @Nullable JsonElement element, boolean prettyPrinting) - { - if (element == null || element.isJsonNull()) - { - try - { - writer.write("null"); - } - catch (Exception ex) - { - ex.printStackTrace(); - } - - return; - } - - JsonWriter jsonWriter = new JsonWriter(writer); - jsonWriter.setLenient(true); - jsonWriter.setHtmlSafe(false); - jsonWriter.setSerializeNulls(true); - - if (prettyPrinting) - { - jsonWriter.setIndent("\t"); - } - - try - { - Streams.write(element, jsonWriter); - } - catch (Exception ex) - { - throw new JsonIOException(ex); - } - } - - public static void toJson(File file, @Nullable JsonElement element, boolean prettyPrinting) - { - try (OutputStreamWriter output = new OutputStreamWriter(new FileOutputStream(newFile(file)), StandardCharsets.UTF_8); - BufferedWriter writer = new BufferedWriter(output)) - { - toJson(writer, element, prettyPrinting); - } - catch (Exception ex) - { - ex.printStackTrace(); - } - } - - public static JsonElement readJson(File file) - { - if (!file.exists()) - { - return JsonNull.INSTANCE; - } - - try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) - { - JsonReader jsonReader = new JsonReader(reader); - jsonReader.setLenient(true); - JsonElement element = Streams.parse(jsonReader); - - if (!element.isJsonNull() && jsonReader.peek() != JsonToken.END_DOCUMENT) - { - throw new JsonSyntaxException("Did not consume the entire document."); - } - - return element; - } - catch (Exception ex) - { - return null; - } - } - - public static String removeAllWhitespace(String s) - { - char[] chars = new char[s.length()]; - int j = 0; - - for (int i = 0; i < chars.length; i++) - { - char c = s.charAt(i); - - if (c > ' ') - { - chars[j] = c; - j++; - } - } - - return new String(chars, 0, j); - } -} \ No newline at end of file +public class BackupUtils { + public static final long KB = 1024L; + public static final long MB = KB * 1024L; + public static final long GB = MB * 1024L; + public static final long TB = GB * 1024L; + + public static final double KB_D = 1024D; + public static final double MB_D = KB_D * 1024D; + public static final double GB_D = MB_D * 1024D; + public static final double TB_D = GB_D * 1024D; + + public static String getTimeString(long millis) { + boolean neg = false; + if (millis < 0L) { + neg = true; + millis = -millis; + } + + StringBuilder sb = new StringBuilder(); + + if (millis < 1000L) { + if (neg) { + sb.append('-'); + } + + sb.append(millis); + sb.append('m'); + sb.append('s'); + return sb.toString(); + } + + long secs = millis / 1000L; + + if (neg) { + sb.append('-'); + } + + long h = (secs / 3600L) % 24; + long m = (secs / 60L) % 60L; + long s = secs % 60L; + + if (secs >= 86400L) { + sb.append(secs / 86400L); + sb.append('d'); + sb.append(' '); + } + + if (h > 0 || secs >= 86400L) { + if (h < 10) { + sb.append('0'); + } + sb.append(h); + //sb.append("h "); + sb.append(':'); + } + + if (m < 10) { + sb.append('0'); + } + sb.append(m); + //sb.append("m "); + sb.append(':'); + if (s < 10) { + sb.append('0'); + } + sb.append(s); + //sb.append('s'); + + return sb.toString(); + } + + public static long getSize(File file) { + if (!file.exists()) { + return 0L; + } else if (file.isFile()) { + return file.length(); + } else if (file.isDirectory()) { + long length = 0L; + File[] f1 = file.listFiles(); + if (f1 != null && f1.length > 0) { + for (File aF1 : f1) { + length += getSize(aF1); + } + } + return length; + } + return 0L; + } + + public static String getSizeString(double b) { + if (b >= TB_D) { + return String.format("%.1fTB", b / TB_D); + } else if (b >= GB_D) { + return String.format("%.1fGB", b / GB_D); + } else if (b >= MB_D) { + return String.format("%.1fMB", b / MB_D); + } else if (b >= KB_D) { + return String.format("%.1fKB", b / KB_D); + } + + return ((long) b) + "B"; + } + + public static String getSizeString(File file) { + return getSizeString(getSize(file)); + } + + public static File newFile(File file) { + if (!file.exists()) { + try { + File parent = file.getParentFile(); + if (!parent.exists()) { + parent.mkdirs(); + } + file.createNewFile(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + return file; + } + + public static List listTree(File file) { + List l = new ArrayList<>(); + listTree0(l, file); + return l; + } + + public static void listTree0(List list, File file) { + if (file.isDirectory()) { + File[] fl = file.listFiles(); + + if (fl != null && fl.length > 0) { + for (File aFl : fl) { + listTree0(list, aFl); + } + } + } else if (file.isFile()) { + list.add(file); + } + } + + public static void copyFile(File src, File dst) throws Exception { + if (src.exists() && !src.equals(dst)) { + if (src.isDirectory() && dst.isDirectory()) { + for (File f : listTree(src)) { + File dst1 = new File(dst.getAbsolutePath() + File.separatorChar + (f.getAbsolutePath().replace(src.getAbsolutePath(), ""))); + copyFile(f, dst1); + } + } else { + dst = newFile(dst); + + try (FileInputStream fis = new FileInputStream(src); + FileOutputStream fos = new FileOutputStream(dst); + FileChannel srcC = fis.getChannel(); + FileChannel dstC = fos.getChannel()) { + dstC.transferFrom(srcC, 0L, srcC.size()); + } + } + } + } + + public static boolean delete(File file) { + if (!file.exists()) { + return false; + } else if (file.isFile()) { + return file.delete(); + } + + String[] files = file.list(); + + if (files != null) { + for (String s : files) { + delete(new File(file, s)); + } + } + + return file.delete(); + } + + public static void toJson(Writer writer, @Nullable JsonElement element, boolean prettyPrinting) { + if (element == null || element.isJsonNull()) { + try { + writer.write("null"); + } catch (Exception ex) { + ex.printStackTrace(); + } + + return; + } + + JsonWriter jsonWriter = new JsonWriter(writer); + jsonWriter.setLenient(true); + jsonWriter.setHtmlSafe(false); + jsonWriter.setSerializeNulls(true); + + if (prettyPrinting) { + jsonWriter.setIndent("\t"); + } + + try { + Streams.write(element, jsonWriter); + } catch (Exception ex) { + throw new JsonIOException(ex); + } + } + + public static void toJson(File file, @Nullable JsonElement element, boolean prettyPrinting) { + try (OutputStreamWriter output = new OutputStreamWriter(new FileOutputStream(newFile(file)), StandardCharsets.UTF_8); + BufferedWriter writer = new BufferedWriter(output)) { + toJson(writer, element, prettyPrinting); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public static JsonElement readJson(File file) { + if (!file.exists()) { + return JsonNull.INSTANCE; + } + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) { + JsonReader jsonReader = new JsonReader(reader); + jsonReader.setLenient(true); + JsonElement element = Streams.parse(jsonReader); + + if (!element.isJsonNull() && jsonReader.peek() != JsonToken.END_DOCUMENT) { + throw new JsonSyntaxException("Did not consume the entire document."); + } + + return element; + } catch (Exception ex) { + return null; + } + } + + public static String removeAllWhitespace(String s) { + char[] chars = new char[s.length()]; + int j = 0; + + for (int i = 0; i < chars.length; i++) { + char c = s.charAt(i); + + if (c > ' ') { + chars[j] = c; + j++; + } + } + + return new String(chars, 0, j); + } +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java index 40296bc9..338ad3d9 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java @@ -26,445 +26,363 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -public enum Backups -{ - INSTANCE; - - public final List backups = new ArrayList<>(); - public File backupsFolder; - public long nextBackup = -1L; - public BackupStatus doingBackup = BackupStatus.NONE; - public boolean printFiles = false; - - public int currentFile = 0; - public int totalFiles = 0; - private String currentFileName = ""; - public boolean hadPlayersOnline = false; - - public void init(MinecraftServer server) - { - Path dataDir = server.getServerDirectory(); - backupsFolder = FTBBackupsConfig.folder.trim().isEmpty() ? FMLPaths.GAMEDIR.get().resolve("backups").toFile() : new File(FTBBackupsConfig.folder); - - try - { - backupsFolder = backupsFolder.getCanonicalFile(); - } - catch (Exception ex) - { - } - - doingBackup = BackupStatus.NONE; - backups.clear(); - - File file = new File(dataDir.toFile(), "local/ftbutilities/backups.json"); - - if (!file.exists()) - { - File oldFile = new File(backupsFolder, "backups.json"); - - if (oldFile.exists()) - { - oldFile.renameTo(file); - } - } - - JsonElement element = BackupUtils.readJson(file); - - if (element != null && element.isJsonArray()) - { - try - { - for (JsonElement e : element.getAsJsonArray()) - { - JsonObject json = e.getAsJsonObject(); - - if (!json.has("size")) - { - json.addProperty("size", BackupUtils.getSize(new File(backupsFolder, json.get("file").getAsString()))); - } - - backups.add(new Backup(json)); - } - } - catch (Throwable ex) - { - ex.printStackTrace(); - } - } - - FTBBackups.LOGGER.info("Backups folder - " + backupsFolder.getAbsolutePath()); - nextBackup = System.currentTimeMillis() + FTBBackupsConfig.backupTimer; - } - - public void tick(MinecraftServer server, long now) - { - if (nextBackup > 0L && nextBackup <= now) - { - if (!FTBBackupsConfig.onlyIfPlayersOnline || hadPlayersOnline || !server.getPlayerList().getPlayers().isEmpty()) - { - hadPlayersOnline = false; - run(server, true, Component.translatable("Server"), ""); - } - } - - if (doingBackup.isDone()) - { - doingBackup = BackupStatus.NONE; - - for (ServerLevel world : server.getAllLevels()) - { - if (world != null) - { - world.noSave = false; - } - } - - if (!FTBBackupsConfig.silent) - { - //TODO: Fix FTBBackupsNetHandler.MAIN.send(PacketDistributor.ALL.noArg(), new BackupProgressPacket(0, 0)); - } - } - else if (doingBackup.isRunning() && printFiles) - { - if (currentFile == 0 || currentFile == totalFiles - 1) - { - FTBBackups.LOGGER.info("[" + currentFile + " | " + (int) ((currentFile / (double) totalFiles) * 100D) + "%]: " + currentFileName); - } - - if (!FTBBackupsConfig.silent) - { - //TODO: Fix FTBBackupsNetHandler.MAIN.send(PacketDistributor.ALL.noArg(), new BackupProgressPacket(currentFile, totalFiles)); - } - } - } - - public void notifyAll(MinecraftServer server, Component component, boolean error) - { - component = component.plainCopy(); - component.getStyle().withColor(TextColor.fromLegacyFormat(error ? ChatFormatting.DARK_RED : ChatFormatting.LIGHT_PURPLE)); - FTBBackups.LOGGER.info(component.getString()); - server.getPlayerList().broadcastSystemMessage(component, true); - } - - public boolean run(MinecraftServer server, boolean auto, Component name, String customName) - { - if (doingBackup.isRunningOrDone()) - { - return false; - } - - if (auto && !FTBBackupsConfig.auto) - { - return false; - } - - notifyAll(server, Component.translatable("ftbbackups.lang.start", name), false); - nextBackup = System.currentTimeMillis() + FTBBackupsConfig.backupTimer; - - for (ServerLevel world : server.getAllLevels()) - { - if (world != null) - { - world.noSave = true; - } - } - - doingBackup = BackupStatus.RUNNING; - server.getPlayerList().saveAll(); - - new Thread(() -> { - try - { - try - { - createBackup(server, customName); - } - catch (Exception ex) - { - ex.printStackTrace(); - notifyAll(server, Component.translatable("ftbbackups.lang.saving_failed"), true); - } - } - catch (Exception ex) - { - notifyAll(server, Component.translatable("ftbbackups.lang.saving_failed"), true); - ex.printStackTrace(); - } - - doingBackup = BackupStatus.DONE; - }).start(); - - return true; - } - - private void createBackup(MinecraftServer server, String customName) - { - File src = server.getWorldPath(LevelResource.ROOT).toFile(); - - try - { - src = src.getCanonicalFile(); - } - catch (Exception ex) - { - } - - Calendar time = Calendar.getInstance(); - File dstFile; - boolean success = false; - StringBuilder out = new StringBuilder(); - - if (customName.isEmpty()) - { - appendNum(out, time.get(Calendar.YEAR), '-'); - appendNum(out, time.get(Calendar.MONTH) + 1, '-'); - appendNum(out, time.get(Calendar.DAY_OF_MONTH), '-'); - appendNum(out, time.get(Calendar.HOUR_OF_DAY), '-'); - appendNum(out, time.get(Calendar.MINUTE), '-'); - appendNum(out, time.get(Calendar.SECOND), '\0'); - } - else - { - out.append(customName); - } - - Exception error = null; - long fileSize = 0L; - - try - { - LinkedHashMap fileMap = new LinkedHashMap<>(); - String mcdir = server.getServerDirectory().getFileSystem().toString(); - - Consumer consumer = file0 -> { - for (File file : BackupUtils.listTree(file0)) - { - String s1 = file.getAbsolutePath().replace(mcdir, ""); - - if (s1.startsWith(File.separator)) - { - s1 = s1.substring(File.separator.length()); - } - - fileMap.put(file, "_extra_" + File.separator + s1); - } - }; - - for (String s : FTBBackupsConfig.extraFiles) - { - consumer.accept(new File(s)); - } - - NeoForge.EVENT_BUS.post(new BackupEvent.Pre(consumer)); - - for (File file : BackupUtils.listTree(src)) - { - if (file.getName().equals("session.lock") || !file.canRead()) - { - continue; - } - - String filePath = file.getAbsolutePath(); - fileMap.put(file, src.getName() + File.separator + filePath.substring(src.getAbsolutePath().length() + 1)); - } - - for (Map.Entry entry : fileMap.entrySet()) - { - fileSize += BackupUtils.getSize(entry.getKey()); - } - - long totalSize = 0L; - - if (!backups.isEmpty()) - { - backups.sort(null); - - int backupsToKeep = FTBBackupsConfig.backupsToKeep - 1; - - if (backupsToKeep > 0 && backups.size() > backupsToKeep) - { - while (backups.size() > backupsToKeep) - { - Backup backup = backups.remove(0); - FTBBackups.LOGGER.info("Deleting old backup: " + backup.fileId); - BackupUtils.delete(backup.getFile()); - } - } - - for (Backup backup : backups) - { - totalSize += backup.size; - } - - if (fileSize > 0L) - { - long freeSpace = Math.min(FTBBackupsConfig.maxTotalSize, backupsFolder.getFreeSpace()); - - while (totalSize + fileSize > freeSpace && !backups.isEmpty()) - { - Backup backup = backups.remove(0); - totalSize -= backup.size; - FTBBackups.LOGGER.info("Deleting backup to free space: " + backup.fileId); - BackupUtils.delete(backup.getFile()); - } - } - } - - totalFiles = fileMap.size(); - FTBBackups.LOGGER.info("Backing up " + totalFiles + " files..."); - printFiles = true; - - if (FTBBackupsConfig.compressionLevel > 0) - { - out.append(".zip"); - dstFile = BackupUtils.newFile(new File(backupsFolder, out.toString())); - - long start = System.currentTimeMillis(); - - ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dstFile)); - zos.setLevel(FTBBackupsConfig.compressionLevel); - - byte[] buffer = new byte[FTBBackupsConfig.bufferSize]; - - FTBBackups.LOGGER.info("Compressing " + totalFiles + " files..."); - - currentFile = 0; - for (Map.Entry entry : fileMap.entrySet()) - { - try - { - ZipEntry ze = new ZipEntry(entry.getValue()); - currentFileName = entry.getValue(); - - zos.putNextEntry(ze); - FileInputStream fis = new FileInputStream(entry.getKey()); - - int len; - while ((len = fis.read(buffer)) > 0) - { - zos.write(buffer, 0, len); - } - zos.closeEntry(); - fis.close(); - } - catch (Exception ex) - { - FTBBackups.LOGGER.error("Failed to read file " + entry.getValue() + ": " + ex); - } - - currentFile++; - } - - zos.close(); - fileSize = BackupUtils.getSize(dstFile); - FTBBackups.LOGGER.info("Done compressing in " + BackupUtils.getTimeString(System.currentTimeMillis() - start) + " seconds (" + BackupUtils.getSizeString(fileSize) + ")!"); - } - else - { - dstFile = new File(backupsFolder, out.toString()); - dstFile.mkdirs(); - - currentFile = 0; - for (Map.Entry entry : fileMap.entrySet()) - { - try - { - File file = entry.getKey(); - currentFileName = entry.getValue(); - File dst1 = new File(dstFile, entry.getValue()); - BackupUtils.copyFile(file, dst1); - } - catch (Exception ex) - { - FTBBackups.LOGGER.error("Failed to copy file " + entry.getValue() + ": " + ex); - } - - currentFile++; - } - } - - FTBBackups.LOGGER.info("Created " + dstFile.getAbsolutePath() + " from " + src.getAbsolutePath()); - success = true; - } - catch (Exception ex) - { - if (!FTBBackupsConfig.silent) - { - String errorName = ex.getClass().getName(); - notifyAll(server, Component.translatable("ftbbackups.lang.fail", errorName), true); - } - - ex.printStackTrace(); - error = ex; - } - - printFiles = false; - - Backup backup = new Backup(time.getTimeInMillis(), out.toString().replace('\\', '/'), getLastIndex() + 1, success, fileSize); - backups.add(backup); - NeoForge.EVENT_BUS.post(new BackupEvent.Post(backup, error)); - - JsonArray array = new JsonArray(); - - for (Backup backup1 : backups) - { - array.add(backup1.toJsonObject()); - } - - BackupUtils.toJson(new File(server.getServerDirectory().toFile(), "local/ftbutilities/backups.json"), array, true); - - if (error == null && !FTBBackupsConfig.silent) - { - String timeString = BackupUtils.getTimeString(System.currentTimeMillis() - time.getTimeInMillis()); - Component component; - - if (FTBBackupsConfig.displayFileSize) - { - long totalSize = 0L; - - for (Backup backup1 : backups) - { - totalSize += backup1.size; - } - - String sizeB = BackupUtils.getSizeString(fileSize); - String sizeT = BackupUtils.getSizeString(totalSize); - String sizeString = sizeB.equals(sizeT) ? sizeB : (sizeB + " | " + sizeT); - component = Component.translatable("ftbbackups.lang.end_2", timeString, sizeString); - } - else - { - component = Component.translatable("ftbbackups.lang.end_1", timeString); - } - - component.getStyle().withColor(TextColor.fromLegacyFormat(ChatFormatting.LIGHT_PURPLE)); - server.getPlayerList().broadcastSystemMessage(component, true); - } - } - - private void appendNum(StringBuilder sb, int num, char c) - { - if (num < 10) - { - sb.append('0'); - } - sb.append(num); - if (c != '\0') - { - sb.append(c); - } - } - - private int getLastIndex() - { - int i = 0; - - for (Backup b : backups) - { - i = Math.max(i, b.index); - } - - return i; - } -} \ No newline at end of file +public enum Backups { + INSTANCE; + + public final List backups = new ArrayList<>(); + public File backupsFolder; + public long nextBackup = -1L; + public BackupStatus doingBackup = BackupStatus.NONE; + public boolean printFiles = false; + + public int currentFile = 0; + public int totalFiles = 0; + private String currentFileName = ""; + public boolean hadPlayersOnline = false; + + public void init(MinecraftServer server) { + Path dataDir = server.getServerDirectory(); + backupsFolder = FTBBackupsConfig.folder.trim().isEmpty() ? FMLPaths.GAMEDIR.get().resolve("backups").toFile() : new File(FTBBackupsConfig.folder); + + try { + backupsFolder = backupsFolder.getCanonicalFile(); + } catch (Exception ex) { + } + + doingBackup = BackupStatus.NONE; + backups.clear(); + + File file = new File(dataDir.toFile(), "local/ftbutilities/backups.json"); + + if (!file.exists()) { + File oldFile = new File(backupsFolder, "backups.json"); + + if (oldFile.exists()) { + oldFile.renameTo(file); + } + } + + JsonElement element = BackupUtils.readJson(file); + + if (element != null && element.isJsonArray()) { + try { + for (JsonElement e : element.getAsJsonArray()) { + JsonObject json = e.getAsJsonObject(); + + if (!json.has("size")) { + json.addProperty("size", BackupUtils.getSize(new File(backupsFolder, json.get("file").getAsString()))); + } + + backups.add(new Backup(json)); + } + } catch (Throwable ex) { + ex.printStackTrace(); + } + } + + FTBBackups.LOGGER.info("Backups folder - " + backupsFolder.getAbsolutePath()); + nextBackup = System.currentTimeMillis() + FTBBackupsConfig.backupTimer; + } + + public void tick(MinecraftServer server, long now) { + if (nextBackup > 0L && nextBackup <= now) { + if (!FTBBackupsConfig.onlyIfPlayersOnline || hadPlayersOnline || !server.getPlayerList().getPlayers().isEmpty()) { + hadPlayersOnline = false; + run(server, true, Component.translatable("Server"), ""); + } + } + + if (doingBackup.isDone()) { + doingBackup = BackupStatus.NONE; + + for (ServerLevel world : server.getAllLevels()) { + if (world != null) { + world.noSave = false; + } + } + + if (!FTBBackupsConfig.silent) { + //TODO: Fix FTBBackupsNetHandler.MAIN.send(PacketDistributor.ALL.noArg(), new BackupProgressPacket(0, 0)); + } + } else if (doingBackup.isRunning() && printFiles) { + if (currentFile == 0 || currentFile == totalFiles - 1) { + FTBBackups.LOGGER.info("[" + currentFile + " | " + (int) ((currentFile / (double) totalFiles) * 100D) + "%]: " + currentFileName); + } + + if (!FTBBackupsConfig.silent) { + //TODO: Fix FTBBackupsNetHandler.MAIN.send(PacketDistributor.ALL.noArg(), new BackupProgressPacket(currentFile, totalFiles)); + } + } + } + + public void notifyAll(MinecraftServer server, Component component, boolean error) { + component = component.plainCopy(); + component.getStyle().withColor(TextColor.fromLegacyFormat(error ? ChatFormatting.DARK_RED : ChatFormatting.LIGHT_PURPLE)); + FTBBackups.LOGGER.info(component.getString()); + server.getPlayerList().broadcastSystemMessage(component, true); + } + + public boolean run(MinecraftServer server, boolean auto, Component name, String customName) { + if (doingBackup.isRunningOrDone()) { + return false; + } + + if (auto && !FTBBackupsConfig.auto) { + return false; + } + + notifyAll(server, Component.translatable("ftbbackups.lang.start", name), false); + nextBackup = System.currentTimeMillis() + FTBBackupsConfig.backupTimer; + + for (ServerLevel world : server.getAllLevels()) { + if (world != null) { + world.noSave = true; + } + } + + doingBackup = BackupStatus.RUNNING; + server.getPlayerList().saveAll(); + + new Thread(() -> { + try { + try { + createBackup(server, customName); + } catch (Exception ex) { + ex.printStackTrace(); + notifyAll(server, Component.translatable("ftbbackups.lang.saving_failed"), true); + } + } catch (Exception ex) { + notifyAll(server, Component.translatable("ftbbackups.lang.saving_failed"), true); + ex.printStackTrace(); + } + + doingBackup = BackupStatus.DONE; + }).start(); + + return true; + } + + private void createBackup(MinecraftServer server, String customName) { + File src = server.getWorldPath(LevelResource.ROOT).toFile(); + + try { + src = src.getCanonicalFile(); + } catch (Exception ex) { + } + + Calendar time = Calendar.getInstance(); + File dstFile; + boolean success = false; + StringBuilder out = new StringBuilder(); + + if (customName.isEmpty()) { + appendNum(out, time.get(Calendar.YEAR), '-'); + appendNum(out, time.get(Calendar.MONTH) + 1, '-'); + appendNum(out, time.get(Calendar.DAY_OF_MONTH), '-'); + appendNum(out, time.get(Calendar.HOUR_OF_DAY), '-'); + appendNum(out, time.get(Calendar.MINUTE), '-'); + appendNum(out, time.get(Calendar.SECOND), '\0'); + } else { + out.append(customName); + } + + Exception error = null; + long fileSize = 0L; + + try { + LinkedHashMap fileMap = new LinkedHashMap<>(); + String mcdir = server.getServerDirectory().getFileSystem().toString(); + + Consumer consumer = file0 -> { + for (File file : BackupUtils.listTree(file0)) { + String s1 = file.getAbsolutePath().replace(mcdir, ""); + + if (s1.startsWith(File.separator)) { + s1 = s1.substring(File.separator.length()); + } + + fileMap.put(file, "_extra_" + File.separator + s1); + } + }; + + for (String s : FTBBackupsConfig.extraFiles) { + consumer.accept(new File(s)); + } + + NeoForge.EVENT_BUS.post(new BackupEvent.Pre(consumer)); + + for (File file : BackupUtils.listTree(src)) { + if (file.getName().equals("session.lock") || !file.canRead()) { + continue; + } + + String filePath = file.getAbsolutePath(); + fileMap.put(file, src.getName() + File.separator + filePath.substring(src.getAbsolutePath().length() + 1)); + } + + for (Map.Entry entry : fileMap.entrySet()) { + fileSize += BackupUtils.getSize(entry.getKey()); + } + + long totalSize = 0L; + + if (!backups.isEmpty()) { + backups.sort(null); + + int backupsToKeep = FTBBackupsConfig.backupsToKeep - 1; + + if (backupsToKeep > 0 && backups.size() > backupsToKeep) { + while (backups.size() > backupsToKeep) { + Backup backup = backups.remove(0); + FTBBackups.LOGGER.info("Deleting old backup: " + backup.fileId); + BackupUtils.delete(backup.getFile()); + } + } + + for (Backup backup : backups) { + totalSize += backup.size; + } + + if (fileSize > 0L) { + long freeSpace = Math.min(FTBBackupsConfig.maxTotalSize, backupsFolder.getFreeSpace()); + + while (totalSize + fileSize > freeSpace && !backups.isEmpty()) { + Backup backup = backups.remove(0); + totalSize -= backup.size; + FTBBackups.LOGGER.info("Deleting backup to free space: " + backup.fileId); + BackupUtils.delete(backup.getFile()); + } + } + } + + totalFiles = fileMap.size(); + FTBBackups.LOGGER.info("Backing up " + totalFiles + " files..."); + printFiles = true; + + if (FTBBackupsConfig.compressionLevel > 0) { + out.append(".zip"); + dstFile = BackupUtils.newFile(new File(backupsFolder, out.toString())); + + long start = System.currentTimeMillis(); + + ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dstFile)); + zos.setLevel(FTBBackupsConfig.compressionLevel); + + byte[] buffer = new byte[FTBBackupsConfig.bufferSize]; + + FTBBackups.LOGGER.info("Compressing " + totalFiles + " files..."); + + currentFile = 0; + for (Map.Entry entry : fileMap.entrySet()) { + try { + ZipEntry ze = new ZipEntry(entry.getValue()); + currentFileName = entry.getValue(); + + zos.putNextEntry(ze); + FileInputStream fis = new FileInputStream(entry.getKey()); + + int len; + while ((len = fis.read(buffer)) > 0) { + zos.write(buffer, 0, len); + } + zos.closeEntry(); + fis.close(); + } catch (Exception ex) { + FTBBackups.LOGGER.error("Failed to read file " + entry.getValue() + ": " + ex); + } + + currentFile++; + } + + zos.close(); + fileSize = BackupUtils.getSize(dstFile); + FTBBackups.LOGGER.info("Done compressing in " + BackupUtils.getTimeString(System.currentTimeMillis() - start) + " seconds (" + BackupUtils.getSizeString(fileSize) + ")!"); + } else { + dstFile = new File(backupsFolder, out.toString()); + dstFile.mkdirs(); + + currentFile = 0; + for (Map.Entry entry : fileMap.entrySet()) { + try { + File file = entry.getKey(); + currentFileName = entry.getValue(); + File dst1 = new File(dstFile, entry.getValue()); + BackupUtils.copyFile(file, dst1); + } catch (Exception ex) { + FTBBackups.LOGGER.error("Failed to copy file " + entry.getValue() + ": " + ex); + } + + currentFile++; + } + } + + FTBBackups.LOGGER.info("Created " + dstFile.getAbsolutePath() + " from " + src.getAbsolutePath()); + success = true; + } catch (Exception ex) { + if (!FTBBackupsConfig.silent) { + String errorName = ex.getClass().getName(); + notifyAll(server, Component.translatable("ftbbackups.lang.fail", errorName), true); + } + + ex.printStackTrace(); + error = ex; + } + + printFiles = false; + + Backup backup = new Backup(time.getTimeInMillis(), out.toString().replace('\\', '/'), getLastIndex() + 1, success, fileSize); + backups.add(backup); + NeoForge.EVENT_BUS.post(new BackupEvent.Post(backup, error)); + + JsonArray array = new JsonArray(); + + for (Backup backup1 : backups) { + array.add(backup1.toJsonObject()); + } + + BackupUtils.toJson(new File(server.getServerDirectory().toFile(), "local/ftbutilities/backups.json"), array, true); + + if (error == null && !FTBBackupsConfig.silent) { + String timeString = BackupUtils.getTimeString(System.currentTimeMillis() - time.getTimeInMillis()); + Component component; + + if (FTBBackupsConfig.displayFileSize) { + long totalSize = 0L; + + for (Backup backup1 : backups) { + totalSize += backup1.size; + } + + String sizeB = BackupUtils.getSizeString(fileSize); + String sizeT = BackupUtils.getSizeString(totalSize); + String sizeString = sizeB.equals(sizeT) ? sizeB : (sizeB + " | " + sizeT); + component = Component.translatable("ftbbackups.lang.end_2", timeString, sizeString); + } else { + component = Component.translatable("ftbbackups.lang.end_1", timeString); + } + + component.getStyle().withColor(TextColor.fromLegacyFormat(ChatFormatting.LIGHT_PURPLE)); + server.getPlayerList().broadcastSystemMessage(component, true); + } + } + + private void appendNum(StringBuilder sb, int num, char c) { + if (num < 10) { + sb.append('0'); + } + sb.append(num); + if (c != '\0') { + sb.append(c); + } + } + + private int getLastIndex() { + int i = 0; + + for (Backup b : backups) { + i = Math.max(i, b.index); + } + + return i; + } +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java index bfb77055..a71e4c34 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java @@ -26,69 +26,59 @@ import org.apache.logging.log4j.Logger; @Mod(FTBBackups.MOD_ID) -public class FTBBackups -{ - public static final String MOD_ID = "ftbbackups"; - public static final Logger LOGGER = LogManager.getLogger("FTB Utilities Backups"); +public class FTBBackups { + public static final String MOD_ID = "ftbbackups"; + public static final Logger LOGGER = LogManager.getLogger("FTB Utilities Backups"); - public FTBBackups(IEventBus eventBus, ModContainer container) - { - eventBus.addListener(this::clientSetup); - NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverAboutToStart); - NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::registerCommands); - NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverStopping); - NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedIn); - NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedOut); - NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::worldTick); + public FTBBackups(IEventBus eventBus, ModContainer container) { + eventBus.addListener(this::clientSetup); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverAboutToStart); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::registerCommands); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverStopping); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedIn); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedOut); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::worldTick); // FTBBackupsNetHandler.init(); - FTBBackupsConfig.register(eventBus, container); - //ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true)); - } - private void clientSetup(FMLClientSetupEvent event) - { - FTBBackupsClient.init(); - } + FTBBackupsConfig.register(eventBus, container); + //ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true)); + } - public void serverAboutToStart(ServerAboutToStartEvent event) - { - Backups.INSTANCE.init(event.getServer()); - } + private void clientSetup(FMLClientSetupEvent event) { + FTBBackupsClient.init(); + } - public void registerCommands(RegisterCommandsEvent event) - { - BackupCommands.register(event.getDispatcher()); - } + public void serverAboutToStart(ServerAboutToStartEvent event) { + Backups.INSTANCE.init(event.getServer()); + } - public void serverStopping(ServerStoppingEvent event) - { - if (FTBBackupsConfig.forceOnShutdown) - { - Backups.INSTANCE.run(event.getServer(), true, Component.literal("Server"), ""); - } - } + public void registerCommands(RegisterCommandsEvent event) { + BackupCommands.register(event.getDispatcher()); + } - public void playerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) - { - if (event.getEntity() instanceof ServerPlayer) - { - //TODO: Send Packets - //FTBBackupsNetHandler.MAIN.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) event.getEntity()), new BackupProgressPacket(Backups.INSTANCE.currentFile, Backups.INSTANCE.totalFiles)); - } - } + public void serverStopping(ServerStoppingEvent event) { + if (FTBBackupsConfig.forceOnShutdown) { + Backups.INSTANCE.run(event.getServer(), true, Component.literal("Server"), ""); + } + } - public void playerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) - { - if (event.getEntity() instanceof ServerPlayer) - { - Backups.INSTANCE.hadPlayersOnline = true; - } - } + public void playerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { + if (event.getEntity() instanceof ServerPlayer) { + //TODO: Send Packets + //FTBBackupsNetHandler.MAIN.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) event.getEntity()), new BackupProgressPacket(Backups.INSTANCE.currentFile, Backups.INSTANCE.totalFiles)); + } + } - public void worldTick(ServerTickEvent.Post event) { + public void playerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) { + if (event.getEntity() instanceof ServerPlayer) { + Backups.INSTANCE.hadPlayersOnline = true; + } + } + + public void worldTick(ServerTickEvent.Post event) { /*if (event.phase != ServerTickEvent.PostTickEvent.Phase.START && !event.side.isClient()) { Backups.INSTANCE.tick(event.getServer(), System.currentTimeMillis()); }*/ - //I Guess this is just called server side now?? - Backups.INSTANCE.tick(event.getServer(), System.currentTimeMillis()); - } -} \ No newline at end of file + //I Guess this is just called server side now?? + Backups.INSTANCE.tick(event.getServer(), System.currentTimeMillis()); + } +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java index 68148b91..b55b96fa 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java @@ -7,22 +7,19 @@ import net.neoforged.neoforge.client.event.RenderGuiLayerEvent; import net.neoforged.neoforge.common.NeoForge; -public class FTBBackupsClient -{ - private static int currentBackupFile = 0; - private static int totalBackupFiles = 0; +public class FTBBackupsClient { + private static int currentBackupFile = 0; + private static int totalBackupFiles = 0; - public static void init() - { - //MinecraftForge.EVENT_BUS.addListener(FTBBackupsClient::onClientDisconnected); - NeoForge.EVENT_BUS.addListener(FTBBackupsClient::onDebugInfoEvent); - } + public static void init() { + //MinecraftForge.EVENT_BUS.addListener(FTBBackupsClient::onClientDisconnected); + NeoForge.EVENT_BUS.addListener(FTBBackupsClient::onDebugInfoEvent); + } - public static void setFiles(int current, int total) - { - currentBackupFile = current; - totalBackupFiles = total; - } + public static void setFiles(int current, int total) { + currentBackupFile = current; + totalBackupFiles = total; + } /*private static void onClientDisconnected(FMLNetworkEvent.ClientDisconnectionFromServerEvent event) { @@ -30,17 +27,14 @@ public static void setFiles(int current, int total) totalBackupFiles = 0; }*/ - private static void onDebugInfoEvent(RenderGuiLayerEvent.Post event) - { - OptionInstance booleanOptionInstance = Minecraft.getInstance().options.reducedDebugInfo(); - if (booleanOptionInstance.get()) - { - return; - } + private static void onDebugInfoEvent(RenderGuiLayerEvent.Post event) { + OptionInstance booleanOptionInstance = Minecraft.getInstance().options.reducedDebugInfo(); + if (booleanOptionInstance.get()) { + return; + } - if (totalBackupFiles > 0 && totalBackupFiles > currentBackupFile) - { - //event.getLeft().add(ChatFormatting.LIGHT_PURPLE + I18n.get("ftbbackups.lang.timer_progress", currentBackupFile * 100 / totalBackupFiles, currentBackupFile, totalBackupFiles)); - } - } -} \ No newline at end of file + if (totalBackupFiles > 0 && totalBackupFiles > currentBackupFile) { + //event.getLeft().add(ChatFormatting.LIGHT_PURPLE + I18n.get("ftbbackups.lang.timer_progress", currentBackupFile * 100 / totalBackupFiles, currentBackupFile, totalBackupFiles)); + } + } +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java index b4ad4142..91ea6411 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java @@ -1,7 +1,6 @@ package dev.ftb.mods.ftbbackups; - import net.neoforged.fml.ModContainer; import org.apache.commons.lang3.tuple.Pair; import net.neoforged.bus.api.IEventBus; @@ -13,181 +12,162 @@ import java.util.ArrayList; import java.util.List; -/** - * @author LatvianModder - */ -public class FTBBackupsConfig -{ - public static boolean auto; - public static boolean silent; - public static int backupsToKeep; - public static long backupTimer; - public static int compressionLevel; - public static String folder; - public static boolean displayFileSize; - public static List extraFiles; - public static long maxTotalSize; - public static boolean onlyIfPlayersOnline; - public static boolean forceOnShutdown; - public static int bufferSize; - - private static Pair common; - - public static void register(IEventBus eventBus, ModContainer container) - { - eventBus.addListener(FTBBackupsConfig::reload); - - common = new ModConfigSpec.Builder().configure(CommonConfig::new); - - container.registerConfig(ModConfig.Type.COMMON, common.getRight()); - } - - public static void reload(ModConfigEvent event) - { - ModConfig config = event.getConfig(); - - if (config.getSpec() == common.getRight()) - { - reloadCommon(); - } - } - - public static void reloadCommon() - { - CommonConfig cfg = common.getLeft(); - auto = cfg.auto.get(); - silent = cfg.silent.get(); - backupsToKeep = cfg.backupsToKeep.get(); - backupTimer = cfg.backupTimer.get() * 60000L; - compressionLevel = cfg.compressionLevel.get(); - folder = cfg.folder.get(); - displayFileSize = cfg.displayFileSize.get(); - extraFiles = new ArrayList<>(cfg.extraFiles.get()); - - String mts = cfg.maxTotalSize.get(); - - long l = Long.parseLong(mts.substring(0, mts.length() - 2).trim()); - if (mts.endsWith("TB")) - { - maxTotalSize = l * BackupUtils.TB; - } - else if (mts.endsWith("GB")) - { - maxTotalSize = l * BackupUtils.GB; - } - else if (mts.endsWith("MB")) - { - maxTotalSize = l * BackupUtils.MB; - } - else if (mts.endsWith("KB")) - { - maxTotalSize = l * BackupUtils.KB; - } - else - { - maxTotalSize = Long.parseLong(mts.trim()); - } - - onlyIfPlayersOnline = cfg.onlyIfPlayersOnline.get(); - forceOnShutdown = cfg.forceOnShutdown.get(); - bufferSize = cfg.bufferSize.get(); - } - - private static class CommonConfig - { - //ModConfigSpec or NeoForgeConfig - private final ModConfigSpec.BooleanValue auto; - private final ModConfigSpec.BooleanValue silent; - private final ModConfigSpec.IntValue backupsToKeep; - private final ModConfigSpec.IntValue backupTimer; - private final ModConfigSpec.IntValue compressionLevel; - private final ModConfigSpec.ConfigValue folder; - private final ModConfigSpec.BooleanValue displayFileSize; - private final ModConfigSpec.ConfigValue> extraFiles; - private final ModConfigSpec.ConfigValue maxTotalSize; - private final ModConfigSpec.BooleanValue onlyIfPlayersOnline; - private final ModConfigSpec.BooleanValue forceOnShutdown; - private final ModConfigSpec.IntValue bufferSize; - - private CommonConfig(ModConfigSpec.Builder builder) - { - auto = builder - .comment("Enables backups to run automatically.") - .translation("ftbbackups.general.auto") - .define("auto", true); - - silent = builder - .comment("If set to true, no messages will be displayed in chat/status bar.") - .translation("ftbbackups.general.silent") - .define("silent", false); - - backupsToKeep = builder - .comment( - "The number of backup files to keep.", - "More backups = more space used", - "0 - Infinite" - ) - .translation("ftbbackups.general.backups_to_keep") - .defineInRange("backups_to_keep", 12, 0, 32000); - - backupTimer = builder - .comment( - "Timer in minutes.", - "5 - backups every 5 minutes", - "60 - backups every hour", - "360 - backups every 6 hours", - "1440 - backups once every day" - ) - .translation("ftbbackups.general.backup_timer") - .defineInRange("backup_timer", 120, 1, 43800); - - compressionLevel = builder - .comment( - "0 - Disabled (output = folders)", - "1 - Best speed", - "9 - Smallest file size" - ) - .translation("ftbbackups.general.compression_level") - .defineInRange("compression_level", 1, 0, 9); - - folder = builder - .comment("Absolute path to backups folder.") - .translation("ftbbackups.general.folder") - .define("folder", ""); - - displayFileSize = builder - .comment("Prints (current size | total size) when backup is done.") - .translation("ftbbackups.general.display_file_size") - .define("display_file_size", true); - - extraFiles = builder - .comment("Add extra files that will be placed in backup _extra_/ folder.") - .translation("ftbbackups.general.extra_files") - .defineList("extra_files", new ArrayList<>(), o -> true); - - maxTotalSize = builder - .comment("Maximum total size that is allowed in backups folder. Older backups will be deleted to free space for newer ones.") - .translation("ftbbackups.general.max_total_size") - .define("max_total_size", "50 GB"); - - onlyIfPlayersOnline = builder - .comment("Only create backups when players have been online.") - .translation("ftbbackups.general.only_if_players_online") - .define("only_if_players_online", true); - - forceOnShutdown = builder - .comment("Create a backup when server is stopped.") - .translation("ftbbackups.general.force_on_shutdown") - .define("force_on_shutdown", false); - - builder.comment("Advanced features that shouldn't be changed unless you know what you are doing.").push("advanced"); - - bufferSize = builder - .comment("Buffer size for writing files.") - .translation("ftbbackups.general.buffer_size") - .defineInRange("buffer_size", 4096, 256, 65536); - - builder.pop(); - } - } -} \ No newline at end of file +public class FTBBackupsConfig { + public static boolean auto; + public static boolean silent; + public static int backupsToKeep; + public static long backupTimer; + public static int compressionLevel; + public static String folder; + public static boolean displayFileSize; + public static List extraFiles; + public static long maxTotalSize; + public static boolean onlyIfPlayersOnline; + public static boolean forceOnShutdown; + public static int bufferSize; + + private static Pair common; + + public static void register(IEventBus eventBus, ModContainer container) { + eventBus.addListener(FTBBackupsConfig::reload); + + common = new ModConfigSpec.Builder().configure(CommonConfig::new); + + container.registerConfig(ModConfig.Type.COMMON, common.getRight()); + } + + public static void reload(ModConfigEvent event) { + ModConfig config = event.getConfig(); + + if (config.getSpec() == common.getRight()) { + reloadCommon(); + } + } + + public static void reloadCommon() { + CommonConfig cfg = common.getLeft(); + auto = cfg.auto.get(); + silent = cfg.silent.get(); + backupsToKeep = cfg.backupsToKeep.get(); + backupTimer = cfg.backupTimer.get() * 60000L; + compressionLevel = cfg.compressionLevel.get(); + folder = cfg.folder.get(); + displayFileSize = cfg.displayFileSize.get(); + extraFiles = new ArrayList<>(cfg.extraFiles.get()); + + String mts = cfg.maxTotalSize.get(); + + long l = Long.parseLong(mts.substring(0, mts.length() - 2).trim()); + if (mts.endsWith("TB")) { + maxTotalSize = l * BackupUtils.TB; + } else if (mts.endsWith("GB")) { + maxTotalSize = l * BackupUtils.GB; + } else if (mts.endsWith("MB")) { + maxTotalSize = l * BackupUtils.MB; + } else if (mts.endsWith("KB")) { + maxTotalSize = l * BackupUtils.KB; + } else { + maxTotalSize = Long.parseLong(mts.trim()); + } + + onlyIfPlayersOnline = cfg.onlyIfPlayersOnline.get(); + forceOnShutdown = cfg.forceOnShutdown.get(); + bufferSize = cfg.bufferSize.get(); + } + + private static class CommonConfig { + //ModConfigSpec or NeoForgeConfig + private final ModConfigSpec.BooleanValue auto; + private final ModConfigSpec.BooleanValue silent; + private final ModConfigSpec.IntValue backupsToKeep; + private final ModConfigSpec.IntValue backupTimer; + private final ModConfigSpec.IntValue compressionLevel; + private final ModConfigSpec.ConfigValue folder; + private final ModConfigSpec.BooleanValue displayFileSize; + private final ModConfigSpec.ConfigValue> extraFiles; + private final ModConfigSpec.ConfigValue maxTotalSize; + private final ModConfigSpec.BooleanValue onlyIfPlayersOnline; + private final ModConfigSpec.BooleanValue forceOnShutdown; + private final ModConfigSpec.IntValue bufferSize; + + private CommonConfig(ModConfigSpec.Builder builder) { + auto = builder + .comment("Enables backups to run automatically.") + .translation("ftbbackups.general.auto") + .define("auto", true); + + silent = builder + .comment("If set to true, no messages will be displayed in chat/status bar.") + .translation("ftbbackups.general.silent") + .define("silent", false); + + backupsToKeep = builder + .comment( + "The number of backup files to keep.", + "More backups = more space used", + "0 - Infinite" + ) + .translation("ftbbackups.general.backups_to_keep") + .defineInRange("backups_to_keep", 12, 0, 32000); + + backupTimer = builder + .comment( + "Timer in minutes.", + "5 - backups every 5 minutes", + "60 - backups every hour", + "360 - backups every 6 hours", + "1440 - backups once every day" + ) + .translation("ftbbackups.general.backup_timer") + .defineInRange("backup_timer", 120, 1, 43800); + + compressionLevel = builder + .comment( + "0 - Disabled (output = folders)", + "1 - Best speed", + "9 - Smallest file size" + ) + .translation("ftbbackups.general.compression_level") + .defineInRange("compression_level", 1, 0, 9); + + folder = builder + .comment("Absolute path to backups folder.") + .translation("ftbbackups.general.folder") + .define("folder", ""); + + displayFileSize = builder + .comment("Prints (current size | total size) when backup is done.") + .translation("ftbbackups.general.display_file_size") + .define("display_file_size", true); + + extraFiles = builder + .comment("Add extra files that will be placed in backup _extra_/ folder.") + .translation("ftbbackups.general.extra_files") + .defineList("extra_files", new ArrayList<>(), o -> true); + + maxTotalSize = builder + .comment("Maximum total size that is allowed in backups folder. Older backups will be deleted to free space for newer ones.") + .translation("ftbbackups.general.max_total_size") + .define("max_total_size", "50 GB"); + + onlyIfPlayersOnline = builder + .comment("Only create backups when players have been online.") + .translation("ftbbackups.general.only_if_players_online") + .define("only_if_players_online", true); + + forceOnShutdown = builder + .comment("Create a backup when server is stopped.") + .translation("ftbbackups.general.force_on_shutdown") + .define("force_on_shutdown", false); + + builder.comment("Advanced features that shouldn't be changed unless you know what you are doing.").push("advanced"); + + bufferSize = builder + .comment("Buffer size for writing files.") + .translation("ftbbackups.general.buffer_size") + .defineInRange("buffer_size", 4096, 256, 65536); + + builder.pop(); + } + } +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java b/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java index 95ab08f7..25a34620 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java @@ -3,35 +3,28 @@ import dev.ftb.mods.ftbbackups.FTBBackupsClient; import net.minecraft.network.FriendlyByteBuf; -/** - * @author LatvianModder - */ -public class BackupProgressPacket -{ - private int current, total; +public class BackupProgressPacket { + private int current, total; - public BackupProgressPacket(int c, int t) - { - current = c; - total = t; - } + public BackupProgressPacket(int c, int t) { + current = c; + total = t; + } - public BackupProgressPacket(FriendlyByteBuf buf) - { - total = buf.readVarInt(); - current = buf.readVarInt(); - } + public BackupProgressPacket(FriendlyByteBuf buf) { + total = buf.readVarInt(); + current = buf.readVarInt(); + } - public void write(FriendlyByteBuf buf) - { - buf.writeVarInt(total); - buf.writeVarInt(current); - } + public void write(FriendlyByteBuf buf) { + buf.writeVarInt(total); + buf.writeVarInt(current); + } - //TODO This + //TODO This /*public void handle(Supplier context) { context.get().enqueueWork(() -> FTBBackupsClient.setFiles(current, total)); context.get().setPacketHandled(true); }*/ -} \ No newline at end of file +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/net/FTBBackupsNetHandler.java b/src/main/java/dev/ftb/mods/ftbbackups/net/FTBBackupsNetHandler.java index 14fccabf..8d2356f6 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/net/FTBBackupsNetHandler.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/net/FTBBackupsNetHandler.java @@ -5,10 +5,9 @@ import java.util.function.Predicate; -public class FTBBackupsNetHandler -{ +public class FTBBackupsNetHandler { - //TODO NetHandler + //TODO NetHandler /*public static SimpleChannel MAIN; private static final String MAIN_VERSION = "1"; @@ -25,4 +24,4 @@ public static void init() MAIN.registerMessage(1, BackupProgressPacket.class, BackupProgressPacket::write, BackupProgressPacket::new, BackupProgressPacket::handle); }*/ -} \ No newline at end of file +} From 170d4d72fcaec4c640e584941963beb1704db7b0 Mon Sep 17 00:00:00 2001 From: Michael Hillcox Date: Tue, 18 Mar 2025 23:35:25 +0000 Subject: [PATCH 14/42] feat: mod dev gradle, publishing, sourcejar, apijar, api package, etc --- .github/workflows/release.yml | 20 ++ LICENSE.md | 5 + README.md | 16 +- build.gradle | 175 ++++++++++++++---- gradle.properties | 23 ++- gradle/wrapper/gradle-wrapper.properties | 4 +- settings.gradle | 4 +- .../dev/ftb/mods/ftbbackups/BackupEvent.java | 1 - .../java/dev/ftb/mods/ftbbackups/Backups.java | 2 - .../dev/ftb/mods/ftbbackups/FTBBackups.java | 25 +-- .../ftb/mods/ftbbackups/FTBBackupsClient.java | 2 - .../ftb/mods/ftbbackups/api/BackupsApi.java | 12 ++ .../resources/META-INF/neoforge.mods.toml | 16 +- 13 files changed, 225 insertions(+), 80 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 LICENSE.md create mode 100644 src/main/java/dev/ftb/mods/ftbbackups/api/BackupsApi.java diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..51eb6d75 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,20 @@ +name: Java CI - Build Release + +on: + push: + tags: + - 'v[0-9]+\.[0-9]+\.[0-9]+' + - 'v[0-9]+\.[0-9]+\.[0-9]+-[a-z]+' + - 'v[0-9]+\.[0-9]+\.[0-9]+-[a-z]+\.[0-9]+' + +jobs: + build: + if: | + !contains(github.event.head_commit.message, '[ciskip]') + uses: FTBTeam/mods-meta/.github/workflows/standard-release.yml@main + with: + curse-publish-task: publishMods + java-version: 21 + secrets: + ftb-maven-token: ${{ secrets.FTB_MAVEN_TOKEN }} + curse-token: ${{ secrets.CURSEFORGE_KEY }} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..69ae4a50 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,5 @@ +All Rights Reserved + +Copyright (c) 2025 Feed The Beast Ltd + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index a35ea7e2..a35b8bba 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,17 @@ # FTB Backups [![](http://cf.way2muchnoise.eu/314904.svg) ![](http://cf.way2muchnoise.eu/versions/314904.svg)](https://www.curseforge.com/minecraft/mc-mods/ftb-backups-forge) -Use https://github.com/FTBTeam/FTB-Mods-Issues for any mod issues \ No newline at end of file +Use https://github.com/FTBTeam/FTB-Mods-Issues for any mod issues + +## Support + +- For **Modpack** issues, please go here: https://go.ftb.team/support-modpack +- For **Mod** issues, please go here: https://go.ftb.team/support-mod-issues +- Just got a question? Check out our Discord: https://go.ftb.team/discord + +## Licence + +All Rights Reserved to Feed The Beast Ltd. Source code is `visible source`, please see our [LICENSE.md](/LICENSE.md) for more information. Any Pull Requests made to this mod must have the CLA (Contributor Licence Agreement) signed and agreed to before the request will be considered. + +## Keep up to date + +[![](https://cdn.feed-the-beast.com/assets/socials/icons/social-discord.webp)](https://go.ftb.team/discord) [![](https://cdn.feed-the-beast.com/assets/socials/icons/social-github.webp)](https://go.ftb.team/github) [![](https://cdn.feed-the-beast.com/assets/socials/icons/social-twitter-x.webp)](https://go.ftb.team/twitter) [![](https://cdn.feed-the-beast.com/assets/socials/icons/social-youtube.webp)](https://go.ftb.team/youtube) [![](https://cdn.feed-the-beast.com/assets/socials/icons/social-twitch.webp)](https://go.ftb.team/twitch) [![](https://cdn.feed-the-beast.com/assets/socials/icons/social-instagram.webp)](https://go.ftb.team/instagram) [![](https://cdn.feed-the-beast.com/assets/socials/icons/social-facebook.webp)](https://go.ftb.team/facebook) [![](https://cdn.feed-the-beast.com/assets/socials/icons/social-tiktok.webp)](https://go.ftb.team/tiktok) diff --git a/build.gradle b/build.gradle index abeed774..51e32f81 100644 --- a/build.gradle +++ b/build.gradle @@ -1,68 +1,129 @@ plugins { id 'java-library' - id 'eclipse' id 'idea' id 'maven-publish' - id 'net.neoforged.gradle.userdev' version "${neo_gradle}" - id "me.modmuss50.mod-publish-plugin" version "0.4.5" + id 'net.neoforged.moddev' version '2.0.78' + id "me.modmuss50.mod-publish-plugin" version "0.8.3" } -def ENV = System.getenv() -version = "${ENV.GITHUB_RUN_NUMBER}" ? "${mod_version}.${ENV.GITHUB_RUN_NUMBER}" : "${mod_version}.9999" -group = project.maven_group - -repositories { - mavenLocal() - maven { - url "https://cursemaven.com" - } +tasks.named('wrapper', Wrapper).configure { + distributionType = Wrapper.DistributionType.BIN } +def mcMinorAndPatch = getMinorAndPatchFromMc() + +version = "${mcMinorAndPatch}.${mod_version}" +group = project.maven_group + base { archivesName = project.archives_base_name } +apply from: 'https://raw.githubusercontent.com/FTBTeam/mods-meta/main/gradle/changelog.gradle' +apply from: "https://raw.githubusercontent.com/FTBTeam/mods-meta/main/gradle/publishing.gradle" + java.toolchain.languageVersion = JavaLanguageVersion.of(21) -runs { - configureEach { - systemProperty 'forge.logging.markers', 'REGISTRIES' - systemProperty 'forge.logging.console.level', 'debug' - modSource project.sourceSets.main - } +neoForge { + version = "${mcMinorAndPatch}.${neo_version}" - client { - systemProperty 'forge.enabledGameTestNamespaces', project.archives_base_name + parchment { + mappingsVersion = project.parchment_mappings_version + minecraftVersion = project.parchment_minecraft_version.replace("\${mc_version}", minecraft_version) } - server { - systemProperty 'forge.enabledGameTestNamespaces', project.archives_base_name - programArgument '--nogui' + runs { + configureEach { + systemProperty 'forge.logging.markers', 'REGISTRIES' + logLevel = org.slf4j.event.Level.DEBUG + } + + client { + client() + + // Comma-separated list of namespaces to load gametests from. Empty = all namespaces. + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + server { + server() + programArgument '--nogui' + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + gameTestServer { + type = "gameTestServer" + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + data { + data() + programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() + } } - data { - programArguments.addAll '--mod', project.archives_base_name, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() + mods { + "${mod_id}" { + sourceSet(sourceSets.main) + } } } -sourceSets.main.resources { - srcDir 'src/generated/resources' +sourceSets.main.resources { srcDir 'src/generated/resources' } + +// Sets up a dependency configuration called 'localRuntime'. +// This configuration should be used instead of 'runtimeOnly' to declare +// a dependency that will be present for runtime testing but that is +// "optional", meaning it will not be pulled by dependents of this mod. +configurations { + runtimeClasspath.extendsFrom localRuntime +} + +repositories { + mavenLocal() } dependencies { - implementation "net.neoforged:neoforge:${neo_version}" } + +tasks.register("sourcesJar", Jar) { + archiveClassifier.set("sources") + from sourceSets.main.allJava +} + +tasks.register("apiJar", Jar) { + archiveClassifier.set("api") + + from sourceSets.main.output + + // Only include the dev.ftb.mods.ftbbackups.api package + include 'dev/ftb/mods/ftbbackups/api/**' +} + +tasks.named("jar") { + dependsOn("sourcesJar") + dependsOn("apiJar") +} + tasks.withType(ProcessResources).configureEach { var replaceProperties = [ - version: version + version: version, + loader_version_range: loader_version_range, + // 99.9% of the time we're only going to target a very vague version of neoforge so this is fine. + mod_loader_range: mod_loader_range.replace("\${mc_base}", mcMinorAndPatch), // Replace the version in the mod loader range ] + inputs.properties replaceProperties filesMatching(['META-INF/neoforge.mods.toml']) { - expand replaceProperties + [project: project] + expand replaceProperties } -} + // Copy in the repos license file + from(project.rootDir) { + include 'LICENSE.md' + } +} tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation @@ -73,4 +134,54 @@ idea { downloadSources = true downloadJavadoc = true } -} \ No newline at end of file +} + +def getMinorAndPatchFromMc() { + String mcVersion = project.minecraft_version + String[] split = mcVersion.split("\\.") + return split[1] + "." + (split[2] ?: "0") +} + +publishMods { + def CFToken = providers.environmentVariable("CURSEFORGE_KEY"); + + dryRun = !CFToken.isPresent() + displayName = "[NEOFORGE] FTB Backups 3 ${version}" + modLoaders.add("neoforge") + + changelog = createChangelog(project) + file = jar.archiveFile + version = project.version // Pretty sure it defaults to this + + def tag = providers.environmentVariable("TAG").getOrElse("release") + type = tag.endsWith("-beta") ? BETA : (tag.endsWith("-alpha") ? ALPHA : STABLE) + + curseforge { + accessToken = CFToken.orElse("missing") + projectId = project.curseforge_id + minecraftVersions.add(project.minecraft_version) + javaVersions.add(JavaVersion.VERSION_21) + } +} + +publishing { + publications { + register('mavenJava', MavenPublication) { + from components.java + artifact sourcesJar + artifact apiJar + } + } + + repositories { + if (ftbPublishing.ftbToken) { + maven { + url ftbPublishing.ftbURL + credentials { + username = ftbPublishing.ftbUser + password = ftbPublishing.ftbToken + } + } + } + } +} diff --git a/gradle.properties b/gradle.properties index bd0ec7ec..6699e294 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,18 +1,25 @@ # 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 +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configuration-cache=true -# Neo Stuff -minecraft_version=1.21 -neo_version=21.0.160 -neo_gradle=7.0.142 +parchment_minecraft_version=${mc_version} +parchment_mappings_version=2024.11.17 + +mod_id=ftbbackups3 +mod_version=0 +minecraft_version=1.21.1 + +loader_version_range=[4,) +mod_loader_range=[${mc_base},) +neo_version=136 #Mod Stuff -archives_base_name=ftbackups +archives_base_name=ftb-backups-3 maven_group=dev.ftb.mods -mod_version=2.1.2 #CurseForge curseforge_id=314904 -curseforge_type=release diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e4a5f610..9355b415 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists \ No newline at end of file +zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle index fd1434c1..90ae98fa 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,5 +7,5 @@ pluginManagement { } plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' -} \ No newline at end of file + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.9.0' +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupEvent.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupEvent.java index 11b07f35..470f18d1 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupEvent.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupEvent.java @@ -1,6 +1,5 @@ package dev.ftb.mods.ftbbackups; - import net.neoforged.bus.api.Event; import javax.annotation.Nullable; diff --git a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java index 338ad3d9..4053b343 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java @@ -1,7 +1,5 @@ package dev.ftb.mods.ftbbackups; -import dev.ftb.mods.ftbbackups.net.BackupProgressPacket; -import dev.ftb.mods.ftbbackups.net.FTBBackupsNetHandler; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java index a71e4c34..05e29780 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java @@ -1,34 +1,25 @@ package dev.ftb.mods.ftbbackups; - -import dev.ftb.mods.ftbbackups.net.BackupProgressPacket; -import dev.ftb.mods.ftbbackups.net.FTBBackupsNetHandler; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import net.neoforged.bus.api.EventPriority; import net.neoforged.bus.api.IEventBus; -import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.ModContainer; -import net.neoforged.fml.ModLoadingContext; import net.neoforged.fml.common.Mod; -import net.neoforged.fml.config.IConfigSpec; -import net.neoforged.fml.config.ModConfig; import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.event.RegisterCommandsEvent; import net.neoforged.neoforge.event.entity.player.PlayerEvent; import net.neoforged.neoforge.event.server.ServerAboutToStartEvent; -import net.neoforged.neoforge.event.server.ServerStartedEvent; -import net.neoforged.neoforge.event.server.ServerStartingEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; import net.neoforged.neoforge.event.tick.ServerTickEvent; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Mod(FTBBackups.MOD_ID) public class FTBBackups { - public static final String MOD_ID = "ftbbackups"; - public static final Logger LOGGER = LogManager.getLogger("FTB Utilities Backups"); + public static final String MOD_ID = "ftbbackups3"; + public static final Logger LOGGER = LoggerFactory.getLogger(FTBBackups.class); public FTBBackups(IEventBus eventBus, ModContainer container) { eventBus.addListener(this::clientSetup); @@ -37,7 +28,7 @@ public FTBBackups(IEventBus eventBus, ModContainer container) { NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverStopping); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedIn); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedOut); - NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::worldTick); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::levelTick); // FTBBackupsNetHandler.init(); FTBBackupsConfig.register(eventBus, container); //ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true)); @@ -74,11 +65,7 @@ public void playerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) { } } - public void worldTick(ServerTickEvent.Post event) { - /*if (event.phase != ServerTickEvent.PostTickEvent.Phase.START && !event.side.isClient()) { - Backups.INSTANCE.tick(event.getServer(), System.currentTimeMillis()); - }*/ - //I Guess this is just called server side now?? + public void levelTick(ServerTickEvent.Post event) { Backups.INSTANCE.tick(event.getServer(), System.currentTimeMillis()); } } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java index b55b96fa..8d2f2c8b 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java @@ -1,9 +1,7 @@ package dev.ftb.mods.ftbbackups; -import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; import net.minecraft.client.OptionInstance; -import net.minecraft.client.resources.language.I18n; import net.neoforged.neoforge.client.event.RenderGuiLayerEvent; import net.neoforged.neoforge.common.NeoForge; diff --git a/src/main/java/dev/ftb/mods/ftbbackups/api/BackupsApi.java b/src/main/java/dev/ftb/mods/ftbbackups/api/BackupsApi.java new file mode 100644 index 00000000..ccb40267 --- /dev/null +++ b/src/main/java/dev/ftb/mods/ftbbackups/api/BackupsApi.java @@ -0,0 +1,12 @@ +package dev.ftb.mods.ftbbackups.api; + +import net.minecraft.client.Minecraft; + +/** + * This class is the API for FTB Backups. + */ +public class BackupsApi { + public BackupsApi() { + Minecraft.getInstance(); + } +} diff --git a/src/main/resources/META-INF/neoforge.mods.toml b/src/main/resources/META-INF/neoforge.mods.toml index 95348a6f..4d61a110 100644 --- a/src/main/resources/META-INF/neoforge.mods.toml +++ b/src/main/resources/META-INF/neoforge.mods.toml @@ -1,27 +1,21 @@ modLoader = "javafml" -loaderVersion = "[1,)" +loaderVersion = "${loader_version_range}" license = "All Rights Reserved" [[mods]] modId = "ftbbackups" version = "${version}" -displayName = "FTB Backups" +displayName = "FTB Backups 3" displayURL = "https://www.curseforge.com/minecraft/mc-mods/ftb-backups" logoFile = "logo.png" authors = "FTB Team" description = ''' -Creates backups of your world''' +Creates backups of your world +''' [[dependencies.ftbbackups]] modId="neoforge" mandatory = true -versionRange = "[21,)" +versionRange = "${mod_loader_range}" ordering = "NONE" side="BOTH" - -#[[dependencies.ftbbackups]] -#modId="minecraft" -#mandatory = true -#versionRange = "[1.21,)" -#ordering = "NONE" -#side="BOTH" From 8846e669c999fdd392ac1e0c33723ba8d03b77fe Mon Sep 17 00:00:00 2001 From: Michael Hillcox Date: Tue, 18 Mar 2025 23:52:24 +0000 Subject: [PATCH 15/42] feat: code cleanup, backup overlay layer fix: debug info adding (hopefully) fix: mock in network registration note: should be ready for you now des --- .../ftb/mods/ftbbackups/BackupCommands.java | 8 +++ .../dev/ftb/mods/ftbbackups/BackupUtils.java | 13 +++++ .../java/dev/ftb/mods/ftbbackups/Backups.java | 33 ++++++++----- .../dev/ftb/mods/ftbbackups/FTBBackups.java | 31 +++++++++--- .../ftb/mods/ftbbackups/FTBBackupsClient.java | 38 -------------- .../ftb/mods/ftbbackups/FTBBackupsConfig.java | 4 ++ .../ftbbackups/client/BackupOverlayLayer.java | 16 ++++++ .../mods/ftbbackups/client/BackupsClient.java | 49 +++++++++++++++++++ .../ftbbackups/net/BackupProgressPacket.java | 4 +- .../ftbbackups/net/FTBBackupsNetHandler.java | 4 ++ 10 files changed, 143 insertions(+), 57 deletions(-) delete mode 100644 src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java create mode 100644 src/main/java/dev/ftb/mods/ftbbackups/client/BackupOverlayLayer.java create mode 100644 src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java index 002338c5..99100fac 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java @@ -63,6 +63,10 @@ private static int size(CommandSourceStack source) { return 1; } + /** + * @deprecated Either remove or improve this + */ + @Deprecated(forRemoval = true, since = "21.1.0") private static int status(CommandSourceStack source) { source.getServer().getAllLevels().forEach(level -> { if (level != null) { @@ -76,6 +80,10 @@ private static int status(CommandSourceStack source) { return 1; } + /** + * @deprecated This seems like a hack around bigger issues + */ + @Deprecated(forRemoval = true, since = "21.1.0") private static int resetState(CommandSourceStack source) { source.getServer().getAllLevels().forEach(level -> { if (level != null) { diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java index 9a202fd7..82578f17 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java @@ -34,6 +34,11 @@ public class BackupUtils { public static final double GB_D = MB_D * 1024D; public static final double TB_D = GB_D * 1024D; + /** + * @deprecated This can very likely be replaced with all the new fancy Time API stuff in java, + * Instants, Durations, etc. + */ + @Deprecated(forRemoval = true, since = "21.1.0") public static String getTimeString(long millis) { boolean neg = false; if (millis < 0L) { @@ -166,6 +171,10 @@ public static void listTree0(List list, File file) { } } + /** + * @deprecated This can likely be replaced with {@link org.apache.commons.io.FileUtils#copyFile(File, File)} + */ + @Deprecated public static void copyFile(File src, File dst) throws Exception { if (src.exists() && !src.equals(dst)) { if (src.isDirectory() && dst.isDirectory()) { @@ -186,6 +195,10 @@ public static void copyFile(File src, File dst) throws Exception { } } + /** + * @deprecated This can likely be replaced with {@link org.apache.commons.io.FileUtils#delete(File)} + */ + @Deprecated public static boolean delete(File file) { if (!file.exists()) { return false; diff --git a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java index 4053b343..cb8b0c14 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java @@ -10,6 +10,8 @@ import net.minecraft.world.level.storage.LevelResource; import net.neoforged.fml.loading.FMLPaths; import net.neoforged.neoforge.common.NeoForge; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileInputStream; @@ -24,9 +26,14 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; +/** + * TODO: Logging! + */ public enum Backups { INSTANCE; + public static final Logger LOGGER = LoggerFactory.getLogger(Backups.class); + public final List backups = new ArrayList<>(); public File backupsFolder; public long nextBackup = -1L; @@ -78,7 +85,7 @@ public void init(MinecraftServer server) { } } - FTBBackups.LOGGER.info("Backups folder - " + backupsFolder.getAbsolutePath()); + LOGGER.info("Backups folder - " + backupsFolder.getAbsolutePath()); nextBackup = System.currentTimeMillis() + FTBBackupsConfig.backupTimer; } @@ -104,7 +111,7 @@ public void tick(MinecraftServer server, long now) { } } else if (doingBackup.isRunning() && printFiles) { if (currentFile == 0 || currentFile == totalFiles - 1) { - FTBBackups.LOGGER.info("[" + currentFile + " | " + (int) ((currentFile / (double) totalFiles) * 100D) + "%]: " + currentFileName); + LOGGER.info("[" + currentFile + " | " + (int) ((currentFile / (double) totalFiles) * 100D) + "%]: " + currentFileName); } if (!FTBBackupsConfig.silent) { @@ -116,7 +123,7 @@ public void tick(MinecraftServer server, long now) { public void notifyAll(MinecraftServer server, Component component, boolean error) { component = component.plainCopy(); component.getStyle().withColor(TextColor.fromLegacyFormat(error ? ChatFormatting.DARK_RED : ChatFormatting.LIGHT_PURPLE)); - FTBBackups.LOGGER.info(component.getString()); + LOGGER.info(component.getString()); server.getPlayerList().broadcastSystemMessage(component, true); } @@ -160,6 +167,10 @@ public boolean run(MinecraftServer server, boolean auto, Component name, String return true; } + /** + * TODO: Wrap in API / Plugin system to allow for different backup providers that accept files / paths and return + * progress / completion status + */ private void createBackup(MinecraftServer server, String customName) { File src = server.getWorldPath(LevelResource.ROOT).toFile(); @@ -232,7 +243,7 @@ private void createBackup(MinecraftServer server, String customName) { if (backupsToKeep > 0 && backups.size() > backupsToKeep) { while (backups.size() > backupsToKeep) { Backup backup = backups.remove(0); - FTBBackups.LOGGER.info("Deleting old backup: " + backup.fileId); + LOGGER.info("Deleting old backup: " + backup.fileId); BackupUtils.delete(backup.getFile()); } } @@ -247,14 +258,14 @@ private void createBackup(MinecraftServer server, String customName) { while (totalSize + fileSize > freeSpace && !backups.isEmpty()) { Backup backup = backups.remove(0); totalSize -= backup.size; - FTBBackups.LOGGER.info("Deleting backup to free space: " + backup.fileId); + LOGGER.info("Deleting backup to free space: " + backup.fileId); BackupUtils.delete(backup.getFile()); } } } totalFiles = fileMap.size(); - FTBBackups.LOGGER.info("Backing up " + totalFiles + " files..."); + LOGGER.info("Backing up " + totalFiles + " files..."); printFiles = true; if (FTBBackupsConfig.compressionLevel > 0) { @@ -268,7 +279,7 @@ private void createBackup(MinecraftServer server, String customName) { byte[] buffer = new byte[FTBBackupsConfig.bufferSize]; - FTBBackups.LOGGER.info("Compressing " + totalFiles + " files..."); + LOGGER.info("Compressing " + totalFiles + " files..."); currentFile = 0; for (Map.Entry entry : fileMap.entrySet()) { @@ -286,7 +297,7 @@ private void createBackup(MinecraftServer server, String customName) { zos.closeEntry(); fis.close(); } catch (Exception ex) { - FTBBackups.LOGGER.error("Failed to read file " + entry.getValue() + ": " + ex); + LOGGER.error("Failed to read file " + entry.getValue() + ": " + ex); } currentFile++; @@ -294,7 +305,7 @@ private void createBackup(MinecraftServer server, String customName) { zos.close(); fileSize = BackupUtils.getSize(dstFile); - FTBBackups.LOGGER.info("Done compressing in " + BackupUtils.getTimeString(System.currentTimeMillis() - start) + " seconds (" + BackupUtils.getSizeString(fileSize) + ")!"); + LOGGER.info("Done compressing in " + BackupUtils.getTimeString(System.currentTimeMillis() - start) + " seconds (" + BackupUtils.getSizeString(fileSize) + ")!"); } else { dstFile = new File(backupsFolder, out.toString()); dstFile.mkdirs(); @@ -307,14 +318,14 @@ private void createBackup(MinecraftServer server, String customName) { File dst1 = new File(dstFile, entry.getValue()); BackupUtils.copyFile(file, dst1); } catch (Exception ex) { - FTBBackups.LOGGER.error("Failed to copy file " + entry.getValue() + ": " + ex); + LOGGER.error("Failed to copy file " + entry.getValue() + ": " + ex); } currentFile++; } } - FTBBackups.LOGGER.info("Created " + dstFile.getAbsolutePath() + " from " + src.getAbsolutePath()); + LOGGER.info("Created " + dstFile.getAbsolutePath() + " from " + src.getAbsolutePath()); success = true; } catch (Exception ex) { if (!FTBBackupsConfig.silent) { diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java index 05e29780..850cba67 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java @@ -1,6 +1,9 @@ package dev.ftb.mods.ftbbackups; +import dev.ftb.mods.ftbbackups.client.BackupsClient; +import dev.ftb.mods.ftbbackups.net.FTBBackupsNetHandler; import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; import net.neoforged.bus.api.EventPriority; import net.neoforged.bus.api.IEventBus; @@ -13,29 +16,34 @@ import net.neoforged.neoforge.event.server.ServerAboutToStartEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; import net.neoforged.neoforge.event.tick.ServerTickEvent; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; +import net.neoforged.neoforge.network.registration.PayloadRegistrar; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Mod(FTBBackups.MOD_ID) public class FTBBackups { public static final String MOD_ID = "ftbbackups3"; - public static final Logger LOGGER = LoggerFactory.getLogger(FTBBackups.class); + + private static final Logger LOGGER = LoggerFactory.getLogger(FTBBackups.class); public FTBBackups(IEventBus eventBus, ModContainer container) { - eventBus.addListener(this::clientSetup); + eventBus.addListener(event -> clientSetup(event, eventBus)); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverAboutToStart); - NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::registerCommands); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverStopping); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedIn); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedOut); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::levelTick); -// FTBBackupsNetHandler.init(); + + NeoForge.EVENT_BUS.addListener(this::registerCommands); + NeoForge.EVENT_BUS.addListener(this::registerNetwork); + FTBBackupsConfig.register(eventBus, container); - //ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true)); } - private void clientSetup(FMLClientSetupEvent event) { - FTBBackupsClient.init(); + private void clientSetup(FMLClientSetupEvent event, IEventBus eventBus) { + BackupsClient.init(eventBus); } public void serverAboutToStart(ServerAboutToStartEvent event) { @@ -46,6 +54,11 @@ public void registerCommands(RegisterCommandsEvent event) { BackupCommands.register(event.getDispatcher()); } + public void registerNetwork(RegisterPayloadHandlersEvent event) { + PayloadRegistrar registrar = event.registrar("3"); + FTBBackupsNetHandler.register(registrar); + } + public void serverStopping(ServerStoppingEvent event) { if (FTBBackupsConfig.forceOnShutdown) { Backups.INSTANCE.run(event.getServer(), true, Component.literal("Server"), ""); @@ -68,4 +81,8 @@ public void playerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) { public void levelTick(ServerTickEvent.Post event) { Backups.INSTANCE.tick(event.getServer(), System.currentTimeMillis()); } + + public static ResourceLocation id(String path) { + return ResourceLocation.fromNamespaceAndPath(MOD_ID, path); + } } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java deleted file mode 100644 index 8d2f2c8b..00000000 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java +++ /dev/null @@ -1,38 +0,0 @@ -package dev.ftb.mods.ftbbackups; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.OptionInstance; -import net.neoforged.neoforge.client.event.RenderGuiLayerEvent; -import net.neoforged.neoforge.common.NeoForge; - -public class FTBBackupsClient { - private static int currentBackupFile = 0; - private static int totalBackupFiles = 0; - - public static void init() { - //MinecraftForge.EVENT_BUS.addListener(FTBBackupsClient::onClientDisconnected); - NeoForge.EVENT_BUS.addListener(FTBBackupsClient::onDebugInfoEvent); - } - - public static void setFiles(int current, int total) { - currentBackupFile = current; - totalBackupFiles = total; - } - - /*private static void onClientDisconnected(FMLNetworkEvent.ClientDisconnectionFromServerEvent event) - { - currentBackupFile = 0; - totalBackupFiles = 0; - }*/ - - private static void onDebugInfoEvent(RenderGuiLayerEvent.Post event) { - OptionInstance booleanOptionInstance = Minecraft.getInstance().options.reducedDebugInfo(); - if (booleanOptionInstance.get()) { - return; - } - - if (totalBackupFiles > 0 && totalBackupFiles > currentBackupFile) { - //event.getLeft().add(ChatFormatting.LIGHT_PURPLE + I18n.get("ftbbackups.lang.timer_progress", currentBackupFile * 100 / totalBackupFiles, currentBackupFile, totalBackupFiles)); - } - } -} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java index 91ea6411..beeb3682 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java @@ -12,6 +12,10 @@ import java.util.ArrayList; import java.util.List; +/** + * @deprecated Move over to FTB Libraries' config system + */ +@Deprecated(forRemoval = true, since = "21.1.0") public class FTBBackupsConfig { public static boolean auto; public static boolean silent; diff --git a/src/main/java/dev/ftb/mods/ftbbackups/client/BackupOverlayLayer.java b/src/main/java/dev/ftb/mods/ftbbackups/client/BackupOverlayLayer.java new file mode 100644 index 00000000..cac4b7b1 --- /dev/null +++ b/src/main/java/dev/ftb/mods/ftbbackups/client/BackupOverlayLayer.java @@ -0,0 +1,16 @@ +package dev.ftb.mods.ftbbackups.client; + +import dev.ftb.mods.ftbbackups.FTBBackups; +import net.minecraft.client.DeltaTracker; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.LayeredDraw; +import net.minecraft.resources.ResourceLocation; + +public class BackupOverlayLayer implements LayeredDraw.Layer { + public static final ResourceLocation ID = FTBBackups.id("overlay"); + + @Override + public void render(GuiGraphics guiGraphics, DeltaTracker deltaTracker) { + // NO-OP + } +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java b/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java new file mode 100644 index 00000000..b038545e --- /dev/null +++ b/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java @@ -0,0 +1,49 @@ +package dev.ftb.mods.ftbbackups.client; + +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.OptionInstance; +import net.minecraft.network.chat.Component; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.neoforge.client.event.CustomizeGuiOverlayEvent; +import net.neoforged.neoforge.client.event.RegisterGuiLayersEvent; +import net.neoforged.neoforge.common.NeoForge; + +public class BackupsClient { + private static int currentBackupFile = 0; + private static int totalBackupFiles = 0; + + public static void init(IEventBus eventBus) { + //MinecraftForge.EVENT_BUS.addListener(FTBBackupsClient::onClientDisconnected); + NeoForge.EVENT_BUS.addListener(BackupsClient::onDebugInfoEvent); + + eventBus.addListener(BackupsClient::registerGuiLayer); + } + + public static void registerGuiLayer(RegisterGuiLayersEvent event) { + event.registerAboveAll(BackupOverlayLayer.ID, new BackupOverlayLayer()); + } + + public static void setFiles(int current, int total) { + currentBackupFile = current; + totalBackupFiles = total; + } + + /*private static void onClientDisconnected(FMLNetworkEvent.ClientDisconnectionFromServerEvent event) + { + currentBackupFile = 0; + totalBackupFiles = 0; + }*/ + + private static void onDebugInfoEvent(CustomizeGuiOverlayEvent.DebugText event) { + OptionInstance booleanOptionInstance = Minecraft.getInstance().options.reducedDebugInfo(); + if (booleanOptionInstance.get()) { + return; + } + + if (totalBackupFiles > 0 && totalBackupFiles > currentBackupFile) { + // I think this is right? @mikey (2025-03-18) + event.getLeft().add(Component.translatable("ftbbackups.lang.timer_progress", currentBackupFile * 100 / totalBackupFiles, currentBackupFile, totalBackupFiles).withStyle(ChatFormatting.LIGHT_PURPLE).toString()); + } + } +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java b/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java index 25a34620..2b7babe7 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java @@ -1,8 +1,10 @@ package dev.ftb.mods.ftbbackups.net; -import dev.ftb.mods.ftbbackups.FTBBackupsClient; import net.minecraft.network.FriendlyByteBuf; +/** + * TODO: Add back @des + */ public class BackupProgressPacket { private int current, total; diff --git a/src/main/java/dev/ftb/mods/ftbbackups/net/FTBBackupsNetHandler.java b/src/main/java/dev/ftb/mods/ftbbackups/net/FTBBackupsNetHandler.java index 8d2356f6..5134fb33 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/net/FTBBackupsNetHandler.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/net/FTBBackupsNetHandler.java @@ -2,10 +2,14 @@ import dev.ftb.mods.ftbbackups.FTBBackups; import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.network.registration.PayloadRegistrar; import java.util.function.Predicate; public class FTBBackupsNetHandler { + public static void register(PayloadRegistrar registrar) { + + } //TODO NetHandler /*public static SimpleChannel MAIN; From b3a1655631e04309d658392c7d2f02c95abee407 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Wed, 19 Mar 2025 10:28:59 +0000 Subject: [PATCH 16/42] chore: made Backup a record, couple of other post-merge fixes --- .../java/dev/ftb/mods/ftbbackups/Backup.java | 39 +++++-------------- .../ftb/mods/ftbbackups/BackupCommands.java | 2 +- .../java/dev/ftb/mods/ftbbackups/Backups.java | 17 ++++---- .../ftbbackups/net/FTBBackupsNetHandler.java | 9 +---- 4 files changed, 21 insertions(+), 46 deletions(-) diff --git a/src/main/java/dev/ftb/mods/ftbbackups/Backup.java b/src/main/java/dev/ftb/mods/ftbbackups/Backup.java index 5bbc25ef..b980113b 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/Backup.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/Backup.java @@ -1,37 +1,18 @@ package dev.ftb.mods.ftbbackups; -import com.google.gson.JsonObject; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; import java.io.File; -public class Backup implements Comparable { - public final long time; - public final String fileId; - public final int index; - public final boolean success; - public final long size; - - public Backup(long t, String f, int i, boolean s, long fs) { - time = t; - fileId = f; - index = i; - success = s; - size = fs; - } - - public Backup(JsonObject o) { - this(o.get("time").getAsLong(), o.get("file").getAsString(), o.get("index").getAsInt(), o.get("success").getAsBoolean(), o.get("size").getAsLong()); - } - - public JsonObject toJsonObject() { - JsonObject o = new JsonObject(); - o.addProperty("time", time); - o.addProperty("file", fileId); - o.addProperty("index", index); - o.addProperty("success", success); - o.addProperty("size", size); - return o; - } +public record Backup(long time, String fileId, int index, boolean success, long size) implements Comparable { + public static final Codec CODEC = RecordCodecBuilder.create(builder -> builder.group( + Codec.LONG.fieldOf("time").forGetter(Backup::time), + Codec.STRING.fieldOf("file").forGetter(Backup::fileId), + Codec.INT.fieldOf("index").forGetter(Backup::index), + Codec.BOOL.fieldOf("success").forGetter(Backup::success), + Codec.LONG.fieldOf("size").forGetter(Backup::size) + ).apply(builder, Backup::new)); public int hashCode() { return Long.hashCode(time); diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java index 99100fac..13f2bcc9 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java @@ -54,7 +54,7 @@ private static int start(CommandSourceStack source, String customName) { } private static int size(CommandSourceStack source) { - long totalSize = Backups.INSTANCE.backups.stream().mapToLong(backup -> backup.size).sum(); + long totalSize = Backups.INSTANCE.backups.stream().mapToLong(Backup::size).sum(); source.sendSuccess(() -> Component.translatable("ftbbackups.lang.size.current", BackupUtils.getSizeString(source.getServer().getWorldPath(LevelResource.ROOT).toFile())), true); source.sendSuccess(() -> Component.translatable("ftbbackups.lang.size.total", BackupUtils.getSizeString(totalSize)), true); diff --git a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java index 7e9e410d..e77b99fa 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java @@ -3,6 +3,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.mojang.serialization.JsonOps; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.TextColor; @@ -75,7 +76,7 @@ public void init(MinecraftServer server) { json.addProperty("size", BackupUtils.getSize(new File(backupsFolder, json.get("file").getAsString()))); } - backups.add(new Backup(json)); + Backup.CODEC.parse(JsonOps.INSTANCE, json).ifSuccess(backups::add); } } catch (Throwable ex) { ex.printStackTrace(); @@ -240,13 +241,13 @@ private void createBackup(MinecraftServer server, String customName) { if (backupsToKeep > 0 && backups.size() > backupsToKeep) { while (backups.size() > backupsToKeep) { Backup backup = backups.remove(0); - LOGGER.info("Deleting old backup: {}", backup.fileId); + LOGGER.info("Deleting old backup: {}", backup.fileId()); BackupUtils.delete(backup.getFile()); } } for (Backup backup : backups) { - totalSize += backup.size; + totalSize += backup.size(); } if (fileSize > 0L) { @@ -254,8 +255,8 @@ private void createBackup(MinecraftServer server, String customName) { while (totalSize + fileSize > freeSpace && !backups.isEmpty()) { Backup backup = backups.remove(0); - totalSize -= backup.size; - LOGGER.info("Deleting backup to free space: {}", backup.fileId); + totalSize -= backup.size(); + LOGGER.info("Deleting backup to free space: {}", backup.fileId()); BackupUtils.delete(backup.getFile()); } } @@ -343,7 +344,7 @@ private void createBackup(MinecraftServer server, String customName) { JsonArray array = new JsonArray(); for (Backup backup1 : backups) { - array.add(backup1.toJsonObject()); + Backup.CODEC.encodeStart(JsonOps.INSTANCE, backup1).ifSuccess(array::add); } BackupUtils.toJson(new File(server.getServerDirectory().toFile(), "local/ftbutilities/backups.json"), array, true); @@ -356,7 +357,7 @@ private void createBackup(MinecraftServer server, String customName) { long totalSize = 0L; for (Backup backup1 : backups) { - totalSize += backup1.size; + totalSize += backup1.size(); } String sizeB = BackupUtils.getSizeString(fileSize); @@ -386,7 +387,7 @@ private int getLastIndex() { int i = 0; for (Backup b : backups) { - i = Math.max(i, b.index); + i = Math.max(i, b.index()); } return i; diff --git a/src/main/java/dev/ftb/mods/ftbbackups/net/FTBBackupsNetHandler.java b/src/main/java/dev/ftb/mods/ftbbackups/net/FTBBackupsNetHandler.java index 790d74fc..b1ff10af 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/net/FTBBackupsNetHandler.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/net/FTBBackupsNetHandler.java @@ -1,16 +1,9 @@ package dev.ftb.mods.ftbbackups.net; -import dev.ftb.mods.ftbbackups.FTBBackups; -import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; import net.neoforged.neoforge.network.registration.PayloadRegistrar; public class FTBBackupsNetHandler { - private static final String NETWORK_VERSION = "1.0"; - - public static void init(final RegisterPayloadHandlersEvent event) { - final PayloadRegistrar registrar = event.registrar(FTBBackups.MOD_ID) - .versioned(NETWORK_VERSION); - + public static void init(final PayloadRegistrar registrar) { registrar.playToClient(BackupProgressPacket.TYPE, BackupProgressPacket.STREAM_CODEC, BackupProgressPacket::handler); } } From a12e22d91ddf6e9b019865835a393c9b572da9ae Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Wed, 19 Mar 2025 11:29:30 +0000 Subject: [PATCH 17/42] chore: add ftb library as a dep, fixed startup errors --- build.gradle | 4 +++- gradle.properties | 3 +++ src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java | 2 +- src/main/resources/META-INF/neoforge.mods.toml | 4 ++-- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 51e32f81..1f8f0da3 100644 --- a/build.gradle +++ b/build.gradle @@ -80,10 +80,12 @@ configurations { } repositories { - mavenLocal() + maven { url "https://maven.ftb.dev/releases" } // FTB Mods + maven { url "https://maven.architectury.dev" } } dependencies { + implementation "dev.ftb.mods:ftb-library-neoforge:${ftb_library_version}" } tasks.register("sourcesJar", Jar) { diff --git a/gradle.properties b/gradle.properties index 6699e294..c2441dcc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,3 +23,6 @@ maven_group=dev.ftb.mods #CurseForge curseforge_id=314904 + +#Deps +ftb_library_version=2101.1.12 \ No newline at end of file diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java index c2c3f30d..44b6edb5 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java @@ -35,6 +35,7 @@ public FTBBackups(IEventBus eventBus, ModContainer container) { if (FMLEnvironment.dist == Dist.CLIENT) { eventBus.addListener(event -> clientSetup(event, eventBus)); } + eventBus.addListener(this::registerNetwork); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverAboutToStart); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverStopping); @@ -43,7 +44,6 @@ public FTBBackups(IEventBus eventBus, ModContainer container) { NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::levelTick); NeoForge.EVENT_BUS.addListener(this::registerCommands); - NeoForge.EVENT_BUS.addListener(this::registerNetwork); FTBBackupsConfig.register(eventBus, container); } diff --git a/src/main/resources/META-INF/neoforge.mods.toml b/src/main/resources/META-INF/neoforge.mods.toml index f9994c4d..f0e52529 100644 --- a/src/main/resources/META-INF/neoforge.mods.toml +++ b/src/main/resources/META-INF/neoforge.mods.toml @@ -3,7 +3,7 @@ loaderVersion = "${loader_version_range}" license = "All Rights Reserved" [[mods]] -modId = "ftbbackups" +modId = "ftbbackups3" version = "${version}" displayName = "FTB Backups 3" displayURL = "https://www.curseforge.com/minecraft/mc-mods/ftb-backups" @@ -13,7 +13,7 @@ description = ''' Creates backups of your world ''' -[[dependencies.ftbbackups]] +[[dependencies.ftbbackups3]] modId = "neoforge" mandatory = true versionRange = "${mod_loader_range}" From c643023ffafa71214754907726ec421788b86850 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Thu, 20 Mar 2025 09:51:22 +0000 Subject: [PATCH 18/42] chore: code cleanup continues... --- .../ftb/mods/ftbbackups/BackupCommands.java | 56 +++-- .../dev/ftb/mods/ftbbackups/BackupUtils.java | 65 ------ .../java/dev/ftb/mods/ftbbackups/Backups.java | 62 ++--- .../dev/ftb/mods/ftbbackups/FTBBackups.java | 7 +- .../ftb/mods/ftbbackups/FTBBackupsClient.java | 39 ---- .../ftb/mods/ftbbackups/FTBBackupsConfig.java | 219 ++++++------------ .../ftbbackups/{ => api}/BackupEvent.java | 3 +- .../mods/ftbbackups/client/BackupsClient.java | 16 +- .../ftbbackups/net/BackupProgressPacket.java | 10 +- .../assets/ftbbackups/lang/en_us.json | 76 +++--- 10 files changed, 208 insertions(+), 345 deletions(-) delete mode 100644 src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java rename src/main/java/dev/ftb/mods/ftbbackups/{ => api}/BackupEvent.java (91%) diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java index 13f2bcc9..f2e3058c 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java @@ -1,13 +1,22 @@ package dev.ftb.mods.ftbbackups; +import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import dev.architectury.networking.NetworkManager; +import dev.ftb.mods.ftblibrary.net.EditConfigPacket; +import net.minecraft.ChatFormatting; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.level.storage.LevelResource; +import java.time.Duration; +import java.time.Instant; +import java.time.format.DateTimeFormatter; + public class BackupCommands { public static void register(CommandDispatcher dispatcher) { dispatcher.register(Commands.literal("ftbbackups") @@ -15,39 +24,55 @@ public static void register(CommandDispatcher dispatcher) { .executes(ctx -> time(ctx.getSource())) ) .then(Commands.literal("start") - .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) + .requires(BackupCommands::isAdmin) .then(Commands.argument("name", StringArgumentType.word()) .executes(ctx -> start(ctx.getSource(), StringArgumentType.getString(ctx, "name"))) ) .executes(ctx -> start(ctx.getSource(), "")) ) .then(Commands.literal("size") - .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) + .requires(BackupCommands::isAdmin) .executes(ctx -> size(ctx.getSource())) ) .then(Commands.literal("status") - .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) + .requires(BackupCommands::isAdmin) .executes(ctx -> status(ctx.getSource())) ) .then(Commands.literal("reset") - .requires(cs -> cs.getServer().isSingleplayer() || cs.hasPermission(3)) + .requires(BackupCommands::isAdmin) .executes(ctx -> resetState(ctx.getSource())) ) + .then(Commands.literal("serverconfig") + .requires(BackupCommands::isAdmin) + .executes(ctx -> editServerConfig(ctx.getSource())) + ) ); } + private static boolean isAdmin(CommandSourceStack cs) { + return cs.getServer().isSingleplayer() || cs.hasPermission(3); + } + + private static int editServerConfig(CommandSourceStack source) throws CommandSyntaxException { + NetworkManager.sendToPlayer(source.getPlayerOrException(), new EditConfigPacket(FTBBackupsConfig.KEY)); + return Command.SINGLE_SUCCESS; + } + private static int time(CommandSourceStack source) { - source.sendSuccess(() -> Component.translatable("ftbbackups.lang.timer", BackupUtils.getTimeString(Backups.INSTANCE.nextBackup - System.currentTimeMillis())), true); + Duration d = Duration.between(Instant.now(), Instant.ofEpochMilli(Backups.INSTANCE.nextBackup)); + var s = String.format("%02d:%02d:%02d", d.toHoursPart(), d.toMinutesPart(), d.toSecondsPart()); + + source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.timer", s), true); return 1; } private static int start(CommandSourceStack source, String customName) { if (Backups.INSTANCE.run(source.getServer(), false, source.getDisplayName(), customName)) { for (ServerPlayer player : source.getServer().getPlayerList().getPlayers()) { - player.sendSystemMessage(Component.translatable("ftbbackups.lang.manual_launch", source.getDisplayName())); + player.sendSystemMessage(Component.translatable("ftbbackups3.lang.manual_launch", source.getDisplayName())); } } else { - source.sendSuccess(() -> Component.translatable("ftbbackups.lang.already_running"), true); + source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.already_running"), true); } return 1; @@ -56,24 +81,19 @@ private static int start(CommandSourceStack source, String customName) { private static int size(CommandSourceStack source) { long totalSize = Backups.INSTANCE.backups.stream().mapToLong(Backup::size).sum(); - source.sendSuccess(() -> Component.translatable("ftbbackups.lang.size.current", BackupUtils.getSizeString(source.getServer().getWorldPath(LevelResource.ROOT).toFile())), true); - source.sendSuccess(() -> Component.translatable("ftbbackups.lang.size.total", BackupUtils.getSizeString(totalSize)), true); - source.sendSuccess(() -> Component.translatable("ftbbackups.lang.size.available", BackupUtils.getSizeString(Math.min(FTBBackupsConfig.maxTotalSize, Backups.INSTANCE.backupsFolder.getFreeSpace()))), true); + source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.size.current", BackupUtils.getSizeString(source.getServer().getWorldPath(LevelResource.ROOT).toFile())), true); + source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.size.total", BackupUtils.getSizeString(totalSize)), true); + source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.size.available", BackupUtils.getSizeString(Math.min(FTBBackupsConfig.MAX_TOTAL_SIZE.get(), Backups.INSTANCE.backupsFolder.getFreeSpace()))), true); return 1; } - /** - * @deprecated Either remove or improve this - */ - @Deprecated(forRemoval = true, since = "21.1.0") private static int status(CommandSourceStack source) { source.getServer().getAllLevels().forEach(level -> { if (level != null) { - //[Dev: Current status for ServerLevel[world]: NO SAVE !!] x3 - //level.dimension().toString() - ResourceKey[minecraft:dimension / minecraft:overworld] - //level.toSting() - ServerLevel[New World] - source.sendSuccess(() -> Component.literal("Current status for " + level.toString() + ": " + (!level.noSave ? "NO SAVE !!" : "Autosave")), true); + Component enabled = Component.translatable("ftbbackups3.lang.autosave." + (level.noSave() ? "disabled" : "enabled")).withStyle(ChatFormatting.AQUA); + Component dim = Component.literal(level.dimension().location().toString()).withStyle(ChatFormatting.YELLOW); + source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.autosave_status", dim, enabled), true); } }); diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java index afc3555a..9ed56e52 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java @@ -27,71 +27,6 @@ public class BackupUtils { public static final double GB_D = MB_D * 1024D; public static final double TB_D = GB_D * 1024D; - /** - * @deprecated This can very likely be replaced with all the new fancy Time API stuff in java, - * Instants, Durations, etc. - */ - @Deprecated(forRemoval = true, since = "21.1.0") - public static String getTimeString(long millis) { - boolean neg = false; - if (millis < 0L) { - neg = true; - millis = -millis; - } - - StringBuilder sb = new StringBuilder(); - - if (millis < 1000L) { - if (neg) { - sb.append('-'); - } - - sb.append(millis); - sb.append('m'); - sb.append('s'); - return sb.toString(); - } - - long secs = millis / 1000L; - - if (neg) { - sb.append('-'); - } - - long h = (secs / 3600L) % 24; - long m = (secs / 60L) % 60L; - long s = secs % 60L; - - if (secs >= 86400L) { - sb.append(secs / 86400L); - sb.append('d'); - sb.append(' '); - } - - if (h > 0 || secs >= 86400L) { - if (h < 10) { - sb.append('0'); - } - sb.append(h); - //sb.append("h "); - sb.append(':'); - } - - if (m < 10) { - sb.append('0'); - } - sb.append(m); - //sb.append("m "); - sb.append(':'); - if (s < 10) { - sb.append('0'); - } - sb.append(s); - //sb.append('s'); - - return sb.toString(); - } - public static long getSize(File file) { if (!file.exists()) { return 0L; diff --git a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java index e77b99fa..eb19fcbc 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java @@ -4,6 +4,8 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.mojang.serialization.JsonOps; +import dev.ftb.mods.ftbbackups.api.BackupEvent; +import dev.ftb.mods.ftbbackups.net.BackupProgressPacket; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.TextColor; @@ -12,6 +14,7 @@ import net.minecraft.world.level.storage.LevelResource; import net.neoforged.fml.loading.FMLPaths; import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.network.PacketDistributor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,6 +22,7 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.nio.file.Path; +import java.time.Duration; import java.util.*; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -45,11 +49,14 @@ public enum Backups { public void init(MinecraftServer server) { Path dataDir = server.getServerDirectory(); - backupsFolder = FTBBackupsConfig.folder.trim().isEmpty() ? FMLPaths.GAMEDIR.get().resolve("backups").toFile() : new File(FTBBackupsConfig.folder); + String folder = FTBBackupsConfig.FOLDER.get(); + backupsFolder = folder.trim().isEmpty() ? + FMLPaths.GAMEDIR.get().resolve("backups").toFile() : + new File(folder); try { backupsFolder = backupsFolder.getCanonicalFile(); - } catch (Exception ex) { + } catch (Exception ignored) { } doingBackup = BackupStatus.NONE; @@ -84,12 +91,12 @@ public void init(MinecraftServer server) { } LOGGER.info("Backups folder - {}", backupsFolder.getAbsolutePath()); - nextBackup = System.currentTimeMillis() + FTBBackupsConfig.backupTimer; + nextBackup = System.currentTimeMillis() + FTBBackupsConfig.getBackupTimerMillis(); } public void tick(MinecraftServer server, long now) { if (nextBackup > 0L && nextBackup <= now) { - if (!FTBBackupsConfig.onlyIfPlayersOnline || hadPlayersOnline || !server.getPlayerList().getPlayers().isEmpty()) { + if (!FTBBackupsConfig.ONLY_IF_PLAYERS_ONLINE.get() || hadPlayersOnline || !server.getPlayerList().getPlayers().isEmpty()) { hadPlayersOnline = false; run(server, true, Component.translatable("Server"), ""); } @@ -104,16 +111,16 @@ public void tick(MinecraftServer server, long now) { } } - if (!FTBBackupsConfig.silent) { - //TODO: Fix FTBBackupsNetHandler.MAIN.send(PacketDistributor.ALL.noArg(), new BackupProgressPacket(0, 0)); + if (!FTBBackupsConfig.SILENT.get()) { + PacketDistributor.sendToAllPlayers(BackupProgressPacket.reset()); } } else if (doingBackup.isRunning() && printFiles) { if (currentFile == 0 || currentFile == totalFiles - 1) { LOGGER.info("[{} | {}%]: {}", currentFile, (int) ((currentFile / (double) totalFiles) * 100D), currentFileName); } - if (!FTBBackupsConfig.silent) { - //TODO: Fix FTBBackupsNetHandler.MAIN.send(PacketDistributor.ALL.noArg(), new BackupProgressPacket(currentFile, totalFiles)); + if (!FTBBackupsConfig.SILENT.get()) { + PacketDistributor.sendToAllPlayers(BackupProgressPacket.create()); } } } @@ -130,12 +137,12 @@ public boolean run(MinecraftServer server, boolean auto, Component name, String return false; } - if (auto && !FTBBackupsConfig.auto) { + if (auto && !FTBBackupsConfig.AUTO.get()) { return false; } - notifyAll(server, Component.translatable("ftbbackups.lang.start", name), false); - nextBackup = System.currentTimeMillis() + FTBBackupsConfig.backupTimer; + notifyAll(server, Component.translatable("ftbbackups3.lang.start", name), false); + nextBackup = System.currentTimeMillis() + FTBBackupsConfig.getBackupTimerMillis(); for (ServerLevel world : server.getAllLevels()) { if (world != null) { @@ -152,10 +159,10 @@ public boolean run(MinecraftServer server, boolean auto, Component name, String createBackup(server, customName); } catch (Exception ex) { ex.printStackTrace(); - notifyAll(server, Component.translatable("ftbbackups.lang.saving_failed"), true); + notifyAll(server, Component.translatable("ftbbackups3.lang.saving_failed"), true); } } catch (Exception ex) { - notifyAll(server, Component.translatable("ftbbackups.lang.saving_failed"), true); + notifyAll(server, Component.translatable("ftbbackups3.lang.saving_failed"), true); ex.printStackTrace(); } @@ -212,7 +219,7 @@ private void createBackup(MinecraftServer server, String customName) { } }; - for (String s : FTBBackupsConfig.extraFiles) { + for (String s : FTBBackupsConfig.EXTRA_FILES.get()) { consumer.accept(new File(s)); } @@ -236,7 +243,7 @@ private void createBackup(MinecraftServer server, String customName) { if (!backups.isEmpty()) { backups.sort(null); - int backupsToKeep = FTBBackupsConfig.backupsToKeep - 1; + int backupsToKeep = FTBBackupsConfig.BACKUPS_TO_KEEP.get() - 1; if (backupsToKeep > 0 && backups.size() > backupsToKeep) { while (backups.size() > backupsToKeep) { @@ -251,7 +258,7 @@ private void createBackup(MinecraftServer server, String customName) { } if (fileSize > 0L) { - long freeSpace = Math.min(FTBBackupsConfig.maxTotalSize, backupsFolder.getFreeSpace()); + long freeSpace = Math.min(FTBBackupsConfig.MAX_TOTAL_SIZE.get(), backupsFolder.getFreeSpace()); while (totalSize + fileSize > freeSpace && !backups.isEmpty()) { Backup backup = backups.remove(0); @@ -266,16 +273,16 @@ private void createBackup(MinecraftServer server, String customName) { LOGGER.info("Backing up {} files...", totalFiles); printFiles = true; - if (FTBBackupsConfig.compressionLevel > 0) { + if (FTBBackupsConfig.COMPRESSION_LEVEL.get() > 0) { out.append(".zip"); dstFile = BackupUtils.newFile(new File(backupsFolder, out.toString())); long start = System.currentTimeMillis(); ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dstFile)); - zos.setLevel(FTBBackupsConfig.compressionLevel); + zos.setLevel(FTBBackupsConfig.COMPRESSION_LEVEL.get()); - byte[] buffer = new byte[FTBBackupsConfig.bufferSize]; + byte[] buffer = new byte[FTBBackupsConfig.BUFFER_SIZE.get()]; LOGGER.info("Compressing {} files...", totalFiles); @@ -303,7 +310,7 @@ private void createBackup(MinecraftServer server, String customName) { zos.close(); fileSize = BackupUtils.getSize(dstFile); - LOGGER.info("Done compressing in {} seconds ({})!", BackupUtils.getTimeString(System.currentTimeMillis() - start), BackupUtils.getSizeString(fileSize)); + LOGGER.info("Done compressing in {} ms ({})!", System.currentTimeMillis() - start, BackupUtils.getSizeString(fileSize)); } else { dstFile = new File(backupsFolder, out.toString()); dstFile.mkdirs(); @@ -326,9 +333,9 @@ private void createBackup(MinecraftServer server, String customName) { LOGGER.info("Created {} from {}", dstFile.getAbsolutePath(), src.getAbsolutePath()); success = true; } catch (Exception ex) { - if (!FTBBackupsConfig.silent) { + if (!FTBBackupsConfig.SILENT.get()) { String errorName = ex.getClass().getName(); - notifyAll(server, Component.translatable("ftbbackups.lang.fail", errorName), true); + notifyAll(server, Component.translatable("ftbbackups3.lang.fail", errorName), true); } ex.printStackTrace(); @@ -349,11 +356,12 @@ private void createBackup(MinecraftServer server, String customName) { BackupUtils.toJson(new File(server.getServerDirectory().toFile(), "local/ftbutilities/backups.json"), array, true); - if (error == null && !FTBBackupsConfig.silent) { - String timeString = BackupUtils.getTimeString(System.currentTimeMillis() - time.getTimeInMillis()); + if (error == null && !FTBBackupsConfig.SILENT.get()) { + Duration d = Duration.ZERO.plusMillis(System.currentTimeMillis() - time.getTimeInMillis()); + String timeString = String.format("%d.%03ds", d.toSeconds(), d.toMillisPart()); Component component; - if (FTBBackupsConfig.displayFileSize) { + if (FTBBackupsConfig.DISPLAY_FILE_SIZE.get()) { long totalSize = 0L; for (Backup backup1 : backups) { @@ -363,9 +371,9 @@ private void createBackup(MinecraftServer server, String customName) { String sizeB = BackupUtils.getSizeString(fileSize); String sizeT = BackupUtils.getSizeString(totalSize); String sizeString = sizeB.equals(sizeT) ? sizeB : (sizeB + " | " + sizeT); - component = Component.translatable("ftbbackups.lang.end_2", timeString, sizeString); + component = Component.translatable("ftbbackups3.lang.end_2", timeString, sizeString); } else { - component = Component.translatable("ftbbackups.lang.end_1", timeString); + component = Component.translatable("ftbbackups3.lang.end_1", timeString); } component.getStyle().withColor(TextColor.fromLegacyFormat(ChatFormatting.LIGHT_PURPLE)); diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java index 44b6edb5..0c09f975 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java @@ -3,6 +3,7 @@ import dev.ftb.mods.ftbbackups.client.BackupsClient; import dev.ftb.mods.ftbbackups.net.BackupProgressPacket; import dev.ftb.mods.ftbbackups.net.FTBBackupsNetHandler; +import dev.ftb.mods.ftblibrary.config.manager.ConfigManager; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; @@ -37,6 +38,8 @@ public FTBBackups(IEventBus eventBus, ModContainer container) { } eventBus.addListener(this::registerNetwork); + ConfigManager.getInstance().registerServerConfig(FTBBackupsConfig.CONFIG, MOD_ID + ".general", true, FTBBackupsConfig::onConfigChanged); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverAboutToStart); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverStopping); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedIn); @@ -44,8 +47,6 @@ public FTBBackups(IEventBus eventBus, ModContainer container) { NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::levelTick); NeoForge.EVENT_BUS.addListener(this::registerCommands); - - FTBBackupsConfig.register(eventBus, container); } private void clientSetup(FMLClientSetupEvent event, IEventBus eventBus) { @@ -66,7 +67,7 @@ public void registerNetwork(RegisterPayloadHandlersEvent event) { } public void serverStopping(ServerStoppingEvent event) { - if (FTBBackupsConfig.forceOnShutdown) { + if (FTBBackupsConfig.FORCE_ON_SHUTDOWN.get()) { Backups.INSTANCE.run(event.getServer(), true, Component.literal("Server"), ""); } } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java deleted file mode 100644 index b48165cb..00000000 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClient.java +++ /dev/null @@ -1,39 +0,0 @@ -package dev.ftb.mods.ftbbackups; - -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.resources.language.I18n; -import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent; -import net.neoforged.neoforge.client.event.CustomizeGuiOverlayEvent; -import net.neoforged.neoforge.common.NeoForge; - -public class FTBBackupsClient { - private static int currentBackupFile = 0; - private static int totalBackupFiles = 0; - - public static void init() { - NeoForge.EVENT_BUS.addListener(FTBBackupsClient::onClientDisconnected); - NeoForge.EVENT_BUS.addListener(FTBBackupsClient::onDebugInfoEvent); - } - - public static void setBackupProgress(int current, int total) { - currentBackupFile = current; - totalBackupFiles = total; - } - - private static void onClientDisconnected(ClientPlayerNetworkEvent.LoggingOut ignoredEvent) { - currentBackupFile = 0; - totalBackupFiles = 0; - } - - private static void onDebugInfoEvent(CustomizeGuiOverlayEvent.DebugText event) { - if (!Minecraft.getInstance().options.reducedDebugInfo().get() - && totalBackupFiles > 0 - && totalBackupFiles > currentBackupFile) - { - event.getLeft().add(ChatFormatting.LIGHT_PURPLE + I18n.get("ftbbackups.lang.timer_progress", - currentBackupFile * 100 / totalBackupFiles, currentBackupFile, totalBackupFiles) - ); - } - } -} \ No newline at end of file diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java index 2ad1b1b3..53830cd6 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java @@ -1,64 +1,78 @@ package dev.ftb.mods.ftbbackups; -import net.neoforged.bus.api.IEventBus; -import net.neoforged.fml.ModContainer; -import net.neoforged.fml.config.ModConfig; -import net.neoforged.fml.event.config.ModConfigEvent; -import net.neoforged.neoforge.common.ModConfigSpec; -import org.apache.commons.lang3.tuple.Pair; +import dev.ftb.mods.ftblibrary.snbt.config.*; +import net.neoforged.neoforge.common.util.Lazy; import java.util.ArrayList; -import java.util.List; - -/** - * @deprecated Move over to FTB Libraries' config system - */ -public class FTBBackupsConfig { - public static boolean auto; - public static boolean silent; - public static int backupsToKeep; - public static long backupTimer; - public static int compressionLevel; - public static String folder; - public static boolean displayFileSize; - public static List extraFiles; - public static long maxTotalSize; - public static boolean onlyIfPlayersOnline; - public static boolean forceOnShutdown; - public static int bufferSize; - - private static Pair common; - - public static void register(IEventBus eventBus, ModContainer container) { - eventBus.addListener(FTBBackupsConfig::reload); - - common = new ModConfigSpec.Builder().configure(CommonConfig::new); - - container.registerConfig(ModConfig.Type.COMMON, common.getRight()); - } - public static void reload(ModConfigEvent event) { - ModConfig config = event.getConfig(); +public interface FTBBackupsConfig { + String KEY = FTBBackups.MOD_ID + "-server"; + SNBTConfig CONFIG = SNBTConfig.create(KEY) + .comment("Server-specific configuration for FTB Backups 3", + "Modpack defaults should be defined in /config/" + KEY + ".snbt", + " (may be overwritten on modpack update)", + "Server admins may locally override this by copying into /world/serverconfig/" + KEY + ".snbt", + " (will NOT be overwritten on modpack update)" + ); - if (config.getSpec() == common.getRight()) { - reloadCommon(); - } - } + BooleanValue AUTO = CONFIG.addBoolean("auto", true) + .comment("Enables backups to run automatically"); + + BooleanValue SILENT = CONFIG.addBoolean("silent", false) + .comment("If set to true, no messages will be displayed in chat/status bar"); + + IntValue BACKUPS_TO_KEEP = CONFIG.addInt("backups_to_keep", 12, 0, 32000) + .comment("The number of backup files to keep.", + "More backups = more space used", + "0 - Infinite" + ); + + IntValue BACKUP_TIMER_MINUTES = CONFIG.addInt("backup_timer", 120, 1, 43800) + .comment( + "Timer in minutes.", + "5 - backups every 5 minutes", + "60 - backups every hour", + "360 - backups every 6 hours", + "1440 - backups once every day" + ); + + IntValue COMPRESSION_LEVEL = CONFIG.addInt("compression_level", 1, 0, 9) + .comment( + "0 - Disabled (output = folders)", + "1 - Best speed", + "9 - Smallest file size" + ); + + StringValue FOLDER = CONFIG.addString("folder", "") + .comment("Absolute path to backups folder."); - public static void reloadCommon() { - CommonConfig cfg = common.getLeft(); - auto = cfg.auto.get(); - silent = cfg.silent.get(); - backupsToKeep = cfg.backupsToKeep.get(); - backupTimer = cfg.backupTimer.get() * 60000L; - compressionLevel = cfg.compressionLevel.get(); - folder = cfg.folder.get(); - displayFileSize = cfg.displayFileSize.get(); - extraFiles = new ArrayList<>(cfg.extraFiles.get()); + BooleanValue DISPLAY_FILE_SIZE = CONFIG.addBoolean("display_file_size", true) + .comment("Prints (current size | total size) when backup is done."); - String mts = cfg.maxTotalSize.get(); + StringListValue EXTRA_FILES = CONFIG.addStringList("extra_files", new ArrayList<>()) + .comment("Add extra files that will be placed in backup _extra_/ folder."); + StringValue MAX_TOTAL_SIZE_RAW = CONFIG.addString("max_total_size", "50 GB") + .comment("Maximum total size that is allowed in backups folder. Older backups will be deleted to free space for newer ones."); + + BooleanValue ONLY_IF_PLAYERS_ONLINE = CONFIG.addBoolean("only_if_players_online", true) + .comment("Only create backups when players have been online."); + + BooleanValue FORCE_ON_SHUTDOWN = CONFIG.addBoolean("force_on_shutdown", false) + .comment("Create a backup when server is stopped."); + + SNBTConfig ADVANCED = CONFIG.addGroup("advanced") + .comment("Advanced features that shouldn't be changed unless you know what you are doing."); + + IntValue BUFFER_SIZE = ADVANCED.addInt("buffer_size", 4096, 256, 65536) + .comment("Buffer size for writing files."); + + + Lazy MAX_TOTAL_SIZE = Lazy.of(() -> { + String mts = MAX_TOTAL_SIZE_RAW.get(); + + long maxTotalSize; long l = Long.parseLong(mts.substring(0, mts.length() - 2).trim()); if (mts.endsWith("TB")) { maxTotalSize = l * BackupUtils.TB; @@ -71,105 +85,14 @@ public static void reloadCommon() { } else { maxTotalSize = Long.parseLong(mts.trim()); } + return maxTotalSize; + }); - onlyIfPlayersOnline = cfg.onlyIfPlayersOnline.get(); - forceOnShutdown = cfg.forceOnShutdown.get(); - bufferSize = cfg.bufferSize.get(); + static long getBackupTimerMillis() { + return BACKUP_TIMER_MINUTES.get() * 60000L; // 60000 millisecs in a minute } - private static class CommonConfig { - //ModConfigSpec or NeoForgeConfig - private final ModConfigSpec.BooleanValue auto; - private final ModConfigSpec.BooleanValue silent; - private final ModConfigSpec.IntValue backupsToKeep; - private final ModConfigSpec.IntValue backupTimer; - private final ModConfigSpec.IntValue compressionLevel; - private final ModConfigSpec.ConfigValue folder; - private final ModConfigSpec.BooleanValue displayFileSize; - private final ModConfigSpec.ConfigValue> extraFiles; - private final ModConfigSpec.ConfigValue maxTotalSize; - private final ModConfigSpec.BooleanValue onlyIfPlayersOnline; - private final ModConfigSpec.BooleanValue forceOnShutdown; - private final ModConfigSpec.IntValue bufferSize; - - private CommonConfig(ModConfigSpec.Builder builder) { - auto = builder - .comment("Enables backups to run automatically.") - .translation("ftbbackups.general.auto") - .define("auto", true); - - silent = builder - .comment("If set to true, no messages will be displayed in chat/status bar.") - .translation("ftbbackups.general.silent") - .define("silent", false); - - backupsToKeep = builder - .comment( - "The number of backup files to keep.", - "More backups = more space used", - "0 - Infinite" - ) - .translation("ftbbackups.general.backups_to_keep") - .defineInRange("backups_to_keep", 12, 0, 32000); - - backupTimer = builder - .comment( - "Timer in minutes.", - "5 - backups every 5 minutes", - "60 - backups every hour", - "360 - backups every 6 hours", - "1440 - backups once every day" - ) - .translation("ftbbackups.general.backup_timer") - .defineInRange("backup_timer", 120, 1, 43800); - - compressionLevel = builder - .comment( - "0 - Disabled (output = folders)", - "1 - Best speed", - "9 - Smallest file size" - ) - .translation("ftbbackups.general.compression_level") - .defineInRange("compression_level", 1, 0, 9); - - folder = builder - .comment("Absolute path to backups folder.") - .translation("ftbbackups.general.folder") - .define("folder", ""); - - displayFileSize = builder - .comment("Prints (current size | total size) when backup is done.") - .translation("ftbbackups.general.display_file_size") - .define("display_file_size", true); - - extraFiles = builder - .comment("Add extra files that will be placed in backup _extra_/ folder.") - .translation("ftbbackups.general.extra_files") - .defineList("extra_files", new ArrayList<>(), o -> true); - - maxTotalSize = builder - .comment("Maximum total size that is allowed in backups folder. Older backups will be deleted to free space for newer ones.") - .translation("ftbbackups.general.max_total_size") - .define("max_total_size", "50 GB"); - - onlyIfPlayersOnline = builder - .comment("Only create backups when players have been online.") - .translation("ftbbackups.general.only_if_players_online") - .define("only_if_players_online", true); - - forceOnShutdown = builder - .comment("Create a backup when server is stopped.") - .translation("ftbbackups.general.force_on_shutdown") - .define("force_on_shutdown", false); - - builder.comment("Advanced features that shouldn't be changed unless you know what you are doing.").push("advanced"); - - bufferSize = builder - .comment("Buffer size for writing files.") - .translation("ftbbackups.general.buffer_size") - .defineInRange("buffer_size", 4096, 256, 65536); - - builder.pop(); - } + static void onConfigChanged(boolean isServer) { + MAX_TOTAL_SIZE.invalidate(); } } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupEvent.java b/src/main/java/dev/ftb/mods/ftbbackups/api/BackupEvent.java similarity index 91% rename from src/main/java/dev/ftb/mods/ftbbackups/BackupEvent.java rename to src/main/java/dev/ftb/mods/ftbbackups/api/BackupEvent.java index 470f18d1..fd3e0f0d 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupEvent.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/api/BackupEvent.java @@ -1,5 +1,6 @@ -package dev.ftb.mods.ftbbackups; +package dev.ftb.mods.ftbbackups.api; +import dev.ftb.mods.ftbbackups.Backup; import net.neoforged.bus.api.Event; import javax.annotation.Nullable; diff --git a/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java b/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java index b038545e..57dfbdb6 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java @@ -5,6 +5,7 @@ import net.minecraft.client.OptionInstance; import net.minecraft.network.chat.Component; import net.neoforged.bus.api.IEventBus; +import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent; import net.neoforged.neoforge.client.event.CustomizeGuiOverlayEvent; import net.neoforged.neoforge.client.event.RegisterGuiLayersEvent; import net.neoforged.neoforge.common.NeoForge; @@ -14,7 +15,7 @@ public class BackupsClient { private static int totalBackupFiles = 0; public static void init(IEventBus eventBus) { - //MinecraftForge.EVENT_BUS.addListener(FTBBackupsClient::onClientDisconnected); + NeoForge.EVENT_BUS.addListener(BackupsClient::onClientDisconnected); NeoForge.EVENT_BUS.addListener(BackupsClient::onDebugInfoEvent); eventBus.addListener(BackupsClient::registerGuiLayer); @@ -24,16 +25,15 @@ public static void registerGuiLayer(RegisterGuiLayersEvent event) { event.registerAboveAll(BackupOverlayLayer.ID, new BackupOverlayLayer()); } - public static void setFiles(int current, int total) { + public static void setBackupProgress(int current, int total) { currentBackupFile = current; totalBackupFiles = total; } - /*private static void onClientDisconnected(FMLNetworkEvent.ClientDisconnectionFromServerEvent event) - { - currentBackupFile = 0; - totalBackupFiles = 0; - }*/ + private static void onClientDisconnected(ClientPlayerNetworkEvent.LoggingOut ignoredEvent) { + currentBackupFile = 0; + totalBackupFiles = 0; + } private static void onDebugInfoEvent(CustomizeGuiOverlayEvent.DebugText event) { OptionInstance booleanOptionInstance = Minecraft.getInstance().options.reducedDebugInfo(); @@ -43,7 +43,7 @@ private static void onDebugInfoEvent(CustomizeGuiOverlayEvent.DebugText event) { if (totalBackupFiles > 0 && totalBackupFiles > currentBackupFile) { // I think this is right? @mikey (2025-03-18) - event.getLeft().add(Component.translatable("ftbbackups.lang.timer_progress", currentBackupFile * 100 / totalBackupFiles, currentBackupFile, totalBackupFiles).withStyle(ChatFormatting.LIGHT_PURPLE).toString()); + event.getLeft().add(Component.translatable("ftbbackups.lang.timer_progress", currentBackupFile * 100 / totalBackupFiles, currentBackupFile, totalBackupFiles).withStyle(ChatFormatting.LIGHT_PURPLE).getString()); } } } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java b/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java index c0e47a3a..d925f795 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java @@ -2,7 +2,7 @@ import dev.ftb.mods.ftbbackups.Backups; import dev.ftb.mods.ftbbackups.FTBBackups; -import dev.ftb.mods.ftbbackups.FTBBackupsClient; +import dev.ftb.mods.ftbbackups.client.BackupsClient; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; @@ -18,14 +18,20 @@ public record BackupProgressPacket(int current, int total) implements CustomPack BackupProgressPacket::new ); + private static final BackupProgressPacket RESET_PACKET = new BackupProgressPacket(0, 0); + public static void handler(BackupProgressPacket message, IPayloadContext context) { - FTBBackupsClient.setBackupProgress(message.current, message.total); + BackupsClient.setBackupProgress(message.current, message.total); } public static CustomPacketPayload create() { return new BackupProgressPacket(Backups.INSTANCE.currentFile, Backups.INSTANCE.totalFiles); } + public static CustomPacketPayload reset() { + return RESET_PACKET; + } + @Override public Type type() { return TYPE; diff --git a/src/main/resources/assets/ftbbackups/lang/en_us.json b/src/main/resources/assets/ftbbackups/lang/en_us.json index b6542f54..8ba2f918 100644 --- a/src/main/resources/assets/ftbbackups/lang/en_us.json +++ b/src/main/resources/assets/ftbbackups/lang/en_us.json @@ -1,36 +1,44 @@ { - "_comment": "Config", - "ftbbackups.general": "General", - "ftbbackups.general.auto": "Auto", - "ftbbackups.general.silent": "Silent", - "ftbbackups.general.backups_to_keep": "Backups to Keep", - "ftbbackups.general.backup_timer": "Backup Timer", - "ftbbackups.general.compression_level": "Compression Level", - "ftbbackups.general.folder": "Folder", - "ftbbackups.general.display_file_size": "Display File Size", - "ftbbackups.general.use_separate_thread": "Use Separate Thread", - "ftbbackups.general.extra_files": "Extra Files", - "ftbbackups.general.max_total_size": "Maximum Total Size", - "ftbbackups.general.disable_level_saving": "Disable Level Saving", - "ftbbackups.general.only_if_players_online": "Backup Only If Players are Online", - "ftbbackups.general.force_on_shutdown": "Force Backup on Shutdown", - "ftbbackups.general.buffer_size": "Buffer Size", - "_comment": "Misc", - "ftbbackups.lang.timer": "Server backup in: %s", - "ftbbackups.lang.timer_progress": "[%d%% | %d / %d]", - "ftbbackups.lang.start": "Server Backup started!", - "ftbbackups.lang.end_1": "Server backup done in %s!", - "ftbbackups.lang.end_2": "Server backup done in %s! (%s)", - "ftbbackups.lang.fail": "Failed to save world! (%s)", - "ftbbackups.lang.manual_launch": "%s launched a manual backup!", - "ftbbackups.lang.already_running": "Backup in progress!", - "ftbbackups.lang.size.current": "Current world size: %s", - "ftbbackups.lang.size.total": "Total backups folder size: %s", - "ftbbackups.lang.size.available": "Available: %s", - "ftbbackups.lang.saving_failed": "World saving failed!", - "_comment": "Commands", - "commands.backup.usage": "/backup", - "commands.backup.start.usage": "/backup start [custom name]", - "commands.backup.time.usage": "/backup time", - "commands.backup.size.usage": "/backup size" + "ftbbackups3.general": "General", + "ftbbackups3.general.auto": "Automatic Backups", + "ftbbackups3.general.auto.tooltip": "Run backups automatically, based on the \"Backup Timer\" setting", + "ftbbackups3.general.silent": "Run Silently", + "ftbbackups3.general.silent.tooltip": "Run Silently", + "ftbbackups3.general.silent.tooltip": "If true, online players are not notified when a backup runs", + "ftbbackups3.general.backups_to_keep": "Backups to Keep", + "ftbbackups3.general.backups_to_keep.tooltip": "Number of backup ZIP files to keep.\nOldest ZIP file will be deleted when a backup runs", + "ftbbackups3.general.backup_timer": "Backup Timer", + "ftbbackups3.general.backup_timer.tooltip": "Frequency, in minutes, to run backups, when \"Automatic Backups\" is true", + "ftbbackups3.general.compression_level": "Compression Level", + "ftbbackups3.general.compression_level.tooltip": "File compression level for backups\n9 = maximum compression\n1 = minimum compression\n0 = don't create a ZIP file, just copy the world folder", + "ftbbackups3.general.folder": "Backup Folder", + "ftbbackups3.general.folder.tooltip": "If not empty, an absolute path to the backup folder\nIf empty, backups are placed in the \"backups\" folder in your game instance", + "ftbbackups3.general.display_file_size": "Display File Size", + "ftbbackups3.general.display_file_size.tooltip": "When true (and \"Run Silently\" is false), also display backed-up file size to players", + "ftbbackups3.general.extra_files": "Extra Files", + "ftbbackups3.general.extra_files.tooltip": "By default, just the files in the world folder are backed up\nThis can be used to add extra files from outside the world folder to the backup\nWorld folder is \"world\" on a dedicated server,\n and \"saves/\" on a single-player instance", + "ftbbackups3.general.max_total_size": "Maximum Total Size", + "ftbbackups3.general.max_total_size.tooltip": "The maximum size to allow for backups.\nOldest backups will be deleted to keep the total backup size under this amount\nValid suffixes: KB, MB, GB, TB", + "ftbbackups3.general.only_if_players_online": "Only if Players are Online", + "ftbbackups3.general.only_if_players_online.tooltip": "Automated backups are only run if at least one player is online", + "ftbbackups3.general.force_on_shutdown": "Force Backup on Shutdown", + "ftbbackups3.general.force_on_shutdown.tooltip": "If true (and \"Automatic Backups\" is true), always run a backup when the server is being stopped", + "ftbbackups3.general.advanced": "Advanced", + "ftbbackups3.general.advanced.buffer_size": "Buffer Size", + "ftbbackups3.general.advanced.buffer_size.tooltip": "Buffer size used when reading files to be backed up.\nDon't adjust this unless you understand the implications!", + "ftbbackups3.lang.timer": "Next backup in: %s", + "ftbbackups3.lang.timer_progress": "[%d%% | %d / %d]", + "ftbbackups3.lang.start": "Server Backup started!", + "ftbbackups3.lang.end_1": "Server backup done in %s!", + "ftbbackups3.lang.end_2": "Server backup done in %s! (%s)", + "ftbbackups3.lang.fail": "Failed to save world! (%s)", + "ftbbackups3.lang.manual_launch": "%s launched a manual backup!", + "ftbbackups3.lang.already_running": "Backup in progress!", + "ftbbackups3.lang.size.current": "Current world size: %s", + "ftbbackups3.lang.size.total": "Total backups folder size: %s", + "ftbbackups3.lang.size.available": "Available: %s", + "ftbbackups3.lang.saving_failed": "World saving failed!", + "ftbbackups3.lang.autosave_status": "Autosave for [%s]: %s", + "ftbbackups3.lang.autosave.enabled": "Enabled", + "ftbbackups3.lang.autosave.disabled": "Disabled!" } \ No newline at end of file From c336e4a26a11ef62f33bc17247c60e0b2e0cc682 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Thu, 20 Mar 2025 12:16:01 +0000 Subject: [PATCH 19/42] chore: more cleanup --- .../java/dev/ftb/mods/ftbbackups/Backup.java | 7 +- .../ftb/mods/ftbbackups/BackupCommands.java | 5 +- .../dev/ftb/mods/ftbbackups/BackupUtils.java | 25 +-- .../java/dev/ftb/mods/ftbbackups/Backups.java | 155 +++++++----------- 4 files changed, 85 insertions(+), 107 deletions(-) diff --git a/src/main/java/dev/ftb/mods/ftbbackups/Backup.java b/src/main/java/dev/ftb/mods/ftbbackups/Backup.java index b980113b..c5e0a64d 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/Backup.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/Backup.java @@ -1,9 +1,12 @@ package dev.ftb.mods.ftbbackups; import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.ListCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import org.checkerframework.checker.units.qual.C; import java.io.File; +import java.util.List; public record Backup(long time, String fileId, int index, boolean success, long size) implements Comparable { public static final Codec CODEC = RecordCodecBuilder.create(builder -> builder.group( @@ -14,6 +17,8 @@ public record Backup(long time, String fileId, int index, boolean success, long Codec.LONG.fieldOf("size").forGetter(Backup::size) ).apply(builder, Backup::new)); + public static final Codec> LIST_CODEC = Codec.list(CODEC); + public int hashCode() { return Long.hashCode(time); } @@ -27,7 +32,7 @@ public boolean equals(Object o) { } public File getFile() { - return new File(Backups.INSTANCE.backupsFolder, fileId); + return new File(Backups.INSTANCE.backupsFolder.toFile(), fileId); } @Override diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java index f2e3058c..5a555ab3 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java @@ -15,7 +15,6 @@ import java.time.Duration; import java.time.Instant; -import java.time.format.DateTimeFormatter; public class BackupCommands { public static void register(CommandDispatcher dispatcher) { @@ -59,7 +58,7 @@ private static int editServerConfig(CommandSourceStack source) throws CommandSyn } private static int time(CommandSourceStack source) { - Duration d = Duration.between(Instant.now(), Instant.ofEpochMilli(Backups.INSTANCE.nextBackup)); + Duration d = Duration.between(Instant.now(), Instant.ofEpochMilli(Backups.INSTANCE.nextBackupTime)); var s = String.format("%02d:%02d:%02d", d.toHoursPart(), d.toMinutesPart(), d.toSecondsPart()); source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.timer", s), true); @@ -83,7 +82,7 @@ private static int size(CommandSourceStack source) { source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.size.current", BackupUtils.getSizeString(source.getServer().getWorldPath(LevelResource.ROOT).toFile())), true); source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.size.total", BackupUtils.getSizeString(totalSize)), true); - source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.size.available", BackupUtils.getSizeString(Math.min(FTBBackupsConfig.MAX_TOTAL_SIZE.get(), Backups.INSTANCE.backupsFolder.getFreeSpace()))), true); + source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.size.available", BackupUtils.getSizeString(Math.min(FTBBackupsConfig.MAX_TOTAL_SIZE.get(), Backups.INSTANCE.backupsFolder.toFile().getFreeSpace()))), true); return 1; } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java index 9ed56e52..8e5c1bb4 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java @@ -13,6 +13,8 @@ import java.io.*; import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; @@ -172,21 +174,24 @@ public static void toJson(Writer writer, @Nullable JsonElement element, boolean } } - public static void toJson(File file, @Nullable JsonElement element, boolean prettyPrinting) { - try (OutputStreamWriter output = new OutputStreamWriter(new FileOutputStream(newFile(file)), StandardCharsets.UTF_8); - BufferedWriter writer = new BufferedWriter(output)) { - toJson(writer, element, prettyPrinting); - } catch (Exception ex) { - ex.printStackTrace(); + public static void toJson(Path jsonFile, @Nullable JsonElement element, boolean prettyPrinting) { + try { + Files.createDirectories(jsonFile.getParent()); + try (OutputStreamWriter output = new OutputStreamWriter(new FileOutputStream(jsonFile.toFile()), StandardCharsets.UTF_8); + BufferedWriter writer = new BufferedWriter(output)) { + toJson(writer, element, prettyPrinting); + } + } catch (IOException e) { + Backups.LOGGER.error("could not write JSON file {}: {} / {}", jsonFile, e.getClass(), e.getMessage()); } } - public static JsonElement readJson(File file) { - if (!file.exists()) { + public static JsonElement readJson(Path jsonFile) { + if (!Files.exists(jsonFile)) { return JsonNull.INSTANCE; } - try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(jsonFile.toFile()), StandardCharsets.UTF_8))) { JsonReader jsonReader = new JsonReader(reader); jsonReader.setLenient(true); JsonElement element = Streams.parse(jsonReader); @@ -197,7 +202,7 @@ public static JsonElement readJson(File file) { return element; } catch (Exception ex) { - return null; + return JsonNull.INSTANCE; } } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java index eb19fcbc..f654662c 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java @@ -1,12 +1,11 @@ package dev.ftb.mods.ftbbackups; -import com.google.gson.JsonArray; import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import com.mojang.serialization.JsonOps; import dev.ftb.mods.ftbbackups.api.BackupEvent; import dev.ftb.mods.ftbbackups.net.BackupProgressPacket; import net.minecraft.ChatFormatting; +import net.minecraft.Util; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.TextColor; import net.minecraft.server.MinecraftServer; @@ -22,8 +21,12 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.nio.file.Path; +import java.nio.file.Paths; import java.time.Duration; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -34,12 +37,15 @@ public enum Backups { INSTANCE; + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss"); + public static final Logger LOGGER = LoggerFactory.getLogger(Backups.class); + public static final String BACKUPS_JSON_FILE = "backups.json"; public final List backups = new ArrayList<>(); - public File backupsFolder; - public long nextBackup = -1L; - public BackupStatus doingBackup = BackupStatus.NONE; + public Path backupsFolder; + public long nextBackupTime = -1L; + public BackupStatus backupStatus = BackupStatus.NONE; public boolean printFiles = false; public int currentFile = 0; @@ -48,73 +54,54 @@ public enum Backups { public boolean hadPlayersOnline = false; public void init(MinecraftServer server) { - Path dataDir = server.getServerDirectory(); String folder = FTBBackupsConfig.FOLDER.get(); backupsFolder = folder.trim().isEmpty() ? - FMLPaths.GAMEDIR.get().resolve("backups").toFile() : - new File(folder); + FMLPaths.GAMEDIR.get().resolve("backups") : + Paths.get(folder); try { - backupsFolder = backupsFolder.getCanonicalFile(); + backupsFolder = backupsFolder.toRealPath(); } catch (Exception ignored) { } - doingBackup = BackupStatus.NONE; + backupStatus = BackupStatus.NONE; backups.clear(); - File file = new File(dataDir.toFile(), "local/ftbutilities/backups.json"); - - if (!file.exists()) { - File oldFile = new File(backupsFolder, "backups.json"); - - if (oldFile.exists()) { - oldFile.renameTo(file); - } - } - - JsonElement element = BackupUtils.readJson(file); + Path backupsIndexFile = indexJsonPath(server); + JsonElement element = BackupUtils.readJson(backupsIndexFile); + Backup.LIST_CODEC.parse(JsonOps.INSTANCE, element) + .resultOrPartial(err -> LOGGER.warn("can't parse backups index {}", backupsIndexFile)) + .ifPresent(backups::addAll); - if (element != null && element.isJsonArray()) { - try { - for (JsonElement e : element.getAsJsonArray()) { - JsonObject json = e.getAsJsonObject(); - - if (!json.has("size")) { - json.addProperty("size", BackupUtils.getSize(new File(backupsFolder, json.get("file").getAsString()))); - } - - Backup.CODEC.parse(JsonOps.INSTANCE, json).ifSuccess(backups::add); - } - } catch (Throwable ex) { - ex.printStackTrace(); - } - } + LOGGER.info("Backups will be written to: {}", backupsFolder); + nextBackupTime = System.currentTimeMillis() + FTBBackupsConfig.getBackupTimerMillis(); + } - LOGGER.info("Backups folder - {}", backupsFolder.getAbsolutePath()); - nextBackup = System.currentTimeMillis() + FTBBackupsConfig.getBackupTimerMillis(); + private static Path indexJsonPath(MinecraftServer server) { + return server.getServerDirectory().resolve(FTBBackups.MOD_ID).resolve(BACKUPS_JSON_FILE); } public void tick(MinecraftServer server, long now) { - if (nextBackup > 0L && nextBackup <= now) { + if (nextBackupTime > 0L && nextBackupTime <= now) { if (!FTBBackupsConfig.ONLY_IF_PLAYERS_ONLINE.get() || hadPlayersOnline || !server.getPlayerList().getPlayers().isEmpty()) { hadPlayersOnline = false; run(server, true, Component.translatable("Server"), ""); } } - if (doingBackup.isDone()) { - doingBackup = BackupStatus.NONE; + if (backupStatus.isDone()) { + backupStatus = BackupStatus.NONE; - for (ServerLevel world : server.getAllLevels()) { - if (world != null) { - world.noSave = false; + for (ServerLevel level : server.getAllLevels()) { + if (level != null) { + level.noSave = false; } } if (!FTBBackupsConfig.SILENT.get()) { PacketDistributor.sendToAllPlayers(BackupProgressPacket.reset()); } - } else if (doingBackup.isRunning() && printFiles) { + } else if (backupStatus.isRunning() && printFiles) { if (currentFile == 0 || currentFile == totalFiles - 1) { LOGGER.info("[{} | {}%]: {}", currentFile, (int) ((currentFile / (double) totalFiles) * 100D), currentFileName); } @@ -133,7 +120,7 @@ public void notifyAll(MinecraftServer server, Component component, boolean error } public boolean run(MinecraftServer server, boolean auto, Component name, String customName) { - if (doingBackup.isRunningOrDone()) { + if (backupStatus.isRunningOrDone()) { return false; } @@ -142,32 +129,27 @@ public boolean run(MinecraftServer server, boolean auto, Component name, String } notifyAll(server, Component.translatable("ftbbackups3.lang.start", name), false); - nextBackup = System.currentTimeMillis() + FTBBackupsConfig.getBackupTimerMillis(); + nextBackupTime = System.currentTimeMillis() + FTBBackupsConfig.getBackupTimerMillis(); - for (ServerLevel world : server.getAllLevels()) { - if (world != null) { - world.noSave = true; + for (ServerLevel level : server.getAllLevels()) { + if (level != null) { + level.noSave = true; } } - doingBackup = BackupStatus.RUNNING; + backupStatus = BackupStatus.RUNNING; server.getPlayerList().saveAll(); - new Thread(() -> { + CompletableFuture.runAsync(() -> { try { - try { - createBackup(server, customName); - } catch (Exception ex) { - ex.printStackTrace(); - notifyAll(server, Component.translatable("ftbbackups3.lang.saving_failed"), true); - } + createBackup(server, customName); } catch (Exception ex) { + LOGGER.error("backup creation failed: {} / {}", ex.getClass(), ex.getMessage()); notifyAll(server, Component.translatable("ftbbackups3.lang.saving_failed"), true); - ex.printStackTrace(); } - - doingBackup = BackupStatus.DONE; - }).start(); + }).thenRun(() -> { + backupStatus = BackupStatus.DONE; + }); return true; } @@ -180,42 +162,35 @@ private void createBackup(MinecraftServer server, String customName) { File src = server.getWorldPath(LevelResource.ROOT).toFile(); try { - src = src.getCanonicalFile(); - } catch (Exception ex) { + src = src.getAbsoluteFile(); + } catch (Exception ignored) { } - Calendar time = Calendar.getInstance(); + long now = Util.getEpochMillis(); File dstFile; boolean success = false; - StringBuilder out = new StringBuilder(); - - if (customName.isEmpty()) { - appendNum(out, time.get(Calendar.YEAR), '-'); - appendNum(out, time.get(Calendar.MONTH) + 1, '-'); - appendNum(out, time.get(Calendar.DAY_OF_MONTH), '-'); - appendNum(out, time.get(Calendar.HOUR_OF_DAY), '-'); - appendNum(out, time.get(Calendar.MINUTE), '-'); - appendNum(out, time.get(Calendar.SECOND), '\0'); - } else { - out.append(customName); - } + + String backupFileName = customName.isEmpty() ? DATE_TIME_FORMATTER.format(LocalDateTime.now()) : customName; Exception error = null; long fileSize = 0L; try { + // maps files on the system to path within the archive file being created LinkedHashMap fileMap = new LinkedHashMap<>(); - String mcdir = server.getServerDirectory().getFileSystem().toString(); + String mcdir = server.getServerDirectory().toRealPath().toString() + File.separator; Consumer consumer = file0 -> { for (File file : BackupUtils.listTree(file0)) { String s1 = file.getAbsolutePath().replace(mcdir, ""); + boolean absolute = false; if (s1.startsWith(File.separator)) { s1 = s1.substring(File.separator.length()); + absolute = true; } - fileMap.put(file, "_extra_" + File.separator + s1); + fileMap.put(file.getAbsoluteFile(), (absolute ? "_extra_absolute_" : "_extra_relative_") + File.separator + s1); } }; @@ -231,7 +206,7 @@ private void createBackup(MinecraftServer server, String customName) { } String filePath = file.getAbsolutePath(); - fileMap.put(file, src.getName() + File.separator + filePath.substring(src.getAbsolutePath().length() + 1)); + fileMap.put(file.getCanonicalFile(), src.getName() + File.separator + filePath.substring(src.getAbsolutePath().length() + 1)); } for (Map.Entry entry : fileMap.entrySet()) { @@ -258,7 +233,7 @@ private void createBackup(MinecraftServer server, String customName) { } if (fileSize > 0L) { - long freeSpace = Math.min(FTBBackupsConfig.MAX_TOTAL_SIZE.get(), backupsFolder.getFreeSpace()); + long freeSpace = Math.min(FTBBackupsConfig.MAX_TOTAL_SIZE.get(), backupsFolder.toFile().getFreeSpace()); while (totalSize + fileSize > freeSpace && !backups.isEmpty()) { Backup backup = backups.remove(0); @@ -274,8 +249,7 @@ private void createBackup(MinecraftServer server, String customName) { printFiles = true; if (FTBBackupsConfig.COMPRESSION_LEVEL.get() > 0) { - out.append(".zip"); - dstFile = BackupUtils.newFile(new File(backupsFolder, out.toString())); + dstFile = BackupUtils.newFile(new File(backupsFolder.toFile(), backupFileName + ".zip")); long start = System.currentTimeMillis(); @@ -312,7 +286,7 @@ private void createBackup(MinecraftServer server, String customName) { fileSize = BackupUtils.getSize(dstFile); LOGGER.info("Done compressing in {} ms ({})!", System.currentTimeMillis() - start, BackupUtils.getSizeString(fileSize)); } else { - dstFile = new File(backupsFolder, out.toString()); + dstFile = new File(backupsFolder.toFile(), backupFileName); dstFile.mkdirs(); currentFile = 0; @@ -344,20 +318,15 @@ private void createBackup(MinecraftServer server, String customName) { printFiles = false; - Backup backup = new Backup(time.getTimeInMillis(), out.toString().replace('\\', '/'), getLastIndex() + 1, success, fileSize); + Backup backup = new Backup(now, backupFileName.replace('\\', '/'), getLastIndex() + 1, success, fileSize); backups.add(backup); NeoForge.EVENT_BUS.post(new BackupEvent.Post(backup, error)); - JsonArray array = new JsonArray(); - - for (Backup backup1 : backups) { - Backup.CODEC.encodeStart(JsonOps.INSTANCE, backup1).ifSuccess(array::add); - } - - BackupUtils.toJson(new File(server.getServerDirectory().toFile(), "local/ftbutilities/backups.json"), array, true); + Backup.LIST_CODEC.encodeStart(JsonOps.INSTANCE, backups) + .ifSuccess(json -> BackupUtils.toJson(indexJsonPath(server), json, true)); if (error == null && !FTBBackupsConfig.SILENT.get()) { - Duration d = Duration.ZERO.plusMillis(System.currentTimeMillis() - time.getTimeInMillis()); + Duration d = Duration.ZERO.plusMillis(Util.getEpochMillis() - now); String timeString = String.format("%d.%03ds", d.toSeconds(), d.toMillisPart()); Component component; From 0c9d5e224820ccd3dc852075f37e945f36e2b392 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Fri, 21 Mar 2025 16:08:06 +0000 Subject: [PATCH 20/42] chore: much more cleanup, added archival plugin system Needs documenting :P --- .../ftb/mods/ftbbackups/BackupCommands.java | 13 +- .../dev/ftb/mods/ftbbackups/BackupUtils.java | 161 ++----- .../java/dev/ftb/mods/ftbbackups/Backups.java | 398 ++++++++++-------- .../dev/ftb/mods/ftbbackups/FTBBackups.java | 13 + .../ftb/mods/ftbbackups/FTBBackupsConfig.java | 19 +- .../ftb/mods/ftbbackups/{ => api}/Backup.java | 30 +- .../mods/ftbbackups/api/IArchivalPlugin.java | 36 ++ .../api/{ => event}/BackupEvent.java | 20 +- .../event/RegisterArchivalPluginEvent.java | 18 + .../archival/ArchivePluginManager.java | 24 ++ .../ftbbackups/archival/FileCopyArchiver.java | 43 ++ .../mods/ftbbackups/archival/ZipArchiver.java | 60 +++ .../ftbbackups/net/BackupProgressPacket.java | 2 +- .../assets/ftbbackups/lang/en_us.json | 1 + 14 files changed, 512 insertions(+), 326 deletions(-) rename src/main/java/dev/ftb/mods/ftbbackups/{ => api}/Backup.java (57%) create mode 100644 src/main/java/dev/ftb/mods/ftbbackups/api/IArchivalPlugin.java rename src/main/java/dev/ftb/mods/ftbbackups/api/{ => event}/BackupEvent.java (58%) create mode 100644 src/main/java/dev/ftb/mods/ftbbackups/api/event/RegisterArchivalPluginEvent.java create mode 100644 src/main/java/dev/ftb/mods/ftbbackups/archival/ArchivePluginManager.java create mode 100644 src/main/java/dev/ftb/mods/ftbbackups/archival/FileCopyArchiver.java create mode 100644 src/main/java/dev/ftb/mods/ftbbackups/archival/ZipArchiver.java diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java index 5a555ab3..d357ab5e 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java @@ -78,11 +78,14 @@ private static int start(CommandSourceStack source, String customName) { } private static int size(CommandSourceStack source) { - long totalSize = Backups.INSTANCE.backups.stream().mapToLong(Backup::size).sum(); - - source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.size.current", BackupUtils.getSizeString(source.getServer().getWorldPath(LevelResource.ROOT).toFile())), true); - source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.size.total", BackupUtils.getSizeString(totalSize)), true); - source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.size.available", BackupUtils.getSizeString(Math.min(FTBBackupsConfig.MAX_TOTAL_SIZE.get(), Backups.INSTANCE.backupsFolder.toFile().getFreeSpace()))), true); + long totalSize = Backups.INSTANCE.totalBackupSize(); + + source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.size.current", + BackupUtils.formatSizeString(source.getServer().getWorldPath(LevelResource.ROOT))), true); + source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.size.total", + BackupUtils.formatSizeString(totalSize)), true); + source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.size.available", + BackupUtils.formatSizeString(Math.min(FTBBackupsConfig.MAX_TOTAL_SIZE.get(), Backups.INSTANCE.backupsFolder.toFile().getFreeSpace()))), true); return 1; } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java index 8e5c1bb4..5c1f9de6 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java @@ -8,15 +8,18 @@ import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; +import org.apache.commons.lang3.mutable.MutableLong; import javax.annotation.Nullable; import java.io.*; -import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Stream; public class BackupUtils { public static final long KB = 1024L; @@ -29,25 +32,39 @@ public class BackupUtils { public static final double GB_D = MB_D * 1024D; public static final double TB_D = GB_D * 1024D; - public static long getSize(File file) { - if (!file.exists()) { - return 0L; - } else if (file.isFile()) { - return file.length(); - } else if (file.isDirectory()) { - long length = 0L; - File[] f1 = file.listFiles(); - if (f1 != null && f1.length > 0) { - for (File aF1 : f1) { - length += getSize(aF1); + public static long getSize(Path path) { + final AtomicLong size = new AtomicLong(0); + + try { + Files.walkFileTree(path, new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + size.addAndGet(attrs.size()); + return FileVisitResult.CONTINUE; } - } - return length; + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) { + Backups.LOGGER.error("getSize: skipped: {} ({})", file, exc); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) { + if (exc != null) { + Backups.LOGGER.error("getSize: had trouble traversing dir: {} ({})", dir, exc); + } + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + throw new AssertionError("walkFileTree will not throw IOException if the FileVisitor does not"); } - return 0L; + + return size.get(); } - public static String getSizeString(double b) { + public static String formatSizeString(double b) { if (b >= TB_D) { return String.format("%.1fTB", b / TB_D); } else if (b >= GB_D) { @@ -61,93 +78,11 @@ public static String getSizeString(double b) { return ((long) b) + "B"; } - public static String getSizeString(File file) { - return getSizeString(getSize(file)); + public static String formatSizeString(Path file) { + return formatSizeString(getSize(file)); } - public static File newFile(File file) { - if (!file.exists()) { - try { - File parent = file.getParentFile(); - if (!parent.exists()) { - parent.mkdirs(); - } - file.createNewFile(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - return file; - } - - public static List listTree(File file) { - List l = new ArrayList<>(); - listTree0(l, file); - return l; - } - - public static void listTree0(List list, File file) { - if (file.isDirectory()) { - File[] fl = file.listFiles(); - - if (fl != null && fl.length > 0) { - for (File aFl : fl) { - listTree0(list, aFl); - } - } - } else if (file.isFile()) { - list.add(file); - } - } - - /** - * @deprecated This can likely be replaced with {@link org.apache.commons.io.FileUtils#copyFile(File, File)} - */ - @Deprecated - public static void copyFile(File src, File dst) throws Exception { - if (src.exists() && !src.equals(dst)) { - if (src.isDirectory() && dst.isDirectory()) { - for (File f : listTree(src)) { - File dst1 = new File(dst.getAbsolutePath() + File.separatorChar + (f.getAbsolutePath().replace(src.getAbsolutePath(), ""))); - copyFile(f, dst1); - } - } else { - dst = newFile(dst); - - try (FileInputStream fis = new FileInputStream(src); - FileOutputStream fos = new FileOutputStream(dst); - FileChannel srcC = fis.getChannel(); - FileChannel dstC = fos.getChannel()) { - dstC.transferFrom(srcC, 0L, srcC.size()); - } - } - } - } - - /** - * @deprecated This can likely be replaced with {@link org.apache.commons.io.FileUtils#delete(File)} - */ - @Deprecated - public static boolean delete(File file) { - if (!file.exists()) { - return false; - } else if (file.isFile()) { - return file.delete(); - } - - String[] files = file.list(); - - if (files != null) { - for (String s : files) { - delete(new File(file, s)); - } - } - - return file.delete(); - } - - public static void toJson(Writer writer, @Nullable JsonElement element, boolean prettyPrinting) { + public static void writeJson(Writer writer, @Nullable JsonElement element, boolean prettyPrinting) { if (element == null || element.isJsonNull()) { try { writer.write("null"); @@ -174,12 +109,12 @@ public static void toJson(Writer writer, @Nullable JsonElement element, boolean } } - public static void toJson(Path jsonFile, @Nullable JsonElement element, boolean prettyPrinting) { + public static void writeJson(Path jsonFile, @Nullable JsonElement element, boolean prettyPrinting) { try { Files.createDirectories(jsonFile.getParent()); try (OutputStreamWriter output = new OutputStreamWriter(new FileOutputStream(jsonFile.toFile()), StandardCharsets.UTF_8); BufferedWriter writer = new BufferedWriter(output)) { - toJson(writer, element, prettyPrinting); + writeJson(writer, element, prettyPrinting); } } catch (IOException e) { Backups.LOGGER.error("could not write JSON file {}: {} / {}", jsonFile, e.getClass(), e.getMessage()); @@ -205,20 +140,4 @@ public static JsonElement readJson(Path jsonFile) { return JsonNull.INSTANCE; } } - - public static String removeAllWhitespace(String s) { - char[] chars = new char[s.length()]; - int j = 0; - - for (int i = 0; i < chars.length; i++) { - char c = s.charAt(i); - - if (c > ' ') { - chars[j] = c; - j++; - } - } - - return new String(chars, 0, j); - } } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java index f654662c..922490ee 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java @@ -2,7 +2,10 @@ import com.google.gson.JsonElement; import com.mojang.serialization.JsonOps; -import dev.ftb.mods.ftbbackups.api.BackupEvent; +import dev.ftb.mods.ftbbackups.api.Backup; +import dev.ftb.mods.ftbbackups.api.IArchivalPlugin; +import dev.ftb.mods.ftbbackups.api.event.BackupEvent; +import dev.ftb.mods.ftbbackups.archival.ArchivePluginManager; import dev.ftb.mods.ftbbackups.net.BackupProgressPacket; import net.minecraft.ChatFormatting; import net.minecraft.Util; @@ -18,18 +21,19 @@ import org.slf4j.LoggerFactory; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.io.IOException; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; import java.time.Duration; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.*; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; /** * TODO: Logging! @@ -42,13 +46,13 @@ public enum Backups { public static final Logger LOGGER = LoggerFactory.getLogger(Backups.class); public static final String BACKUPS_JSON_FILE = "backups.json"; - public final List backups = new ArrayList<>(); + private final List backups = new ArrayList<>(); public Path backupsFolder; public long nextBackupTime = -1L; public BackupStatus backupStatus = BackupStatus.NONE; public boolean printFiles = false; - public int currentFile = 0; + private final AtomicInteger currentFileIndex = new AtomicInteger(0); public int totalFiles = 0; private String currentFileName = ""; public boolean hadPlayersOnline = false; @@ -85,7 +89,7 @@ public void tick(MinecraftServer server, long now) { if (nextBackupTime > 0L && nextBackupTime <= now) { if (!FTBBackupsConfig.ONLY_IF_PLAYERS_ONLINE.get() || hadPlayersOnline || !server.getPlayerList().getPlayers().isEmpty()) { hadPlayersOnline = false; - run(server, true, Component.translatable("Server"), ""); + run(server, true, Component.translatable("ftbbackups3.lang.server"), ""); } } @@ -102,8 +106,9 @@ public void tick(MinecraftServer server, long now) { PacketDistributor.sendToAllPlayers(BackupProgressPacket.reset()); } } else if (backupStatus.isRunning() && printFiles) { - if (currentFile == 0 || currentFile == totalFiles - 1) { - LOGGER.info("[{} | {}%]: {}", currentFile, (int) ((currentFile / (double) totalFiles) * 100D), currentFileName); + int curIdx = currentFileIndex.get(); + if (curIdx == 0 || curIdx == totalFiles - 1) { + LOGGER.info("[{} | {}%]: {}", currentFileIndex, (int) ((curIdx / (double) totalFiles) * 100D), currentFileName); } if (!FTBBackupsConfig.SILENT.get()) { @@ -115,7 +120,6 @@ public void tick(MinecraftServer server, long now) { public void notifyAll(MinecraftServer server, Component component, boolean error) { component = component.plainCopy(); component.getStyle().withColor(TextColor.fromLegacyFormat(error ? ChatFormatting.DARK_RED : ChatFormatting.LIGHT_PURPLE)); - LOGGER.info(component.getString()); server.getPlayerList().broadcastSystemMessage(component, true); } @@ -129,6 +133,7 @@ public boolean run(MinecraftServer server, boolean auto, Component name, String } notifyAll(server, Component.translatable("ftbbackups3.lang.start", name), false); + LOGGER.info("backup starting: {}", name.getString()); nextBackupTime = System.currentTimeMillis() + FTBBackupsConfig.getBackupTimerMillis(); for (ServerLevel level : server.getAllLevels()) { @@ -144,220 +149,233 @@ public boolean run(MinecraftServer server, boolean auto, Component name, String try { createBackup(server, customName); } catch (Exception ex) { - LOGGER.error("backup creation failed: {} / {}", ex.getClass(), ex.getMessage()); - notifyAll(server, Component.translatable("ftbbackups3.lang.saving_failed"), true); + LOGGER.error("backup creation failed: {} -> {}", ex.getClass(), ex.getMessage()); + server.execute(() -> notifyAll(server, Component.translatable("ftbbackups3.lang.saving_failed"), true)); } - }).thenRun(() -> { - backupStatus = BackupStatus.DONE; - }); + }).thenRun(() -> backupStatus = BackupStatus.DONE); return true; } - /** - * TODO: Wrap in API / Plugin system to allow for different backup providers that accept files / paths and return - * progress / completion status - */ private void createBackup(MinecraftServer server, String customName) { - File src = server.getWorldPath(LevelResource.ROOT).toFile(); + Path absoluteWorldPath = server.getWorldPath(LevelResource.ROOT).toAbsolutePath(); - try { - src = src.getAbsoluteFile(); - } catch (Exception ignored) { - } + IArchivalPlugin archivalPlugin = ArchivePluginManager.INSTANCE.getPlugin(FTBBackupsConfig.archivalPlugin()); + + String backupFileName = IArchivalPlugin.addFileExtension(archivalPlugin, customName.isEmpty() ? DATE_TIME_FORMATTER.format(LocalDateTime.now()) : customName); long now = Util.getEpochMillis(); - File dstFile; boolean success = false; - - String backupFileName = customName.isEmpty() ? DATE_TIME_FORMATTER.format(LocalDateTime.now()) : customName; - Exception error = null; - long fileSize = 0L; + long archiveSize = 0L; try { - // maps files on the system to path within the archive file being created - LinkedHashMap fileMap = new LinkedHashMap<>(); - String mcdir = server.getServerDirectory().toRealPath().toString() + File.separator; - - Consumer consumer = file0 -> { - for (File file : BackupUtils.listTree(file0)) { - String s1 = file.getAbsolutePath().replace(mcdir, ""); - - boolean absolute = false; - if (s1.startsWith(File.separator)) { - s1 = s1.substring(File.separator.length()); - absolute = true; - } - - fileMap.put(file.getAbsoluteFile(), (absolute ? "_extra_absolute_" : "_extra_relative_") + File.separator + s1); - } - }; - - for (String s : FTBBackupsConfig.EXTRA_FILES.get()) { - consumer.accept(new File(s)); - } - - NeoForge.EVENT_BUS.post(new BackupEvent.Pre(consumer)); - - for (File file : BackupUtils.listTree(src)) { - if (file.getName().equals("session.lock") || !file.canRead()) { - continue; - } + var manifest = gatherFiles(server); - String filePath = file.getAbsolutePath(); - fileMap.put(file.getCanonicalFile(), src.getName() + File.separator + filePath.substring(src.getAbsolutePath().length() + 1)); + for (Map.Entry entry : manifest.entrySet()) { + archiveSize += Files.size(entry.getKey()); } - for (Map.Entry entry : fileMap.entrySet()) { - fileSize += BackupUtils.getSize(entry.getKey()); - } - - long totalSize = 0L; - - if (!backups.isEmpty()) { - backups.sort(null); - - int backupsToKeep = FTBBackupsConfig.BACKUPS_TO_KEEP.get() - 1; - - if (backupsToKeep > 0 && backups.size() > backupsToKeep) { - while (backups.size() > backupsToKeep) { - Backup backup = backups.remove(0); - LOGGER.info("Deleting old backup: {}", backup.fileId()); - BackupUtils.delete(backup.getFile()); - } - } - - for (Backup backup : backups) { - totalSize += backup.size(); - } - - if (fileSize > 0L) { - long freeSpace = Math.min(FTBBackupsConfig.MAX_TOTAL_SIZE.get(), backupsFolder.toFile().getFreeSpace()); + doArchiveCleanup(archiveSize); - while (totalSize + fileSize > freeSpace && !backups.isEmpty()) { - Backup backup = backups.remove(0); - totalSize -= backup.size(); - LOGGER.info("Deleting backup to free space: {}", backup.fileId()); - BackupUtils.delete(backup.getFile()); - } - } - } - - totalFiles = fileMap.size(); + totalFiles = manifest.size(); LOGGER.info("Backing up {} files...", totalFiles); printFiles = true; - if (FTBBackupsConfig.COMPRESSION_LEVEL.get() > 0) { - dstFile = BackupUtils.newFile(new File(backupsFolder.toFile(), backupFileName + ".zip")); - - long start = System.currentTimeMillis(); - - ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dstFile)); - zos.setLevel(FTBBackupsConfig.COMPRESSION_LEVEL.get()); - - byte[] buffer = new byte[FTBBackupsConfig.BUFFER_SIZE.get()]; - - LOGGER.info("Compressing {} files...", totalFiles); - - currentFile = 0; - for (Map.Entry entry : fileMap.entrySet()) { - try { - ZipEntry ze = new ZipEntry(entry.getValue()); - currentFileName = entry.getValue(); - - zos.putNextEntry(ze); - FileInputStream fis = new FileInputStream(entry.getKey()); - - int len; - while ((len = fis.read(buffer)) > 0) { - zos.write(buffer, 0, len); - } - zos.closeEntry(); - fis.close(); - } catch (Exception ex) { - LOGGER.error("Failed to read file {}: {}", entry.getValue(), ex); - } - - currentFile++; - } - - zos.close(); - fileSize = BackupUtils.getSize(dstFile); - LOGGER.info("Done compressing in {} ms ({})!", System.currentTimeMillis() - start, BackupUtils.getSizeString(fileSize)); - } else { - dstFile = new File(backupsFolder.toFile(), backupFileName); - dstFile.mkdirs(); - - currentFile = 0; - for (Map.Entry entry : fileMap.entrySet()) { - try { - File file = entry.getKey(); - currentFileName = entry.getValue(); - File dst1 = new File(dstFile, entry.getValue()); - BackupUtils.copyFile(file, dst1); - } catch (Exception ex) { - LOGGER.error("Failed to copy file {}: {}", entry.getValue(), ex); - } - - currentFile++; - } - } + currentFileIndex.set(0); + Path archiveDest = backupsFolder.resolve(backupFileName); + long start = Util.getEpochMillis(); + archiveSize = archivalPlugin.createArchive(new BackupContext(manifest, LOGGER, archiveDest, FTBBackupsConfig.COMPRESSION_LEVEL.get(), this)); + LOGGER.info("Created archive {} from {} in {} ms ({})!", + archiveDest, absoluteWorldPath, + Util.getEpochMillis() - start, BackupUtils.formatSizeString(archiveSize) + ); - LOGGER.info("Created {} from {}", dstFile.getAbsolutePath(), src.getAbsolutePath()); success = true; } catch (Exception ex) { if (!FTBBackupsConfig.SILENT.get()) { String errorName = ex.getClass().getName(); notifyAll(server, Component.translatable("ftbbackups3.lang.fail", errorName), true); } - - ex.printStackTrace(); + LOGGER.error("Backup to {} failed: {} -> {}", backupFileName, ex.getClass(), ex.getMessage()); error = ex; } printFiles = false; - Backup backup = new Backup(now, backupFileName.replace('\\', '/'), getLastIndex() + 1, success, fileSize); + Backup backup = new Backup(now, backupFileName, getLastIndex() + 1, success, archiveSize); backups.add(backup); - NeoForge.EVENT_BUS.post(new BackupEvent.Post(backup, error)); Backup.LIST_CODEC.encodeStart(JsonOps.INSTANCE, backups) - .ifSuccess(json -> BackupUtils.toJson(indexJsonPath(server), json, true)); + .ifSuccess(json -> BackupUtils.writeJson(indexJsonPath(server), json, true)); + + NeoForge.EVENT_BUS.post(new BackupEvent.Post(backup, error)); if (error == null && !FTBBackupsConfig.SILENT.get()) { - Duration d = Duration.ZERO.plusMillis(Util.getEpochMillis() - now); - String timeString = String.format("%d.%03ds", d.toSeconds(), d.toMillisPart()); - Component component; + processBackupResults(server, now, archiveSize); + } + } - if (FTBBackupsConfig.DISPLAY_FILE_SIZE.get()) { - long totalSize = 0L; + /** + * Scan the instance, gathering files which will be added to the backup archive + *
    + *
  • All files in the instance's world directory (world/ for dedicated server, saves/{worldname} for SSP
  • + *
  • All extra files found the "extra_files" config setting (may be absolute paths, or relative to instance dir)
  • + *
  • Any extra files added by listeners of {@code BackupEvent.Pre}
  • + *
+ * @param server the server + * @return a map of absolute filepath -> string path of location within the archive + * @throws IOException if any file-related problems occur + */ + private Map gatherFiles(MinecraftServer server) throws IOException { + Map res = new LinkedHashMap<>(); - for (Backup backup1 : backups) { - totalSize += backup1.size(); - } + Path instanceDir = server.getServerDirectory().toRealPath(); - String sizeB = BackupUtils.getSizeString(fileSize); - String sizeT = BackupUtils.getSizeString(totalSize); - String sizeString = sizeB.equals(sizeT) ? sizeB : (sizeB + " | " + sizeT); - component = Component.translatable("ftbbackups3.lang.end_2", timeString, sizeString); - } else { - component = Component.translatable("ftbbackups3.lang.end_1", timeString); + Consumer extras = path -> { + try { + Files.walkFileTree(path, new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + res.put(file.toRealPath(), file.isAbsolute() ? "_extra_absolute_" + file : "_extra_relative_" + File.separator + file); + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + throw new RuntimeException(e); } + }; - component.getStyle().withColor(TextColor.fromLegacyFormat(ChatFormatting.LIGHT_PURPLE)); - server.getPlayerList().broadcastSystemMessage(component, true); + for (String s : FTBBackupsConfig.EXTRA_FILES.get()) { + extras.accept(Path.of(s)); } + + NeoForge.EVENT_BUS.post(new BackupEvent.Pre(extras)); + + Files.walkFileTree(server.getWorldPath(LevelResource.ROOT).toAbsolutePath(), new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (!file.endsWith("session.lock") && Files.isReadable(file)) { + res.put(file.toRealPath(), instanceDir.relativize(file).toString()); + } + return FileVisitResult.CONTINUE; + } + }); + + return res; } - private void appendNum(StringBuilder sb, int num, char c) { - if (num < 10) { - sb.append('0'); +// private long archiveFiles(Map manifest, Path dstFile) throws IOException { +// long archiveSize = 0L; +// if (FTBBackupsConfig.COMPRESSION_LEVEL.get() > 0) { +// ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dstFile.toFile())); +// zos.setLevel(FTBBackupsConfig.COMPRESSION_LEVEL.get()); +// +// byte[] buffer = new byte[FTBBackupsConfig.BUFFER_SIZE.get()]; +// +// LOGGER.info("Compressing {} files...", totalFiles); +// +// currentFile = 0; +// manifest.forEach((absPath, archiveEntry) -> { +// try { +// ZipEntry ze = new ZipEntry(archiveEntry); +// currentFileName = archiveEntry; +// +// zos.putNextEntry(ze); +// FileInputStream fis = new FileInputStream(absPath.toFile()); +// +// int len; +// while ((len = fis.read(buffer)) > 0) { +// zos.write(buffer, 0, len); +// } +// zos.closeEntry(); +// fis.close(); +// } catch (Exception ex) { +// LOGGER.error("Failed to read file {}: {}", archiveEntry, ex); +// } +// +// currentFile++; +// }); +// +// zos.close(); +// archiveSize = Files.size(dstFile); +// } else { +// Files.createDirectories(backupsFolder); +// +// currentFile = 0; +// for (Map.Entry entry : manifest.entrySet()) { +// try { +// Path file = entry.getKey(); +// currentFileName = entry.getValue(); +// Path newFile = dstFile.resolve(Path.of(entry.getValue())); +// Files.createDirectories(newFile.getParent()); +// Files.copy(file, newFile); +// archiveSize += Files.size(newFile); +// } catch (Exception ex) { +// LOGGER.error("Failed to copy file {}: {}", entry.getValue(), ex); +// } +// +// currentFile++; +// } +// } +// +// return archiveSize; +// } + + /** + * Check if any existing backup archives need to be purged, based on the "backups_to_keep" and "max_total_size" + * config settings. + * + * @param fileSize the file size of the archive about to be created + */ + private void doArchiveCleanup(long fileSize) { + if (!backups.isEmpty()) { + backups.sort(null); + + int backupsToKeep = FTBBackupsConfig.BACKUPS_TO_KEEP.get() - 1; + if (backupsToKeep > 0 && backups.size() > backupsToKeep) { + while (backups.size() > backupsToKeep) { + Backup backup = backups.removeFirst(); + backup.deleteFiles(); + } + } + + long totalSize = backups.stream().mapToLong(Backup::size).sum(); + + if (fileSize > 0L) { + long freeSpace = Math.min(FTBBackupsConfig.MAX_TOTAL_SIZE.get(), backupsFolder.toFile().getFreeSpace()); + while (totalSize + fileSize > freeSpace && !backups.isEmpty()) { + Backup backup = backups.removeFirst(); + if (backup.deleteFiles()) { + totalSize -= backup.size(); + } + } + } } - sb.append(num); - if (c != '\0') { - sb.append(c); + } + + private void processBackupResults(MinecraftServer server, long backupTime, long archiveSize) { + Duration d = Duration.ZERO.plusMillis(Util.getEpochMillis() - backupTime); + String timeString = String.format("%d.%03ds", d.toSeconds(), d.toMillisPart()); + Component component; + + if (FTBBackupsConfig.DISPLAY_FILE_SIZE.get()) { + long totalSize = backups.stream().mapToLong(Backup::size).sum(); + + String sizeB = BackupUtils.formatSizeString(archiveSize); + String sizeT = BackupUtils.formatSizeString(totalSize); + String sizeString = sizeB.equals(sizeT) ? sizeB : (sizeB + " | " + sizeT); + component = Component.translatable("ftbbackups3.lang.end_2", timeString, sizeString); + } else { + component = Component.translatable("ftbbackups3.lang.end_1", timeString); } + + component.getStyle().withColor(TextColor.fromLegacyFormat(ChatFormatting.LIGHT_PURPLE)); + server.getPlayerList().broadcastSystemMessage(component, true); + } + + public long totalBackupSize() { + return backups.stream().mapToLong(Backup::size).sum(); } private int getLastIndex() { @@ -369,4 +387,22 @@ private int getLastIndex() { return i; } + + public int getCurrentFileIndex() { + return currentFileIndex.get(); + } + + private record BackupContext( + Map manifest, + Logger logger, + Path archivePath, + int compressionLevel, + Backups backups + ) implements IArchivalPlugin.ArchivalContext { + @Override + public synchronized void notifyProcessingFile(String filename) { + backups.currentFileName = filename; + backups.currentFileIndex.incrementAndGet(); + } + } } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java index 0c09f975..700d523b 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java @@ -1,5 +1,9 @@ package dev.ftb.mods.ftbbackups; +import dev.ftb.mods.ftbbackups.api.event.RegisterArchivalPluginEvent; +import dev.ftb.mods.ftbbackups.archival.ArchivePluginManager; +import dev.ftb.mods.ftbbackups.archival.FileCopyArchiver; +import dev.ftb.mods.ftbbackups.archival.ZipArchiver; import dev.ftb.mods.ftbbackups.client.BackupsClient; import dev.ftb.mods.ftbbackups.net.BackupProgressPacket; import dev.ftb.mods.ftbbackups.net.FTBBackupsNetHandler; @@ -8,6 +12,7 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.Event; import net.neoforged.bus.api.EventPriority; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.ModContainer; @@ -45,6 +50,7 @@ public FTBBackups(IEventBus eventBus, ModContainer container) { NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedIn); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedOut); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::levelTick); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::registerArchivalPlugins); NeoForge.EVENT_BUS.addListener(this::registerCommands); } @@ -55,6 +61,8 @@ private void clientSetup(FMLClientSetupEvent event, IEventBus eventBus) { public void serverAboutToStart(ServerAboutToStartEvent event) { Backups.INSTANCE.init(event.getServer()); + + NeoForge.EVENT_BUS.post(new RegisterArchivalPluginEvent(ArchivePluginManager.INSTANCE::register)); } public void registerCommands(RegisterCommandsEvent event) { @@ -72,6 +80,11 @@ public void serverStopping(ServerStoppingEvent event) { } } + private void registerArchivalPlugins(RegisterArchivalPluginEvent event) { + event.register(FileCopyArchiver.INSTANCE); + event.register(ZipArchiver.INSTANCE); + } + public void playerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { if (event.getEntity() instanceof ServerPlayer serverPlayer) { PacketDistributor.sendToPlayer(serverPlayer, BackupProgressPacket.create()); diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java index 53830cd6..cb2857d3 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java @@ -1,7 +1,9 @@ package dev.ftb.mods.ftbbackups; +import dev.ftb.mods.ftbbackups.archival.ZipArchiver; import dev.ftb.mods.ftblibrary.snbt.config.*; +import net.minecraft.resources.ResourceLocation; import net.neoforged.neoforge.common.util.Lazy; import java.util.ArrayList; @@ -38,8 +40,8 @@ public interface FTBBackupsConfig { ); IntValue COMPRESSION_LEVEL = CONFIG.addInt("compression_level", 1, 0, 9) - .comment( - "0 - Disabled (output = folders)", + .comment("Compression level for archived files. Note that this is dependent on the particular plugin in use", + "0 - No compression", "1 - Best speed", "9 - Smallest file size" ); @@ -68,6 +70,10 @@ public interface FTBBackupsConfig { IntValue BUFFER_SIZE = ADVANCED.addInt("buffer_size", 4096, 256, 65536) .comment("Buffer size for writing files."); + StringValue ARCHIVAL_PLUGIN = CONFIG.addString("archival_plugin", ZipArchiver.ID.toString()) + .comment("Method to use to create a backup archive.", + "Builtin methods are \"ftbbackups:zip\" (create a ZIP file) and \"ftbbackups:filecopy\" (simple recursive copy of files with no compression)", + "More archival plugins may be added by other mods."); Lazy MAX_TOTAL_SIZE = Lazy.of(() -> { String mts = MAX_TOTAL_SIZE_RAW.get(); @@ -95,4 +101,13 @@ static long getBackupTimerMillis() { static void onConfigChanged(boolean isServer) { MAX_TOTAL_SIZE.invalidate(); } + + static ResourceLocation archivalPlugin() { + ResourceLocation rl = ResourceLocation.tryParse(ARCHIVAL_PLUGIN.get()); + if (rl == null) { + Backups.LOGGER.error("Invalid archive plugin id {}, defaulting to ftbbackups:zip!", ARCHIVAL_PLUGIN.get()); + return ZipArchiver.ID; + } + return rl; + } } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/Backup.java b/src/main/java/dev/ftb/mods/ftbbackups/api/Backup.java similarity index 57% rename from src/main/java/dev/ftb/mods/ftbbackups/Backup.java rename to src/main/java/dev/ftb/mods/ftbbackups/api/Backup.java index c5e0a64d..bf718ff8 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/Backup.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/api/Backup.java @@ -1,11 +1,13 @@ -package dev.ftb.mods.ftbbackups; +package dev.ftb.mods.ftbbackups.api; import com.mojang.serialization.Codec; -import com.mojang.serialization.codecs.ListCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; -import org.checkerframework.checker.units.qual.C; +import dev.ftb.mods.ftbbackups.Backups; +import org.apache.commons.io.FileUtils; -import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; public record Backup(long time, String fileId, int index, boolean success, long size) implements Comparable { @@ -31,8 +33,24 @@ public boolean equals(Object o) { return o == this || (o instanceof Backup && ((Backup) o).time == time); } - public File getFile() { - return new File(Backups.INSTANCE.backupsFolder.toFile(), fileId); + public Path getPath() { + return Backups.INSTANCE.backupsFolder.resolve(fileId); + } + + public boolean deleteFiles() { + Path path = getPath(); + try { + if (Files.isRegularFile(path)) { + Files.delete(path); + } else if (Files.isDirectory(path)) { + FileUtils.deleteDirectory(path.toFile()); + } + } catch (IOException e) { + Backups.LOGGER.error("Can't delete backup {}: {} / {}", fileId(), e.getClass(), e.getMessage()); + return false; + } + Backups.LOGGER.info("Deleted backup: {}", fileId()); + return true; } @Override diff --git a/src/main/java/dev/ftb/mods/ftbbackups/api/IArchivalPlugin.java b/src/main/java/dev/ftb/mods/ftbbackups/api/IArchivalPlugin.java new file mode 100644 index 00000000..c5d55c27 --- /dev/null +++ b/src/main/java/dev/ftb/mods/ftbbackups/api/IArchivalPlugin.java @@ -0,0 +1,36 @@ +package dev.ftb.mods.ftbbackups.api; + +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.ApiStatus; +import org.slf4j.Logger; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Map; + +public interface IArchivalPlugin { + ResourceLocation getId(); + + default String getFileExtension() { + return getId().getPath(); + } + + long createArchive(ArchivalContext context) throws IOException; + + static String addFileExtension(IArchivalPlugin plugin, String fileName) { + return plugin.getFileExtension().isEmpty() ? fileName : fileName + "." + plugin.getFileExtension(); + } + + @ApiStatus.NonExtendable + interface ArchivalContext { + Map manifest(); + + Logger logger(); + + Path archivePath(); + + void notifyProcessingFile(String filename); + + int compressionLevel(); + } +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/api/BackupEvent.java b/src/main/java/dev/ftb/mods/ftbbackups/api/event/BackupEvent.java similarity index 58% rename from src/main/java/dev/ftb/mods/ftbbackups/api/BackupEvent.java rename to src/main/java/dev/ftb/mods/ftbbackups/api/event/BackupEvent.java index fd3e0f0d..31ee6b0e 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/api/BackupEvent.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/api/event/BackupEvent.java @@ -1,21 +1,21 @@ -package dev.ftb.mods.ftbbackups.api; +package dev.ftb.mods.ftbbackups.api.event; -import dev.ftb.mods.ftbbackups.Backup; +import dev.ftb.mods.ftbbackups.api.Backup; import net.neoforged.bus.api.Event; import javax.annotation.Nullable; -import java.io.File; +import java.nio.file.Path; import java.util.function.Consumer; public class BackupEvent extends Event { public static class Pre extends BackupEvent { - private final Consumer callback; + private final Consumer callback; - public Pre(Consumer c) { - callback = c; + public Pre(Consumer callback) { + this.callback = callback; } - public void add(File file) { + public void add(Path file) { callback.accept(file); } } @@ -24,9 +24,9 @@ public static class Post extends BackupEvent { private final Backup backup; private final Exception error; - public Post(Backup b, @Nullable Exception ex) { - backup = b; - error = ex; + public Post(Backup backup, @Nullable Exception error) { + this.backup = backup; + this.error = error; } public Backup getBackup() { diff --git a/src/main/java/dev/ftb/mods/ftbbackups/api/event/RegisterArchivalPluginEvent.java b/src/main/java/dev/ftb/mods/ftbbackups/api/event/RegisterArchivalPluginEvent.java new file mode 100644 index 00000000..0cbcf4f5 --- /dev/null +++ b/src/main/java/dev/ftb/mods/ftbbackups/api/event/RegisterArchivalPluginEvent.java @@ -0,0 +1,18 @@ +package dev.ftb.mods.ftbbackups.api.event; + +import dev.ftb.mods.ftbbackups.api.IArchivalPlugin; +import net.neoforged.bus.api.Event; + +import java.util.function.Consumer; + +public class RegisterArchivalPluginEvent extends Event { + private final Consumer consumer; + + public RegisterArchivalPluginEvent(Consumer consumer) { + this.consumer = consumer; + } + + public void register(IArchivalPlugin plugin) { + consumer.accept(plugin); + } +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/archival/ArchivePluginManager.java b/src/main/java/dev/ftb/mods/ftbbackups/archival/ArchivePluginManager.java new file mode 100644 index 00000000..b3751dce --- /dev/null +++ b/src/main/java/dev/ftb/mods/ftbbackups/archival/ArchivePluginManager.java @@ -0,0 +1,24 @@ +package dev.ftb.mods.ftbbackups.archival; + +import dev.ftb.mods.ftbbackups.api.IArchivalPlugin; +import net.minecraft.resources.ResourceLocation; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public enum ArchivePluginManager { + INSTANCE; + + private final Map plugins = new ConcurrentHashMap<>(); + + public synchronized void register(IArchivalPlugin plugin) { + if (plugins.containsKey(plugin.getId())) { + throw new IllegalStateException("archival plugin " + plugin.getId() + " already registered!"); + } + plugins.put(plugin.getId(), plugin); + } + + public IArchivalPlugin getPlugin(ResourceLocation id) { + return plugins.get(id); + } +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/archival/FileCopyArchiver.java b/src/main/java/dev/ftb/mods/ftbbackups/archival/FileCopyArchiver.java new file mode 100644 index 00000000..d59afd4b --- /dev/null +++ b/src/main/java/dev/ftb/mods/ftbbackups/archival/FileCopyArchiver.java @@ -0,0 +1,43 @@ +package dev.ftb.mods.ftbbackups.archival; + +import dev.ftb.mods.ftbbackups.FTBBackups; +import dev.ftb.mods.ftbbackups.api.IArchivalPlugin; +import net.minecraft.resources.ResourceLocation; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +public enum FileCopyArchiver implements IArchivalPlugin { + INSTANCE; + + public static final ResourceLocation ID = FTBBackups.id("filecopy"); + + @Override + public ResourceLocation getId() { + return ID; + } + + @Override + public String getFileExtension() { + return ""; + } + + @Override + public long createArchive(ArchivalContext context) { + long archiveSize = 0L; + for (Map.Entry entry : context.manifest().entrySet()) { + try { + Path file = entry.getKey(); + context.notifyProcessingFile(entry.getValue()); + Path newFile = context.archivePath().resolve(Path.of(entry.getValue())); + Files.createDirectories(newFile.getParent()); + Files.copy(file, newFile); + archiveSize += Files.size(newFile); + } catch (Exception ex) { + context.logger().error("Failed to copy file {}: {}", entry.getValue(), ex); + } + } + return archiveSize; + } +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/archival/ZipArchiver.java b/src/main/java/dev/ftb/mods/ftbbackups/archival/ZipArchiver.java new file mode 100644 index 00000000..91dde1de --- /dev/null +++ b/src/main/java/dev/ftb/mods/ftbbackups/archival/ZipArchiver.java @@ -0,0 +1,60 @@ +package dev.ftb.mods.ftbbackups.archival; + +import dev.ftb.mods.ftbbackups.FTBBackups; +import dev.ftb.mods.ftbbackups.FTBBackupsConfig; +import dev.ftb.mods.ftbbackups.api.IArchivalPlugin; +import net.minecraft.resources.ResourceLocation; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +public enum ZipArchiver implements IArchivalPlugin { + INSTANCE; + + public static final ResourceLocation ID = FTBBackups.id("zip"); + + @Override + public ResourceLocation getId() { + return ID; + } + + @Override + public long createArchive(ArchivalContext context) throws IOException { + ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(context.archivePath().toFile())); + zos.setLevel(context.compressionLevel()); + + byte[] buffer = new byte[FTBBackupsConfig.BUFFER_SIZE.get()]; + + context.logger().info("Zipping {} files...", context.manifest().size()); + + for (Map.Entry entry : context.manifest().entrySet()) { + Path absPath = entry.getKey(); + String archiveEntry = entry.getValue(); + try { + context.notifyProcessingFile(archiveEntry); + ZipEntry ze = new ZipEntry(archiveEntry); + + zos.putNextEntry(ze); + FileInputStream fis = new FileInputStream(absPath.toFile()); + + int len; + while ((len = fis.read(buffer)) > 0) { + zos.write(buffer, 0, len); + } + zos.closeEntry(); + fis.close(); + } catch (IOException ex) { + throw new IOException(String.format("Failed to add file %s to ZIP archive: %s", archiveEntry, ex)); + } + } + + zos.close(); + return Files.size(context.archivePath()); + } +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java b/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java index d925f795..7cb4146e 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java @@ -25,7 +25,7 @@ public static void handler(BackupProgressPacket message, IPayloadContext context } public static CustomPacketPayload create() { - return new BackupProgressPacket(Backups.INSTANCE.currentFile, Backups.INSTANCE.totalFiles); + return new BackupProgressPacket(Backups.INSTANCE.getCurrentFileIndex(), Backups.INSTANCE.totalFiles); } public static CustomPacketPayload reset() { diff --git a/src/main/resources/assets/ftbbackups/lang/en_us.json b/src/main/resources/assets/ftbbackups/lang/en_us.json index 8ba2f918..24a9441a 100644 --- a/src/main/resources/assets/ftbbackups/lang/en_us.json +++ b/src/main/resources/assets/ftbbackups/lang/en_us.json @@ -26,6 +26,7 @@ "ftbbackups3.general.advanced": "Advanced", "ftbbackups3.general.advanced.buffer_size": "Buffer Size", "ftbbackups3.general.advanced.buffer_size.tooltip": "Buffer size used when reading files to be backed up.\nDon't adjust this unless you understand the implications!", + "ftbbackups3.lang.server": "Server", "ftbbackups3.lang.timer": "Next backup in: %s", "ftbbackups3.lang.timer_progress": "[%d%% | %d / %d]", "ftbbackups3.lang.start": "Server Backup started!", From 04160ee115de0fbfd7e4bb187e90de09cb4b7216 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Thu, 24 Apr 2025 16:56:40 +0100 Subject: [PATCH 21/42] chore: lots of dev work now in a usable alpha state! --- build.gradle | 1 + gradle.properties | 2 +- .../ftb/mods/ftbbackups/BackupCommands.java | 34 +- .../java/dev/ftb/mods/ftbbackups/Backups.java | 183 +++++----- .../dev/ftb/mods/ftbbackups/FTBBackups.java | 20 +- .../ftbbackups/FTBBackupsClientConfig.java | 24 ++ ...onfig.java => FTBBackupsServerConfig.java} | 22 +- .../dev/ftb/mods/ftbbackups/api/Backup.java | 27 +- .../ftb/mods/ftbbackups/api/BackupsApi.java | 12 - .../mods/ftbbackups/api/IArchivalPlugin.java | 102 +++++- .../ftbbackups/api/event/BackupEvent.java | 20 +- .../event/RegisterArchivalPluginEvent.java | 6 + .../archival/ArchivePluginManager.java | 30 +- .../ftbbackups/archival/FileCopyArchiver.java | 23 +- .../mods/ftbbackups/archival/ZipArchiver.java | 43 ++- .../ftbbackups/client/BackupOverlayLayer.java | 60 +++- .../mods/ftbbackups/client/BackupsClient.java | 95 +++++- .../client/RestoreBackupScreen.java | 316 ++++++++++++++++++ .../client/SelectArchivalPluginScreen.java | 79 +++++ .../config/ArchivalPluginConfig.java | 17 + .../config/ArchivalPluginValue.java | 35 ++ .../ftbbackups/net/BackupProgressPacket.java | 14 +- .../assets/ftbbackups/lang/de_de.json | 34 -- .../assets/ftbbackups/lang/pl_pl.json | 36 -- .../assets/ftbbackups/lang/ru_ru.json | 36 -- .../assets/ftbbackups/lang/zh_cn.json | 25 -- .../lang/en_us.json | 27 +- .../ftbbackups3/sidebar_buttons/config.json | 8 + 28 files changed, 1011 insertions(+), 320 deletions(-) create mode 100644 src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClientConfig.java rename src/main/java/dev/ftb/mods/ftbbackups/{FTBBackupsConfig.java => FTBBackupsServerConfig.java} (86%) delete mode 100644 src/main/java/dev/ftb/mods/ftbbackups/api/BackupsApi.java create mode 100644 src/main/java/dev/ftb/mods/ftbbackups/client/RestoreBackupScreen.java create mode 100644 src/main/java/dev/ftb/mods/ftbbackups/client/SelectArchivalPluginScreen.java create mode 100644 src/main/java/dev/ftb/mods/ftbbackups/config/ArchivalPluginConfig.java create mode 100644 src/main/java/dev/ftb/mods/ftbbackups/config/ArchivalPluginValue.java delete mode 100644 src/main/resources/assets/ftbbackups/lang/de_de.json delete mode 100644 src/main/resources/assets/ftbbackups/lang/pl_pl.json delete mode 100644 src/main/resources/assets/ftbbackups/lang/ru_ru.json delete mode 100644 src/main/resources/assets/ftbbackups/lang/zh_cn.json rename src/main/resources/assets/{ftbbackups => ftbbackups3}/lang/en_us.json (69%) create mode 100644 src/main/resources/assets/ftbbackups3/sidebar_buttons/config.json diff --git a/build.gradle b/build.gradle index 1f8f0da3..549ac6dc 100644 --- a/build.gradle +++ b/build.gradle @@ -80,6 +80,7 @@ configurations { } repositories { + mavenLocal() maven { url "https://maven.ftb.dev/releases" } // FTB Mods maven { url "https://maven.architectury.dev" } } diff --git a/gradle.properties b/gradle.properties index c2441dcc..eb85ea68 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,4 +25,4 @@ maven_group=dev.ftb.mods curseforge_id=314904 #Deps -ftb_library_version=2101.1.12 \ No newline at end of file +ftb_library_version=2101.1.13-SNAPSHOT \ No newline at end of file diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java index d357ab5e..adf24a13 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java @@ -5,6 +5,8 @@ import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.exceptions.CommandSyntaxException; import dev.architectury.networking.NetworkManager; +import dev.ftb.mods.ftblibrary.net.EditConfigChoicePacket; +import dev.ftb.mods.ftblibrary.net.EditConfigChoicePacket.ConfigType; import dev.ftb.mods.ftblibrary.net.EditConfigPacket; import net.minecraft.ChatFormatting; import net.minecraft.commands.CommandSourceStack; @@ -18,7 +20,7 @@ public class BackupCommands { public static void register(CommandDispatcher dispatcher) { - dispatcher.register(Commands.literal("ftbbackups") + dispatcher.register(Commands.literal(FTBBackups.MOD_ID) .then(Commands.literal("time") .executes(ctx -> time(ctx.getSource())) ) @@ -45,6 +47,14 @@ public static void register(CommandDispatcher dispatcher) { .requires(BackupCommands::isAdmin) .executes(ctx -> editServerConfig(ctx.getSource())) ) + .then(Commands.literal("clientconfig") + .requires(CommandSourceStack::isPlayer) + .executes(ctx -> editClientConfig(ctx.getSource())) + ) + .then(Commands.literal("config") + .requires(CommandSourceStack::isPlayer) + .executes(ctx -> editConfig(ctx.getSource())) + ) ); } @@ -53,12 +63,24 @@ private static boolean isAdmin(CommandSourceStack cs) { } private static int editServerConfig(CommandSourceStack source) throws CommandSyntaxException { - NetworkManager.sendToPlayer(source.getPlayerOrException(), new EditConfigPacket(FTBBackupsConfig.KEY)); + NetworkManager.sendToPlayer(source.getPlayerOrException(), EditConfigChoicePacket.server(FTBBackupsServerConfig.KEY)); + return Command.SINGLE_SUCCESS; + } + + private static int editClientConfig(CommandSourceStack source) throws CommandSyntaxException { + NetworkManager.sendToPlayer(source.getPlayerOrException(), EditConfigChoicePacket.client(FTBBackupsClientConfig.KEY)); + return Command.SINGLE_SUCCESS; + } + + private static int editConfig(CommandSourceStack source) throws CommandSyntaxException { + NetworkManager.sendToPlayer(source.getPlayerOrException(), EditConfigChoicePacket.choose( + FTBBackupsClientConfig.KEY, FTBBackupsServerConfig.KEY, Component.literal("FTB Backups 3")) + ); return Command.SINGLE_SUCCESS; } private static int time(CommandSourceStack source) { - Duration d = Duration.between(Instant.now(), Instant.ofEpochMilli(Backups.INSTANCE.nextBackupTime)); + Duration d = Duration.between(Instant.now(), Instant.ofEpochMilli(Backups.getServerInstance().nextBackupTime)); var s = String.format("%02d:%02d:%02d", d.toHoursPart(), d.toMinutesPart(), d.toSecondsPart()); source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.timer", s), true); @@ -66,7 +88,7 @@ private static int time(CommandSourceStack source) { } private static int start(CommandSourceStack source, String customName) { - if (Backups.INSTANCE.run(source.getServer(), false, source.getDisplayName(), customName)) { + if (Backups.getServerInstance().run(source.getServer(), false, source.getDisplayName(), customName)) { for (ServerPlayer player : source.getServer().getPlayerList().getPlayers()) { player.sendSystemMessage(Component.translatable("ftbbackups3.lang.manual_launch", source.getDisplayName())); } @@ -78,14 +100,14 @@ private static int start(CommandSourceStack source, String customName) { } private static int size(CommandSourceStack source) { - long totalSize = Backups.INSTANCE.totalBackupSize(); + long totalSize = Backups.getServerInstance().totalBackupSize(); source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.size.current", BackupUtils.formatSizeString(source.getServer().getWorldPath(LevelResource.ROOT))), true); source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.size.total", BackupUtils.formatSizeString(totalSize)), true); source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.size.available", - BackupUtils.formatSizeString(Math.min(FTBBackupsConfig.MAX_TOTAL_SIZE.get(), Backups.INSTANCE.backupsFolder.toFile().getFreeSpace()))), true); + BackupUtils.formatSizeString(Math.min(FTBBackupsServerConfig.MAX_TOTAL_SIZE.get(), Backups.getServerInstance().backupsFolder.toFile().getFreeSpace()))), true); return 1; } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java index 922490ee..e86736c1 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java @@ -17,51 +17,63 @@ import net.neoforged.fml.loading.FMLPaths; import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.network.PacketDistributor; +import org.apache.commons.lang3.mutable.MutableInt; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; import java.io.IOException; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.time.Duration; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; -/** - * TODO: Logging! - */ -public enum Backups { - INSTANCE; - +public class Backups { private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss"); public static final Logger LOGGER = LoggerFactory.getLogger(Backups.class); public static final String BACKUPS_JSON_FILE = "backups.json"; + private static Backups clientInstance = null; + private static Backups serverInstance = null; + private final List backups = new ArrayList<>(); public Path backupsFolder; public long nextBackupTime = -1L; - public BackupStatus backupStatus = BackupStatus.NONE; + public BackupStatus backupStatus; public boolean printFiles = false; private final AtomicInteger currentFileIndex = new AtomicInteger(0); + private int prevFileIndex = 0; public int totalFiles = 0; private String currentFileName = ""; public boolean hadPlayersOnline = false; - public void init(MinecraftServer server) { - String folder = FTBBackupsConfig.FOLDER.get(); - backupsFolder = folder.trim().isEmpty() ? - FMLPaths.GAMEDIR.get().resolve("backups") : - Paths.get(folder); + public static Backups getClientInstance() { + if (clientInstance == null) { + clientInstance = new Backups(); + } + clientInstance.loadIndexFile(); + return clientInstance; + } + + public static void initServerInstance() { + serverInstance = new Backups(); + } + + public static Backups getServerInstance() { + if (serverInstance == null) { + initServerInstance(); + } + return serverInstance; + } + + private Backups() { + backupsFolder = FTBBackupsServerConfig.getBackupFolder(FMLPaths.GAMEDIR.get()); try { backupsFolder = backupsFolder.toRealPath(); @@ -69,25 +81,30 @@ public void init(MinecraftServer server) { } backupStatus = BackupStatus.NONE; + + loadIndexFile(); + + LOGGER.info("Backups will be written to: {}", backupsFolder); + nextBackupTime = System.currentTimeMillis() + FTBBackupsServerConfig.getBackupTimerMillis(); + } + + public void loadIndexFile() { backups.clear(); - Path backupsIndexFile = indexJsonPath(server); - JsonElement element = BackupUtils.readJson(backupsIndexFile); + Path backupsIndex = backupsIndexPath(); + JsonElement element = BackupUtils.readJson(backupsIndex); Backup.LIST_CODEC.parse(JsonOps.INSTANCE, element) - .resultOrPartial(err -> LOGGER.warn("can't parse backups index {}", backupsIndexFile)) + .resultOrPartial(err -> LOGGER.warn("can't parse backups index {}", backupsIndex)) .ifPresent(backups::addAll); - - LOGGER.info("Backups will be written to: {}", backupsFolder); - nextBackupTime = System.currentTimeMillis() + FTBBackupsConfig.getBackupTimerMillis(); } - private static Path indexJsonPath(MinecraftServer server) { - return server.getServerDirectory().resolve(FTBBackups.MOD_ID).resolve(BACKUPS_JSON_FILE); + private static Path backupsIndexPath() { + return FMLPaths.GAMEDIR.get().resolve(FTBBackupsServerConfig.DEFAULT_BACKUP_FOLDER).resolve(BACKUPS_JSON_FILE); } public void tick(MinecraftServer server, long now) { if (nextBackupTime > 0L && nextBackupTime <= now) { - if (!FTBBackupsConfig.ONLY_IF_PLAYERS_ONLINE.get() || hadPlayersOnline || !server.getPlayerList().getPlayers().isEmpty()) { + if (!FTBBackupsServerConfig.ONLY_IF_PLAYERS_ONLINE.get() || hadPlayersOnline || !server.getPlayerList().getPlayers().isEmpty()) { hadPlayersOnline = false; run(server, true, Component.translatable("ftbbackups3.lang.server"), ""); } @@ -102,8 +119,9 @@ public void tick(MinecraftServer server, long now) { } } - if (!FTBBackupsConfig.SILENT.get()) { - PacketDistributor.sendToAllPlayers(BackupProgressPacket.reset()); + currentFileIndex.set(0); + if (!FTBBackupsServerConfig.SILENT.get()) { + PacketDistributor.sendToAllPlayers(BackupProgressPacket.complete()); } } else if (backupStatus.isRunning() && printFiles) { int curIdx = currentFileIndex.get(); @@ -111,8 +129,9 @@ public void tick(MinecraftServer server, long now) { LOGGER.info("[{} | {}%]: {}", currentFileIndex, (int) ((curIdx / (double) totalFiles) * 100D), currentFileName); } - if (!FTBBackupsConfig.SILENT.get()) { - PacketDistributor.sendToAllPlayers(BackupProgressPacket.create()); + if (!FTBBackupsServerConfig.SILENT.get() && prevFileIndex != currentFileIndex.get()) { + PacketDistributor.sendToAllPlayers(BackupProgressPacket.update()); + prevFileIndex = currentFileIndex.get(); } } } @@ -128,13 +147,13 @@ public boolean run(MinecraftServer server, boolean auto, Component name, String return false; } - if (auto && !FTBBackupsConfig.AUTO.get()) { + if (auto && !FTBBackupsServerConfig.AUTO.get()) { return false; } notifyAll(server, Component.translatable("ftbbackups3.lang.start", name), false); LOGGER.info("backup starting: {}", name.getString()); - nextBackupTime = System.currentTimeMillis() + FTBBackupsConfig.getBackupTimerMillis(); + nextBackupTime = System.currentTimeMillis() + FTBBackupsServerConfig.getBackupTimerMillis(); for (ServerLevel level : server.getAllLevels()) { if (level != null) { @@ -160,17 +179,19 @@ public boolean run(MinecraftServer server, boolean auto, Component name, String private void createBackup(MinecraftServer server, String customName) { Path absoluteWorldPath = server.getWorldPath(LevelResource.ROOT).toAbsolutePath(); - IArchivalPlugin archivalPlugin = ArchivePluginManager.INSTANCE.getPlugin(FTBBackupsConfig.archivalPlugin()); + IArchivalPlugin archivalPlugin = ArchivePluginManager.serverInstance().getPlugin(FTBBackupsServerConfig.archivalPlugin()); - String backupFileName = IArchivalPlugin.addFileExtension(archivalPlugin, customName.isEmpty() ? DATE_TIME_FORMATTER.format(LocalDateTime.now()) : customName); + String backupFileName = archivalPlugin.addFileExtension(customName.isEmpty() ? DATE_TIME_FORMATTER.format(LocalDateTime.now()) : customName); long now = Util.getEpochMillis(); boolean success = false; Exception error = null; long archiveSize = 0L; + MutableInt fileCount = new MutableInt(0); try { var manifest = gatherFiles(server); + fileCount.setValue(manifest.size()); for (Map.Entry entry : manifest.entrySet()) { archiveSize += Files.size(entry.getKey()); @@ -182,10 +203,12 @@ private void createBackup(MinecraftServer server, String customName) { LOGGER.info("Backing up {} files...", totalFiles); printFiles = true; + PacketDistributor.sendToAllPlayers(BackupProgressPacket.start()); + currentFileIndex.set(0); Path archiveDest = backupsFolder.resolve(backupFileName); long start = Util.getEpochMillis(); - archiveSize = archivalPlugin.createArchive(new BackupContext(manifest, LOGGER, archiveDest, FTBBackupsConfig.COMPRESSION_LEVEL.get(), this)); + archiveSize = archivalPlugin.createArchive(new BackupContext(manifest, LOGGER, archiveDest, FTBBackupsServerConfig.COMPRESSION_LEVEL.get(), this)); LOGGER.info("Created archive {} from {} in {} ms ({})!", archiveDest, absoluteWorldPath, Util.getEpochMillis() - start, BackupUtils.formatSizeString(archiveSize) @@ -193,7 +216,7 @@ private void createBackup(MinecraftServer server, String customName) { success = true; } catch (Exception ex) { - if (!FTBBackupsConfig.SILENT.get()) { + if (!FTBBackupsServerConfig.SILENT.get()) { String errorName = ex.getClass().getName(); notifyAll(server, Component.translatable("ftbbackups3.lang.fail", errorName), true); } @@ -203,15 +226,15 @@ private void createBackup(MinecraftServer server, String customName) { printFiles = false; - Backup backup = new Backup(now, backupFileName, getLastIndex() + 1, success, archiveSize); + Backup backup = new Backup(now, archivalPlugin.getId(), backupFileName, server.getWorldData().getLevelName(), getLastIndex() + 1, success, archiveSize, fileCount.getValue()); backups.add(backup); Backup.LIST_CODEC.encodeStart(JsonOps.INSTANCE, backups) - .ifSuccess(json -> BackupUtils.writeJson(indexJsonPath(server), json, true)); + .ifSuccess(json -> BackupUtils.writeJson(backupsIndexPath(), json, true)); NeoForge.EVENT_BUS.post(new BackupEvent.Post(backup, error)); - if (error == null && !FTBBackupsConfig.SILENT.get()) { + if (error == null && !FTBBackupsServerConfig.SILENT.get()) { processBackupResults(server, now, archiveSize); } } @@ -220,7 +243,7 @@ private void createBackup(MinecraftServer server, String customName) { * Scan the instance, gathering files which will be added to the backup archive *
    *
  • All files in the instance's world directory (world/ for dedicated server, saves/{worldname} for SSP
  • - *
  • All extra files found the "extra_files" config setting (may be absolute paths, or relative to instance dir)
  • + *
  • All extra files found the "extra_files" config setting (must be relative to instance dir)
  • *
  • Any extra files added by listeners of {@code BackupEvent.Pre}
  • *
* @param server the server @@ -237,7 +260,12 @@ private Map gatherFiles(MinecraftServer server) throws IOException Files.walkFileTree(path, new SimpleFileVisitor<>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - res.put(file.toRealPath(), file.isAbsolute() ? "_extra_absolute_" + file : "_extra_relative_" + File.separator + file); + if (file.isAbsolute()) { + LOGGER.error("ignoring absolute file {} in extras!", file); + } else { +// res.put(file.toRealPath(), file.isAbsolute() ? "_extra_absolute_" + file : "_extra_relative_" + File.separator + file); + res.put(file.toRealPath(), file.toString()); + } return FileVisitResult.CONTINUE; } }); @@ -246,7 +274,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO } }; - for (String s : FTBBackupsConfig.EXTRA_FILES.get()) { + for (String s : FTBBackupsServerConfig.EXTRA_FILES.get()) { extras.accept(Path.of(s)); } @@ -265,63 +293,6 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO return res; } -// private long archiveFiles(Map manifest, Path dstFile) throws IOException { -// long archiveSize = 0L; -// if (FTBBackupsConfig.COMPRESSION_LEVEL.get() > 0) { -// ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dstFile.toFile())); -// zos.setLevel(FTBBackupsConfig.COMPRESSION_LEVEL.get()); -// -// byte[] buffer = new byte[FTBBackupsConfig.BUFFER_SIZE.get()]; -// -// LOGGER.info("Compressing {} files...", totalFiles); -// -// currentFile = 0; -// manifest.forEach((absPath, archiveEntry) -> { -// try { -// ZipEntry ze = new ZipEntry(archiveEntry); -// currentFileName = archiveEntry; -// -// zos.putNextEntry(ze); -// FileInputStream fis = new FileInputStream(absPath.toFile()); -// -// int len; -// while ((len = fis.read(buffer)) > 0) { -// zos.write(buffer, 0, len); -// } -// zos.closeEntry(); -// fis.close(); -// } catch (Exception ex) { -// LOGGER.error("Failed to read file {}: {}", archiveEntry, ex); -// } -// -// currentFile++; -// }); -// -// zos.close(); -// archiveSize = Files.size(dstFile); -// } else { -// Files.createDirectories(backupsFolder); -// -// currentFile = 0; -// for (Map.Entry entry : manifest.entrySet()) { -// try { -// Path file = entry.getKey(); -// currentFileName = entry.getValue(); -// Path newFile = dstFile.resolve(Path.of(entry.getValue())); -// Files.createDirectories(newFile.getParent()); -// Files.copy(file, newFile); -// archiveSize += Files.size(newFile); -// } catch (Exception ex) { -// LOGGER.error("Failed to copy file {}: {}", entry.getValue(), ex); -// } -// -// currentFile++; -// } -// } -// -// return archiveSize; -// } - /** * Check if any existing backup archives need to be purged, based on the "backups_to_keep" and "max_total_size" * config settings. @@ -332,21 +303,21 @@ private void doArchiveCleanup(long fileSize) { if (!backups.isEmpty()) { backups.sort(null); - int backupsToKeep = FTBBackupsConfig.BACKUPS_TO_KEEP.get() - 1; + int backupsToKeep = FTBBackupsServerConfig.BACKUPS_TO_KEEP.get() - 1; if (backupsToKeep > 0 && backups.size() > backupsToKeep) { while (backups.size() > backupsToKeep) { Backup backup = backups.removeFirst(); - backup.deleteFiles(); + backup.deleteFiles(backupsFolder); } } long totalSize = backups.stream().mapToLong(Backup::size).sum(); if (fileSize > 0L) { - long freeSpace = Math.min(FTBBackupsConfig.MAX_TOTAL_SIZE.get(), backupsFolder.toFile().getFreeSpace()); + long freeSpace = Math.min(FTBBackupsServerConfig.MAX_TOTAL_SIZE.get(), backupsFolder.toFile().getFreeSpace()); while (totalSize + fileSize > freeSpace && !backups.isEmpty()) { Backup backup = backups.removeFirst(); - if (backup.deleteFiles()) { + if (backup.deleteFiles(backupsFolder)) { totalSize -= backup.size(); } } @@ -359,7 +330,7 @@ private void processBackupResults(MinecraftServer server, long backupTime, long String timeString = String.format("%d.%03ds", d.toSeconds(), d.toMillisPart()); Component component; - if (FTBBackupsConfig.DISPLAY_FILE_SIZE.get()) { + if (FTBBackupsServerConfig.DISPLAY_FILE_SIZE.get()) { long totalSize = backups.stream().mapToLong(Backup::size).sum(); String sizeB = BackupUtils.formatSizeString(archiveSize); @@ -392,6 +363,10 @@ public int getCurrentFileIndex() { return currentFileIndex.get(); } + public Collection backups() { + return Collections.unmodifiableCollection(backups); + } + private record BackupContext( Map manifest, Logger logger, diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java index 700d523b..ce1ae969 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java @@ -12,7 +12,6 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; import net.neoforged.api.distmarker.Dist; -import net.neoforged.bus.api.Event; import net.neoforged.bus.api.EventPriority; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.ModContainer; @@ -43,7 +42,8 @@ public FTBBackups(IEventBus eventBus, ModContainer container) { } eventBus.addListener(this::registerNetwork); - ConfigManager.getInstance().registerServerConfig(FTBBackupsConfig.CONFIG, MOD_ID + ".general", true, FTBBackupsConfig::onConfigChanged); + ConfigManager.getInstance().registerServerConfig(FTBBackupsServerConfig.CONFIG, MOD_ID + ".general", true, FTBBackupsServerConfig::onConfigChanged); + ConfigManager.getInstance().registerClientConfig(FTBBackupsClientConfig.CONFIG, MOD_ID + ".general"); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverAboutToStart); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverStopping); @@ -60,9 +60,9 @@ private void clientSetup(FMLClientSetupEvent event, IEventBus eventBus) { } public void serverAboutToStart(ServerAboutToStartEvent event) { - Backups.INSTANCE.init(event.getServer()); + Backups.initServerInstance(); - NeoForge.EVENT_BUS.post(new RegisterArchivalPluginEvent(ArchivePluginManager.INSTANCE::register)); + NeoForge.EVENT_BUS.post(new RegisterArchivalPluginEvent(ArchivePluginManager.serverInstance()::register)); } public void registerCommands(RegisterCommandsEvent event) { @@ -75,8 +75,10 @@ public void registerNetwork(RegisterPayloadHandlersEvent event) { } public void serverStopping(ServerStoppingEvent event) { - if (FTBBackupsConfig.FORCE_ON_SHUTDOWN.get()) { - Backups.INSTANCE.run(event.getServer(), true, Component.literal("Server"), ""); + ArchivePluginManager.serverInstance().clear(); + + if (FTBBackupsServerConfig.FORCE_ON_SHUTDOWN.get()) { + Backups.getServerInstance().run(event.getServer(), true, Component.literal("Server"), ""); } } @@ -87,18 +89,18 @@ private void registerArchivalPlugins(RegisterArchivalPluginEvent event) { public void playerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { if (event.getEntity() instanceof ServerPlayer serverPlayer) { - PacketDistributor.sendToPlayer(serverPlayer, BackupProgressPacket.create()); + PacketDistributor.sendToPlayer(serverPlayer, BackupProgressPacket.update()); } } public void playerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) { if (event.getEntity() instanceof ServerPlayer) { - Backups.INSTANCE.hadPlayersOnline = true; + Backups.getServerInstance().hadPlayersOnline = true; } } public void levelTick(ServerTickEvent.Post event) { - Backups.INSTANCE.tick(event.getServer(), System.currentTimeMillis()); + Backups.getServerInstance().tick(event.getServer(), System.currentTimeMillis()); } public static ResourceLocation id(String path) { diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClientConfig.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClientConfig.java new file mode 100644 index 00000000..c460a451 --- /dev/null +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClientConfig.java @@ -0,0 +1,24 @@ +package dev.ftb.mods.ftbbackups; + +import dev.ftb.mods.ftblibrary.snbt.config.BooleanValue; +import dev.ftb.mods.ftblibrary.snbt.config.EnumValue; +import dev.ftb.mods.ftblibrary.snbt.config.IntValue; +import dev.ftb.mods.ftblibrary.snbt.config.SNBTConfig; +import dev.ftb.mods.ftblibrary.util.PanelPositioning; + +public interface FTBBackupsClientConfig { + String KEY = FTBBackups.MOD_ID + "-client"; + SNBTConfig CONFIG = SNBTConfig.create(KEY) + .comment("Client-specific configuration for FTB Backups 3", + "Modpack defaults should be defined in /config/" + KEY + ".snbt", + " (may be overwritten on modpack update)", + "Players may locally override this by copying into /local/" + KEY + ".snbt", + " (will NOT be overwritten on modpack update)" + ); + + SNBTConfig OVERLAY = CONFIG.addGroup("overlay"); + BooleanValue SHOW_OVERLAY = OVERLAY.addBoolean("show_overlay", true); + EnumValue OVERLAY_POS = OVERLAY.addEnum("overlay_pos", PanelPositioning.NAME_MAP, PanelPositioning.TOP_LEFT); + IntValue OVERLAY_INSET_X = OVERLAY.addInt("overlay_inset_x", 4); + IntValue OVERLAY_INSET_Y = OVERLAY.addInt("overlay_inset_y", 4); +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsServerConfig.java similarity index 86% rename from src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java rename to src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsServerConfig.java index cb2857d3..74106943 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsConfig.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsServerConfig.java @@ -2,13 +2,17 @@ import dev.ftb.mods.ftbbackups.archival.ZipArchiver; +import dev.ftb.mods.ftbbackups.config.ArchivalPluginValue; import dev.ftb.mods.ftblibrary.snbt.config.*; import net.minecraft.resources.ResourceLocation; +import net.neoforged.fml.loading.FMLPaths; import net.neoforged.neoforge.common.util.Lazy; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; -public interface FTBBackupsConfig { +public interface FTBBackupsServerConfig { String KEY = FTBBackups.MOD_ID + "-server"; SNBTConfig CONFIG = SNBTConfig.create(KEY) .comment("Server-specific configuration for FTB Backups 3", @@ -18,6 +22,9 @@ public interface FTBBackupsConfig { " (will NOT be overwritten on modpack update)" ); + // relative to the game directory: /ftbbackups3 + String DEFAULT_BACKUP_FOLDER = FTBBackups.MOD_ID; + BooleanValue AUTO = CONFIG.addBoolean("auto", true) .comment("Enables backups to run automatically"); @@ -39,7 +46,7 @@ public interface FTBBackupsConfig { "1440 - backups once every day" ); - IntValue COMPRESSION_LEVEL = CONFIG.addInt("compression_level", 1, 0, 9) + IntValue COMPRESSION_LEVEL = CONFIG.addInt("compression_level", 5, 0, 9) .comment("Compression level for archived files. Note that this is dependent on the particular plugin in use", "0 - No compression", "1 - Best speed", @@ -70,7 +77,7 @@ public interface FTBBackupsConfig { IntValue BUFFER_SIZE = ADVANCED.addInt("buffer_size", 4096, 256, 65536) .comment("Buffer size for writing files."); - StringValue ARCHIVAL_PLUGIN = CONFIG.addString("archival_plugin", ZipArchiver.ID.toString()) + ArchivalPluginValue ARCHIVAL_PLUGIN = CONFIG.add(new ArchivalPluginValue(CONFIG, "archival_plugin", ZipArchiver.ID)) .comment("Method to use to create a backup archive.", "Builtin methods are \"ftbbackups:zip\" (create a ZIP file) and \"ftbbackups:filecopy\" (simple recursive copy of files with no compression)", "More archival plugins may be added by other mods."); @@ -103,11 +110,18 @@ static void onConfigChanged(boolean isServer) { } static ResourceLocation archivalPlugin() { - ResourceLocation rl = ResourceLocation.tryParse(ARCHIVAL_PLUGIN.get()); + ResourceLocation rl = ARCHIVAL_PLUGIN.get(); if (rl == null) { Backups.LOGGER.error("Invalid archive plugin id {}, defaulting to ftbbackups:zip!", ARCHIVAL_PLUGIN.get()); return ZipArchiver.ID; } return rl; } + + static Path getBackupFolder(Path gameDir) { + String folder = FTBBackupsServerConfig.FOLDER.get(); + return folder.trim().isEmpty() ? + FMLPaths.GAMEDIR.get().resolve(DEFAULT_BACKUP_FOLDER) : + Paths.get(folder); + } } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/api/Backup.java b/src/main/java/dev/ftb/mods/ftbbackups/api/Backup.java index bf718ff8..5a9c90e5 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/api/Backup.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/api/Backup.java @@ -3,6 +3,7 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import dev.ftb.mods.ftbbackups.Backups; +import net.minecraft.resources.ResourceLocation; import org.apache.commons.io.FileUtils; import java.io.IOException; @@ -10,13 +11,27 @@ import java.nio.file.Path; import java.util.List; -public record Backup(long time, String fileId, int index, boolean success, long size) implements Comparable { +/** + * Holds information about one specific backup. + * + * @param time time the backup was created (milliseconds since the epoch) + * @param archivalPlugin the archival plugin used to create this backup + * @param fileId name of the backup on disk, relative to the backup directory defined in config (could be a file or a directory) + * @param worldName human-readable name of the world that is backed up + * @param index a monotonically increasing numeric index + * @param success true if the backup succeeded, false if there were any problems + * @param size nominal size of the backup in bytes + */ +public record Backup(long time, ResourceLocation archivalPlugin, String fileId, String worldName, int index, boolean success, long size, int fileCount) implements Comparable { public static final Codec CODEC = RecordCodecBuilder.create(builder -> builder.group( Codec.LONG.fieldOf("time").forGetter(Backup::time), + ResourceLocation.CODEC.fieldOf("archival_plugin").forGetter(Backup::archivalPlugin), Codec.STRING.fieldOf("file").forGetter(Backup::fileId), + Codec.STRING.optionalFieldOf("world_name", "").forGetter(Backup::worldName), Codec.INT.fieldOf("index").forGetter(Backup::index), Codec.BOOL.fieldOf("success").forGetter(Backup::success), - Codec.LONG.fieldOf("size").forGetter(Backup::size) + Codec.LONG.fieldOf("size").forGetter(Backup::size), + Codec.INT.fieldOf("file_count").forGetter(Backup::fileCount) ).apply(builder, Backup::new)); public static final Codec> LIST_CODEC = Codec.list(CODEC); @@ -33,12 +48,8 @@ public boolean equals(Object o) { return o == this || (o instanceof Backup && ((Backup) o).time == time); } - public Path getPath() { - return Backups.INSTANCE.backupsFolder.resolve(fileId); - } - - public boolean deleteFiles() { - Path path = getPath(); + public boolean deleteFiles(Path backupsFolder) { + Path path = backupsFolder.resolve(fileId); try { if (Files.isRegularFile(path)) { Files.delete(path); diff --git a/src/main/java/dev/ftb/mods/ftbbackups/api/BackupsApi.java b/src/main/java/dev/ftb/mods/ftbbackups/api/BackupsApi.java deleted file mode 100644 index ccb40267..00000000 --- a/src/main/java/dev/ftb/mods/ftbbackups/api/BackupsApi.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.ftb.mods.ftbbackups.api; - -import net.minecraft.client.Minecraft; - -/** - * This class is the API for FTB Backups. - */ -public class BackupsApi { - public BackupsApi() { - Minecraft.getInstance(); - } -} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/api/IArchivalPlugin.java b/src/main/java/dev/ftb/mods/ftbbackups/api/IArchivalPlugin.java index c5d55c27..320c7ad7 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/api/IArchivalPlugin.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/api/IArchivalPlugin.java @@ -4,33 +4,121 @@ import org.jetbrains.annotations.ApiStatus; import org.slf4j.Logger; -import java.io.IOException; import java.nio.file.Path; import java.util.Map; +/** + * Implementations of this interface can be provided to + * {@link dev.ftb.mods.ftbbackups.api.event.RegisterArchivalPluginEvent#register(IArchivalPlugin)} to register new + * archival plugins. The event is fired on the server, when the server is starting up, and lazily on the client + * when populating GUI data. + */ public interface IArchivalPlugin { + /** + * {@return a unique ID for this plugin; it should be namespaced with your mod name} + */ ResourceLocation getId(); + /** + * Get the file extension for backup files created by this plugin. By default, this is the path component + * of the ID returned by {@link #getId()}, and is appended (along with a ".") to the filename. + *

+ * An empty string return is valid here to indicate no file extension. + * + * @return the file extension + */ default String getFileExtension() { return getId().getPath(); } - long createArchive(ArchivalContext context) throws IOException; + /** + * Called when actually creating the archive file + * + * @param context the archival context + * @return the nominal size of the archived, in bytes + * @throws Exception thrown if anything goes wrong + */ + @ApiStatus.OverrideOnly + long createArchive(ArchivalContext context) throws Exception; - static String addFileExtension(IArchivalPlugin plugin, String fileName) { - return plugin.getFileExtension().isEmpty() ? fileName : fileName + "." + plugin.getFileExtension(); - } + /** + * Called to restore a backup archive, overwriting existing files in a game instance. This is only + * called by the client for SSP instances, while the game is not actually running; it's not applicable to + * dedicated server instances. + * + * @param context the restoration context + * @throws Exception thrown if anything goes wrong + */ + @ApiStatus.OverrideOnly + void restoreArchive(RestorationContext context) throws Exception; + /** + * Convenience method to append a file extension + * @param fileName the base file name + * @return a filename with the archive extension appended + */ @ApiStatus.NonExtendable - interface ArchivalContext { - Map manifest(); + default String addFileExtension(String fileName) { + return getFileExtension().isEmpty() ? fileName : fileName + "." + getFileExtension(); + } + /** + * Base interface for archival and restoration contexts. + */ + @ApiStatus.NonExtendable + interface Context { + /** + * {@return a Logger instance, which can be used for any logging that's needed} + */ Logger logger(); + /** + * {@return the path to the file (or directory) where the archive should be written to or extracted from} + */ Path archivePath(); + /** + * This method should be called by the plugin implementation for each file that is being backed-up or + * restored. This is primarily used to provide feedback to players about backup/restoration progress. + * + * @param filename the filename currently being handled + */ void notifyProcessingFile(String filename); + } + /** + * Passed to {@link IArchivalPlugin#createArchive(ArchivalContext)}. This provides information about the backup + * to be made, along with some methods to provide feedback about the backup process. + */ + @ApiStatus.NonExtendable + interface ArchivalContext extends Context { + /** + * Get a manifest of the files to be archived; this is a map of the absolute file path to a string path within + * the archive that is relative to the game instance directory, + * e.g. "/path/to/your/instance/saves/New World/level.dat" -> "saves/New World/level.dat" + * @return the archive manifest + */ + Map manifest(); + + /** + * An integer compression level, in the range 0..9. The plugin is free to interpret this value as appropriate + * (or ignore it completely), but a value of 0 indicates no compression is desired, and a value of 9 + * indicates the maximum possible compression is desired. + * + * @return the desired compression level + */ int compressionLevel(); } + + /** + * Passed to {@link IArchivalPlugin#restoreArchive(RestorationContext)}. This provides information about the backup + * being restored, along with some methods to provide feedback about the restoration process. + */ + @ApiStatus.NonExtendable + interface RestorationContext extends Context { + /** + * {@return the folder into which files should be extracted, typically the game instance folder} + */ + Path destinationFolder(); + } } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/api/event/BackupEvent.java b/src/main/java/dev/ftb/mods/ftbbackups/api/event/BackupEvent.java index 31ee6b0e..f1efc9f8 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/api/event/BackupEvent.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/api/event/BackupEvent.java @@ -5,9 +5,14 @@ import javax.annotation.Nullable; import java.nio.file.Path; +import java.util.Optional; import java.util.function.Consumer; public class BackupEvent extends Event { + /** + * Fired just before a backup is made. This can be used to add extra files to the backup; files added + * must be relative to the game instance directory. + */ public static class Pre extends BackupEvent { private final Consumer callback; @@ -20,6 +25,9 @@ public void add(Path file) { } } + /** + * Fired immediately after a backup is made. + */ public static class Post extends BackupEvent { private final Backup backup; private final Exception error; @@ -29,13 +37,19 @@ public Post(Backup backup, @Nullable Exception error) { this.error = error; } + /** + * {@return the details for the backup that was just made. + */ public Backup getBackup() { return backup; } - @Nullable - public Exception getError() { - return error; + /** + * Get the error, if any, that occurred during this backup. + * @return the exception that was thrown, or {@code Optional.empty()} if the backup was successful + */ + public Optional getError() { + return Optional.ofNullable(error); } } } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/api/event/RegisterArchivalPluginEvent.java b/src/main/java/dev/ftb/mods/ftbbackups/api/event/RegisterArchivalPluginEvent.java index 0cbcf4f5..edcffb4f 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/api/event/RegisterArchivalPluginEvent.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/api/event/RegisterArchivalPluginEvent.java @@ -5,6 +5,12 @@ import java.util.function.Consumer; +/** + * Listen to this event to register custom archival plugins; alternative methods of creating an archive file. Note + * that the mod by default provides archive plugins to create ZIP files, and do a simple recursive file copy. + *

+ * This event is fired on both client and server and plugins should be registered on both sides. + */ public class RegisterArchivalPluginEvent extends Event { private final Consumer consumer; diff --git a/src/main/java/dev/ftb/mods/ftbbackups/archival/ArchivePluginManager.java b/src/main/java/dev/ftb/mods/ftbbackups/archival/ArchivePluginManager.java index b3751dce..9dd8dd49 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/archival/ArchivePluginManager.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/archival/ArchivePluginManager.java @@ -1,16 +1,34 @@ package dev.ftb.mods.ftbbackups.archival; import dev.ftb.mods.ftbbackups.api.IArchivalPlugin; +import dev.ftb.mods.ftbbackups.api.event.RegisterArchivalPluginEvent; import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.common.NeoForge; +import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -public enum ArchivePluginManager { - INSTANCE; +public class ArchivePluginManager { + private static final ArchivePluginManager CLIENT = new ArchivePluginManager(); + private static final ArchivePluginManager SERVER = new ArchivePluginManager(); private final Map plugins = new ConcurrentHashMap<>(); + private static boolean pluginsRegistered = false; + + public static ArchivePluginManager clientInstance() { + if (!pluginsRegistered) { + NeoForge.EVENT_BUS.post(new RegisterArchivalPluginEvent(CLIENT::register)); + pluginsRegistered = true; + } + return CLIENT; + } + + public static ArchivePluginManager serverInstance() { + return SERVER; + } + public synchronized void register(IArchivalPlugin plugin) { if (plugins.containsKey(plugin.getId())) { throw new IllegalStateException("archival plugin " + plugin.getId() + " already registered!"); @@ -21,4 +39,12 @@ public synchronized void register(IArchivalPlugin plugin) { public IArchivalPlugin getPlugin(ResourceLocation id) { return plugins.get(id); } + + public void clear() { + plugins.clear(); + } + + public Map plugins() { + return Collections.unmodifiableMap(plugins); + } } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/archival/FileCopyArchiver.java b/src/main/java/dev/ftb/mods/ftbbackups/archival/FileCopyArchiver.java index d59afd4b..be91f706 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/archival/FileCopyArchiver.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/archival/FileCopyArchiver.java @@ -4,8 +4,11 @@ import dev.ftb.mods.ftbbackups.api.IArchivalPlugin; import net.minecraft.resources.ResourceLocation; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.Map; public enum FileCopyArchiver implements IArchivalPlugin { @@ -32,7 +35,7 @@ public long createArchive(ArchivalContext context) { context.notifyProcessingFile(entry.getValue()); Path newFile = context.archivePath().resolve(Path.of(entry.getValue())); Files.createDirectories(newFile.getParent()); - Files.copy(file, newFile); + Files.copy(file, newFile, StandardCopyOption.COPY_ATTRIBUTES); archiveSize += Files.size(newFile); } catch (Exception ex) { context.logger().error("Failed to copy file {}: {}", entry.getValue(), ex); @@ -40,4 +43,22 @@ public long createArchive(ArchivalContext context) { } return archiveSize; } + + @Override + public void restoreArchive(RestorationContext context) throws Exception { + Path srcDir = context.archivePath(); + try (var s = Files.walk(srcDir)) { + s.forEach(source -> { + if (Files.isRegularFile(source)) { + Path destination = context.destinationFolder().resolve(srcDir.relativize(source)); + try { + context.notifyProcessingFile(source.toString()); + Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES); + } catch (Exception ex) { + context.logger().error("Failed to copy {} -> {}: {}", source, destination, ex); + } + } + }); + } + } } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/archival/ZipArchiver.java b/src/main/java/dev/ftb/mods/ftbbackups/archival/ZipArchiver.java index 91dde1de..b3d8a052 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/archival/ZipArchiver.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/archival/ZipArchiver.java @@ -1,7 +1,7 @@ package dev.ftb.mods.ftbbackups.archival; import dev.ftb.mods.ftbbackups.FTBBackups; -import dev.ftb.mods.ftbbackups.FTBBackupsConfig; +import dev.ftb.mods.ftbbackups.FTBBackupsServerConfig; import dev.ftb.mods.ftbbackups.api.IArchivalPlugin; import net.minecraft.resources.ResourceLocation; @@ -12,6 +12,7 @@ import java.nio.file.Path; import java.util.Map; import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; public enum ZipArchiver implements IArchivalPlugin { @@ -29,7 +30,7 @@ public long createArchive(ArchivalContext context) throws IOException { ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(context.archivePath().toFile())); zos.setLevel(context.compressionLevel()); - byte[] buffer = new byte[FTBBackupsConfig.BUFFER_SIZE.get()]; + byte[] buffer = new byte[FTBBackupsServerConfig.BUFFER_SIZE.get()]; context.logger().info("Zipping {} files...", context.manifest().size()); @@ -57,4 +58,42 @@ public long createArchive(ArchivalContext context) throws IOException { zos.close(); return Files.size(context.archivePath()); } + + @Override + public void restoreArchive(RestorationContext context) throws IOException { + ZipInputStream zis = new ZipInputStream(new FileInputStream(context.archivePath().toFile())); + + byte[] buffer = new byte[FTBBackupsServerConfig.BUFFER_SIZE.get()]; + + ZipEntry zipEntry = zis.getNextEntry(); + while (zipEntry != null) { + context.notifyProcessingFile(zipEntry.getName()); + if (zipEntry.isDirectory()) { + context.logger().warn("ignoring directory entry {}", zipEntry.getName()); + continue; + } + + Path destFile = safePath(context.destinationFolder(), zipEntry); + context.logger().info("extract to {}", destFile); + try (FileOutputStream fos = new FileOutputStream(destFile.toFile())) { + int len; + while ((len = zis.read(buffer)) > 0) { + fos.write(buffer, 0, len); + } + } + + zipEntry = zis.getNextEntry(); + } + + zis.closeEntry(); + zis.close(); + } + + private static Path safePath(Path destDir, ZipEntry zipEntry) throws IOException { + Path subPath = Path.of(zipEntry.getName()); + if (subPath.isAbsolute()) { + throw new IOException("absolute paths not allowed!"); + } + return destDir.resolve(subPath); + } } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/client/BackupOverlayLayer.java b/src/main/java/dev/ftb/mods/ftbbackups/client/BackupOverlayLayer.java index cac4b7b1..2714205f 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/client/BackupOverlayLayer.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/client/BackupOverlayLayer.java @@ -1,16 +1,74 @@ package dev.ftb.mods.ftbbackups.client; import dev.ftb.mods.ftbbackups.FTBBackups; +import dev.ftb.mods.ftbbackups.FTBBackupsClientConfig; +import dev.ftb.mods.ftblibrary.icon.Color4I; +import dev.ftb.mods.ftblibrary.ui.GuiHelper; import net.minecraft.client.DeltaTracker; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.LayeredDraw; +import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; public class BackupOverlayLayer implements LayeredDraw.Layer { public static final ResourceLocation ID = FTBBackups.id("overlay"); + private int prevProgress; + @Override public void render(GuiGraphics guiGraphics, DeltaTracker deltaTracker) { - // NO-OP + if (!FTBBackupsClientConfig.SHOW_OVERLAY.get()) { + return; + } + + BackupsClient.BackupProgress progress = BackupsClient.getBackupProgress(); + if (!progress.inProgress()) { + return; + } + + Component line1 = progress.finished() ? + Component.translatable("ftbbackups3.lang.finished") : + Component.translatable("ftbbackups3.lang.already_running"); + Component line2 = BackupsClient.progressMessage(); + + Minecraft mc = Minecraft.getInstance(); + Font font = mc.font; + int width = mc.getWindow().getGuiScaledWidth() / 4; + + int progressBarHeight = font.lineHeight + 4; + int height = font.lineHeight + progressBarHeight + 8; + float scale = 1f; + + int insetX = FTBBackupsClientConfig.OVERLAY_INSET_X.get(); + int insetY = FTBBackupsClientConfig.OVERLAY_INSET_Y.get(); + var pos = FTBBackupsClientConfig.OVERLAY_POS.get().getPanelPos( + mc.getWindow().getGuiScaledWidth(), mc.getWindow().getGuiScaledHeight(), + (int) (width * scale), (int) (height * scale), + insetX, insetY + ); + + guiGraphics.pose().pushPose(); + guiGraphics.pose().translate(pos.x(), pos.y(), 100); + guiGraphics.pose().scale(scale, scale, 1F); + + // panel background + Color4I.rgba(0x80200020).draw(guiGraphics, -2, -2, width + 4, height + 4); + GuiHelper.drawHollowRect(guiGraphics, -2, -2, width + 4, height + 4, Color4I.rgba(0x80400040), false); + + guiGraphics.drawString(font, line1, 2, 2, 0xFFFAF9F6, false); + + int pw = (int) ((width - 4) * Mth.lerp(deltaTracker.getGameTimeDeltaTicks(), prevProgress, progress.current()) / progress.total()); + int sy = font.lineHeight + 4; + guiGraphics.fill(2, sy - 1, width - 3, sy + progressBarHeight + 1, 0xFF0060E0); + guiGraphics.fill(3, sy, pw, sy + progressBarHeight, 0xFF0040A0); + + guiGraphics.drawString(font, line2, 14, 2 + font.lineHeight + 5, 0xFFFAF9F6, false); + + guiGraphics.pose().popPose(); + + prevProgress = progress.current(); } } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java b/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java index 57dfbdb6..da448dfb 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java @@ -1,49 +1,108 @@ package dev.ftb.mods.ftbbackups.client; -import net.minecraft.ChatFormatting; +import dev.ftb.mods.ftbbackups.Backups; +import dev.ftb.mods.ftbbackups.FTBBackups; import net.minecraft.client.Minecraft; -import net.minecraft.client.OptionInstance; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.gui.screens.worldselection.SelectWorldScreen; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.Event; import net.neoforged.bus.api.IEventBus; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent; -import net.neoforged.neoforge.client.event.CustomizeGuiOverlayEvent; +import net.neoforged.neoforge.client.event.ClientTickEvent; import net.neoforged.neoforge.client.event.RegisterGuiLayersEvent; +import net.neoforged.neoforge.client.event.ScreenEvent; import net.neoforged.neoforge.common.NeoForge; +import org.jetbrains.annotations.NotNull; +@EventBusSubscriber(modid = FTBBackups.MOD_ID, value = Dist.CLIENT, bus = EventBusSubscriber.Bus.MOD) public class BackupsClient { - private static int currentBackupFile = 0; - private static int totalBackupFiles = 0; + private static BackupProgress backupProgress = BackupProgress.NONE; + + private static int progressTicker = 0; public static void init(IEventBus eventBus) { NeoForge.EVENT_BUS.addListener(BackupsClient::onClientDisconnected); - NeoForge.EVENT_BUS.addListener(BackupsClient::onDebugInfoEvent); +// NeoForge.EVENT_BUS.addListener(BackupsClient::onDebugInfoEvent); + NeoForge.EVENT_BUS.addListener(BackupsClient::addRestoreButton); + NeoForge.EVENT_BUS.addListener(BackupsClient::clientTick); + +// eventBus.addListener(BackupsClient::registerGuiLayer); + } - eventBus.addListener(BackupsClient::registerGuiLayer); + private static void clientTick(ClientTickEvent.Post event) { + if (progressTicker > 0 && --progressTicker == 0) { + backupProgress = BackupProgress.NONE; + } + } + + private static void addRestoreButton(ScreenEvent.Init.Post event) { + if (event.getScreen() instanceof SelectWorldScreen s) { + Backups backups = Backups.getClientInstance(); + if (!backups.backups().isEmpty()) { + Component btnLabel = Component.translatable("ftbbackups3.gui.restore").append("..."); + Component title = Component.translatable("ftbbackups3.gui.restore"); + int w = s.getMinecraft().font.width(btnLabel) + 20; + event.addListener(Button.builder(btnLabel, b -> Minecraft.getInstance().setScreen(new RestoreBackupScreen(s, title))) + .bounds(s.width - w - 10, 22, w, 20) + .tooltip(Tooltip.create(Component.translatable("ftbbackups3.gui.restore.tooltip"))) + .build()); + } + } } + @SubscribeEvent public static void registerGuiLayer(RegisterGuiLayersEvent event) { + // needs to be done with SubscribeEvent, addListener() is called too late event.registerAboveAll(BackupOverlayLayer.ID, new BackupOverlayLayer()); } public static void setBackupProgress(int current, int total) { - currentBackupFile = current; - totalBackupFiles = total; + backupProgress = new BackupProgress(current, total); + if (backupProgress.finished()) { + progressTicker = 40; + } + Backups.LOGGER.info("progress: {}", backupProgress); } private static void onClientDisconnected(ClientPlayerNetworkEvent.LoggingOut ignoredEvent) { - currentBackupFile = 0; - totalBackupFiles = 0; + backupProgress = BackupProgress.NONE; + } + +// private static void onDebugInfoEvent(CustomizeGuiOverlayEvent.DebugText event) { +// OptionInstance booleanOptionInstance = Minecraft.getInstance().options.reducedDebugInfo(); +// if (booleanOptionInstance.get()) { +// return; +// } +// +// if (backupProgress.total() > 0 && backupProgress.total() > backupProgress.current()) { +// // I think this is right? @mikey (2025-03-18) +// event.getLeft().add(progressMessage().withStyle(ChatFormatting.LIGHT_PURPLE).getString()); +// } +// } + + public static @NotNull MutableComponent progressMessage() { + return Component.translatable("ftbbackups3.lang.timer_progress", backupProgress.current() * 100 / backupProgress.total(), backupProgress.current(), backupProgress.total()); } - private static void onDebugInfoEvent(CustomizeGuiOverlayEvent.DebugText event) { - OptionInstance booleanOptionInstance = Minecraft.getInstance().options.reducedDebugInfo(); - if (booleanOptionInstance.get()) { - return; + public static BackupProgress getBackupProgress() { + return backupProgress; + } + + public record BackupProgress(int current, int total) { + public static final BackupProgress NONE = new BackupProgress(0, 0); + + public boolean inProgress() { + return total > 0; } - if (totalBackupFiles > 0 && totalBackupFiles > currentBackupFile) { - // I think this is right? @mikey (2025-03-18) - event.getLeft().add(Component.translatable("ftbbackups.lang.timer_progress", currentBackupFile * 100 / totalBackupFiles, currentBackupFile, totalBackupFiles).withStyle(ChatFormatting.LIGHT_PURPLE).getString()); + public boolean finished() { + return current == total; } } } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/client/RestoreBackupScreen.java b/src/main/java/dev/ftb/mods/ftbbackups/client/RestoreBackupScreen.java new file mode 100644 index 00000000..22cc60f9 --- /dev/null +++ b/src/main/java/dev/ftb/mods/ftbbackups/client/RestoreBackupScreen.java @@ -0,0 +1,316 @@ +package dev.ftb.mods.ftbbackups.client; + +import dev.ftb.mods.ftbbackups.Backups; +import dev.ftb.mods.ftbbackups.FTBBackupsServerConfig; +import dev.ftb.mods.ftbbackups.api.Backup; +import dev.ftb.mods.ftbbackups.api.IArchivalPlugin; +import dev.ftb.mods.ftbbackups.api.event.RegisterArchivalPluginEvent; +import dev.ftb.mods.ftbbackups.archival.ArchivePluginManager; +import net.minecraft.ChatFormatting; +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractSelectionList; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.ConfirmScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.fml.loading.FMLPaths; +import net.neoforged.neoforge.common.NeoForge; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; + +import java.nio.file.Path; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Stream; + +public class RestoreBackupScreen extends Screen { + private Button restoreButton; + private BackupsList backupsList; + private EditBox searchBox; + private long totalFiles; + private final AtomicLong currentFile = new AtomicLong(0L); + private String restoringPath = ""; + private int completedTimer = 0; + private String lastError = ""; + + private static final ResourceLocation SUCCESS = ResourceLocation.withDefaultNamespace("pending_invite/accept"); + private static final ResourceLocation FAILURE = ResourceLocation.withDefaultNamespace("pending_invite/reject"); + + static final int UPPER_HEIGHT = 80; + static final int LOWER_HEIGHT = 40; + private final Screen prevScreen; + + protected RestoreBackupScreen(Screen prevScreen, Component title) { + super(title); + + this.prevScreen = prevScreen; + } + + @Override + protected void init() { + super.init(); + + backupsList = new BackupsList(minecraft, width, height - UPPER_HEIGHT - LOWER_HEIGHT, UPPER_HEIGHT); + + searchBox = new EditBox(font, width / 2 - 160 / 2, 40, 160, 20, Component.empty()); + searchBox.setResponder(backupsList::onFilterChanged); + + addRenderableWidget(Button.builder(CommonComponents.GUI_BACK, btn -> onClose()) + .size(100, 20).pos(width / 2 - 130, height - 30).build()); + + restoreButton = Button.builder(Component.translatable("ftbbackups3.gui.restore_now"), btn -> onActivate()) + .size(150, 20).pos(width / 2 - 20, height - 30).build(); + addRenderableWidget(restoreButton); + restoreButton.active = false; + + addRenderableWidget(searchBox); + addRenderableWidget(backupsList); + } + + private void onActivate() { + BackupsList.Entry selected = backupsList.getSelected(); + + if (selected != null) { + minecraft.setScreen(new ConfirmScreen(confirmed -> doRestore(confirmed, selected), + Component.translatable("ftbbackups3.gui.confirm_restore", selected.backup.worldName(), selected.backup.fileId()), + Component.translatable("ftbbackups3.gui.confirm_restore.line_2")) + ); + } + } + + private void doRestore(boolean confirmed, BackupsList.Entry selected) { + minecraft.setScreen(this); + + if (confirmed) { + Path backupsFolder = FTBBackupsServerConfig.getBackupFolder(FMLPaths.GAMEDIR.get()); + + Backup backup = selected.backup; + + IArchivalPlugin plugin = ArchivePluginManager.clientInstance().getPlugin(backup.archivalPlugin()); + + if (plugin != null) { + CompletableFuture.runAsync(() -> { + IArchivalPlugin.RestorationContext ctx = new RestoreContext(backupsFolder.resolve(backup.fileId()), FMLPaths.GAMEDIR.get(), Backups.LOGGER, this); + totalFiles = backup.fileCount(); + currentFile.set(0L); + Backups.LOGGER.info("STUB: do restore! {} -> {}", ctx.archivePath(), ctx.destinationFolder()); + try { + plugin.restoreArchive(ctx); + } catch (Exception e) { + throw new RuntimeException(e); + } + }).whenComplete((unused, ex) -> { + if (ex != null) { + Backups.LOGGER.error("backup restoration failed: {} -> {}", ex.getClass(), ex.getMessage()); + lastError = ex.getMessage(); + } else { + Backups.LOGGER.info("backup restoration complete!"); + lastError = ""; + } + totalFiles = 0L; + currentFile.set(0L); + restoringPath = ""; + completedTimer = 50; + }); + } else { + Backups.LOGGER.info("unknown archival plugin {}!", backup.archivalPlugin()); + } + } + } + + @Override + protected void setInitialFocus() { + setInitialFocus(searchBox); + } + + @Override + public void onClose() { + minecraft.setScreen(prevScreen); + } + + @Override + public boolean shouldCloseOnEsc() { + return totalFiles == 0L && super.shouldCloseOnEsc(); + } + + @Override + public void tick() { + super.tick(); + + if (completedTimer > 0) { + completedTimer--; + } + } + + @Override + public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + super.render(guiGraphics, mouseX, mouseY, partialTick); + + guiGraphics.drawCenteredString(font, title, width / 2, 8, 0xFFFFFF); + + renderExtractionProgress(guiGraphics, totalFiles); + } + + private void renderExtractionProgress(GuiGraphics guiGraphics, long total) { + if (total == 0L && completedTimer == 0) { + return; + } + + int pw = guiGraphics.guiWidth() * 3 / 4; + int ph = guiGraphics.guiHeight() / 4; + int px = (guiGraphics.guiWidth() - pw) / 2; + int py = (guiGraphics.guiHeight() - ph) / 2; + + guiGraphics.pose().pushPose(); + guiGraphics.pose().translate(0, 0, 200); + guiGraphics.fill(px - 2, py - 2, px + pw + 2, py + ph + 2, 0xFF404040); + guiGraphics.fill(px, py, px + pw, py + ph, 0xFFC0C0C0); + + if (total > 0L) { + guiGraphics.drawString(font, Component.translatable("ftbbackups3.gui.restore.in_progress"), px + 5, py + 5, 0xFF404040, false); + guiGraphics.drawString(font, Component.literal(restoringPath), px + 5, py + ph - font.lineHeight - 5, 0xFF404040, false); + + // progress bar + int sh = ph / 4; + int sy = py + (ph - sh) / 2; + guiGraphics.fill(px + 10, sy, px + pw - 10, sy + sh, 0xFF202020); + int sw = (int) ((pw - 24) * currentFile.get() / total); + guiGraphics.fill(px + 12, sy + 2, px + 10 + sw, sy + sh - 2, 0xFF0040C0); + + guiGraphics.pose().popPose(); + } else if (completedTimer > 0) { + if (lastError.isEmpty()) { + guiGraphics.blitSprite(SUCCESS, px + 10, py + (ph - 32) / 2, 0, 32, 32); + guiGraphics.drawString(font, Component.translatable("ftbbackups3.gui.restore.success"), px + 50, py + (ph - font.lineHeight) / 2, 0x404040, false); + } else { + guiGraphics.blitSprite(FAILURE, px + 10, py + (ph - 32) / 2, 0, 32, 32); + guiGraphics.drawString(font, Component.translatable("ftbbackups3.gui.restore.failure", lastError), px + 50, py + (ph - font.lineHeight) / 2, 0x404040, false); + } + } + } + + private class BackupsList extends AbstractSelectionList { + public BackupsList(Minecraft minecraft, int width, int height, int y) { + super(minecraft, width, height, y, 30); + + addChildren(""); + } + + private void addChildren(String filter) { + List entries = new ArrayList<>(); + + String filterL = filter.toLowerCase(Locale.ROOT); + + for (Backup b : Backups.getClientInstance().backups()) { + if (b.success() && b.fileCount() > 0 && (filter.isEmpty() || b.worldName().toLowerCase(Locale.ROOT).contains(filterL))) { + entries.add(new Entry(b)); + } + } + + // TODO controls to alter sort order + + children().addAll(entries.stream().sorted((o1, o2) -> o2.backup.compareTo(o1.backup)).toList()); + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput ignored) { + } + + @Override + public int getRowWidth() { + return 400; + } + + @Override + protected int getScrollbarPosition() { + return width / 2 + 200; + } + + @Override + public void setSelected(@Nullable RestoreBackupScreen.BackupsList.Entry entry) { + RestoreBackupScreen.this.restoreButton.active = entry != null; + super.setSelected(entry); + } + + private void onFilterChanged(String filter) { + children().clear(); + addChildren(filter); + } + + private class Entry extends AbstractSelectionList.Entry { + private static final String DATE_FORMAT = "MMM d, yyyy HH:mm a"; + + private final Backup backup; + private long lastClickTime; + + public Entry(Backup backup) { + this.backup = backup; + } + + @Override + public boolean mouseClicked(double x, double y, int partialTick) { + BackupsList.this.setSelected(this); + + if (Util.getMillis() - lastClickTime < 250L) { + RestoreBackupScreen.this.onActivate(); + return true; + } else { + lastClickTime = Util.getMillis(); + return false; + } + } + + @Override + public void render(GuiGraphics guiGraphics, int index, int top, int left, int width, int height, int mouseX, int mouseY, boolean hovering, float partialTick) { + Font font = Minecraft.getInstance().font; + + int startX = left + 80; + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT); + ZonedDateTime ldt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(backup.time()), ZoneId.systemDefault()); + + int y0 = top + 5; + int lh = font.lineHeight + 2; + + guiGraphics.drawString(font, Component.translatable(backup.worldName()), startX, y0, 0xFFFFFF); + guiGraphics.drawString(font, Component.literal(ldt.format(formatter)), startX + 10, y0 + lh, 0xC0C0C0); + + if (isMouseOver(mouseX, mouseY)) { + setTooltipForNextRenderPass(Stream.of( + Component.translatable("ftbbackups3.gui.list.file", + Component.literal(backup.fileId()).withStyle(ChatFormatting.GRAY)), + Component.translatable("ftbbackups3.gui.list.plugin", + Component.literal(backup.archivalPlugin().toString()).withStyle(ChatFormatting.GRAY)) + ) + .map(MutableComponent::getVisualOrderText) + .toList() + ); + } + } + } + } + + private record RestoreContext(Path archivePath, Path destinationFolder, Logger logger, RestoreBackupScreen screen) implements IArchivalPlugin.RestorationContext { + @Override + public void notifyProcessingFile(String filename) { + screen.currentFile.incrementAndGet(); + screen.restoringPath = filename; + logger.debug("restore file: {}", filename); + } + } +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/client/SelectArchivalPluginScreen.java b/src/main/java/dev/ftb/mods/ftbbackups/client/SelectArchivalPluginScreen.java new file mode 100644 index 00000000..9ec4841d --- /dev/null +++ b/src/main/java/dev/ftb/mods/ftbbackups/client/SelectArchivalPluginScreen.java @@ -0,0 +1,79 @@ +package dev.ftb.mods.ftbbackups.client; + +import dev.ftb.mods.ftbbackups.config.ArchivalPluginConfig; +import dev.ftb.mods.ftbbackups.archival.ArchivePluginManager; +import dev.ftb.mods.ftblibrary.config.ConfigCallback; +import dev.ftb.mods.ftblibrary.icon.Color4I; +import dev.ftb.mods.ftblibrary.ui.Panel; +import dev.ftb.mods.ftblibrary.ui.SimpleTextButton; +import dev.ftb.mods.ftblibrary.ui.Theme; +import dev.ftb.mods.ftblibrary.ui.input.Key; +import dev.ftb.mods.ftblibrary.ui.input.MouseButton; +import dev.ftb.mods.ftblibrary.ui.misc.AbstractButtonListScreen; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +public class SelectArchivalPluginScreen extends AbstractButtonListScreen { + private final ArchivalPluginConfig config; + private final ConfigCallback callback; + + public SelectArchivalPluginScreen(ArchivalPluginConfig config, ConfigCallback callback) { + this.config = config; + this.callback = callback; + + setTitle(Component.translatable("ftbbackups3.general.archival_plugin")); + showBottomPanel(false); + showCloseButton(true); + } + + @Override + public void addButtons(Panel panel) { + ArchivePluginManager.clientInstance().plugins().keySet() + .forEach(k -> panel.add(new PluginButton(panel, k))); + } + + @Override + public boolean onClosedByKey(Key key) { + if (super.onClosedByKey(key)) { + callback.save(false); + return true; + } + + return false; + } + + @Override + protected void doCancel() { + callback.save(false); + } + + @Override + protected void doAccept() { + callback.save(true); + } + + private class PluginButton extends SimpleTextButton { + private final ResourceLocation value; + + PluginButton(Panel panel, ResourceLocation value) { + super(panel, Component.literal(value.toString()), Color4I.empty()); + this.value = value; + } + + @Override + public void drawBackground(GuiGraphics graphics, Theme theme, int x, int y, int w, int h) { + if (isMouseOver) { + Color4I.WHITE.withAlpha(30).draw(graphics, x, y, w, h); + } + Color4I.GRAY.withAlpha(40).draw(graphics, x, y + h, w, 1); + } + + @Override + public void onClicked(MouseButton mouseButton) { + playClickSound(); + config.setCurrentValue(value); + callback.save(true); + } + } +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/config/ArchivalPluginConfig.java b/src/main/java/dev/ftb/mods/ftbbackups/config/ArchivalPluginConfig.java new file mode 100644 index 00000000..49e4b0b4 --- /dev/null +++ b/src/main/java/dev/ftb/mods/ftbbackups/config/ArchivalPluginConfig.java @@ -0,0 +1,17 @@ +package dev.ftb.mods.ftbbackups.config; + +import dev.ftb.mods.ftbbackups.client.SelectArchivalPluginScreen; +import dev.ftb.mods.ftblibrary.config.ConfigCallback; +import dev.ftb.mods.ftblibrary.config.ConfigValue; +import dev.ftb.mods.ftblibrary.ui.Widget; +import dev.ftb.mods.ftblibrary.ui.input.MouseButton; +import net.minecraft.resources.ResourceLocation; + +public class ArchivalPluginConfig extends ConfigValue { + @Override + public void onClicked(Widget widget, MouseButton mouseButton, ConfigCallback configCallback) { + if (getCanEdit()) { + new SelectArchivalPluginScreen(this, configCallback).openGui(); + } + } +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/config/ArchivalPluginValue.java b/src/main/java/dev/ftb/mods/ftbbackups/config/ArchivalPluginValue.java new file mode 100644 index 00000000..cc86ef23 --- /dev/null +++ b/src/main/java/dev/ftb/mods/ftbbackups/config/ArchivalPluginValue.java @@ -0,0 +1,35 @@ +package dev.ftb.mods.ftbbackups.config; + +import dev.ftb.mods.ftblibrary.config.ConfigGroup; +import dev.ftb.mods.ftblibrary.snbt.SNBTCompoundTag; +import dev.ftb.mods.ftblibrary.snbt.config.BaseValue; +import dev.ftb.mods.ftblibrary.snbt.config.SNBTConfig; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class ArchivalPluginValue extends BaseValue { + public ArchivalPluginValue(@Nullable SNBTConfig config, String key, ResourceLocation def) { + super(config, key, def); + } + + @Override + public void write(SNBTCompoundTag tag) { + List s = new ArrayList<>(comment); + s.add("Default: " + defaultValue); + tag.comment(key, String.join("\n", s)); + tag.putString(key, get().toString()); + } + + @Override + public void read(SNBTCompoundTag snbtCompoundTag) { + set(ResourceLocation.parse(snbtCompoundTag.getString(key))); + } + + @Override + public void createClientConfig(ConfigGroup group) { + group.add(key, new ArchivalPluginConfig(), get(), this::set, defaultValue); + } +} diff --git a/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java b/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java index 7cb4146e..0d2e66d7 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java @@ -18,18 +18,20 @@ public record BackupProgressPacket(int current, int total) implements CustomPack BackupProgressPacket::new ); - private static final BackupProgressPacket RESET_PACKET = new BackupProgressPacket(0, 0); - public static void handler(BackupProgressPacket message, IPayloadContext context) { BackupsClient.setBackupProgress(message.current, message.total); } - public static CustomPacketPayload create() { - return new BackupProgressPacket(Backups.INSTANCE.getCurrentFileIndex(), Backups.INSTANCE.totalFiles); + public static CustomPacketPayload start() { + return new BackupProgressPacket(0, Backups.getServerInstance().totalFiles); + } + + public static CustomPacketPayload update() { + return new BackupProgressPacket(Backups.getServerInstance().getCurrentFileIndex(), Backups.getServerInstance().totalFiles); } - public static CustomPacketPayload reset() { - return RESET_PACKET; + public static CustomPacketPayload complete() { + return new BackupProgressPacket(Backups.getServerInstance().totalFiles, Backups.getServerInstance().totalFiles); } @Override diff --git a/src/main/resources/assets/ftbbackups/lang/de_de.json b/src/main/resources/assets/ftbbackups/lang/de_de.json deleted file mode 100644 index d2016433..00000000 --- a/src/main/resources/assets/ftbbackups/lang/de_de.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "_comment": "Config", - "ftbbackups.general": "Allgemein", - "ftbbackups.general.silent": "Still", - "ftbbackups.general.backups_to_keep": "Sicherungen zum Behalten", - "ftbbackups.general.backup_timer": "Sicherungszeitgeber", - "ftbbackups.general.compression_level": "Komprimierungsstufe", - "ftbbackups.general.folder": "Ordner", - "ftbbackups.general.display_file_size": "Dateigröße Anzeigen", - "ftbbackups.general.use_separate_thread": "Separaten Aktivitätsträger Nutzen", - "ftbbackups.general.extra_files": "Zusätzliche Dateien", - "ftbbackups.general.max_total_size": "Maximale Gesamtgröße", - "ftbbackups.general.disable_level_saving": "Stufenspeicherung Deaktivieren", - "ftbbackups.general.only_if_players_online": "Sicherung machen, nur wenn Spieler online sind.", - "ftbbackups.general.force_on_shutdown": "Das Erstellen einer Sicherung erzwingen beim Herunterfahren.", - "ftbbackups.general.buffer_size": "Puffergröße", - "_comment": "Misc", - "ftbbackups.lang.timer": "Serversicherung in: %s", - "ftbbackups.lang.timer_progress": "[%d%% | %d / %d]", - "ftbbackups.lang.start": "Serversicherung startete!", - "ftbbackups.lang.end_1": "Serversicherung ist in %s fertig!", - "ftbbackups.lang.end_2": "Serversicherung ist in %s fertig! (%s)", - "ftbbackups.lang.fail": "Die Speicherung der Welt ist fehlgeschlagen! (%s)", - "ftbbackups.lang.manual_launch": "%s startete eine manuelle Sicherung!", - "ftbbackups.lang.already_running": "Sicherung in Arbeit!", - "ftbbackups.lang.size.current": "Aktuelle Weltgröße: %s", - "ftbbackups.lang.size.total": "Die Größe des Sicherungsordners insgesamt: %s", - "ftbbackups.lang.saving_failed": "Die Speicherung der Welt ist fehlgeschlagen!", - "_comment": "Commands", - "commands.backup.usage": "/backup", - "commands.backup.start.usage": "/backup start [benutzerdefinierter Name]", - "commands.backup.time.usage": "/backup time", - "commands.backup.size.usage": "/backup size" -} \ No newline at end of file diff --git a/src/main/resources/assets/ftbbackups/lang/pl_pl.json b/src/main/resources/assets/ftbbackups/lang/pl_pl.json deleted file mode 100644 index 03ca7eb5..00000000 --- a/src/main/resources/assets/ftbbackups/lang/pl_pl.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "_comment": "Konfiguracja", - "ftbbackups.general": "Ogólny", - "ftbbackups.general.auto": "Automatyczny", - "ftbbackups.general.silent": "Cichy", - "ftbbackups.general.backups_to_keep": "Kopia zapasowa do zachowania", - "ftbbackups.general.backup_timer": "Czas kopii zapasowej", - "ftbbackups.general.compression_level": "Poziom kompresji", - "ftbbackups.general.folder": "Folder", - "ftbbackups.general.display_file_size": "WyÅ›wietl rozmiar pliku", - "ftbbackups.general.use_separate_thread": "Użyj oddzielnego wÄ…tku", - "ftbbackups.general.extra_files": "Dodatkowe pliki", - "ftbbackups.general.max_total_size": "Maksymalny caÅ‚kowity rozmiar", - "ftbbackups.general.disable_level_saving": "Wyłącz zapisywanie poziomu", - "ftbbackups.general.only_if_players_online": "Rób tylko kopie zapasowe kiedy gracze sÄ… w grze", - "ftbbackups.general.force_on_shutdown": "WymuÅ› kopiÄ™ zapasowÄ… podczas zamykania", - "ftbbackups.general.buffer_size": "Rozmiar bufora", - "_comment": "Misc", - "ftbbackups.lang.timer": "Serwerowa kopia zapasowa zapisze siÄ™ w: %s", - "ftbbackups.lang.timer_progress": "[%d%% | %d / %d]", - "ftbbackups.lang.start": "Robienie kopii zapasowej serwera zaczęło siÄ™!", - "ftbbackups.lang.end_1": "Robienie kopii zapasowej skoÅ„czyÅ‚o siÄ™ w %s!", - "ftbbackups.lang.end_2": "Robienie kopii zapasowej skoÅ„czyÅ‚o siÄ™ w %s! (%s)", - "ftbbackups.lang.fail": "Niepowodzenie w zapisaniu Å›wiata! (%s)", - "ftbbackups.lang.manual_launch": "%s odpaliÅ‚ rÄ™cznÄ… kopiÄ™ zapasowÄ…!", - "ftbbackups.lang.already_running": "Kopia zapasowa siÄ™ robi!", - "ftbbackups.lang.size.current": "Aktualny rozmiar Å›wiata: %s", - "ftbbackups.lang.size.total": "CaÅ‚kowity rozmiar folderu kopii zapasowych: %s", - "ftbbackups.lang.size.available": "DostÄ™pne: %s", - "ftbbackups.lang.saving_failed": "Niepowodzenie w zapisywaniu Å›wiata!", - "_comment": "Commands", - "commands.backup.usage": "/backup", - "commands.backup.start.usage": "/backup start [custom name]", - "commands.backup.time.usage": "/backup time", - "commands.backup.size.usage": "/backup size" -} diff --git a/src/main/resources/assets/ftbbackups/lang/ru_ru.json b/src/main/resources/assets/ftbbackups/lang/ru_ru.json deleted file mode 100644 index 48b2d1ec..00000000 --- a/src/main/resources/assets/ftbbackups/lang/ru_ru.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "_comment": "Config", - "ftbbackups.general": "Общее", - "ftbbackups.general.auto": "ÐвтоматичеÑкий режим", - "ftbbackups.general.silent": "Тихий режим", - "ftbbackups.general.backups_to_keep": "КоличеÑтво резервных копий", - "ftbbackups.general.backup_timer": "ЧаÑтота ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñ€ÐµÐ·ÐµÑ€Ð²Ð½Ñ‹Ñ… копий", - "ftbbackups.general.compression_level": "Уровень ÑжатиÑ", - "ftbbackups.general.folder": "Папка", - "ftbbackups.general.display_file_size": "Отображать размер файла", - "ftbbackups.general.use_separate_thread": "ИÑпользовать отдельный поток", - "ftbbackups.general.extra_files": "Дополнительные файлы", - "ftbbackups.general.max_total_size": "МакÑимальный общий размер", - "ftbbackups.general.disable_level_saving": "Отключить Ñохранение уровнÑ", - "ftbbackups.general.only_if_players_online": "Резервное копирование только еÑли игроки находÑÑ‚ÑÑ Ð² Ñети", - "ftbbackups.general.force_on_shutdown": "Принудительное резервное копирование при завершении работы", - "ftbbackups.general.buffer_size": "Размер буфера", - "_comment": "Misc", - "ftbbackups.lang.timer": "Резервное копирование Ñервера в: %s", - "ftbbackups.lang.timer_progress": "[%d%% | %d / %d]", - "ftbbackups.lang.start": "Резервное копирование Ñервера началоÑÑŒ!", - "ftbbackups.lang.end_1": "Резервное копирование Ñервера выполнено в %s!", - "ftbbackups.lang.end_2": "Резервное копирование Ñервера выполнено в %s! (%s)", - "ftbbackups.lang.fail": "Ðе удалоÑÑŒ Ñохранить мир! (%s)", - "ftbbackups.lang.manual_launch": "%s запуÑтил ручное резервное копирование!", - "ftbbackups.lang.already_running": "Резервное копирование выполнÑетÑÑ!", - "ftbbackups.lang.size.current": "Текущий размер мира: %s", - "ftbbackups.lang.size.total": "Общий размер папки резервных копий: %s", - "ftbbackups.lang.size.available": "ДоÑтупно: %s", - "ftbbackups.lang.saving_failed": "Ðе удалоÑÑŒ Ñохранить мир!", - "_comment": "Commands", - "commands.backup.usage": "/backup", - "commands.backup.start.usage": "/backup start [пользовательÑкое имÑ]", - "commands.backup.time.usage": "/backup time", - "commands.backup.size.usage": "/backup size" -} \ No newline at end of file diff --git a/src/main/resources/assets/ftbbackups/lang/zh_cn.json b/src/main/resources/assets/ftbbackups/lang/zh_cn.json deleted file mode 100644 index b185973e..00000000 --- a/src/main/resources/assets/ftbbackups/lang/zh_cn.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "_comment": "Config", - "ftbbackups.general.silent": "é™é»˜æ¨¡å¼", - "ftbbackups.general.backups_to_keep": "备份ä¿ç•™æ•°", - "ftbbackups.general.backup_timer": "备份间隔", - "ftbbackups.general.compression_level": "压缩等级", - "ftbbackups.general.folder": "文件夹", - "ftbbackups.general.display_file_size": "显示文件大å°", - "ftbbackups.general.use_separate_thread": "使用独立线程", - "ftbbackups.general.extra_files": "é¢å¤–文件", - "ftbbackups.general.max_total_size": "最大总大å°", - "ftbbackups.general.disable_level_saving": "ç¦ç”¨ç­‰çº§ä¿å­˜", - "_comment": "Misc", - "ftbbackups.lang.timer": "æœåŠ¡å™¨å¤‡ä»½ï¼š%s", - "ftbbackups.lang.start": "æœåС噍备份开始ï¼", - "ftbbackups.lang.end_1": "æœåŠ¡å™¨å¤‡ä»½å®Œæˆï¼Œè€—æ—¶ %sï¼", - "ftbbackups.lang.end_2": "æœåŠ¡å™¨å¤‡ä»½å®Œæˆï¼Œè€—æ—¶ %sï¼ï¼ˆ%s)", - "ftbbackups.lang.fail": "ä¿å­˜ä¸–界失败ï¼ï¼ˆ%s)", - "ftbbackups.lang.manual_launch": "%s å¯åŠ¨äº†ä¸€ä¸ªæ‰‹åŠ¨å¤‡ä»½ï¼", - "ftbbackups.lang.already_running": "正在备份中ï¼", - "ftbbackups.lang.size.current": "当å‰ä¸–界大å°ï¼š%s", - "ftbbackups.lang.size.total": "总备份文件夹大å°ï¼š%s", - "ftbbackups.lang.saving_failed": "世界存储失败ï¼", - "commands.backup.start.usage": "/backup start [自定义åç§°]" -} \ No newline at end of file diff --git a/src/main/resources/assets/ftbbackups/lang/en_us.json b/src/main/resources/assets/ftbbackups3/lang/en_us.json similarity index 69% rename from src/main/resources/assets/ftbbackups/lang/en_us.json rename to src/main/resources/assets/ftbbackups3/lang/en_us.json index 24a9441a..ed7a6874 100644 --- a/src/main/resources/assets/ftbbackups/lang/en_us.json +++ b/src/main/resources/assets/ftbbackups3/lang/en_us.json @@ -3,16 +3,15 @@ "ftbbackups3.general.auto": "Automatic Backups", "ftbbackups3.general.auto.tooltip": "Run backups automatically, based on the \"Backup Timer\" setting", "ftbbackups3.general.silent": "Run Silently", - "ftbbackups3.general.silent.tooltip": "Run Silently", "ftbbackups3.general.silent.tooltip": "If true, online players are not notified when a backup runs", "ftbbackups3.general.backups_to_keep": "Backups to Keep", - "ftbbackups3.general.backups_to_keep.tooltip": "Number of backup ZIP files to keep.\nOldest ZIP file will be deleted when a backup runs", + "ftbbackups3.general.backups_to_keep.tooltip": "Number of backups to keep.\nOldest backup will be deleted when a backup runs", "ftbbackups3.general.backup_timer": "Backup Timer", "ftbbackups3.general.backup_timer.tooltip": "Frequency, in minutes, to run backups, when \"Automatic Backups\" is true", "ftbbackups3.general.compression_level": "Compression Level", - "ftbbackups3.general.compression_level.tooltip": "File compression level for backups\n9 = maximum compression\n1 = minimum compression\n0 = don't create a ZIP file, just copy the world folder", + "ftbbackups3.general.compression_level.tooltip": "File compression level for backups\n9 = maximum compression\n1 = minimum compression\n0 = no compression at all", "ftbbackups3.general.folder": "Backup Folder", - "ftbbackups3.general.folder.tooltip": "If not empty, an absolute path to the backup folder\nIf empty, backups are placed in the \"backups\" folder in your game instance", + "ftbbackups3.general.folder.tooltip": "If not empty, an absolute path to the backup folder\nIf empty, backups are placed in the \"ftbbackups3\" folder in your game instance", "ftbbackups3.general.display_file_size": "Display File Size", "ftbbackups3.general.display_file_size.tooltip": "When true (and \"Run Silently\" is false), also display backed-up file size to players", "ftbbackups3.general.extra_files": "Extra Files", @@ -23,9 +22,15 @@ "ftbbackups3.general.only_if_players_online.tooltip": "Automated backups are only run if at least one player is online", "ftbbackups3.general.force_on_shutdown": "Force Backup on Shutdown", "ftbbackups3.general.force_on_shutdown.tooltip": "If true (and \"Automatic Backups\" is true), always run a backup when the server is being stopped", + "ftbbackups3.general.archival_plugin": "Archival Plugin", + "ftbbackups3.general.archival_plugin.tooltip": "The plugin used to actually create files on disk.\nBuiltin options create ZIP files or flat file copies.", "ftbbackups3.general.advanced": "Advanced", "ftbbackups3.general.advanced.buffer_size": "Buffer Size", "ftbbackups3.general.advanced.buffer_size.tooltip": "Buffer size used when reading files to be backed up.\nDon't adjust this unless you understand the implications!", + "ftbbackups3.general.overlay.show_overlay": "Show Backup Progress Panel", + "ftbbackups3.general.overlay.overlay_inset_x": "Progress Panel X Inset", + "ftbbackups3.general.overlay.overlay_inset_y": "Progress Panel Y Inset", + "ftbbackups3.general.overlay.overlay_pos": "Progress Panel Position", "ftbbackups3.lang.server": "Server", "ftbbackups3.lang.timer": "Next backup in: %s", "ftbbackups3.lang.timer_progress": "[%d%% | %d / %d]", @@ -35,11 +40,23 @@ "ftbbackups3.lang.fail": "Failed to save world! (%s)", "ftbbackups3.lang.manual_launch": "%s launched a manual backup!", "ftbbackups3.lang.already_running": "Backup in progress!", + "ftbbackups3.lang.finished": "Backup complete!", "ftbbackups3.lang.size.current": "Current world size: %s", "ftbbackups3.lang.size.total": "Total backups folder size: %s", "ftbbackups3.lang.size.available": "Available: %s", "ftbbackups3.lang.saving_failed": "World saving failed!", "ftbbackups3.lang.autosave_status": "Autosave for [%s]: %s", "ftbbackups3.lang.autosave.enabled": "Enabled", - "ftbbackups3.lang.autosave.disabled": "Disabled!" + "ftbbackups3.lang.autosave.disabled": "Disabled!", + "ftbbackups3.gui.confirm_restore": "Restore world '%s' from %s ?", + "ftbbackups3.gui.confirm_restore.line_2": "This cannot be undone!", + "ftbbackups3.gui.restore": "Restore World", + "ftbbackups3.gui.restore.tooltip": "Restore from a backup created by FTB Backups 3", + "ftbbackups3.gui.restore.in_progress": "Restoring world...", + "ftbbackups3.gui.restore.success": "Backup successfully restored!", + "ftbbackups3.gui.restore.failure": "Restoration failed: %s", + "ftbbackups3.gui.restore_now": "Restore", + "ftbbackups3.gui.list.file": "Filename: %s", + "ftbbackups3.gui.list.plugin": "Archival Plugin: %s", + "sidebar_button.ftbbackups3.config": "FTB Backups 3 Configuration" } \ No newline at end of file diff --git a/src/main/resources/assets/ftbbackups3/sidebar_buttons/config.json b/src/main/resources/assets/ftbbackups3/sidebar_buttons/config.json new file mode 100644 index 00000000..49c408c3 --- /dev/null +++ b/src/main/resources/assets/ftbbackups3/sidebar_buttons/config.json @@ -0,0 +1,8 @@ +{ + "icon": [ + "ftblibrary:icons/settings; tint=#FFC0C0FF", + "ftblibrary:icons/down" + ], + "sort_index": 700, + "click": ["command:/ftbbackups3 config"] +} \ No newline at end of file From a4404a242e2fdd618d66f418a3dba37943fec766 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Fri, 25 Apr 2025 08:30:43 +0100 Subject: [PATCH 22/42] build: add build.yml --- .github/workflows/build.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..a3fef849 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,24 @@ +name: Java CI - Build on Push + +on: + push: + branches: [ main, dev, "1.**" ] + workflow_dispatch: + inputs: + skip_maven_publish: + description: 'Skip Maven publishing' + required: true + default: 'false' + +jobs: + build: + if: | + !contains(github.event.head_commit.message, '[ciskip]') + uses: FTBTeam/mods-meta/.github/workflows/standard-release.yml@main + with: + curse-publish-task: "" + maven-snapshots: true + java-version: 21 + secrets: + ftb-maven-token: ${{ secrets.FTB_MAVEN_TOKEN }} + saps-token: ${{ secrets.SAPS_TOKEN }} \ No newline at end of file From 5ef361f04d60e548f8858710dbd701892ef5d39b Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Fri, 25 Apr 2025 08:35:22 +0100 Subject: [PATCH 23/42] build: add ftb snapshot maven to repos --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 549ac6dc..46146293 100644 --- a/build.gradle +++ b/build.gradle @@ -82,6 +82,7 @@ configurations { repositories { mavenLocal() maven { url "https://maven.ftb.dev/releases" } // FTB Mods + maven { url "https://maven.ftb.dev/snapshots" } // FTB Mods maven { url "https://maven.architectury.dev" } } From cfe21060aa2e99ddaab064ed7f9198a4ddce5bb2 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Fri, 25 Apr 2025 08:46:17 +0100 Subject: [PATCH 24/42] build: get artifactId right --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 46146293..fe4f920f 100644 --- a/build.gradle +++ b/build.gradle @@ -171,6 +171,7 @@ publishMods { publishing { publications { register('mavenJava', MavenPublication) { + artifactId = archives_base_name from components.java artifact sourcesJar artifact apiJar From fcd60d02f0cce33ad575fcd71d3a81b216e4293d Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Fri, 25 Apr 2025 09:19:43 +0100 Subject: [PATCH 25/42] chore: improved comments in the config file --- .../ftbbackups/FTBBackupsServerConfig.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsServerConfig.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsServerConfig.java index 74106943..20113cbc 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsServerConfig.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsServerConfig.java @@ -39,7 +39,7 @@ public interface FTBBackupsServerConfig { IntValue BACKUP_TIMER_MINUTES = CONFIG.addInt("backup_timer", 120, 1, 43800) .comment( - "Timer in minutes.", + "Backup frequency in minutes.", "5 - backups every 5 minutes", "60 - backups every hour", "360 - backups every 6 hours", @@ -47,26 +47,27 @@ public interface FTBBackupsServerConfig { ); IntValue COMPRESSION_LEVEL = CONFIG.addInt("compression_level", 5, 0, 9) - .comment("Compression level for archived files. Note that this is dependent on the particular plugin in use", + .comment("Compression level for archived files. Note that this is dependent on the particular plugin in use.", + "Higher values typically mean backups take longer to do but take less space on disk.", "0 - No compression", - "1 - Best speed", - "9 - Smallest file size" + "1 - Minimal compression", + "9 - Full compression" ); StringValue FOLDER = CONFIG.addString("folder", "") - .comment("Absolute path to backups folder."); + .comment("Absolute path to backups folder. Default of \"\" means to use \"ftbackups3\" within the game instance folder."); BooleanValue DISPLAY_FILE_SIZE = CONFIG.addBoolean("display_file_size", true) - .comment("Prints (current size | total size) when backup is done."); + .comment("Broadcasts to all online players a \"(current size | total size)\" message when backup is done."); StringListValue EXTRA_FILES = CONFIG.addStringList("extra_files", new ArrayList<>()) - .comment("Add extra files that will be placed in backup _extra_/ folder."); + .comment("Add extra files/folders to be backed up, in addition to the world folder. These files *must* be within the game instance!"); StringValue MAX_TOTAL_SIZE_RAW = CONFIG.addString("max_total_size", "50 GB") .comment("Maximum total size that is allowed in backups folder. Older backups will be deleted to free space for newer ones."); BooleanValue ONLY_IF_PLAYERS_ONLINE = CONFIG.addBoolean("only_if_players_online", true) - .comment("Only create backups when players have been online."); + .comment("Only create backups when at least one player is online."); BooleanValue FORCE_ON_SHUTDOWN = CONFIG.addBoolean("force_on_shutdown", false) .comment("Create a backup when server is stopped."); @@ -75,7 +76,7 @@ public interface FTBBackupsServerConfig { .comment("Advanced features that shouldn't be changed unless you know what you are doing."); IntValue BUFFER_SIZE = ADVANCED.addInt("buffer_size", 4096, 256, 65536) - .comment("Buffer size for writing files."); + .comment("Buffer size for reading/writing files."); ArchivalPluginValue ARCHIVAL_PLUGIN = CONFIG.add(new ArchivalPluginValue(CONFIG, "archival_plugin", ZipArchiver.ID)) .comment("Method to use to create a backup archive.", From d5c7b43004f1b61b73b105723393ddb6c050223e Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Fri, 25 Apr 2025 16:03:38 +0100 Subject: [PATCH 26/42] fix: ensure output folder definitely exists --- src/main/java/dev/ftb/mods/ftbbackups/Backups.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java index e86736c1..4c636ad1 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java @@ -77,6 +77,7 @@ private Backups() { try { backupsFolder = backupsFolder.toRealPath(); + Files.createDirectories(backupsFolder); } catch (Exception ignored) { } From d36e932ecaefda1789611a770c52aa214fc18439 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Fri, 25 Apr 2025 16:12:47 +0100 Subject: [PATCH 27/42] build: fix maven publishing? --- build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index fe4f920f..8f85b553 100644 --- a/build.gradle +++ b/build.gradle @@ -172,9 +172,8 @@ publishing { publications { register('mavenJava', MavenPublication) { artifactId = archives_base_name + version ftbPublishing.mavenVersion from components.java - artifact sourcesJar - artifact apiJar } } From 54199c8bbf333bba5f54a65ebc6610df3a747678 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Mon, 19 May 2025 08:55:14 +0100 Subject: [PATCH 28/42] feat: added a proper sidebar icon --- .../resources/assets/ftbbackups3/lang/en_us.json | 3 ++- .../ftbbackups3/sidebar_buttons/config.json | 3 +-- .../textures/icons/ftb-backups3-icon.png | Bin 0 -> 488 bytes 3 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 src/main/resources/assets/ftbbackups3/textures/icons/ftb-backups3-icon.png diff --git a/src/main/resources/assets/ftbbackups3/lang/en_us.json b/src/main/resources/assets/ftbbackups3/lang/en_us.json index ed7a6874..e859c5a0 100644 --- a/src/main/resources/assets/ftbbackups3/lang/en_us.json +++ b/src/main/resources/assets/ftbbackups3/lang/en_us.json @@ -58,5 +58,6 @@ "ftbbackups3.gui.restore_now": "Restore", "ftbbackups3.gui.list.file": "Filename: %s", "ftbbackups3.gui.list.plugin": "Archival Plugin: %s", - "sidebar_button.ftbbackups3.config": "FTB Backups 3 Configuration" + "sidebar_button.ftbbackups3.config": "FTB Backups 3 Configuration", + "sidebar_button.ftbbackups3.config.tooltip": "Settings for the FTB Backups 3 mod" } \ No newline at end of file diff --git a/src/main/resources/assets/ftbbackups3/sidebar_buttons/config.json b/src/main/resources/assets/ftbbackups3/sidebar_buttons/config.json index 49c408c3..f1709de5 100644 --- a/src/main/resources/assets/ftbbackups3/sidebar_buttons/config.json +++ b/src/main/resources/assets/ftbbackups3/sidebar_buttons/config.json @@ -1,7 +1,6 @@ { "icon": [ - "ftblibrary:icons/settings; tint=#FFC0C0FF", - "ftblibrary:icons/down" + "ftbbackups3:textures/icons/ftb-backups3-icon.png" ], "sort_index": 700, "click": ["command:/ftbbackups3 config"] diff --git a/src/main/resources/assets/ftbbackups3/textures/icons/ftb-backups3-icon.png b/src/main/resources/assets/ftbbackups3/textures/icons/ftb-backups3-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..349b373e7515ba0cadf6810c553a4cdbcba1af9d GIT binary patch literal 488 zcmVP)i7`^4(RZOj(U4vs zLUUIsn|LH@s;zHlN_2WArGU(Zy%Q+zw?aN(lOQ&}MYf9H=N!1asBuRArQdjLl6aWd0Zk2yk0Ltp^yX;yo5jCzQ0FSW*)P9b6n0((p_vcB;xpcf_x0z4iM z<8rz9T~Ofo*2^dU9AVLM^gRL Date: Tue, 20 May 2025 09:49:51 +0100 Subject: [PATCH 29/42] chore: small translation update --- src/main/resources/assets/ftbbackups3/lang/en_us.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/assets/ftbbackups3/lang/en_us.json b/src/main/resources/assets/ftbbackups3/lang/en_us.json index e859c5a0..927f95bd 100644 --- a/src/main/resources/assets/ftbbackups3/lang/en_us.json +++ b/src/main/resources/assets/ftbbackups3/lang/en_us.json @@ -15,7 +15,7 @@ "ftbbackups3.general.display_file_size": "Display File Size", "ftbbackups3.general.display_file_size.tooltip": "When true (and \"Run Silently\" is false), also display backed-up file size to players", "ftbbackups3.general.extra_files": "Extra Files", - "ftbbackups3.general.extra_files.tooltip": "By default, just the files in the world folder are backed up\nThis can be used to add extra files from outside the world folder to the backup\nWorld folder is \"world\" on a dedicated server,\n and \"saves/\" on a single-player instance", + "ftbbackups3.general.extra_files.tooltip": "By default, just the files in the world folder are backed up\nThis can be used to add extra files from outside the world folder (but inside the game instance!) to the backup\nWorld folder is \"world\" on a dedicated server,\n and \"saves/\" on a single-player instance", "ftbbackups3.general.max_total_size": "Maximum Total Size", "ftbbackups3.general.max_total_size.tooltip": "The maximum size to allow for backups.\nOldest backups will be deleted to keep the total backup size under this amount\nValid suffixes: KB, MB, GB, TB", "ftbbackups3.general.only_if_players_online": "Only if Players are Online", From 0fc9ac617a476bfb9f3d27022a5fc7e119f7e74b Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Tue, 20 May 2025 09:57:40 +0100 Subject: [PATCH 30/42] chore: quick code cleanup --- src/main/java/dev/ftb/mods/ftbbackups/Backups.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java index 4c636ad1..1db9a5c1 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java @@ -264,7 +264,6 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO if (file.isAbsolute()) { LOGGER.error("ignoring absolute file {} in extras!", file); } else { -// res.put(file.toRealPath(), file.isAbsolute() ? "_extra_absolute_" + file : "_extra_relative_" + File.separator + file); res.put(file.toRealPath(), file.toString()); } return FileVisitResult.CONTINUE; From 91a4e32a1168872eb5a449faf2c164272a00a21b Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Tue, 20 May 2025 10:04:34 +0100 Subject: [PATCH 31/42] build: added changelog --- CHANGELOG.md | 11 +++++++++++ build.gradle | 2 +- gradle.properties | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..a1e2c135 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [21.0.0] + +### Added +* Initial release of FTB Backups 3, a major rewrite and update for Minecraft 1.21.1 of the original FTB Backups mod + * Note that this mod is not related to FTB Backups 2 in any way diff --git a/build.gradle b/build.gradle index 8f85b553..03e1c562 100644 --- a/build.gradle +++ b/build.gradle @@ -150,7 +150,7 @@ publishMods { def CFToken = providers.environmentVariable("CURSEFORGE_KEY"); dryRun = !CFToken.isPresent() - displayName = "[NEOFORGE] FTB Backups 3 ${version}" + displayName = "[NEOFORGE] ${project.mod_name} ${project.version}" modLoaders.add("neoforge") changelog = createChangelog(project) diff --git a/gradle.properties b/gradle.properties index eb85ea68..85146ec7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,6 +10,7 @@ parchment_minecraft_version=${mc_version} parchment_mappings_version=2024.11.17 mod_id=ftbbackups3 +mod_name=FTB Backups 3 mod_version=0 minecraft_version=1.21.1 From c87287a97c6c71cf95920c4ddfeed77fbb393011 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Wed, 4 Jun 2025 12:29:27 +0100 Subject: [PATCH 32/42] fix: a collection fixes & improvements - Fixed issue with players getting a stuck progress notification on client - Fixed server backup interval being ignored on server startup - Added "Notify Admins Only" server config setting - Better `/ftbbackups3 time` output if backup skipped due to no players - A bit of general code cleanup --- .../ftb/mods/ftbbackups/BackupCommands.java | 21 ++++---- .../dev/ftb/mods/ftbbackups/BackupUtils.java | 8 ++-- .../java/dev/ftb/mods/ftbbackups/Backups.java | 48 ++++++++++++------- .../dev/ftb/mods/ftbbackups/FTBBackups.java | 24 ++-------- .../ftbbackups/FTBBackupsServerConfig.java | 13 +++-- .../mods/ftbbackups/api/IArchivalPlugin.java | 4 +- .../archival/ArchivePluginManager.java | 2 + .../ftbbackups/archival/FileCopyArchiver.java | 2 - .../ftbbackups/client/BackupOverlayLayer.java | 4 +- .../mods/ftbbackups/client/BackupsClient.java | 19 +------- .../client/RestoreBackupScreen.java | 4 +- .../client/SelectArchivalPluginScreen.java | 2 +- .../ftbbackups/net/BackupProgressPacket.java | 30 +++++++++--- .../assets/ftbbackups3/lang/en_us.json | 4 ++ 14 files changed, 96 insertions(+), 89 deletions(-) diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java index adf24a13..d5614915 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java @@ -6,8 +6,6 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import dev.architectury.networking.NetworkManager; import dev.ftb.mods.ftblibrary.net.EditConfigChoicePacket; -import dev.ftb.mods.ftblibrary.net.EditConfigChoicePacket.ConfigType; -import dev.ftb.mods.ftblibrary.net.EditConfigPacket; import net.minecraft.ChatFormatting; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; @@ -81,10 +79,15 @@ private static int editConfig(CommandSourceStack source) throws CommandSyntaxExc private static int time(CommandSourceStack source) { Duration d = Duration.between(Instant.now(), Instant.ofEpochMilli(Backups.getServerInstance().nextBackupTime)); - var s = String.format("%02d:%02d:%02d", d.toHoursPart(), d.toMinutesPart(), d.toSecondsPart()); + String key = d.isNegative() ? "ftbbackups3.lang.timer.in_past" : "ftbbackups3.lang.timer"; + Duration a = d.abs(); + Component msg = Component.translatable(key, String.format("%02d:%02d:%02d", a.toHoursPart(), a.toMinutesPart(), a.toSecondsPart())); + source.sendSuccess(() -> msg, true); + if (d.isNegative() && FTBBackupsServerConfig.ONLY_IF_PLAYERS_ONLINE.get()) { + source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.timer.no_players"), true); + } - source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.timer", s), true); - return 1; + return Command.SINGLE_SUCCESS; } private static int start(CommandSourceStack source, String customName) { @@ -96,7 +99,7 @@ private static int start(CommandSourceStack source, String customName) { source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.already_running"), true); } - return 1; + return Command.SINGLE_SUCCESS; } private static int size(CommandSourceStack source) { @@ -109,7 +112,7 @@ private static int size(CommandSourceStack source) { source.sendSuccess(() -> Component.translatable("ftbbackups3.lang.size.available", BackupUtils.formatSizeString(Math.min(FTBBackupsServerConfig.MAX_TOTAL_SIZE.get(), Backups.getServerInstance().backupsFolder.toFile().getFreeSpace()))), true); - return 1; + return Command.SINGLE_SUCCESS; } private static int status(CommandSourceStack source) { @@ -121,7 +124,7 @@ private static int status(CommandSourceStack source) { } }); - return 1; + return Command.SINGLE_SUCCESS; } /** @@ -131,7 +134,7 @@ private static int status(CommandSourceStack source) { private static int resetState(CommandSourceStack source) { source.getServer().getAllLevels().forEach(level -> { if (level != null) { - source.sendSuccess(() -> Component.literal("Reseting state + saving for " + level.toString()), true); + source.sendSuccess(() -> Component.literal("Reseting state + saving for " + level.dimension().location()), true); level.noSave = false; level.save(null, true, true); } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java index 5c1f9de6..b13b18e5 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupUtils.java @@ -8,7 +8,6 @@ import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; -import org.apache.commons.lang3.mutable.MutableLong; import javax.annotation.Nullable; import java.io.*; @@ -19,7 +18,6 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.concurrent.atomic.AtomicLong; -import java.util.stream.Stream; public class BackupUtils { public static final long KB = 1024L; @@ -86,8 +84,8 @@ public static void writeJson(Writer writer, @Nullable JsonElement element, boole if (element == null || element.isJsonNull()) { try { writer.write("null"); - } catch (Exception ex) { - ex.printStackTrace(); + } catch (IOException ex) { + throw new JsonIOException(ex); } return; @@ -104,7 +102,7 @@ public static void writeJson(Writer writer, @Nullable JsonElement element, boole try { Streams.write(element, jsonWriter); - } catch (Exception ex) { + } catch (IOException ex) { throw new JsonIOException(ex); } } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java index 1db9a5c1..3c625bb3 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java @@ -16,13 +16,15 @@ import net.minecraft.world.level.storage.LevelResource; import net.neoforged.fml.loading.FMLPaths; import net.neoforged.neoforge.common.NeoForge; -import net.neoforged.neoforge.network.PacketDistributor; import org.apache.commons.lang3.mutable.MutableInt; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.nio.file.*; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.time.Duration; import java.time.LocalDateTime; @@ -48,10 +50,10 @@ public class Backups { public boolean printFiles = false; private final AtomicInteger currentFileIndex = new AtomicInteger(0); + private final AtomicInteger totalFileCount = new AtomicInteger(0); private int prevFileIndex = 0; - public int totalFiles = 0; private String currentFileName = ""; - public boolean hadPlayersOnline = false; + public boolean playersOnlineSinceLastBackup = false; public static Backups getClientInstance() { if (clientInstance == null) { @@ -73,7 +75,7 @@ public static Backups getServerInstance() { } private Backups() { - backupsFolder = FTBBackupsServerConfig.getBackupFolder(FMLPaths.GAMEDIR.get()); + backupsFolder = FTBBackupsServerConfig.getBackupFolder(); try { backupsFolder = backupsFolder.toRealPath(); @@ -86,7 +88,6 @@ private Backups() { loadIndexFile(); LOGGER.info("Backups will be written to: {}", backupsFolder); - nextBackupTime = System.currentTimeMillis() + FTBBackupsServerConfig.getBackupTimerMillis(); } public void loadIndexFile() { @@ -104,9 +105,14 @@ private static Path backupsIndexPath() { } public void tick(MinecraftServer server, long now) { + if (nextBackupTime < 0) { + // will be the case on the first tick + nextBackupTime = System.currentTimeMillis() + FTBBackupsServerConfig.getBackupTimerMillis(); + } + if (nextBackupTime > 0L && nextBackupTime <= now) { - if (!FTBBackupsServerConfig.ONLY_IF_PLAYERS_ONLINE.get() || hadPlayersOnline || !server.getPlayerList().getPlayers().isEmpty()) { - hadPlayersOnline = false; + if (!FTBBackupsServerConfig.ONLY_IF_PLAYERS_ONLINE.get() || playersOnlineSinceLastBackup || !server.getPlayerList().getPlayers().isEmpty()) { + playersOnlineSinceLastBackup = false; run(server, true, Component.translatable("ftbbackups3.lang.server"), ""); } } @@ -120,18 +126,20 @@ public void tick(MinecraftServer server, long now) { } } - currentFileIndex.set(0); if (!FTBBackupsServerConfig.SILENT.get()) { - PacketDistributor.sendToAllPlayers(BackupProgressPacket.complete()); + BackupProgressPacket.sendProgress(server, BackupProgressPacket.complete()); } + + currentFileIndex.set(0); + totalFileCount.set(0); } else if (backupStatus.isRunning() && printFiles) { int curIdx = currentFileIndex.get(); - if (curIdx == 0 || curIdx == totalFiles - 1) { - LOGGER.info("[{} | {}%]: {}", currentFileIndex, (int) ((curIdx / (double) totalFiles) * 100D), currentFileName); + if (curIdx == 0 || curIdx == totalFileCount.get() - 1) { + LOGGER.info("[{} | {}%]: {}", currentFileIndex, (int) ((curIdx / (double) totalFileCount.get()) * 100D), currentFileName); } if (!FTBBackupsServerConfig.SILENT.get() && prevFileIndex != currentFileIndex.get()) { - PacketDistributor.sendToAllPlayers(BackupProgressPacket.update()); + BackupProgressPacket.sendProgress(server, BackupProgressPacket.update()); prevFileIndex = currentFileIndex.get(); } } @@ -181,6 +189,10 @@ private void createBackup(MinecraftServer server, String customName) { Path absoluteWorldPath = server.getWorldPath(LevelResource.ROOT).toAbsolutePath(); IArchivalPlugin archivalPlugin = ArchivePluginManager.serverInstance().getPlugin(FTBBackupsServerConfig.archivalPlugin()); + if (archivalPlugin == null) { + Backups.LOGGER.error("bad archive plugin setting in server config: {}! backup skipped", FTBBackupsServerConfig.archivalPlugin()); + return; + } String backupFileName = archivalPlugin.addFileExtension(customName.isEmpty() ? DATE_TIME_FORMATTER.format(LocalDateTime.now()) : customName); @@ -200,11 +212,11 @@ private void createBackup(MinecraftServer server, String customName) { doArchiveCleanup(archiveSize); - totalFiles = manifest.size(); - LOGGER.info("Backing up {} files...", totalFiles); + totalFileCount.set(manifest.size()); + LOGGER.info("Backing up {} files...", totalFileCount); printFiles = true; - PacketDistributor.sendToAllPlayers(BackupProgressPacket.start()); + BackupProgressPacket.sendProgress(server, BackupProgressPacket.start()); currentFileIndex.set(0); Path archiveDest = backupsFolder.resolve(backupFileName); @@ -363,6 +375,10 @@ public int getCurrentFileIndex() { return currentFileIndex.get(); } + public int getTotalFileCount() { + return totalFileCount.get(); + } + public Collection backups() { return Collections.unmodifiableCollection(backups); } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java index ce1ae969..f2a108ee 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java @@ -5,7 +5,6 @@ import dev.ftb.mods.ftbbackups.archival.FileCopyArchiver; import dev.ftb.mods.ftbbackups.archival.ZipArchiver; import dev.ftb.mods.ftbbackups.client.BackupsClient; -import dev.ftb.mods.ftbbackups.net.BackupProgressPacket; import dev.ftb.mods.ftbbackups.net.FTBBackupsNetHandler; import dev.ftb.mods.ftblibrary.config.manager.ConfigManager; import net.minecraft.network.chat.Component; @@ -24,30 +23,25 @@ import net.neoforged.neoforge.event.server.ServerAboutToStartEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; import net.neoforged.neoforge.event.tick.ServerTickEvent; -import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; import net.neoforged.neoforge.network.registration.PayloadRegistrar; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @Mod(FTBBackups.MOD_ID) public class FTBBackups { public static final String MOD_ID = "ftbbackups3"; - private static final Logger LOGGER = LoggerFactory.getLogger(FTBBackups.class); - public FTBBackups(IEventBus eventBus, ModContainer container) { if (FMLEnvironment.dist == Dist.CLIENT) { - eventBus.addListener(event -> clientSetup(event, eventBus)); + eventBus.addListener(event -> BackupsClient.onModConstruction()); } eventBus.addListener(this::registerNetwork); - ConfigManager.getInstance().registerServerConfig(FTBBackupsServerConfig.CONFIG, MOD_ID + ".general", true, FTBBackupsServerConfig::onConfigChanged); + ConfigManager.getInstance().registerServerConfig(FTBBackupsServerConfig.CONFIG, MOD_ID + ".general", + true, FTBBackupsServerConfig::onConfigChanged); ConfigManager.getInstance().registerClientConfig(FTBBackupsClientConfig.CONFIG, MOD_ID + ".general"); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverAboutToStart); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverStopping); - NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedIn); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedOut); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::levelTick); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::registerArchivalPlugins); @@ -55,10 +49,6 @@ public FTBBackups(IEventBus eventBus, ModContainer container) { NeoForge.EVENT_BUS.addListener(this::registerCommands); } - private void clientSetup(FMLClientSetupEvent event, IEventBus eventBus) { - BackupsClient.init(eventBus); - } - public void serverAboutToStart(ServerAboutToStartEvent event) { Backups.initServerInstance(); @@ -87,15 +77,9 @@ private void registerArchivalPlugins(RegisterArchivalPluginEvent event) { event.register(ZipArchiver.INSTANCE); } - public void playerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { - if (event.getEntity() instanceof ServerPlayer serverPlayer) { - PacketDistributor.sendToPlayer(serverPlayer, BackupProgressPacket.update()); - } - } - public void playerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) { if (event.getEntity() instanceof ServerPlayer) { - Backups.getServerInstance().hadPlayersOnline = true; + Backups.getServerInstance().playersOnlineSinceLastBackup = true; } } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsServerConfig.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsServerConfig.java index 20113cbc..84955b8d 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsServerConfig.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsServerConfig.java @@ -75,14 +75,17 @@ public interface FTBBackupsServerConfig { SNBTConfig ADVANCED = CONFIG.addGroup("advanced") .comment("Advanced features that shouldn't be changed unless you know what you are doing."); - IntValue BUFFER_SIZE = ADVANCED.addInt("buffer_size", 4096, 256, 65536) - .comment("Buffer size for reading/writing files."); - ArchivalPluginValue ARCHIVAL_PLUGIN = CONFIG.add(new ArchivalPluginValue(CONFIG, "archival_plugin", ZipArchiver.ID)) .comment("Method to use to create a backup archive.", "Builtin methods are \"ftbbackups:zip\" (create a ZIP file) and \"ftbbackups:filecopy\" (simple recursive copy of files with no compression)", "More archival plugins may be added by other mods."); + BooleanValue NOTIFY_ADMINS_ONLY = CONFIG.addBoolean("notify_admins_only", false) + .comment("If true, only player with permission level >= 2 (or SSP integrated server owners) will be notified on-screen about backup progress"); + + IntValue BUFFER_SIZE = ADVANCED.addInt("buffer_size", 4096, 256, 65536) + .comment("Buffer size for reading/writing files."); + Lazy MAX_TOTAL_SIZE = Lazy.of(() -> { String mts = MAX_TOTAL_SIZE_RAW.get(); @@ -106,7 +109,7 @@ static long getBackupTimerMillis() { return BACKUP_TIMER_MINUTES.get() * 60000L; // 60000 millisecs in a minute } - static void onConfigChanged(boolean isServer) { + static void onConfigChanged(boolean ignoredIsServer) { MAX_TOTAL_SIZE.invalidate(); } @@ -119,7 +122,7 @@ static ResourceLocation archivalPlugin() { return rl; } - static Path getBackupFolder(Path gameDir) { + static Path getBackupFolder() { String folder = FTBBackupsServerConfig.FOLDER.get(); return folder.trim().isEmpty() ? FMLPaths.GAMEDIR.get().resolve(DEFAULT_BACKUP_FOLDER) : diff --git a/src/main/java/dev/ftb/mods/ftbbackups/api/IArchivalPlugin.java b/src/main/java/dev/ftb/mods/ftbbackups/api/IArchivalPlugin.java index 320c7ad7..99fceb82 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/api/IArchivalPlugin.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/api/IArchivalPlugin.java @@ -43,8 +43,8 @@ default String getFileExtension() { /** * Called to restore a backup archive, overwriting existing files in a game instance. This is only - * called by the client for SSP instances, while the game is not actually running; it's not applicable to - * dedicated server instances. + * called from the client world selection screen for SSP instances, while no world is actually running; + * it's not relevant for dedicated server instances. * * @param context the restoration context * @throws Exception thrown if anything goes wrong diff --git a/src/main/java/dev/ftb/mods/ftbbackups/archival/ArchivePluginManager.java b/src/main/java/dev/ftb/mods/ftbbackups/archival/ArchivePluginManager.java index 9dd8dd49..072d5161 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/archival/ArchivePluginManager.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/archival/ArchivePluginManager.java @@ -4,6 +4,7 @@ import dev.ftb.mods.ftbbackups.api.event.RegisterArchivalPluginEvent; import net.minecraft.resources.ResourceLocation; import net.neoforged.neoforge.common.NeoForge; +import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.Map; @@ -36,6 +37,7 @@ public synchronized void register(IArchivalPlugin plugin) { plugins.put(plugin.getId(), plugin); } + @Nullable public IArchivalPlugin getPlugin(ResourceLocation id) { return plugins.get(id); } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/archival/FileCopyArchiver.java b/src/main/java/dev/ftb/mods/ftbbackups/archival/FileCopyArchiver.java index be91f706..2f5a747a 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/archival/FileCopyArchiver.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/archival/FileCopyArchiver.java @@ -4,10 +4,8 @@ import dev.ftb.mods.ftbbackups.api.IArchivalPlugin; import net.minecraft.resources.ResourceLocation; -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.Map; diff --git a/src/main/java/dev/ftb/mods/ftbbackups/client/BackupOverlayLayer.java b/src/main/java/dev/ftb/mods/ftbbackups/client/BackupOverlayLayer.java index 2714205f..b0b55879 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/client/BackupOverlayLayer.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/client/BackupOverlayLayer.java @@ -62,8 +62,8 @@ public void render(GuiGraphics guiGraphics, DeltaTracker deltaTracker) { int pw = (int) ((width - 4) * Mth.lerp(deltaTracker.getGameTimeDeltaTicks(), prevProgress, progress.current()) / progress.total()); int sy = font.lineHeight + 4; - guiGraphics.fill(2, sy - 1, width - 3, sy + progressBarHeight + 1, 0xFF0060E0); - guiGraphics.fill(3, sy, pw, sy + progressBarHeight, 0xFF0040A0); + guiGraphics.fill(2, sy - 1, width - 3, sy + progressBarHeight + 1, 0xC00060E0); + guiGraphics.fill(3, sy, pw, sy + progressBarHeight, 0xC00040A0); guiGraphics.drawString(font, line2, 14, 2 + font.lineHeight + 5, 0xFFFAF9F6, false); diff --git a/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java b/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java index da448dfb..cacedabb 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java @@ -9,8 +9,6 @@ import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import net.neoforged.api.distmarker.Dist; -import net.neoforged.bus.api.Event; -import net.neoforged.bus.api.IEventBus; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent; @@ -26,13 +24,10 @@ public class BackupsClient { private static int progressTicker = 0; - public static void init(IEventBus eventBus) { + public static void onModConstruction() { NeoForge.EVENT_BUS.addListener(BackupsClient::onClientDisconnected); -// NeoForge.EVENT_BUS.addListener(BackupsClient::onDebugInfoEvent); NeoForge.EVENT_BUS.addListener(BackupsClient::addRestoreButton); NeoForge.EVENT_BUS.addListener(BackupsClient::clientTick); - -// eventBus.addListener(BackupsClient::registerGuiLayer); } private static void clientTick(ClientTickEvent.Post event) { @@ -74,18 +69,6 @@ private static void onClientDisconnected(ClientPlayerNetworkEvent.LoggingOut ign backupProgress = BackupProgress.NONE; } -// private static void onDebugInfoEvent(CustomizeGuiOverlayEvent.DebugText event) { -// OptionInstance booleanOptionInstance = Minecraft.getInstance().options.reducedDebugInfo(); -// if (booleanOptionInstance.get()) { -// return; -// } -// -// if (backupProgress.total() > 0 && backupProgress.total() > backupProgress.current()) { -// // I think this is right? @mikey (2025-03-18) -// event.getLeft().add(progressMessage().withStyle(ChatFormatting.LIGHT_PURPLE).getString()); -// } -// } - public static @NotNull MutableComponent progressMessage() { return Component.translatable("ftbbackups3.lang.timer_progress", backupProgress.current() * 100 / backupProgress.total(), backupProgress.current(), backupProgress.total()); } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/client/RestoreBackupScreen.java b/src/main/java/dev/ftb/mods/ftbbackups/client/RestoreBackupScreen.java index 22cc60f9..ec2f3737 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/client/RestoreBackupScreen.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/client/RestoreBackupScreen.java @@ -4,7 +4,6 @@ import dev.ftb.mods.ftbbackups.FTBBackupsServerConfig; import dev.ftb.mods.ftbbackups.api.Backup; import dev.ftb.mods.ftbbackups.api.IArchivalPlugin; -import dev.ftb.mods.ftbbackups.api.event.RegisterArchivalPluginEvent; import dev.ftb.mods.ftbbackups.archival.ArchivePluginManager; import net.minecraft.ChatFormatting; import net.minecraft.Util; @@ -22,7 +21,6 @@ import net.minecraft.network.chat.MutableComponent; import net.minecraft.resources.ResourceLocation; import net.neoforged.fml.loading.FMLPaths; -import net.neoforged.neoforge.common.NeoForge; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -97,7 +95,7 @@ private void doRestore(boolean confirmed, BackupsList.Entry selected) { minecraft.setScreen(this); if (confirmed) { - Path backupsFolder = FTBBackupsServerConfig.getBackupFolder(FMLPaths.GAMEDIR.get()); + Path backupsFolder = FTBBackupsServerConfig.getBackupFolder(); Backup backup = selected.backup; diff --git a/src/main/java/dev/ftb/mods/ftbbackups/client/SelectArchivalPluginScreen.java b/src/main/java/dev/ftb/mods/ftbbackups/client/SelectArchivalPluginScreen.java index 9ec4841d..8a80065f 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/client/SelectArchivalPluginScreen.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/client/SelectArchivalPluginScreen.java @@ -1,7 +1,7 @@ package dev.ftb.mods.ftbbackups.client; -import dev.ftb.mods.ftbbackups.config.ArchivalPluginConfig; import dev.ftb.mods.ftbbackups.archival.ArchivePluginManager; +import dev.ftb.mods.ftbbackups.config.ArchivalPluginConfig; import dev.ftb.mods.ftblibrary.config.ConfigCallback; import dev.ftb.mods.ftblibrary.icon.Color4I; import dev.ftb.mods.ftblibrary.ui.Panel; diff --git a/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java b/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java index 0d2e66d7..007efaae 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java @@ -2,13 +2,19 @@ import dev.ftb.mods.ftbbackups.Backups; import dev.ftb.mods.ftbbackups.FTBBackups; +import dev.ftb.mods.ftbbackups.FTBBackupsServerConfig; import dev.ftb.mods.ftbbackups.client.BackupsClient; 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.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.handling.IPayloadContext; +import java.util.List; + public record BackupProgressPacket(int current, int total) implements CustomPacketPayload { public static final Type TYPE = new Type<>(FTBBackups.id("backup_progress")); @@ -22,16 +28,28 @@ public static void handler(BackupProgressPacket message, IPayloadContext context BackupsClient.setBackupProgress(message.current, message.total); } - public static CustomPacketPayload start() { - return new BackupProgressPacket(0, Backups.getServerInstance().totalFiles); + public static BackupProgressPacket start() { + return new BackupProgressPacket(0, Backups.getServerInstance().getTotalFileCount()); + } + + public static BackupProgressPacket update() { + return new BackupProgressPacket(Backups.getServerInstance().getCurrentFileIndex(), Backups.getServerInstance().getTotalFileCount()); + } + + public static BackupProgressPacket complete() { + return new BackupProgressPacket(Backups.getServerInstance().getTotalFileCount(), Backups.getServerInstance().getTotalFileCount()); } - public static CustomPacketPayload update() { - return new BackupProgressPacket(Backups.getServerInstance().getCurrentFileIndex(), Backups.getServerInstance().totalFiles); + public static void sendProgress(MinecraftServer server, BackupProgressPacket packet) { + getPlayersToNotify(server).forEach(sp -> PacketDistributor.sendToPlayer(sp, packet)); } - public static CustomPacketPayload complete() { - return new BackupProgressPacket(Backups.getServerInstance().totalFiles, Backups.getServerInstance().totalFiles); + private static List getPlayersToNotify(MinecraftServer server) { + return FTBBackupsServerConfig.NOTIFY_ADMINS_ONLY.get() ? + server.getPlayerList().getPlayers().stream() + .filter(sp -> sp.hasPermissions(2) || server.isSingleplayerOwner(sp.getGameProfile())) + .toList() : + server.getPlayerList().getPlayers(); } @Override diff --git a/src/main/resources/assets/ftbbackups3/lang/en_us.json b/src/main/resources/assets/ftbbackups3/lang/en_us.json index 927f95bd..dbc17075 100644 --- a/src/main/resources/assets/ftbbackups3/lang/en_us.json +++ b/src/main/resources/assets/ftbbackups3/lang/en_us.json @@ -24,6 +24,8 @@ "ftbbackups3.general.force_on_shutdown.tooltip": "If true (and \"Automatic Backups\" is true), always run a backup when the server is being stopped", "ftbbackups3.general.archival_plugin": "Archival Plugin", "ftbbackups3.general.archival_plugin.tooltip": "The plugin used to actually create files on disk.\nBuiltin options create ZIP files or flat file copies.", + "ftbbackups3.general.notify_admins_only": "Notify Admins Only", + "ftbbackups3.general.notify_admins_only.tooltip": "If true, only admin-level players (and SSP server owners) are notified on-screen about current backup progress\nIf false, all players are notified", "ftbbackups3.general.advanced": "Advanced", "ftbbackups3.general.advanced.buffer_size": "Buffer Size", "ftbbackups3.general.advanced.buffer_size.tooltip": "Buffer size used when reading files to be backed up.\nDon't adjust this unless you understand the implications!", @@ -33,6 +35,8 @@ "ftbbackups3.general.overlay.overlay_pos": "Progress Panel Position", "ftbbackups3.lang.server": "Server", "ftbbackups3.lang.timer": "Next backup in: %s", + "ftbbackups3.lang.timer.in_past": "Next backup was due: %s ago", + "ftbbackups3.lang.timer.no_players": "Backup will be run on next player login", "ftbbackups3.lang.timer_progress": "[%d%% | %d / %d]", "ftbbackups3.lang.start": "Server Backup started!", "ftbbackups3.lang.end_1": "Server backup done in %s!", From 548a0ac23678f37967e8fe793314ae4b49c0881a Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Wed, 4 Jun 2025 12:32:35 +0100 Subject: [PATCH 33/42] build: version -> 21.1.1, changelog updated --- CHANGELOG.md | 11 +++++++++++ gradle.properties | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1e2c135..eaf0dd44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [21.0.1] + +### Added +* Added "Notify Admins Only" server config setting + * When true, only players with permission level >= 2, and SSP server owners, will be notified about in-progress backups + +### Fixed +* Fixed issue where players on SMP could get a stuck backup progress notification on the client +* Fixed backup interval being ignored on server startup and always treated as 120 minutes, even if changed in config +* Better command output for `/ftbbackups3 time` is a backup is overdue (due to no players being online) + ## [21.0.0] ### Added diff --git a/gradle.properties b/gradle.properties index 85146ec7..c1d196c0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ parchment_mappings_version=2024.11.17 mod_id=ftbbackups3 mod_name=FTB Backups 3 -mod_version=0 +mod_version=1 minecraft_version=1.21.1 loader_version_range=[4,) From e91d46c5d9026c241d6b3d1a9e1e81d9716351e3 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Wed, 4 Jun 2025 12:39:01 +0100 Subject: [PATCH 34/42] fix: removed a spammy clientside info message --- src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java b/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java index cacedabb..8f91f6fa 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java @@ -62,7 +62,6 @@ public static void setBackupProgress(int current, int total) { if (backupProgress.finished()) { progressTicker = 40; } - Backups.LOGGER.info("progress: {}", backupProgress); } private static void onClientDisconnected(ClientPlayerNetworkEvent.LoggingOut ignoredEvent) { From 8fb826ef6b79f136c49f64373a66223965dda6bf Mon Sep 17 00:00:00 2001 From: Michael Hillcox Date: Wed, 25 Jun 2025 17:20:17 +0100 Subject: [PATCH 35/42] fix: backup not creating folder due to relpath --- src/main/java/dev/ftb/mods/ftbbackups/Backups.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java index 3c625bb3..83b2b88b 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java @@ -78,9 +78,10 @@ private Backups() { backupsFolder = FTBBackupsServerConfig.getBackupFolder(); try { - backupsFolder = backupsFolder.toRealPath(); Files.createDirectories(backupsFolder); + backupsFolder = backupsFolder.toRealPath(); } catch (Exception ignored) { + LOGGER.info("Failed to create backups folder: {}", backupsFolder); } backupStatus = BackupStatus.NONE; From c826e668fe3e97b9f0aa8ad3d5aae1adf9dac60d Mon Sep 17 00:00:00 2001 From: Michael Hillcox Date: Wed, 25 Jun 2025 17:30:45 +0100 Subject: [PATCH 36/42] feat: add redirect support via config for a /backup command --- .../dev/ftb/mods/ftbbackups/BackupCommands.java | 14 +++++++++++--- .../mods/ftbbackups/FTBBackupsServerConfig.java | 3 +++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java index d5614915..b389e48c 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java @@ -3,6 +3,7 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.exceptions.CommandSyntaxException; import dev.architectury.networking.NetworkManager; import dev.ftb.mods.ftblibrary.net.EditConfigChoicePacket; @@ -18,7 +19,7 @@ public class BackupCommands { public static void register(CommandDispatcher dispatcher) { - dispatcher.register(Commands.literal(FTBBackups.MOD_ID) + LiteralArgumentBuilder backupCommands = Commands.literal(FTBBackups.MOD_ID) .then(Commands.literal("time") .executes(ctx -> time(ctx.getSource())) ) @@ -52,8 +53,15 @@ public static void register(CommandDispatcher dispatcher) { .then(Commands.literal("config") .requires(CommandSourceStack::isPlayer) .executes(ctx -> editConfig(ctx.getSource())) - ) - ); + ); + + dispatcher.register(backupCommands); + + if (FTBBackupsServerConfig.ADD_BACKUP_COMMAND_ALIAS.get()) { + dispatcher.register(Commands.literal("backup") + .redirect(backupCommands.build())); + + } } private static boolean isAdmin(CommandSourceStack cs) { diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsServerConfig.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsServerConfig.java index 84955b8d..d0623430 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsServerConfig.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsServerConfig.java @@ -86,6 +86,9 @@ public interface FTBBackupsServerConfig { IntValue BUFFER_SIZE = ADVANCED.addInt("buffer_size", 4096, 256, 65536) .comment("Buffer size for reading/writing files."); + BooleanValue ADD_BACKUP_COMMAND_ALIAS = CONFIG.addBoolean("add_backup_command_alias", true) + .comment("If true, the /backup command will be aliased to /ftbbackups3 backup"); + Lazy MAX_TOTAL_SIZE = Lazy.of(() -> { String mts = MAX_TOTAL_SIZE_RAW.get(); From a6ee9031c0d7aa65a61a780d0a560dabf403dfea Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Thu, 26 Jun 2025 09:20:28 +0100 Subject: [PATCH 37/42] fix: message should be logged as an error --- src/main/java/dev/ftb/mods/ftbbackups/Backups.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java index 83b2b88b..b7b8bf8c 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java @@ -81,7 +81,7 @@ private Backups() { Files.createDirectories(backupsFolder); backupsFolder = backupsFolder.toRealPath(); } catch (Exception ignored) { - LOGGER.info("Failed to create backups folder: {}", backupsFolder); + LOGGER.error("Failed to create backups folder: {}", backupsFolder); } backupStatus = BackupStatus.NONE; From ed4ef1f7555f9201291a0bf2b8af2fca6f2dd1d2 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Thu, 26 Jun 2025 09:27:01 +0100 Subject: [PATCH 38/42] fix: clear plugin registry *after* forced shutdown backup is done --- src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java index f2a108ee..a94cafe1 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java @@ -21,6 +21,7 @@ import net.neoforged.neoforge.event.RegisterCommandsEvent; import net.neoforged.neoforge.event.entity.player.PlayerEvent; import net.neoforged.neoforge.event.server.ServerAboutToStartEvent; +import net.neoforged.neoforge.event.server.ServerStoppedEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; import net.neoforged.neoforge.event.tick.ServerTickEvent; import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; @@ -42,6 +43,7 @@ public FTBBackups(IEventBus eventBus, ModContainer container) { NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverAboutToStart); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverStopping); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::serverStopped); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::playerLoggedOut); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::levelTick); NeoForge.EVENT_BUS.addListener(EventPriority.HIGH, this::registerArchivalPlugins); @@ -65,13 +67,15 @@ public void registerNetwork(RegisterPayloadHandlersEvent event) { } public void serverStopping(ServerStoppingEvent event) { - ArchivePluginManager.serverInstance().clear(); - if (FTBBackupsServerConfig.FORCE_ON_SHUTDOWN.get()) { Backups.getServerInstance().run(event.getServer(), true, Component.literal("Server"), ""); } } + public void serverStopped(ServerStoppedEvent event) { + ArchivePluginManager.serverInstance().clear(); + } + private void registerArchivalPlugins(RegisterArchivalPluginEvent event) { event.register(FileCopyArchiver.INSTANCE); event.register(ZipArchiver.INSTANCE); From 22648fea71ef82d743bcf59a1e40285e4fb90b83 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Thu, 26 Jun 2025 09:27:13 +0100 Subject: [PATCH 39/42] fix: add missing translation --- src/main/resources/assets/ftbbackups3/lang/en_us.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/resources/assets/ftbbackups3/lang/en_us.json b/src/main/resources/assets/ftbbackups3/lang/en_us.json index dbc17075..723f007e 100644 --- a/src/main/resources/assets/ftbbackups3/lang/en_us.json +++ b/src/main/resources/assets/ftbbackups3/lang/en_us.json @@ -26,6 +26,8 @@ "ftbbackups3.general.archival_plugin.tooltip": "The plugin used to actually create files on disk.\nBuiltin options create ZIP files or flat file copies.", "ftbbackups3.general.notify_admins_only": "Notify Admins Only", "ftbbackups3.general.notify_admins_only.tooltip": "If true, only admin-level players (and SSP server owners) are notified on-screen about current backup progress\nIf false, all players are notified", + "ftbbackups3.general.add_backup_command_alias": "Add backup command alias", + "ftbbackups3.general.add_backup_command_alias.tooltip": "If true, register a /backups command as an alias to /ftbbackups3", "ftbbackups3.general.advanced": "Advanced", "ftbbackups3.general.advanced.buffer_size": "Buffer Size", "ftbbackups3.general.advanced.buffer_size.tooltip": "Buffer size used when reading files to be backed up.\nDon't adjust this unless you understand the implications!", From 19ab259132c911048dd885b166ac287dd62bb3d9 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Thu, 26 Jun 2025 09:29:05 +0100 Subject: [PATCH 40/42] chore: code reorg for config classes --- src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java | 2 ++ src/main/java/dev/ftb/mods/ftbbackups/Backups.java | 1 + src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java | 2 ++ .../java/dev/ftb/mods/ftbbackups/archival/ZipArchiver.java | 2 +- .../dev/ftb/mods/ftbbackups/client/BackupOverlayLayer.java | 2 +- .../dev/ftb/mods/ftbbackups/client/RestoreBackupScreen.java | 2 +- .../ftbbackups/{ => config}/FTBBackupsClientConfig.java | 3 ++- .../ftbbackups/{ => config}/FTBBackupsServerConfig.java | 6 ++++-- .../dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java | 2 +- 9 files changed, 15 insertions(+), 7 deletions(-) rename src/main/java/dev/ftb/mods/ftbbackups/{ => config}/FTBBackupsClientConfig.java (93%) rename src/main/java/dev/ftb/mods/ftbbackups/{ => config}/FTBBackupsServerConfig.java (97%) diff --git a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java index b389e48c..ed5bf128 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/BackupCommands.java @@ -6,6 +6,8 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.exceptions.CommandSyntaxException; import dev.architectury.networking.NetworkManager; +import dev.ftb.mods.ftbbackups.config.FTBBackupsClientConfig; +import dev.ftb.mods.ftbbackups.config.FTBBackupsServerConfig; import dev.ftb.mods.ftblibrary.net.EditConfigChoicePacket; import net.minecraft.ChatFormatting; import net.minecraft.commands.CommandSourceStack; diff --git a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java index b7b8bf8c..47410048 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/Backups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/Backups.java @@ -6,6 +6,7 @@ import dev.ftb.mods.ftbbackups.api.IArchivalPlugin; import dev.ftb.mods.ftbbackups.api.event.BackupEvent; import dev.ftb.mods.ftbbackups.archival.ArchivePluginManager; +import dev.ftb.mods.ftbbackups.config.FTBBackupsServerConfig; import dev.ftb.mods.ftbbackups.net.BackupProgressPacket; import net.minecraft.ChatFormatting; import net.minecraft.Util; diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java index a94cafe1..6258a2d8 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java @@ -5,6 +5,8 @@ import dev.ftb.mods.ftbbackups.archival.FileCopyArchiver; import dev.ftb.mods.ftbbackups.archival.ZipArchiver; import dev.ftb.mods.ftbbackups.client.BackupsClient; +import dev.ftb.mods.ftbbackups.config.FTBBackupsClientConfig; +import dev.ftb.mods.ftbbackups.config.FTBBackupsServerConfig; import dev.ftb.mods.ftbbackups.net.FTBBackupsNetHandler; import dev.ftb.mods.ftblibrary.config.manager.ConfigManager; import net.minecraft.network.chat.Component; diff --git a/src/main/java/dev/ftb/mods/ftbbackups/archival/ZipArchiver.java b/src/main/java/dev/ftb/mods/ftbbackups/archival/ZipArchiver.java index b3d8a052..7761e949 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/archival/ZipArchiver.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/archival/ZipArchiver.java @@ -1,7 +1,7 @@ package dev.ftb.mods.ftbbackups.archival; import dev.ftb.mods.ftbbackups.FTBBackups; -import dev.ftb.mods.ftbbackups.FTBBackupsServerConfig; +import dev.ftb.mods.ftbbackups.config.FTBBackupsServerConfig; import dev.ftb.mods.ftbbackups.api.IArchivalPlugin; import net.minecraft.resources.ResourceLocation; diff --git a/src/main/java/dev/ftb/mods/ftbbackups/client/BackupOverlayLayer.java b/src/main/java/dev/ftb/mods/ftbbackups/client/BackupOverlayLayer.java index b0b55879..5ff5b0bf 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/client/BackupOverlayLayer.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/client/BackupOverlayLayer.java @@ -1,7 +1,7 @@ package dev.ftb.mods.ftbbackups.client; import dev.ftb.mods.ftbbackups.FTBBackups; -import dev.ftb.mods.ftbbackups.FTBBackupsClientConfig; +import dev.ftb.mods.ftbbackups.config.FTBBackupsClientConfig; import dev.ftb.mods.ftblibrary.icon.Color4I; import dev.ftb.mods.ftblibrary.ui.GuiHelper; import net.minecraft.client.DeltaTracker; diff --git a/src/main/java/dev/ftb/mods/ftbbackups/client/RestoreBackupScreen.java b/src/main/java/dev/ftb/mods/ftbbackups/client/RestoreBackupScreen.java index ec2f3737..75650a40 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/client/RestoreBackupScreen.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/client/RestoreBackupScreen.java @@ -1,7 +1,7 @@ package dev.ftb.mods.ftbbackups.client; import dev.ftb.mods.ftbbackups.Backups; -import dev.ftb.mods.ftbbackups.FTBBackupsServerConfig; +import dev.ftb.mods.ftbbackups.config.FTBBackupsServerConfig; import dev.ftb.mods.ftbbackups.api.Backup; import dev.ftb.mods.ftbbackups.api.IArchivalPlugin; import dev.ftb.mods.ftbbackups.archival.ArchivePluginManager; diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClientConfig.java b/src/main/java/dev/ftb/mods/ftbbackups/config/FTBBackupsClientConfig.java similarity index 93% rename from src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClientConfig.java rename to src/main/java/dev/ftb/mods/ftbbackups/config/FTBBackupsClientConfig.java index c460a451..5c2347c6 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsClientConfig.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/config/FTBBackupsClientConfig.java @@ -1,5 +1,6 @@ -package dev.ftb.mods.ftbbackups; +package dev.ftb.mods.ftbbackups.config; +import dev.ftb.mods.ftbbackups.FTBBackups; import dev.ftb.mods.ftblibrary.snbt.config.BooleanValue; import dev.ftb.mods.ftblibrary.snbt.config.EnumValue; import dev.ftb.mods.ftblibrary.snbt.config.IntValue; diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsServerConfig.java b/src/main/java/dev/ftb/mods/ftbbackups/config/FTBBackupsServerConfig.java similarity index 97% rename from src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsServerConfig.java rename to src/main/java/dev/ftb/mods/ftbbackups/config/FTBBackupsServerConfig.java index d0623430..60b66c04 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackupsServerConfig.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/config/FTBBackupsServerConfig.java @@ -1,8 +1,10 @@ -package dev.ftb.mods.ftbbackups; +package dev.ftb.mods.ftbbackups.config; +import dev.ftb.mods.ftbbackups.BackupUtils; +import dev.ftb.mods.ftbbackups.Backups; +import dev.ftb.mods.ftbbackups.FTBBackups; import dev.ftb.mods.ftbbackups.archival.ZipArchiver; -import dev.ftb.mods.ftbbackups.config.ArchivalPluginValue; import dev.ftb.mods.ftblibrary.snbt.config.*; import net.minecraft.resources.ResourceLocation; import net.neoforged.fml.loading.FMLPaths; diff --git a/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java b/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java index 007efaae..02df7758 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/net/BackupProgressPacket.java @@ -2,7 +2,7 @@ import dev.ftb.mods.ftbbackups.Backups; import dev.ftb.mods.ftbbackups.FTBBackups; -import dev.ftb.mods.ftbbackups.FTBBackupsServerConfig; +import dev.ftb.mods.ftbbackups.config.FTBBackupsServerConfig; import dev.ftb.mods.ftbbackups.client.BackupsClient; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.ByteBufCodecs; From d12071c567dbe9d5d950d452d1fc0fb449c931d0 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Thu, 26 Jun 2025 09:58:39 +0100 Subject: [PATCH 41/42] fix: ensure parent folder hierarchy is created when copying files Fixes case of restoring a world that has been deleted entirely --- .../java/dev/ftb/mods/ftbbackups/archival/FileCopyArchiver.java | 1 + src/main/java/dev/ftb/mods/ftbbackups/archival/ZipArchiver.java | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/java/dev/ftb/mods/ftbbackups/archival/FileCopyArchiver.java b/src/main/java/dev/ftb/mods/ftbbackups/archival/FileCopyArchiver.java index 2f5a747a..06ab2b51 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/archival/FileCopyArchiver.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/archival/FileCopyArchiver.java @@ -50,6 +50,7 @@ public void restoreArchive(RestorationContext context) throws Exception { if (Files.isRegularFile(source)) { Path destination = context.destinationFolder().resolve(srcDir.relativize(source)); try { + Files.createDirectories(destination.getParent()); context.notifyProcessingFile(source.toString()); Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES); } catch (Exception ex) { diff --git a/src/main/java/dev/ftb/mods/ftbbackups/archival/ZipArchiver.java b/src/main/java/dev/ftb/mods/ftbbackups/archival/ZipArchiver.java index 7761e949..9e49bf4b 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/archival/ZipArchiver.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/archival/ZipArchiver.java @@ -74,6 +74,7 @@ public void restoreArchive(RestorationContext context) throws IOException { } Path destFile = safePath(context.destinationFolder(), zipEntry); + Files.createDirectories(destFile.getParent()); context.logger().info("extract to {}", destFile); try (FileOutputStream fos = new FileOutputStream(destFile.toFile())) { int len; From f73e54b6dcfda07a3efc5e0b68a40b01c636d3d5 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Thu, 26 Jun 2025 12:35:50 +0100 Subject: [PATCH 42/42] feat: support for disabling mod via environment variable If "FTB_BACKUPS_DISABLED" is set (to anything), the mod does nothing Note: FTB Library 2101.1.15+ is needed to hide the sidebar button --- src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java | 10 ++++++++++ .../dev/ftb/mods/ftbbackups/client/BackupsClient.java | 4 +++- .../assets/ftbbackups3/sidebar_buttons/config.json | 3 ++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java index 6258a2d8..94faac73 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/FTBBackups.java @@ -28,12 +28,18 @@ import net.neoforged.neoforge.event.tick.ServerTickEvent; import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; import net.neoforged.neoforge.network.registration.PayloadRegistrar; +import org.slf4j.LoggerFactory; @Mod(FTBBackups.MOD_ID) public class FTBBackups { public static final String MOD_ID = "ftbbackups3"; public FTBBackups(IEventBus eventBus, ModContainer container) { + if (isDisabledByEnvironmentVar()) { + LoggerFactory.getLogger(MOD_ID).info("FTB Backups 3 is disabled by environment variable! (FTB_BACKUPS_DISABLED is set)"); + return; + } + if (FMLEnvironment.dist == Dist.CLIENT) { eventBus.addListener(event -> BackupsClient.onModConstruction()); } @@ -93,6 +99,10 @@ public void levelTick(ServerTickEvent.Post event) { Backups.getServerInstance().tick(event.getServer(), System.currentTimeMillis()); } + public static boolean isDisabledByEnvironmentVar() { + return System.getenv().containsKey("FTB_BACKUPS_DISABLED"); + } + public static ResourceLocation id(String path) { return ResourceLocation.fromNamespaceAndPath(MOD_ID, path); } diff --git a/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java b/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java index 8f91f6fa..0b5f7f87 100644 --- a/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java +++ b/src/main/java/dev/ftb/mods/ftbbackups/client/BackupsClient.java @@ -54,7 +54,9 @@ private static void addRestoreButton(ScreenEvent.Init.Post event) { @SubscribeEvent public static void registerGuiLayer(RegisterGuiLayersEvent event) { // needs to be done with SubscribeEvent, addListener() is called too late - event.registerAboveAll(BackupOverlayLayer.ID, new BackupOverlayLayer()); + if (!FTBBackups.isDisabledByEnvironmentVar()) { + event.registerAboveAll(BackupOverlayLayer.ID, new BackupOverlayLayer()); + } } public static void setBackupProgress(int current, int total) { diff --git a/src/main/resources/assets/ftbbackups3/sidebar_buttons/config.json b/src/main/resources/assets/ftbbackups3/sidebar_buttons/config.json index f1709de5..51b42e1a 100644 --- a/src/main/resources/assets/ftbbackups3/sidebar_buttons/config.json +++ b/src/main/resources/assets/ftbbackups3/sidebar_buttons/config.json @@ -3,5 +3,6 @@ "ftbbackups3:textures/icons/ftb-backups3-icon.png" ], "sort_index": 700, - "click": ["command:/ftbbackups3 config"] + "click": ["command:/ftbbackups3 config"], + "environment_condition": "!FTB_BACKUPS_DISABLED" } \ No newline at end of file