Added three new classes, with some methods
classes: Backup, RegionChunkList, NameCollisionException.
This commit is contained in:
parent
0dfa339856
commit
67691336e2
14 changed files with 481 additions and 394 deletions
19
.classpath
19
.classpath
|
@ -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
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
/bin/
|
||||
/target/
|
||||
|
|
6
.project
6
.project
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
4
.settings/org.eclipse.m2e.core.prefs
Normal file
4
.settings/org.eclipse.m2e.core.prefs
Normal file
|
@ -0,0 +1,4 @@
|
|||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
47
pom.xml
Normal file
47
pom.xml
Normal 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>
|
54
src/simpleWarBackup/Backup.java
Normal file
54
src/simpleWarBackup/Backup.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
132
src/simpleWarBackup/RegionFile_Cache.java
Normal file
132
src/simpleWarBackup/RegionFile_Cache.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package simpleWarBackup.exceptions;
|
||||
|
||||
public class NameCollisionException extends Throwable
|
||||
{
|
||||
//TODO serialVersionUID?
|
||||
|
||||
//TODO write the damn class
|
||||
}
|
Loading…
Add table
Reference in a new issue