Added three new classes, with some methods

classes: Backup, RegionChunkList, NameCollisionException.
This commit is contained in:
BuildTools 2018-08-06 00:50:47 -07:00
parent 0dfa339856
commit 67691336e2
14 changed files with 481 additions and 394 deletions

View file

@ -1,11 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="lib" path="/Users/kevinmathewson/Local_Test_Server_1.12.2/spigot-1.12.2.jar">
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="javadoc_location" value="jar:file:/Users/kevinmathewson/Local_Test_Server_1.12.2/craftbukkit-1.12.2.jar!/"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="bin"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/classes" path="src">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

1
.gitignore vendored
View file

@ -1 +1,2 @@
/bin/
/target/

View file

@ -10,8 +10,14 @@
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View file

@ -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

View file

@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

47
pom.xml Normal file
View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.TBMCPlugins</groupId>
<artifactId>SimpleWarBackup</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spigot</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository>
<id>jitpack</id>
<url>https://jitpack.io/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>1.12.2-R0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.github.TBMCPlugins.ButtonCore</groupId>
<artifactId>Towny</artifactId>
<version>master-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

View file

@ -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<String, //town
HashMap<String, //world
HashMap<Long, //region
RegionChunkList>>> lists
= new HashMap<String,
HashMap<String,
HashMap<Long,
RegionChunkList>>>();
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();
}
}
}

View file

@ -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);

View file

@ -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<RegionFile, Access> cache = new HashMap<RegionFile, Access>();
static Map<RegionFile, Access> cache = new HashMap<RegionFile, Access>();
/**
* 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);
}
}

View file

@ -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;

View file

@ -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<String, HashMap
<String, HashMap
<String, RegionChunkList>>> lists =
new HashMap<String, HashMap
<String, HashMap
<String, RegionChunkList>>>();
/**
* 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.<p>
*
* When you are done with this RegionChunkList, you should invoke this method.
*/
void close()
{
try
{
readwrite.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}

View file

@ -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<File, RegionFile> cache = new HashMap<File, RegionFile>();
/**
* 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).<p>
*
* 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<RegionFile> 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<RegionFile,
ChunkAccess> cacheChunkAccess = new HashMap<RegionFile,
ChunkAccess>();
/**
* 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);
}
}
}

View file

@ -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<File, RegionFile> cache = new HashMap<File, RegionFile>();
/**
* 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).<p>
*
* 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().<p>
*
* Called by {@link #get(File, int, int) RegionFile_Cache.get(File, int, int)}.<p>
*
* Removes all entries from the cache.
*/
private static synchronized void clearCache()
{
Iterator<RegionFile> 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);
}
}

View file

@ -0,0 +1,8 @@
package simpleWarBackup.exceptions;
public class NameCollisionException extends Throwable
{
//TODO serialVersionUID?
//TODO write the damn class
}