From 28ebd3ae91dc6087a9f9acd93f80dd92be2d64aa Mon Sep 17 00:00:00 2001 From: Keuin Date: Thu, 23 Apr 2020 00:16:13 +0800 Subject: Finish backup. TODO: - restore --- .../com/keuin/kbackupfabric/KBCommandHandler.java | 184 +++++++++++++++++ .../com/keuin/kbackupfabric/KBCommandRegister.java | 86 ++++++++ src/main/java/com/keuin/kbackupfabric/KBMain.java | 24 +++ .../keuin/kbackupfabric/KBPluginInitializer.java | 12 ++ .../keuin/kbackupfabric/util/ReflectionUtils.java | 59 ++++++ .../com/keuin/kbackupfabric/util/WorldUtil.java | 15 ++ .../java/com/keuin/kbackupfabric/util/ZipUtil.java | 221 +++++++++++++++++++++ .../keuin/kbackupfabric/util/ZipUtilException.java | 12 ++ src/main/java/net/fabricmc/example/ExampleMod.java | 14 -- .../net/fabricmc/example/mixin/ExampleMixin.java | 15 -- 10 files changed, 613 insertions(+), 29 deletions(-) create mode 100644 src/main/java/com/keuin/kbackupfabric/KBCommandHandler.java create mode 100644 src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java create mode 100644 src/main/java/com/keuin/kbackupfabric/KBMain.java create mode 100644 src/main/java/com/keuin/kbackupfabric/KBPluginInitializer.java create mode 100644 src/main/java/com/keuin/kbackupfabric/util/ReflectionUtils.java create mode 100644 src/main/java/com/keuin/kbackupfabric/util/WorldUtil.java create mode 100644 src/main/java/com/keuin/kbackupfabric/util/ZipUtil.java create mode 100644 src/main/java/com/keuin/kbackupfabric/util/ZipUtilException.java delete mode 100644 src/main/java/net/fabricmc/example/ExampleMod.java delete mode 100644 src/main/java/net/fabricmc/example/mixin/ExampleMixin.java (limited to 'src/main/java') 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 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 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 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 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 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 context, String backupName) { + String destPathFolderName = ""; + try { + message(context, String.format("Making backup %s ...", backupName), true); + Map 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 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 message(CommandContext context, String messageText) { + return message(context, messageText, false); + } + + private static CommandContext message(CommandContext 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 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 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 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 bio = new ArrayList(); +// 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 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 entries = (Enumeration) 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!"); - } -} -- cgit v1.2.3