From 67691336e2939cfb3c5a4a221bd681d525698352 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Mon, 6 Aug 2018 00:50:47 -0700 Subject: [PATCH] Added three new classes, with some methods classes: Backup, RegionChunkList, NameCollisionException. --- .classpath | 19 +- .gitignore | 1 + .project | 6 + .settings/org.eclipse.jdt.core.prefs | 1 + .settings/org.eclipse.m2e.core.prefs | 4 + pom.xml | 47 ++++ src/simpleWarBackup/Backup.java | 54 ++++ src/simpleWarBackup/BackupIO.java | 130 ++++----- ...ccessCache.java => ChunkAccess_Cache.java} | 55 ++-- src/simpleWarBackup/Main.java | 39 ++- src/simpleWarBackup/RegionChunkList.java | 128 +++++++-- src/simpleWarBackup/RegionFileCache.java | 251 ------------------ src/simpleWarBackup/RegionFile_Cache.java | 132 +++++++++ .../exceptions/NameCollisionException.java | 8 + 14 files changed, 481 insertions(+), 394 deletions(-) create mode 100644 .settings/org.eclipse.m2e.core.prefs create mode 100644 pom.xml create mode 100644 src/simpleWarBackup/Backup.java rename src/simpleWarBackup/{ChunkAccessCache.java => ChunkAccess_Cache.java} (56%) delete mode 100644 src/simpleWarBackup/RegionFileCache.java create mode 100644 src/simpleWarBackup/RegionFile_Cache.java create mode 100644 src/simpleWarBackup/exceptions/NameCollisionException.java diff --git a/.classpath b/.classpath index 253a75c..9f0c38b 100644 --- a/.classpath +++ b/.classpath @@ -1,11 +1,20 @@ - - - + - + - + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore index ae3c172..09e3bc9 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /bin/ +/target/ diff --git a/.project b/.project index 51d26e7..75f7e50 100644 --- a/.project +++ b/.project @@ -10,8 +10,14 @@ + + org.eclipse.m2e.core.maven2Builder + + + + org.eclipse.m2e.core.maven2Nature org.eclipse.jdt.core.javanature diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index 3a21537..672496e 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -8,4 +8,5 @@ org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning org.eclipse.jdt.core.compiler.source=1.8 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..21f12e2 --- /dev/null +++ b/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + com.github.TBMCPlugins + SimpleWarBackup + 1.0-SNAPSHOT + + + src + + + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + + + spigot + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + jitpack + https://jitpack.io/ + + + + + + org.spigotmc + spigot + 1.12.2-R0.1-SNAPSHOT + + + com.github.TBMCPlugins.ButtonCore + Towny + master-SNAPSHOT + + + \ No newline at end of file diff --git a/src/simpleWarBackup/Backup.java b/src/simpleWarBackup/Backup.java new file mode 100644 index 0000000..bb136e3 --- /dev/null +++ b/src/simpleWarBackup/Backup.java @@ -0,0 +1,54 @@ +package simpleWarBackup; + +import java.io.File; +import java.util.HashMap; + +import org.bukkit.Bukkit; + +import com.palmergames.bukkit.towny.Towny; +import com.palmergames.bukkit.towny.object.Town; +import com.palmergames.bukkit.towny.object.TownBlock; + +import simpleWarBackup.exceptions.NameCollisionException; + +public class Backup +{ + private final HashMap>> lists + = new HashMap>>(); + + private final String name; + private final File directory; + + /** + * TODO write javadoc (this is the default constructor) + * + * @param name + * @throws NameCollisionException + */ + Backup(String name) throws NameCollisionException + { + checkName(this.name = name); //throws NameCollisionException + + //com.palmergames.bukkit.towny.object.TownyUniverse.getWorld(null).getTowns(); + } + + /** + * TODO write javadoc + * + * @param name + * @throws NameCollisionException + */ + private static void checkName(String name) throws NameCollisionException + { + for (String filename : Main.backupsDir.list()) if (filename == name) + { + throw new NameCollisionException(); + } + } +} diff --git a/src/simpleWarBackup/BackupIO.java b/src/simpleWarBackup/BackupIO.java index 493c589..1367676 100644 --- a/src/simpleWarBackup/BackupIO.java +++ b/src/simpleWarBackup/BackupIO.java @@ -20,22 +20,6 @@ import net.minecraft.server.v1_12_R1.World; public class BackupIO { - private static File pluginDir, backupsDir; - - /** - * Defines two File variables: for the plugin's data folder, and for the "Backup Files" - * folder. Does not create either directory. The directories themselves are created - * as-needed, elsewhere in the plugin, during the process of saving a backup. - * - * @param plugin - */ - static void initialize(JavaPlugin plugin) //called from Main onEnable() - { - pluginDir = plugin.getDataFolder(); - backupsDir = new File(pluginDir, "Backup Files"); - } - - /** * Returns the parent directory containing the "region" destination directory, where * the .mca files are stored. The path proceeds: backups > backup > town > world. @@ -49,13 +33,9 @@ public class BackupIO * @param world * @return */ - private static File getDir(String backup, String town, CraftWorld world) + private static File getDir(String backup, String town, String worldUID) { - return new File( - new File( - new File(backupsDir, backup ) - , town ) - , world.getName() ); + return new File(new File (new File(Main.backupsDir, backup), town), worldUID); } @@ -67,41 +47,31 @@ public class BackupIO * @param chunks * @throws IOException */ - public static void backup( - String backup, String town, CraftWorld world, CraftChunk... chunks) + public static void backup(File worldDir, World world, Chunk... chunks) throws IOException { - final World worldNMS = world.getHandle(); - Chunk chunkNMS; - NBTTagCompound chunkNBT; // to be serialized + NBTTagCompound chunkNBT; // to be serialized + DataOutputStream dataOutput; // serialized to here - final File worldDir = getDir(backup, town, world); - int x, z; - RegionFile regionFile; - DataOutputStream chunkPipe; // serialized to this - - /* chunkPipe is a series of data outputs pointing to a ChunkBuffer. - * ChunkBuffer is an internal class defined within RegionFile that - * extends ByteArrayOutputStream. It holds a byte[] buffer and two - * int values representing the chunk's coordinates. + /* dataOutput is the first of a series of outputs, which proceed in + * the following order: DataOutputStream > BufferedOutputStream > + * DeflaterOutputStream > RegionFile.ChunkBuffer + * + * ChunkBuffer extends ByteArrayOutputStream. It holds a byte[] buffer, + * and two int values which represent the chunk's coordinates. */ - for (CraftChunk chunk : chunks) + for (Chunk chunk : chunks) { - chunkNMS = chunk.getHandle(); - chunkNBT = ChunkNBTTools.save(chunkNMS, worldNMS); + chunkNBT = ChunkNBTTools.save(chunk, world); + dataOutput = RegionFile_Cache.get(worldDir, chunk.locX, chunk.locZ) + .b(chunk.locX & 31, chunk.locZ & 31); - x = chunk.getX(); - z = chunk.getZ(); - regionFile = RegionFileCache.get(worldDir, x, z); - chunkPipe = regionFile.b(x & 31, z & 31); - - /* below serializes chunkNBT, and writes those bytes to chunkPipe, - * which then, on close(), writes the bytes into this RegionFile's - * internal RandomAccessFile, which points to the actual .mca file. + /* a(...) serializes the chunk, and writes the bytes to chunkPipe. + * dataOutput.close() writes the bytes to the actual .mca file. */ - NBTCompressedStreamTools.a(chunkNBT, (DataOutput) chunkPipe); - chunkPipe.close(); + NBTCompressedStreamTools.a(chunkNBT, (DataOutput) dataOutput); + dataOutput.close(); } } @@ -115,45 +85,43 @@ public class BackupIO * * @throws IOException */ - private static void restore( - String backup, String town, CraftWorld world, long... chunks) + private static void restore(File worldDir, World world, long[] chunks) throws IOException { - final File worldDir = getDir(backup, town, world); - - int x, z, i = 0; - - RegionFile source; - RegionFile target; - DataInputStream dataInput; - DataOutputStream dataOutput; - - int length; - byte[] buf; + int x, z, i = 0; + + RegionFile source; + RegionFile target; + DataInputStream dataInput; + DataOutputStream dataOutput; + + int length; + byte[] buf; /* dataInput is the end of a series of inputs, which proceed in * the following order: ByteArrayInputStream > GZIPInputStream > * BufferedInputStream > DataInputStream. * - * dataOutput is a series of outputs pointing to a ChunkBuffer. - * ChunkBuffer is an internal class defined within RegionFile that - * extends ByteArrayOutputStream. It holds a byte[] buffer and two - * int values representing the chunk's coordinates. + * dataOutput is the first of a series of outputs, which proceed in + * the following order: DataOutputStream > BufferedOutputStream > + * DeflaterOutputStream > RegionFile.ChunkBuffer + * + * ChunkBuffer extends ByteArrayOutputStream. It holds a byte[] buffer, + * and two int values which represent the chunk's coordinates. */ for (long chunk : chunks) { x = (int) chunk; z = (int) ( chunk >> 32 ); - source = RegionFileCache.get(worldDir, x, z); - target = RegionFileCache.getFromMinecraft(world, x, z); + source = RegionFile_Cache.get(worldDir, x, z); + target = RegionFile_Cache.getFromMinecraft(world, x, z); dataInput = source.a(x & 31, z & 31); dataOutput = target.b(x & 31, z & 31); - length = RegionFileCache.getChunkLength(source, x, z); + length = ChunkAccess_Cache.getChunkLength(source, x, z); - /* getChunkLength(...) attempts to read from RegionFile source's - * private .mca file. It will return length 0 if there is an error + /* getChunkLength(...) will return length 0 if there is an error * or there is no chunk data. Otherwise, it will return the length * value recorded in the file. */ @@ -169,11 +137,11 @@ public class BackupIO * ((long) Integer.MAX_VALUE) | (((long) Integer.MAX_VALUE) << 32) * * coordinates in long[] 'chunks' are set to this value when they - * have been written successfully back into the game world. This - * value is outside the range of natural chunk coordinate pairs. + * have been written successfully back into the game world, because + * this value is outside the range of natural chunk coordinate pairs. */ } - dataOutput.close(); //writes to the target file + dataOutput.close(); //writes to target's .mca file dataInput.close(); i++; } @@ -193,17 +161,25 @@ public class BackupIO + + + + + + + + public static boolean restoreTest(int x, int z) throws IOException { CraftWorld world = (CraftWorld) Bukkit.getWorld("world"); - File worldDir = new File(backupsDir, "world"); + File worldDir = new File(Main.backupsDir, "world"); File backupDir = new File(worldDir, "test"); File regionDir = new File(backupDir, "region"); File mcaFile = new File(regionDir, "r.1.1.mca"); - RegionFile regionFileSource = RegionFileCache.get(backupDir, x, z); - RegionFile regionFileTarget = RegionFileCache.getFromMinecraft(world, x, z); + RegionFile regionFileSource = RegionFile_Cache.get(backupDir, x, z); + RegionFile regionFileTarget = RegionFile_Cache.getFromMinecraft(world, x, z); DataInputStream dataInput = regionFileSource.a(x & 31, z & 31); DataOutputStream dataOutput = regionFileTarget.b(x & 31, z & 31); diff --git a/src/simpleWarBackup/ChunkAccessCache.java b/src/simpleWarBackup/ChunkAccess_Cache.java similarity index 56% rename from src/simpleWarBackup/ChunkAccessCache.java rename to src/simpleWarBackup/ChunkAccess_Cache.java index b5fec18..335693d 100644 --- a/src/simpleWarBackup/ChunkAccessCache.java +++ b/src/simpleWarBackup/ChunkAccess_Cache.java @@ -11,27 +11,19 @@ import java.util.Map; import net.minecraft.server.v1_12_R1.RegionFile; /** - * --------------------------------------------------------------- - * --------------------------------------------------------------- - * --------------------------------------------------------------- - * --------------------------------------------------------------- - * CURRENTLY A DUPLICATE OF THE SECONDARY CACHE IN RegionFileCache - * --------------------------------------------------------------- - * --------------------------------------------------------------- - * --------------------------------------------------------------- - * --------------------------------------------------------------- + * TODO write javadoc */ -public class ChunkAccessCache +public class ChunkAccess_Cache { - private static Map cache = new HashMap(); + static Map cache = new HashMap(); /** - * TODO + * TODO write javadoc * * @param file * @param regionFile */ - static void cacheChunkAccess(File file, RegionFile regionFile) + static void createAndPut(File file, RegionFile regionFile) { try { @@ -59,20 +51,19 @@ public class ChunkAccessCache Access access = cache.get(regionFile); /* "offset" refers to the location of the chunk's bytes - * within the .mca file - or how many bytes into the file - * to seek when reading - divided by 4096. + * within the .mca file (i.e. how many bytes into the file + * to seek when reading) divided by 4096. */ int offset; - /* The first four bytes at that location, the first four - * bytes of the chunk's data, are an int recording the - * chunk's length in bytes, not including those four. + /* The first four bytes at that location encode an int. This + * int is the chunk's remaining length in bytes, excluding the + * four bytes of the int. * - * The next byte represents compression scheme. This byte, - * like the four previous bytes, is omitted when the game - * reads a chunk's data. + * The fifth byte represents compression scheme. The first five + * bytes are omitted when the game reads a chunk's data. * - * This method returns the length of data actually read. + * The following method returns the length of data actually read. */ if (access != null && (offset = access.offset[x + z * 32]) > 0) { @@ -88,16 +79,15 @@ public class ChunkAccessCache /** - * Gives access to the private fields {@code int[] d}, and {@code RandomAccessFile c}, + * Provides access to the private fields {@code int[] d}, and {@code RandomAccessFile c}, * within a RegionFile object. The names of these fields may change in new releases of * Spigot. TODO explain what both of those fields are */ private static class Access { - static final Field offsetField; - final int[] offset; - - static final Field bytesField; + static final Field offsetField; + static final Field bytesField; + final int[] offset; final RandomAccessFile bytes; static @@ -106,18 +96,13 @@ public class ChunkAccessCache Field tmp2 = null; try { - tmp1 = RegionFile.class.getDeclaredField("d"); - tmp1.setAccessible(true); - + tmp1 = RegionFile.class.getDeclaredField("d"); tmp2 = RegionFile.class.getDeclaredField("c"); + tmp1.setAccessible(true); tmp2.setAccessible(true); } catch (NoSuchFieldException | SecurityException e) { - /* TODO take more drastic action? Either - * Field missing would mean that Minecraft - * has refactored, breaking the whole plugin - */ e.printStackTrace(); } offsetField = tmp1; @@ -140,7 +125,7 @@ public class ChunkAccessCache IllegalAccessException, IllegalArgumentException { - offset = (int[]) offsetField.get(regionFile); + offset = (int[]) offsetField.get(regionFile); bytes = (RandomAccessFile) bytesField .get(regionFile); } } diff --git a/src/simpleWarBackup/Main.java b/src/simpleWarBackup/Main.java index 176752e..3952536 100644 --- a/src/simpleWarBackup/Main.java +++ b/src/simpleWarBackup/Main.java @@ -18,14 +18,49 @@ import org.bukkit.plugin.java.JavaPlugin; public class Main extends JavaPlugin implements Listener { + // --------------------- STATIC --------------------- + + static File pluginDir; + static File backupsDir; + + /** + * Defines two File variables: for the plugin's data folder, and for the "Backup Files" + * folder. Does not create either directory. The directories themselves are created + * as-needed, elsewhere in the plugin, during the process of saving a backup. + * + * @param plugin + */ + static void initialize(JavaPlugin plugin) + { + pluginDir = plugin.getDataFolder(); + backupsDir = new File(pluginDir, "Backup Files"); + } + + + // -------------------- INSTANCE -------------------- + + public void onEnable() { + initialize(this); + + //TEST getCommand("testBackupChunk").setExecutor(this); getCommand("testRestoreChunk").setExecutor(this); - - BackupIO.initialize(this); } + + + + + + + + + + + + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (!(sender instanceof Player)) return false; diff --git a/src/simpleWarBackup/RegionChunkList.java b/src/simpleWarBackup/RegionChunkList.java index 6efd164..5820541 100644 --- a/src/simpleWarBackup/RegionChunkList.java +++ b/src/simpleWarBackup/RegionChunkList.java @@ -4,53 +4,90 @@ import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.util.BitSet; -import java.util.HashMap; +/** + * TODO write javadoc + */ public class RegionChunkList { - private final File file; + /** + * TODO write javadoc + */ private final RandomAccessFile readwrite; - private final BitSet chunks = new BitSet(1024); - private final HashMap>> lists = - - new HashMap>>(); + /** + * TODO write javadoc + */ + final BitSet chunks = new BitSet(1024); + + /** + * TODO write javadoc + */ + final long regionCoordinates; + /** - * TODO + * TODO write javadoc * * @param file * @throws IOException */ - RegionChunkList(File file, String backup, String town, String world) throws IOException + RegionChunkList(File file) throws IOException { - readwrite = new RandomAccessFile((this.file = file), "rw"); + readwrite = new RandomAccessFile(file, "rw"); - byte[] bytes = new byte[128]; - if (readwrite.length() >= 128) - { + // Write chunk list to BitSet: + { + byte[] bytes = new byte[128]; readwrite.readFully(bytes); - + + /* Each bit represents one of the region's 1024 chunks (32x32). Chunks are + * listed in ascending x,z order: (0,0),(1,0)...(31,0),(0,1),(1,1)......(31,31). + * + * Bits in each byte are read from smallest to largest position: 1,2,4...128. + * The values are written to a BitSet, which represents the region in runtime. + * + * 1 = true, 0 = false. + */ int i = 0, j, k; for (byte b : bytes) for (j = 0, k = 1; j < 8; i++, j++, k <<= 1) if ((b & k) == k) chunks.set(i); } - else readwrite.write(bytes); - lists.get(backup) - .get(town) - .put(world, this); + // Get region coordinate from filename: + { + String[] name = file.getName().split("."); + int x = Integer.parseInt(name[1]); + int z = Integer.parseInt(name[2]); + + regionCoordinates = (long) x | ((long) z << 32); + } } /** - * TODO + * TODO write javadoc + * + * @param directory + * @param regionCoordinates + */ + RegionChunkList(File directory, long regionCoordinates) throws IOException + { + this.regionCoordinates = regionCoordinates; + + int x = (int) regionCoordinates; + int z = (int) (regionCoordinates >>> 32); + String filename = "r." + x + "." + z + ".chunklist"; + + File file = new File(directory, filename); + readwrite = new RandomAccessFile(file, "rw"); + } + + + /** + * TODO write javadoc (and add comment to explain numbers) * * @param x * @param z @@ -65,7 +102,7 @@ public class RegionChunkList /** - * TODO + * TODO write javadoc (and add comment to explain numbers) * * @param x * @param z @@ -79,7 +116,7 @@ public class RegionChunkList /** - * TODO + * TODO write javadoc (and add comment to explain numbers) * * @param x * @param z @@ -90,4 +127,47 @@ public class RegionChunkList z &= 31; chunks.clear(x + z * 32); } + + + /** + * TODO write javadoc + * + * @throws IOException + */ + void saveToFile() throws IOException + { + int i = 0; + byte bytes[] = new byte[128]; + for (byte b : bytes) + { + if (chunks.get(i++)) b = (byte) (b | (byte) 1 ); + if (chunks.get(i++)) b = (byte) (b | (byte) 2 ); + if (chunks.get(i++)) b = (byte) (b | (byte) 4 ); + if (chunks.get(i++)) b = (byte) (b | (byte) 8 ); + if (chunks.get(i++)) b = (byte) (b | (byte) 16 ); + if (chunks.get(i++)) b = (byte) (b | (byte) 32 ); + if (chunks.get(i++)) b = (byte) (b | (byte) 64 ); + if (chunks.get(i++)) b = (byte) (b | (byte) 128); + } + readwrite.seek(0); + readwrite.write(bytes); + } + + + /** + * Closes the RandomAccessFile associated with this RegionChunkList.

