summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKeuin <[email protected]>2020-04-23 00:16:13 +0800
committerkeuin <[email protected]>2020-04-23 00:20:29 +0800
commit28ebd3ae91dc6087a9f9acd93f80dd92be2d64aa (patch)
tree9a051d4784cd41953a5c6aa02e55005e14d907bf /src
parentd00b439204b7ddfe381f1ff7c0dc1f02e16da8de (diff)
Finish backup.
TODO: - restore
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/keuin/kbackupfabric/KBCommandHandler.java184
-rw-r--r--src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java86
-rw-r--r--src/main/java/com/keuin/kbackupfabric/KBMain.java24
-rw-r--r--src/main/java/com/keuin/kbackupfabric/KBPluginInitializer.java12
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/ReflectionUtils.java59
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/WorldUtil.java15
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/ZipUtil.java221
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/ZipUtilException.java12
-rw-r--r--src/main/java/net/fabricmc/example/ExampleMod.java14
-rw-r--r--src/main/java/net/fabricmc/example/mixin/ExampleMixin.java15
-rw-r--r--src/main/resources/assets/kbackupfabric/icon.png (renamed from src/main/resources/assets/modid/icon.png)bin453 -> 453 bytes
-rw-r--r--src/main/resources/fabric.mod.json24
12 files changed, 623 insertions, 43 deletions
diff --git a/src/main/java/com/keuin/kbackupfabric/KBCommandHandler.java b/src/main/java/com/keuin/kbackupfabric/KBCommandHandler.java
new file mode 100644
index 0000000..c1d176c
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/KBCommandHandler.java
@@ -0,0 +1,184 @@
+package com.keuin.kbackupfabric;
+
+import com.keuin.kbackupfabric.util.ZipUtil;
+import com.keuin.kbackupfabric.util.ZipUtilException;
+import com.mojang.brigadier.arguments.StringArgumentType;
+import com.mojang.brigadier.context.CommandContext;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.command.ServerCommandSource;
+import net.minecraft.text.LiteralText;
+import net.minecraft.world.World;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.Map;
+
+public class KBCommandHandler {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+ private static final int SUCCESS = 1;
+ private static final int FAILED = -1;
+ private static final boolean printDebugMessages = true;
+ private static final boolean printErrorMessages = true;
+ private static final String backupSaveDirectoryName = "backups";
+ private static final String backupFileNamePrefix = "kbackup-";
+
+ /**
+ * Print the help menu.
+ *
+ * @param context the context.
+ * @return stat code.
+ */
+ public static int help(CommandContext<ServerCommandSource> context) {
+ message(context, "KBackup Manual");
+ message(context, "/kb | /kb help Print help menu.");
+ message(context, "/kb list Show all backups.");
+ message(context, "/kb backup [backup_name] Backup world, nether, end to backup_name. By default, the name is current system time.");
+ message(context, "/kb restore <backup_name> Delete current three worlds, restore the older version from given backup. By default, this command is identical with /kb list.");
+ return SUCCESS;
+ }
+
+ public static int list(CommandContext<ServerCommandSource> context) {
+ message(context, "Available backups: (file is not checked, manipulation may affect this plugin)");
+ MinecraftServer server = context.getSource().getMinecraftServer();
+ File[] files = getBackupSaveDirectory(server).listFiles(
+ (dir, name) -> dir.isDirectory() && name.toLowerCase().endsWith(".zip") && name.toLowerCase().startsWith(backupFileNamePrefix)
+ );
+ if (files != null) {
+ for (File file : files) {
+ message(context, file.getName());
+ }
+ }
+ return SUCCESS;
+ }
+
+ /**
+ * Backup with context parameter backupName.
+ *
+ * @param context the context.
+ * @return stat code.
+ */
+ public static int backup(CommandContext<ServerCommandSource> context) {
+ //KBMain.backup("name")
+ return doBackup(context, StringArgumentType.getString(context, "backupName"));
+ }
+
+ /**
+ * Backup with default name.
+ *
+ * @param context the context.
+ * @return stat code.
+ */
+ public static int backupWithDefaultName(CommandContext<ServerCommandSource> context) {
+ //KBMain.backup("name")
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss");
+ String timeString = LocalDateTime.now().format(formatter);
+ return doBackup(context, timeString);
+ }
+
+ private static int doBackup(CommandContext<ServerCommandSource> context, String backupName) {
+ String destPathFolderName = "";
+ try {
+ message(context, String.format("Making backup %s ...", backupName), true);
+ Map<World, Boolean> oldWorldsSavingDisabled = new HashMap<>(); // old switch stat
+
+ // Get server
+ MinecraftServer server = context.getSource().getMinecraftServer();
+
+ // Save old autosave switch stat temporally
+ server.getWorlds().forEach(world -> {
+ oldWorldsSavingDisabled.put(world, world.savingDisabled);
+ world.savingDisabled = true;
+ });
+
+ // Force to save all player data and worlds
+ debug("Saving players ...");
+ server.getPlayerManager().saveAllPlayerData();
+ debug("Saving worlds ...");
+ server.save(true, true, true);
+
+ //// Do our main backup logic
+
+ // Get the level folder
+ File sourcePathFile = new File(server.getRunDirectory(), server.getLevelName());
+
+ // Create backup saving directory
+ File destPathFile = getBackupSaveDirectory(server);
+ destPathFolderName = destPathFile.getName();
+ if (!destPathFile.mkdir() && !destPathFile.isDirectory()) {
+ message(context, String.format("Failed to create backup saving directory: %s. Failed to backup.", destPathFolderName));
+ return FAILED;
+ }
+
+ // Make zip
+ debug(String.format("zip(srcPath=%s, destPath=%s)", sourcePathFile.getAbsolutePath(), destPathFile.toString()));
+ ZipUtil.zip(sourcePathFile.getAbsolutePath(), destPathFile.toString(), backupFileNamePrefix + backupName + ".zip");
+
+
+ // Restore old autosave switch stat
+ server.getWorlds().forEach(world -> world.savingDisabled = oldWorldsSavingDisabled.getOrDefault(world, true));
+
+ message(context, "Done.", true);
+ return SUCCESS;
+ } catch (SecurityException e) {
+ message(context, String.format("Failed to create backup saving directory: %s. Failed to backup.", destPathFolderName));
+ return FAILED;
+ } catch (IOException | ZipUtilException e) {
+ message(context, "Failed to make zip: " + e.getMessage());
+ return FAILED;
+ }
+ }
+
+ private static File getBackupSaveDirectory(MinecraftServer server) {
+ return new File(server.getRunDirectory(), backupSaveDirectoryName);
+ }
+
+ /**
+ * Restore with context parameter backupName.
+ *
+ * @param context the context.
+ * @return stat code.
+ */
+ public static int restore(CommandContext<ServerCommandSource> context) {
+ //KBMain.restore("name")
+ String backupName = StringArgumentType.getString(context, "backupName");
+ message(context, String.format("Restoring worlds to %s ...", backupName), true);
+ // do restore to backupName
+
+ message(context, "Done.", true);
+ return SUCCESS;
+ }
+
+ private static CommandContext<ServerCommandSource> message(CommandContext<ServerCommandSource> context, String messageText) {
+ return message(context, messageText, false);
+ }
+
+ private static CommandContext<ServerCommandSource> message(CommandContext<ServerCommandSource> context, String messageText, boolean broadcastToOps) {
+ context.getSource().sendFeedback(new LiteralText("[KBackup] " + messageText), broadcastToOps);
+ return context;
+ }
+
+ static boolean opPermissionValidator(ServerCommandSource commandSource) {
+ return commandSource.hasPermissionLevel(4);
+ }
+
+ private static void debug(String debugMessage) {
+ if (printDebugMessages) {
+ System.out.println(String.format("[DEBUG] [KBackup] %s", debugMessage));
+ LOGGER.debug(debugMessage);
+ }
+ }
+
+ private static void error(String errorMessage) {
+ if (printErrorMessages) {
+ System.out.println(String.format("[ERROR] [KBackup] %s", errorMessage));
+ LOGGER.error(errorMessage);
+ }
+ }
+
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java b/src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java
new file mode 100644
index 0000000..96f05e8
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java
@@ -0,0 +1,86 @@
+package com.keuin.kbackupfabric;
+
+import com.mojang.brigadier.CommandDispatcher;
+import com.mojang.brigadier.arguments.StringArgumentType;
+import net.minecraft.server.command.CommandManager;
+import net.minecraft.server.command.ServerCommandSource;
+
+public class KBCommandRegister {
+
+ // First make method to register
+ public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
+ // register /kb and /kb help for help menu
+ dispatcher.register(CommandManager.literal("kb").executes(KBCommandHandler::help));
+ dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("help").executes(KBCommandHandler::help)));
+
+ // register /kb list for showing the backup list
+ dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("list").executes(KBCommandHandler::list)));
+
+ // register /kb backup [name] for performing backup. OP is required.
+ dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("backup").then(CommandManager.argument("backupName", StringArgumentType.string()).requires(KBCommandHandler::opPermissionValidator).executes(KBCommandHandler::backup)).requires(KBCommandHandler::opPermissionValidator).executes(KBCommandHandler::backupWithDefaultName)));
+
+ // register /kb restore <name> for performing restore. OP is required.
+ dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("restore").then(CommandManager.argument("backupName", StringArgumentType.string()).requires(KBCommandHandler::opPermissionValidator).executes(KBCommandHandler::restore)).executes(KBCommandHandler::list)));
+
+
+// LiteralCommandNode<ServerCommandSource> basenode = dispatcher.register(literal("findBiome")
+// .then(argument("biome_identifier", identifier()).suggests(BiomeCompletionProvider.BIOMES) // We use Biome suggestions for identifier argument
+// .then(argument("distance", integer(0, 20000))
+// .executes(ctx -> execute(ctx.getSource(), getIdentifier(ctx, "biome_identifier"), getInteger(ctx, "distance"))))
+// .executes(ctx -> execute(ctx.getSource(), getIdentifier(ctx, "biome_identifier"), 1000))));
+// // Register redirect
+// dispatcher.register(literal("biome")
+// .redirect(basenode));
+ }
+
+// // Beginning of the method
+// private static int execute(ServerCommandSource source, Identifier biomeId, int range) throws CommandSyntaxException {
+// Biome biome = Registry.BIOME.get(biomeId);
+//
+// if(biome == null) { // Since the argument is an Identifier we need to check if the identifier actually exists in the registry
+// throw new SimpleCommandExceptionType(new TranslatableText("biome.not.exist", biomeId)).create();
+// }
+//
+// List<Biome> bio = new ArrayList<Biome>();
+// bio.add(biome);
+//
+// ServerWorld world = source.getWorld();
+//
+// BiomeSource bsource = world.getChunkManager().getChunkGenerator().getBiomeSource();
+//
+// BlockPos loc = new BlockPos(source.getPosition());
+// // Now here is the heaviest part of the method.
+// BlockPos pos = bsource.locateBiome(loc.getX(), loc.getZ(), range, bio, new Random(world.getSeed()));
+//
+// // Since this method can return null if it failed to find a biome
+// if(pos == null) {
+// throw new SimpleCommandExceptionType(new TranslatableText("biome.notfound", biome.getTranslationKey())).create();
+// }
+//
+// int distance = MathHelper.floor(getDistance(loc.getX(), loc.getZ(), pos.getX(), pos.getZ()));
+// // Popup text that can suggest commands. This is the exact same system that /locate uses.
+// Text teleportButtonPopup = Texts.bracketed(new TranslatableText("chat.coordinates", new Object[] { pos.getX(), "~", pos.getZ()})).styled((style_1x) -> {
+// style_1x.setColor(Formatting.GREEN).setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/tp @s " + pos.getX() + " ~ " + pos.getZ())).setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new TranslatableText("chat.coordinates.tooltip", new Object[0])));
+// });
+//
+// source.sendFeedback(new TranslatableText("commands.locate.success", new Object[] { new TranslatableText(Registry.BIOME.get(biomeId).getTranslationKey()), teleportButtonPopup, distance}), false);
+//
+// return 1;
+// }
+// // Just a normal old 2d distance method.
+// private static float getDistance(int int_1, int int_2, int int_3, int int_4) {
+// int int_5 = int_3 - int_1;
+// int int_6 = int_4 - int_2;
+//
+// return MathHelper.sqrt((float) (int_5 * int_5 + int_6 * int_6));
+// }
+//
+// public static class BiomeCompletionProvider {
+// // This provides suggestions of what biomes can be selected. Since this uses the registry, mods that add new biomes will work without modification.
+// public static final SuggestionProvider<ServerCommandSource> BIOMES = SuggestionProviders.register(new Identifier("biomes"), (ctx, builder) -> {
+// Registry.BIOME.getIds().stream().forEach(identifier -> builder.suggest(identifier.toString(), new TranslatableText(Registry.BIOME.get(identifier).getTranslationKey())));
+// return builder.buildFuture();
+// });
+//
+// }
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/KBMain.java b/src/main/java/com/keuin/kbackupfabric/KBMain.java
new file mode 100644
index 0000000..f2a3789
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/KBMain.java
@@ -0,0 +1,24 @@
+package com.keuin.kbackupfabric;
+
+public class KBMain {
+ /**
+ * Perform real backup process.
+ *
+ * @param backupName the backup name.
+ * @return true if success, false if failed.
+ */
+ public static boolean backup(String backupName) {
+
+ return true;
+ }
+
+ /**
+ * Perform real restore process.
+ *
+ * @param backupName the backup name.
+ * @return true if success, false if failed.
+ */
+ public static boolean restore(String backupName) {
+ return true;
+ }
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/KBPluginInitializer.java b/src/main/java/com/keuin/kbackupfabric/KBPluginInitializer.java
new file mode 100644
index 0000000..684ec07
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/KBPluginInitializer.java
@@ -0,0 +1,12 @@
+package com.keuin.kbackupfabric;
+
+import net.fabricmc.api.ModInitializer;
+import net.fabricmc.fabric.api.registry.CommandRegistry;
+
+public class KBPluginInitializer implements ModInitializer {
+ @Override
+ public void onInitialize() {
+ System.out.println("Initializing KBackup...");
+ CommandRegistry.INSTANCE.register(false, KBCommandRegister::register);
+ }
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/util/ReflectionUtils.java b/src/main/java/com/keuin/kbackupfabric/util/ReflectionUtils.java
new file mode 100644
index 0000000..f7bc351
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/util/ReflectionUtils.java
@@ -0,0 +1,59 @@
+package com.keuin.kbackupfabric.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * @Author 落叶飞翔的蜗牛
+ * @Date 2018/3/10
+ * @Description
+ */
+public class ReflectionUtils {
+
+ /**
+ * 获取私有成员变量的值
+ *
+ * @param instance
+ * @param filedName
+ * @return
+ */
+ public static Object getPrivateField(Object instance, String filedName) throws NoSuchFieldException, IllegalAccessException {
+ Field field = instance.getClass().getDeclaredField(filedName);
+ field.setAccessible(true);
+ return field.get(instance);
+ }
+
+ /**
+ * 设置私有成员的值
+ *
+ * @param instance
+ * @param fieldName
+ * @param value
+ * @throws NoSuchFieldException
+ * @throws IllegalAccessException
+ */
+ public static void setPrivateField(Object instance, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
+ Field field = instance.getClass().getDeclaredField(fieldName);
+ field.setAccessible(true);
+ field.set(instance, value);
+ }
+
+ /**
+ * 访问私有方法
+ *
+ * @param instance
+ * @param methodName
+ * @param classes
+ * @param objects
+ * @return
+ * @throws NoSuchMethodException
+ * @throws InvocationTargetException
+ * @throws IllegalAccessException
+ */
+ public static Object invokePrivateMethod(Object instance, String methodName, Class[] classes, String objects) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ Method method = instance.getClass().getDeclaredMethod(methodName, classes);
+ method.setAccessible(true);
+ return method.invoke(instance, objects);
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/keuin/kbackupfabric/util/WorldUtil.java b/src/main/java/com/keuin/kbackupfabric/util/WorldUtil.java
new file mode 100644
index 0000000..badc068
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/util/WorldUtil.java
@@ -0,0 +1,15 @@
+package com.keuin.kbackupfabric.util;
+
+import net.minecraft.server.world.ThreadedAnvilChunkStorage;
+import net.minecraft.world.World;
+
+import java.io.File;
+
+public class WorldUtil {
+ public static String getWorldDirectoryName(World world) throws NoSuchFieldException, IllegalAccessException {
+ File saveDir;
+ ThreadedAnvilChunkStorage threadedAnvilChunkStorage = (ThreadedAnvilChunkStorage) ReflectionUtils.getPrivateField(world.getChunkManager(), "threadedAnvilChunkStorage");
+ saveDir = (File) ReflectionUtils.getPrivateField(threadedAnvilChunkStorage, "saveDir");
+ return saveDir.getName();
+ }
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/util/ZipUtil.java b/src/main/java/com/keuin/kbackupfabric/util/ZipUtil.java
new file mode 100644
index 0000000..d92ab12
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/util/ZipUtil.java
@@ -0,0 +1,221 @@
+package com.keuin.kbackupfabric.util;
+
+import java.io.*;
+import java.util.Enumeration;
+import java.util.zip.*;
+
+public class ZipUtil {
+
+ /**
+ * 递归压缩文件夹
+ *
+ * @param srcRootDir 压缩文件夹根目录的子路径
+ * @param file 当前递归压缩的文件或目录对象
+ * @param zos 压缩文件存储对象
+ * @throws Exception
+ */
+ private static void zip(String srcRootDir, File file, ZipOutputStream zos) throws IOException {
+ if (file == null) {
+ return;
+ }
+
+ // 如果是文件,则直接压缩该文件
+ if (file.isFile()) {
+ int count, bufferLen = 1024;
+ byte[] data = new byte[bufferLen];
+
+ // 获取文件相对于压缩文件夹根目录的子路径
+ String subPath = file.getAbsolutePath();
+ int index = subPath.indexOf(srcRootDir);
+ if (index != -1) {
+ subPath = subPath.substring(srcRootDir.length() + File.separator.length());
+ }
+ ZipEntry entry = new ZipEntry(subPath);
+ zos.putNextEntry(entry);
+ BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
+ while ((count = bis.read(data, 0, bufferLen)) != -1) {
+ zos.write(data, 0, count);
+ }
+ bis.close();
+ zos.closeEntry();
+ }
+ // 如果是目录,则压缩整个目录
+ else {
+ // 压缩目录中的文件或子目录
+ File[] childFileList = file.listFiles();
+ for (int n = 0; n < childFileList.length; n++) {
+ childFileList[n].getAbsolutePath().indexOf(file.getAbsolutePath());
+ zip(srcRootDir, childFileList[n], zos);
+ }
+ }
+ }
+
+ /**
+ * 对文件或文件目录进行压缩
+ *
+ * @param srcPath 要压缩的源文件路径。如果是目录,则将递归压缩这个目录及其所有子文件、子目录树。
+ * @param zipPath 压缩文件保存的路径。注意:zipPath不能是srcPath路径下的子文件夹
+ * @param zipFileName 压缩文件名
+ * @throws Exception
+ */
+ public static void zip(String srcPath, String zipPath, String zipFileName) throws IOException, ZipUtilException {
+ if (srcPath.isEmpty() || zipPath.isEmpty() || zipFileName.isEmpty()) {
+ throw new ZipUtilException("Parameter for zip() contains null.");
+ }
+ CheckedOutputStream cos = null;
+ ZipOutputStream zos = null;
+ try {
+ File srcFile = new File(srcPath);
+
+ //判断压缩文件保存的路径是否为源文件路径的子文件夹,如果是,则抛出异常(防止无限递归压缩的发生)
+ if (srcFile.isDirectory() && zipPath.indexOf(srcPath) != -1) {
+ throw new ZipUtilException("Detected loop recursion in directory structure, please check symlink linking to parent directories.");
+ }
+
+ //判断压缩文件保存的路径是否存在,如果不存在,则创建目录
+ File zipDir = new File(zipPath);
+ if (!zipDir.exists() || !zipDir.isDirectory()) {
+ zipDir.mkdirs();
+ }
+
+ //创建压缩文件保存的文件对象
+ String zipFilePath = zipPath + File.separator + zipFileName;
+ File zipFile = new File(zipFilePath);
+ if (zipFile.exists()) {
+ //检测文件是否允许删除,如果不允许删除,将会抛出SecurityException
+ SecurityManager securityManager = new SecurityManager();
+ securityManager.checkDelete(zipFilePath);
+ //删除已存在的目标文件
+ zipFile.delete();
+ }
+
+ cos = new CheckedOutputStream(new FileOutputStream(zipFile), new CRC32());
+ zos = new ZipOutputStream(cos);
+
+ //如果只是压缩一个文件,则需要截取该文件的父目录
+ String srcRootDir = srcPath;
+ if (srcFile.isFile() || true) { // Hack this stupid setting. We want to keep our least parent folder!
+ int index = srcPath.lastIndexOf(File.separator);
+ if (index != -1) {
+ srcRootDir = srcPath.substring(0, index);
+ }
+ }
+ //调用递归压缩方法进行目录或文件压缩
+ zip(srcRootDir, srcFile, zos);
+ zos.flush();
+ } catch (Exception e) {
+ throw e;
+ } finally {
+ try {
+ if (zos != null) {
+ zos.close();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * 解压缩zip包
+ *
+ * @param zipFilePath zip文件的全路径
+ * @param unzipFilePath 解压后的文件保存的路径
+ * @param includeZipFileName 解压后的文件保存的路径是否包含压缩文件的文件名。true-包含;false-不包含
+ */
+ @SuppressWarnings("unchecked")
+ public static void unzip(String zipFilePath, String unzipFilePath, boolean includeZipFileName) throws ZipUtilException, IOException {
+ if (zipFilePath.isEmpty() || unzipFilePath.isEmpty()) {
+ throw new ZipUtilException("Parameter for unzip() contains null.");
+ }
+ File zipFile = new File(zipFilePath);
+ // 如果解压后的文件保存路径包含压缩文件的文件名,则追加该文件名到解压路径
+ if (includeZipFileName) {
+ String fileName = zipFile.getName();
+ if (!fileName.isEmpty()) {
+ fileName = fileName.substring(0, fileName.lastIndexOf("."));
+ }
+ unzipFilePath = unzipFilePath + File.separator + fileName;
+ }
+ // 创建解压缩文件保存的路径
+ File unzipFileDir = new File(unzipFilePath);
+ if (!unzipFileDir.exists() || !unzipFileDir.isDirectory()) {
+ unzipFileDir.mkdirs();
+ }
+
+ // 开始解压
+ ZipEntry entry = null;
+ String entryFilePath = null, entryDirPath = null;
+ File entryFile = null, entryDir = null;
+ int index = 0, count = 0, bufferSize = 1024;
+ byte[] buffer = new byte[bufferSize];
+ BufferedInputStream bis = null;
+ BufferedOutputStream bos = null;
+ ZipFile zip = new ZipFile(zipFile);
+ Enumeration<ZipEntry> entries = (Enumeration<ZipEntry>) zip.entries();
+ // 循环对压缩包里的每一个文件进行解压
+ while (entries.hasMoreElements()) {
+ entry = entries.nextElement();
+ // 构建压缩包中一个文件解压后保存的文件全路径
+ entryFilePath = unzipFilePath + File.separator + entry.getName();
+ // 构建解压后保存的文件夹路径
+ index = entryFilePath.lastIndexOf(File.separator);
+ if (index != -1) {
+ entryDirPath = entryFilePath.substring(0, index);
+ } else {
+ entryDirPath = "";
+ }
+ entryDir = new File(entryDirPath);
+ // 如果文件夹路径不存在,则创建文件夹
+ if (!entryDir.exists() || !entryDir.isDirectory()) {
+ entryDir.mkdirs();
+ }
+
+ // 创建解压文件
+ entryFile = new File(entryFilePath);
+ if (entryFile.exists()) {
+ // 检测文件是否允许删除,如果不允许删除,将会抛出SecurityException
+ SecurityManager securityManager = new SecurityManager();
+ securityManager.checkDelete(entryFilePath);
+ // 删除已存在的目标文件
+ entryFile.delete();
+ }
+
+ // 写入文件
+ bos = new BufferedOutputStream(new FileOutputStream(entryFile));
+ bis = new BufferedInputStream(zip.getInputStream(entry));
+ while ((count = bis.read(buffer, 0, bufferSize)) != -1) {
+ bos.write(buffer, 0, count);
+ }
+ bos.flush();
+ bos.close();
+ }
+ zip.close();
+ }
+
+// public static void main(String[] args)
+// {
+// String zipPath = "d:\\ziptest\\zipPath";
+// String dir = "d:\\ziptest\\rawfiles";
+// String zipFileName = "test.zip";
+// try
+// {
+// zip(dir, zipPath, zipFileName);
+// }
+// catch (Exception e)
+// {
+// e.printStackTrace();
+// }
+//
+// String zipFilePath = "D:\\ziptest\\zipPath\\test.zip";
+// String unzipFilePath = "D:\\ziptest\\zipPath";
+// try
+// {
+// unzip(zipFilePath, unzipFilePath, true);
+// }
+// catch (Exception e)
+// {
+// e.printStackTrace();
+// }
+// }
+} \ No newline at end of file
diff --git a/src/main/java/com/keuin/kbackupfabric/util/ZipUtilException.java b/src/main/java/com/keuin/kbackupfabric/util/ZipUtilException.java
new file mode 100644
index 0000000..85bfec5
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/util/ZipUtilException.java
@@ -0,0 +1,12 @@
+package com.keuin.kbackupfabric.util;
+
+public class ZipUtilException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public ZipUtilException() {
+ }
+
+ public ZipUtilException(String gripe) {
+ super(gripe);
+ }
+}
diff --git a/src/main/java/net/fabricmc/example/ExampleMod.java b/src/main/java/net/fabricmc/example/ExampleMod.java
deleted file mode 100644
index e5ed082..0000000
--- a/src/main/java/net/fabricmc/example/ExampleMod.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package net.fabricmc.example;
-
-import net.fabricmc.api.ModInitializer;
-
-public class ExampleMod implements ModInitializer {
- @Override
- public void onInitialize() {
- // This code runs as soon as Minecraft is in a mod-load-ready state.
- // However, some things (like resources) may still be uninitialized.
- // Proceed with mild caution.
-
- System.out.println("Hello Fabric world!");
- }
-}
diff --git a/src/main/java/net/fabricmc/example/mixin/ExampleMixin.java b/src/main/java/net/fabricmc/example/mixin/ExampleMixin.java
deleted file mode 100644
index 83ee1a8..0000000
--- a/src/main/java/net/fabricmc/example/mixin/ExampleMixin.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package net.fabricmc.example.mixin;
-
-import net.minecraft.client.gui.screen.TitleScreen;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-
-@Mixin(TitleScreen.class)
-public class ExampleMixin {
- @Inject(at = @At("HEAD"), method = "init()V")
- private void init(CallbackInfo info) {
- System.out.println("This line is printed by an example mod mixin!");
- }
-}
diff --git a/src/main/resources/assets/modid/icon.png b/src/main/resources/assets/kbackupfabric/icon.png
index 047b91f..047b91f 100644
--- a/src/main/resources/assets/modid/icon.png
+++ b/src/main/resources/assets/kbackupfabric/icon.png
Binary files differ
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index 197c798..0d471c0 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -1,35 +1,31 @@
{
"schemaVersion": 1,
- "id": "modid",
+ "id": "kbackupfabric",
"version": "${version}",
-
- "name": "Example Mod",
- "description": "This is an example description! Tell everyone what your mod is about!",
+ "name": "KBackup Fabric",
+ "description": "A simple backup mod for Minecraft fabric server",
"authors": [
- "Me!"
+ "Keuin"
],
"contact": {
- "homepage": "https://fabricmc.net/",
- "sources": "https://github.com/FabricMC/fabric-example-mod"
+ "homepage": "https://lab.keuin.cc/kbackup",
+ "sources": "https://github.com/keuin"
},
-
- "license": "CC0-1.0",
- "icon": "assets/modid/icon.png",
-
+ "license": "GPL V3",
+ "icon": "assets/kbackupfabric/icon.png",
"environment": "*",
"entrypoints": {
"main": [
- "net.fabricmc.example.ExampleMod"
+ "com.keuin.kbackupfabric.KBPluginInitializer"
]
},
"mixins": [
- "modid.mixins.json"
],
"depends": {
"fabricloader": ">=0.7.4",
"fabric": "*",
- "minecraft": "1.15.x"
+ "minecraft": "1.14.x"
},
"suggests": {
"flamingo": "*"