+ * + * When you are done with this RegionChunkList, you should invoke this method. + */ + void close() + { + try + { + readwrite.close(); + } + catch (IOException e) + { + e.printStackTrace(); + } + } } diff --git a/src/simpleWarBackup/RegionFileCache.java b/src/simpleWarBackup/RegionFileCache.java deleted file mode 100644 index e707aaf..0000000 --- a/src/simpleWarBackup/RegionFileCache.java +++ /dev/null @@ -1,251 +0,0 @@ -package simpleWarBackup; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -import org.bukkit.Bukkit; -import org.bukkit.craftbukkit.v1_12_R1.CraftWorld; - -import net.minecraft.server.v1_12_R1.RegionFile; -import net.minecraft.server.v1_12_R1.World; - -/** - * TODO write this - */ -public final class RegionFileCache -{ - private static final int cap = 32; //capacity - how many regionFiles to keep cached - private static final Map cache = new HashMap(); - - /** - * Returns name of the region file that would contain the given chunk coordinates. - * Name follows the standard minecraft format: "r.[region x].[region z].mca". - * Region coordinate = chunk coordinate >> 5. - * - * @param x global chunk coordinate x - * @param z global chunk coordinate z - * @return - */ - public static String path(int x, int z) - { - return "r." + (x >> 5) + "." + (z >> 5) + ".mca"; - } - - /** - * Copied from net.minecraft.server.RefionFileCache.a(File, int, int).

- * - * Returns any cached RegionFile mapped to the given File. Not finding one, creates - * and caches, then returns, a new RegionFile. Before caching a new RegionFile, checks - * that the cache contains fewer than 16 RegionFiles, and, finding 16 or more, calls - * {@link #clearCache() RegionFileCache.clearCache()}. - * - * @param backupDir parent folder containing the "region" folder - * @param x global chunk coordinate x - * @param z global chunk coordinate z - * @return - */ - public static synchronized RegionFile get(File backupDir, int x, int z) - { - File regionDir = new File(backupDir, "region"); - File mcaFile = new File(regionDir, path(x,z)); - - RegionFile regionFile = cache.get(mcaFile); - - if (regionFile != null) - return regionFile; - if (!regionDir.exists()) - regionDir.mkdirs(); - if (cache.size() >= cap) - clearCache(); - - regionFile = new RegionFile(mcaFile); - RegionFileCache.cache.put(mcaFile, regionFile); - RegionFileCache.cacheChunkAccess(mcaFile, regionFile); - return regionFile; - } - - - /** - * Copied from net.minecraft.server.RegionFileCache.a(). Iterates through all cached - * RegionFiles, closing each one's private RandomAccessFile, then clears the cache. - */ - public static synchronized void clearCache() - { - Iterator iterator = RegionFileCache.cache.values().iterator(); - - /* regionFile.c() closes the private RandomAccessFile - * inside the regionFile object, and that's all it does. - * The entire method: if (file != null) file.close(). - */ - while (iterator.hasNext()) - { - RegionFile regionFile = iterator.next(); - try { if (regionFile != null) regionFile.c(); } - catch (IOException e) { e.printStackTrace(); } - } - RegionFileCache.cache.clear(); - RegionFileCache.cacheChunkAccess.clear(); - } - - - /** - * TODO - * - * @param world - * @param x - * @param z - * @return - */ - public static RegionFile getFromMinecraft(CraftWorld world, int x, int z) - { - /* root directory of the world, the directory - * sharing the world's name and holding its - * data. 'region' folder is found in here. - */ - File dir = world.getHandle().getDataManager().getDirectory(); - - return net.minecraft.server.v1_12_R1.RegionFileCache.a(dir, x, z); - } - - - - /*=================================Chunk Access=================================*/ - - /* Secondary cache, for holding references to the private fields "c" and "d" in each - * RegionFile cached above. TODO write more - */ - - private static Map cacheChunkAccess = new HashMap(); - - /** - * TODO - * - * @param file - * @param regionFile - */ - private static void cacheChunkAccess(File file, RegionFile regionFile) - { - try - { - cacheChunkAccess.put(regionFile, new ChunkAccess(regionFile)); - } - catch (FileNotFoundException | - IllegalAccessException | - IllegalArgumentException e) - { - e.printStackTrace();//TODO - } - } - - - /** - * TODO - * - * @param regionFile - * @param x - * @param z - * @return - */ - public static int getChunkLength(RegionFile regionFile, int x, int z) - { - ChunkAccess access = cacheChunkAccess.get(regionFile); - - /* "offset" refers to the location of the chunk's bytes - * within the .mca file - or how many bytes into the file - * to seek when reading - divided by 4096. - */ - int offset; - - /* The first four bytes at that location, the first four - * bytes of the chunk's data, are an int recording the - * chunk's length in bytes, not including those four. - * - * The next byte represents compression scheme. This byte, - * like the four previous bytes, is omitted when the game - * reads a chunk's data. - * - * This method returns the length of data actually read. - */ - if (access != null && (offset = access.offset[x + z * 32]) > 0) - { - try - { - access.bytes.seek(offset * 4096); - return access.bytes.readInt() - 1; - } - catch (IOException e) { e.printStackTrace(); } - } - return 0; - } - - - - /** - * Gives access to the private fields {@code int[] d}, and {@code RandomAccessFile c}, - * within a RegionFile object. The names of these fields may change in new releases of - * Spigot. TODO explain what both of those fields are - */ - private static class ChunkAccess - { - static final Field offsetField; - final int[] offset; - - static final Field bytesField; - final RandomAccessFile bytes; - - static - { - Field tmp1 = null; - Field tmp2 = null; - try - { - tmp1 = RegionFile.class.getDeclaredField("d"); - tmp1.setAccessible(true); - - tmp2 = RegionFile.class.getDeclaredField("c"); - tmp2.setAccessible(true); - } - catch (NoSuchFieldException | SecurityException e) - { - /* TODO take more drastic action? Either - * Field missing would mean that Minecraft - * has refactored, breaking the whole plugin - */ - e.printStackTrace(); - } - offsetField = tmp1; - bytesField = tmp2; - } - - - /** - * Creates a new DataAccess object, which holds references to the private variables - * {@code int[] d} and {@code RandomAccessFile c} within the given RegionFile. Variables - * are accessed by reflection. TODO write more - * - * @param regionFile the RegionFile whose chunks are to be accessed - * - * @throws FileNotFoundException - * @throws IllegalAccessException - * @throws IllegalArgumentException - */ - ChunkAccess(RegionFile regionFile) - throws FileNotFoundException, - IllegalAccessException, - IllegalArgumentException - { - this.offset = (int[]) offsetField.get(regionFile); - this.bytes = (RandomAccessFile) bytesField .get(regionFile); - } - } - - -} diff --git a/src/simpleWarBackup/RegionFile_Cache.java b/src/simpleWarBackup/RegionFile_Cache.java new file mode 100644 index 0000000..dd8c780 --- /dev/null +++ b/src/simpleWarBackup/RegionFile_Cache.java @@ -0,0 +1,132 @@ +package simpleWarBackup; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.bukkit.craftbukkit.v1_12_R1.CraftWorld; + +import net.minecraft.server.v1_12_R1.RegionFile; +import net.minecraft.server.v1_12_R1.World; + +/** + * TODO write this + */ +public final class RegionFile_Cache +{ + private static final int cap = 32; //capacity - how many regionFiles to keep cached + private static final Map cache = new HashMap(); + + /** + * Returns name of the region file that would contain the given chunk coordinates. + * Name follows the standard minecraft format: "r.[region x].[region z].mca". + * Region coordinate = chunk coordinate >> 5. + * + * @param x global chunk coordinate x + * @param z global chunk coordinate z + * @return + */ + public static String path(int x, int z) + { + return "r." + (x >> 5) + "." + (z >> 5) + ".mca"; + } + + /** + * Method copied from net.minecraft.server.RefionFileCache.a(File, int, int).

+ * + * If there is a cached RegionFile for the given file and coordinates, returns this. + * Otherwise, creates, caches, then returns a new RegionFile for the given file and + * coordinates. + * + * @param backupDir parent folder containing the "region" folder + * @param x global chunk coordinate x + * @param z global chunk coordinate z + * @return + */ + public static synchronized RegionFile get(File backupDir, int x, int z) + { + File regionDir = new File(backupDir, "region"); + File mcaFile = new File(regionDir, path(x,z)); + + RegionFile regionFile = cache.get(mcaFile); + + if (regionFile != null) + return regionFile; + if (!regionDir.exists()) + regionDir.mkdirs(); + if (cache.size() >= cap) + clearCache(); + + regionFile = new RegionFile(mcaFile); + RegionFile_Cache.cache.put(mcaFile, regionFile); + ChunkAccess_Cache.createAndPut(mcaFile, regionFile); + return regionFile; + } + + + /** + * Method Copied from net.minecraft.server.RegionFileCache.a().

+ * + * Called by {@link #get(File, int, int) RegionFile_Cache.get(File, int, int)}.

+ * + * Removes all entries from the cache. + */ + private static synchronized void clearCache() + { + Iterator iterator = RegionFile_Cache.cache.values().iterator(); + + /* regionFile.c() closes the private RandomAccessFile + * inside the regionFile object, and that's all it does. + * The entire method: if (file != null) file.close(). + */ + while (iterator.hasNext()) + { + RegionFile regionFile = iterator.next(); + try { if (regionFile != null) regionFile.c(); } + catch (IOException e) { e.printStackTrace(); } + } + RegionFile_Cache.cache.clear(); + ChunkAccess_Cache.cache.clear(); + } + + + /** + * TODO + * + * @param world + * @param x + * @param z + * @return + */ + public static RegionFile getFromMinecraft(CraftWorld world, int x, int z) + { + /* root directory of the world, the directory + * sharing the world's name and holding its + * data. 'region' folder is found in here. + */ + File dir = world.getHandle().getDataManager().getDirectory(); + + return net.minecraft.server.v1_12_R1.RegionFileCache.a(dir, x, z); + } + + /** + * TODO + * + * @param world + * @param x + * @param z + * @return + */ + public static RegionFile getFromMinecraft(World world, int x, int z) + { + /* root directory of the world, the directory + * sharing the world's name and holding its + * data. 'region' folder is found in here. + */ + File dir = world.getDataManager().getDirectory(); + + return net.minecraft.server.v1_12_R1.RegionFileCache.a(dir, x, z); + } +} diff --git a/src/simpleWarBackup/exceptions/NameCollisionException.java b/src/simpleWarBackup/exceptions/NameCollisionException.java new file mode 100644 index 0000000..26d7f3d --- /dev/null +++ b/src/simpleWarBackup/exceptions/NameCollisionException.java @@ -0,0 +1,8 @@ +package simpleWarBackup.exceptions; + +public class NameCollisionException extends Throwable +{ + //TODO serialVersionUID? + + //TODO write the damn class +}