Compare commits
11 commits
Author | SHA1 | Date | |
---|---|---|---|
|
f14eb4693a | ||
|
f69fb22f88 | ||
|
7a5668b1d7 | ||
|
55ea36c165 | ||
|
1ce45d6635 | ||
|
67691336e2 | ||
|
0dfa339856 | ||
|
d40d7a5a6f | ||
|
faf8bc98ff | ||
|
748ed70f50 | ||
|
fda2afbd49 |
16 changed files with 960 additions and 185 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>
|
186
src/simpleWarBackup/Backup.java
Normal file
186
src/simpleWarBackup/Backup.java
Normal file
|
@ -0,0 +1,186 @@
|
|||
package simpleWarBackup;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
|
||||
import com.palmergames.bukkit.towny.Towny;
|
||||
import com.palmergames.bukkit.towny.object.Town;
|
||||
import com.palmergames.bukkit.towny.object.TownBlock;
|
||||
import com.palmergames.bukkit.towny.object.TownyUniverse;
|
||||
|
||||
import simpleWarBackup.exceptions.NameCollisionException;
|
||||
|
||||
/**
|
||||
* TODO write javadoc
|
||||
*/
|
||||
public class Backup
|
||||
{
|
||||
/**
|
||||
* TODO write javadoc
|
||||
*/
|
||||
private final HashMap<Integer, //town
|
||||
HashMap<String, //world
|
||||
HashMap<Coord, //region
|
||||
RegionChunkList>>> tree
|
||||
|
||||
= new HashMap<Integer,
|
||||
HashMap<String,
|
||||
HashMap<Coord,
|
||||
RegionChunkList>>>();
|
||||
|
||||
/**
|
||||
* TODO write javadoc
|
||||
*/
|
||||
private final File folder;
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* TODO write javadoc (this is the default constructor)
|
||||
*
|
||||
* @param name
|
||||
* @throws IOException
|
||||
* @throws NameCollisionException
|
||||
*/
|
||||
Backup(String name, Main plugin) throws IOException, NameCollisionException
|
||||
{
|
||||
checkName(this.name = name); //throws NameCollisionException
|
||||
|
||||
folder = new File(plugin.getDataFolder(), name);
|
||||
|
||||
Server server = plugin.getServer();
|
||||
List<World> worlds = server.getWorlds();
|
||||
|
||||
maketree(TownyUniverse.getDataSource().getTowns(), worlds, server);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TODO write javadoc
|
||||
*
|
||||
* @param name
|
||||
* @param plugin
|
||||
* @throws IOException
|
||||
* @throws NameCollisionException
|
||||
*/
|
||||
Backup(String name, Main plugin, String... townlist)
|
||||
throws IOException, NameCollisionException
|
||||
{
|
||||
checkName(this.name = name); //throws NameCollisionException
|
||||
|
||||
folder = new File(plugin.getDataFolder(), name);
|
||||
|
||||
Server server = plugin.getServer();
|
||||
List<World> worlds = server.getWorlds();
|
||||
|
||||
maketree(TownyUniverse.getDataSource().getTowns(townlist), worlds, server);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TODO write javadoc
|
||||
*
|
||||
* @param towns
|
||||
* @param worlds
|
||||
* @param server
|
||||
* @param folder
|
||||
* @throws IOException
|
||||
*/
|
||||
private void maketree(List<Town> towns, List<World> worlds, Server server) throws IOException
|
||||
{
|
||||
HashMap<String, HashMap<Coord, RegionChunkList>> townbranch;
|
||||
HashMap<Coord, RegionChunkList> worldbranch;
|
||||
|
||||
RegionChunkList chunklist;
|
||||
Coord coord;
|
||||
int x,z;
|
||||
|
||||
String worldname;
|
||||
|
||||
for (Town town : towns)
|
||||
{
|
||||
townbranch = new HashMap<String, HashMap<Coord, RegionChunkList>>();
|
||||
|
||||
//make world sub-branches
|
||||
for (World world : worlds)
|
||||
townbranch.put(world.getName(),
|
||||
new HashMap<Coord, RegionChunkList>());
|
||||
|
||||
//populate world sub-branches
|
||||
for (TownBlock block : town.getTownBlocks())
|
||||
{
|
||||
worldname = block.getWorld().getName();
|
||||
worldbranch = townbranch.get(worldname);
|
||||
|
||||
//convert block to chunk
|
||||
x = block.getX() >> 4;
|
||||
z = block.getZ() >> 4;
|
||||
|
||||
coord = new Coord(x >> 5, z >> 5);
|
||||
chunklist = worldbranch.get(coord);
|
||||
|
||||
//create chunk list if nonexistent
|
||||
if (chunklist == null)
|
||||
{
|
||||
File townDir = new File(folder, town.getUID().toString());
|
||||
File worldDir = new File(townDir, worldname);
|
||||
File chunksDir = new File(worldDir, "region chunk lists");
|
||||
File chlistDir = new File(chunksDir, "r."+coord.x+"."+coord.z+".chunklist");
|
||||
|
||||
chunklist = new RegionChunkList(chlistDir);
|
||||
worldbranch.put(coord, chunklist);
|
||||
}
|
||||
|
||||
|
||||
chunklist.storeChunk(x, z);
|
||||
}
|
||||
|
||||
tree.put(town.getUID(), townbranch);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* TODO write javadoc
|
||||
*
|
||||
* @param name
|
||||
* @throws NameCollisionException
|
||||
*/
|
||||
private static void checkName(String name) throws NameCollisionException
|
||||
{
|
||||
for (String filename : Main.backupsDir.list()) if (filename.equals(name))
|
||||
{
|
||||
throw new NameCollisionException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Holds region coordinates. A region is 32x32 chunks. A chunk is 16x16 blocks.<p>
|
||||
*
|
||||
* The chunk coordinates of a block are (block x >> 4, block z >> 4).<p>
|
||||
*
|
||||
* The region coordinates of a chunk are (chunk x >> 5, chunk z >> 5).<p>
|
||||
*/
|
||||
public static class Coord
|
||||
{
|
||||
final int x, z;
|
||||
|
||||
/**
|
||||
* @param x region x coordinate
|
||||
* @param z region z coordinate
|
||||
*/
|
||||
public Coord(int x, int z)
|
||||
{
|
||||
this.x = x;
|
||||
this.z = z;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,113 +1,161 @@
|
|||
package simpleWarBackup;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_12_R1.CraftChunk;
|
||||
import org.bukkit.craftbukkit.v1_12_R1.CraftWorld;
|
||||
|
||||
import net.minecraft.server.v1_12_R1.Chunk;
|
||||
import net.minecraft.server.v1_12_R1.NBTCompressedStreamTools;
|
||||
import net.minecraft.server.v1_12_R1.NBTTagCompound;
|
||||
import net.minecraft.server.v1_12_R1.RegionFile;
|
||||
import net.minecraft.server.v1_12_R1.World;
|
||||
|
||||
/**
|
||||
* TODO write javadoc
|
||||
*/
|
||||
public class BackupIO
|
||||
{
|
||||
private static File dataFolder, backupsFolder;
|
||||
|
||||
public static final HashMap<World, HashMap<String, File>> backups
|
||||
= new HashMap<World, HashMap<String, File>>();
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* Returns the parent directory containing the "region" destination directory, where
|
||||
* the .mca files are stored. The path proceeds: backups > backup > town > world.
|
||||
* This method returns the world directory.<p>
|
||||
*
|
||||
* @param dataFolder
|
||||
* @param backupsFolder
|
||||
*/
|
||||
public static void initialize(File dataFolder, File backupsFolder)
|
||||
{
|
||||
BackupIO.dataFolder = dataFolder;
|
||||
BackupIO.backupsFolder = backupsFolder;
|
||||
BackupIO.dataFolder.mkdir();
|
||||
BackupIO.backupsFolder.mkdir();
|
||||
|
||||
initializeBackupsMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
public static void initializeBackupsMap()
|
||||
{
|
||||
HashMap<String, File> map;
|
||||
File worldFolder;
|
||||
File backupFolder;
|
||||
|
||||
/* Directories that do not exist will be made when
|
||||
* the plugin attempts to save a backup for the
|
||||
* given world.
|
||||
*/
|
||||
for (World world : Bukkit.getWorlds())
|
||||
{
|
||||
backups.put(world, map = new HashMap<String, File>());
|
||||
worldFolder = new File(backupsFolder, world.getName());
|
||||
if (worldFolder.exists() && worldFolder.list() != null)
|
||||
{
|
||||
for (String backupName : worldFolder.list())
|
||||
{
|
||||
backupFolder = new File(worldFolder, backupName);
|
||||
map.put(backupName, backupFolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* Note: this method does not create any of these directories. All directories are
|
||||
* created as-needed, elsewhere in the plugin, during the process of saving a backup.
|
||||
*
|
||||
* @param backupFolder
|
||||
* @param chunkX
|
||||
* @param chunkZ
|
||||
* @param backup TODO
|
||||
* @param town
|
||||
* @param world
|
||||
* @return
|
||||
*/
|
||||
public static DataOutputStream chunkDataOutputStream(File backupFolder, int chunkX, int chunkZ)
|
||||
private static File getDir(String backup, String town, String world)
|
||||
{
|
||||
return RegionFileCache.get(backupFolder, chunkX, chunkZ) // get(...) returns a RegionFile
|
||||
.b(chunkX & 31, chunkZ & 31); // b(...) returns a DataOutputStream
|
||||
return new File(new File (new File(Main.backupsDir, backup), town), world);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @param backup
|
||||
* @param world
|
||||
* @param backup
|
||||
* @param chunks
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void backupChunks(String backup, World world, Chunk... chunks) throws IOException
|
||||
public static void backup(File worldDir, World world, Chunk... chunks)
|
||||
throws IOException
|
||||
{
|
||||
System.out.println("backupChunks has been called");//TODO
|
||||
final File folder = backups.get(world).get(backup);
|
||||
final net.minecraft.server.v1_12_R1.World worldNMS
|
||||
= ((CraftWorld) world).getHandle();
|
||||
|
||||
NBTTagCompound chunkNBT;
|
||||
DataOutputStream output;
|
||||
NBTTagCompound chunkNBT; // to be serialized
|
||||
DataOutputStream dataOutput; // serialized to here
|
||||
|
||||
/* 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 (Chunk chunk : chunks)
|
||||
{
|
||||
chunkNBT = ChunkNBTWriter.write(((CraftChunk) chunk).getHandle(), worldNMS);
|
||||
output = BackupIO.chunkDataOutputStream(folder, chunk.getX(), chunk.getZ());
|
||||
chunkNBT = ChunkNBTTools.save(chunk, world);
|
||||
dataOutput = RegionFile_Cache.get(worldDir, chunk.locX, chunk.locZ)
|
||||
.b(chunk.locX & 31, chunk.locZ & 31);
|
||||
|
||||
NBTCompressedStreamTools.a(chunkNBT, (DataOutput) output);
|
||||
output.close();
|
||||
/* a(...) serializes the chunk, and writes the bytes to chunkPipe.
|
||||
* dataOutput.close() writes the bytes to the actual .mca file.
|
||||
*/
|
||||
NBTCompressedStreamTools.a(chunkNBT, (DataOutput) dataOutput);
|
||||
dataOutput.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @param world
|
||||
* @param backup
|
||||
* @param chunks (chunk coords, larger half is z)
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private static void restore(File worldDir, World world, long[] chunks)
|
||||
throws IOException
|
||||
{
|
||||
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 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 = 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 = ChunkAccess_Cache.getChunkLength(source, x, z);
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
if (length > 0)
|
||||
{
|
||||
buf = new byte[length];
|
||||
dataInput.readFully(buf);
|
||||
dataOutput.write(buf);
|
||||
|
||||
chunks[i] = 9223372034707292159L;
|
||||
|
||||
/* the value 9223372034707292159 is equivalent to the expression:
|
||||
* ((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, because
|
||||
* this value is outside the range of natural chunk coordinate pairs.
|
||||
*/
|
||||
}
|
||||
dataOutput.close(); //writes to target's .mca file
|
||||
dataInput.close();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @param world
|
||||
* @param backup
|
||||
*/
|
||||
private static void restoreAll(CraftWorld world, String backup)
|
||||
{
|
||||
//world.isChunkLoaded(x,z);
|
||||
}
|
||||
}
|
||||
|
|
132
src/simpleWarBackup/ChunkAccess_Cache.java
Normal file
132
src/simpleWarBackup/ChunkAccess_Cache.java
Normal file
|
@ -0,0 +1,132 @@
|
|||
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.Map;
|
||||
|
||||
import net.minecraft.server.v1_12_R1.RegionFile;
|
||||
|
||||
/**
|
||||
* TODO write javadoc (mention that everything in this class is package private)
|
||||
*/
|
||||
public class ChunkAccess_Cache
|
||||
{
|
||||
static Map<RegionFile, Access> cache = new HashMap<RegionFile, Access>();
|
||||
|
||||
/**
|
||||
* TODO write javadoc
|
||||
*
|
||||
* @param file
|
||||
* @param regionFile
|
||||
*/
|
||||
static void createAndPut(File file, RegionFile regionFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
cache.put(regionFile, new Access(regionFile));
|
||||
}
|
||||
catch (FileNotFoundException |
|
||||
IllegalAccessException |
|
||||
IllegalArgumentException e)
|
||||
{
|
||||
e.printStackTrace();//TODO
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @param regionFile
|
||||
* @param x
|
||||
* @param z
|
||||
* @return
|
||||
*/
|
||||
static int getChunkLength(RegionFile regionFile, int x, int z)
|
||||
{
|
||||
Access access = cache.get(regionFile);
|
||||
|
||||
/* "offset" refers to the location of the chunk's bytes
|
||||
* 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 encode an int. This
|
||||
* int is the chunk's remaining length in bytes, excluding the
|
||||
* four bytes of the int.
|
||||
*
|
||||
* The fifth byte represents compression scheme. The first five
|
||||
* bytes are omitted when the game reads a chunk's data.
|
||||
*
|
||||
* The following 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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
static final Field bytesField;
|
||||
final int[] offset;
|
||||
final RandomAccessFile bytes;
|
||||
|
||||
static
|
||||
{
|
||||
Field tmp1 = null;
|
||||
Field tmp2 = null;
|
||||
try
|
||||
{
|
||||
tmp1 = RegionFile.class.getDeclaredField("d");
|
||||
tmp2 = RegionFile.class.getDeclaredField("c");
|
||||
tmp1.setAccessible(true);
|
||||
tmp2.setAccessible(true);
|
||||
}
|
||||
catch (NoSuchFieldException | SecurityException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
offsetField = tmp1;
|
||||
bytesField = tmp2;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new Access object to hold 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
|
||||
*/
|
||||
Access(RegionFile regionFile) throws FileNotFoundException,
|
||||
IllegalAccessException,
|
||||
IllegalArgumentException
|
||||
{
|
||||
offset = (int[]) offsetField.get(regionFile);
|
||||
bytes = (RandomAccessFile) bytesField .get(regionFile);
|
||||
}
|
||||
}
|
||||
}
|
98
src/simpleWarBackup/ChunkCoordsListOLD.java
Normal file
98
src/simpleWarBackup/ChunkCoordsListOLD.java
Normal file
|
@ -0,0 +1,98 @@
|
|||
package simpleWarBackup;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public class ChunkCoordsListOLD
|
||||
{
|
||||
private static final YamlConfiguration yml = new YamlConfiguration();
|
||||
private static final char ps = yml.options().pathSeparator();
|
||||
private static File file;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @param plugin
|
||||
*/
|
||||
private static void initialize(JavaPlugin plugin)
|
||||
{
|
||||
file = new File(plugin.getDataFolder(), "Chunks.yml");
|
||||
|
||||
/* "Garbage Bin" contains the names of backups that have already been fully
|
||||
* written back into the game world, and are presumably no longer needed.
|
||||
* Each entry includes a timestamp, and is deleted after 30 days.
|
||||
*
|
||||
* "Storage" lists by coordinates the chunks stored in each of the backups.
|
||||
* Coordinates are stored as longs, with the smaller half representing x and
|
||||
* the larger half representing z.
|
||||
*
|
||||
* "Queue" lists by coordinates the chunks in each of the backups that are
|
||||
* slated to be written back into the game world. Coordinates are stored
|
||||
* in the same manner as in "Storage."
|
||||
*/
|
||||
try
|
||||
{
|
||||
if (file.exists())
|
||||
{
|
||||
yml.load(file);
|
||||
|
||||
if (!yml.contains("Garbage Bin")) yml.createSection("Garbage Bin");
|
||||
if (!yml.contains("Storage")) yml.createSection("Storage");
|
||||
if (!yml.contains("Queue")) yml.createSection("Queue");
|
||||
}
|
||||
else
|
||||
{
|
||||
yml.createSection("Garbage Bin");
|
||||
yml.createSection("Storage");
|
||||
yml.createSection("Queue");
|
||||
}
|
||||
yml.save(file);
|
||||
}
|
||||
catch(IOException | InvalidConfigurationException e) { e.printStackTrace(); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void write(String path, int chunkX)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @param backup
|
||||
* @param town
|
||||
* @param world
|
||||
* @param chunks (chunk coords)
|
||||
*/
|
||||
static void backup(String backup, String town, String world, long... chunks)
|
||||
{
|
||||
String path = "Storage" + ps + backup + ps + town + ps + world;
|
||||
|
||||
int x, z;
|
||||
int[] rows;
|
||||
|
||||
for (long chunk : chunks)
|
||||
{
|
||||
x = (int) chunk;
|
||||
z = (int) ( chunk >> 32 );
|
||||
|
||||
//write(path + ps + (x >> 5) + ", " + (z >> 5) + ps + (z & 31));
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void queue(String backup, String town, String world, long... chunks)
|
||||
{
|
||||
yml.set("Queue" + ps + backup + ps + town + ps + world, chunks);
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ import net.minecraft.server.v1_12_R1.World;
|
|||
* methods in ChunkRegionLoader, an NMS class. Changes have been commented, so that these
|
||||
* methods may be more easily updated as Spigot releases newer versions.
|
||||
*/
|
||||
public final class ChunkNBTWriter
|
||||
public final class ChunkNBTTools
|
||||
{
|
||||
/**
|
||||
* To find the new data version after an update,
|
||||
|
@ -38,7 +38,7 @@ public final class ChunkNBTWriter
|
|||
* @param DATA_VERSION
|
||||
* @return
|
||||
*/
|
||||
public static NBTTagCompound write(Chunk chunk, World world)
|
||||
public static NBTTagCompound save(Chunk chunk, World world)
|
||||
{
|
||||
System.out.println("ChunkNBTWriter.write has been called");//TODO
|
||||
NBTTagCompound chunkNBT = new NBTTagCompound();
|
||||
|
@ -47,8 +47,8 @@ public final class ChunkNBTWriter
|
|||
chunkNBT.set("Level", levelNBT);
|
||||
chunkNBT.setInt("DataVersion", DATA_VERSION);
|
||||
|
||||
ChunkNBTWriter.saveEntities(levelNBT, chunk, world);
|
||||
ChunkNBTWriter.saveBody(levelNBT, chunk, world);
|
||||
ChunkNBTTools.saveEntities(levelNBT, chunk, world);
|
||||
ChunkNBTTools.saveBody(levelNBT, chunk, world);
|
||||
|
||||
return chunkNBT;
|
||||
}
|
|
@ -18,43 +18,50 @@ import org.bukkit.plugin.java.JavaPlugin;
|
|||
|
||||
public class Main extends JavaPlugin implements Listener
|
||||
{
|
||||
public void onEnable()
|
||||
// --------------------- STATIC ---------------------
|
||||
|
||||
/**
|
||||
* TODO write javadoc
|
||||
*/
|
||||
static File pluginDir;
|
||||
|
||||
/**
|
||||
* TODO write javadoc
|
||||
*/
|
||||
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)
|
||||
{
|
||||
getCommand("testBackupChunk").setExecutor(this);
|
||||
getCommand("testRestoreChunk").setExecutor(this);
|
||||
|
||||
File dataFolder = this.getDataFolder();
|
||||
BackupIO.initialize(dataFolder,
|
||||
new File(dataFolder,
|
||||
"Backup Files"));
|
||||
pluginDir = plugin.getDataFolder();
|
||||
backupsDir = new File(pluginDir, "Backup Files");
|
||||
}
|
||||
|
||||
|
||||
// -------------------- INSTANCE --------------------
|
||||
|
||||
|
||||
public void onEnable()
|
||||
{
|
||||
initialize(this);
|
||||
|
||||
getCommand("warbackup").setExecutor(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* TODO write javadoc
|
||||
*/
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args)
|
||||
{
|
||||
if (!(sender instanceof Player) || !sender.getName().equals("iie")) return false;
|
||||
System.out.println("player is iie");//TODO
|
||||
|
||||
Location loc = ((Player) sender).getLocation();
|
||||
|
||||
World world = loc.getWorld();
|
||||
Chunk chunk = loc.getChunk();
|
||||
|
||||
if (command.getName().equals("testBackupChunk"))
|
||||
{
|
||||
System.out.println("command label = testBackupChunk");//TODO
|
||||
try {
|
||||
BackupIO.backupChunks("test", world, chunk);
|
||||
System.out.println("tried BackupIO.backupChunks");//TODO
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
else if (label.equals("testRestoreChunk"))
|
||||
{
|
||||
|
||||
}
|
||||
//world.isChunkLoaded(x,z);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
167
src/simpleWarBackup/RegionChunkList.java
Normal file
167
src/simpleWarBackup/RegionChunkList.java
Normal file
|
@ -0,0 +1,167 @@
|
|||
package simpleWarBackup;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.BitSet;
|
||||
|
||||
/**
|
||||
* TODO write javadoc
|
||||
*/
|
||||
public class RegionChunkList
|
||||
{
|
||||
/**
|
||||
* TODO write javadoc
|
||||
*/
|
||||
private final RandomAccessFile readwrite;
|
||||
|
||||
/**
|
||||
* TODO write javadoc
|
||||
*/
|
||||
final BitSet chunks = new BitSet(1024);
|
||||
|
||||
/**
|
||||
* TODO write javadoc
|
||||
*/
|
||||
final long regionCoordinates;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* TODO write javadoc
|
||||
*
|
||||
* @param file
|
||||
* @throws IOException
|
||||
*/
|
||||
RegionChunkList(File file) throws IOException
|
||||
{
|
||||
readwrite = new RandomAccessFile(file, "rw");
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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 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";
|
||||
|
||||
readwrite = new RandomAccessFile(new File(directory, filename), "rw");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TODO write javadoc (and add comment to explain numbers)
|
||||
*
|
||||
* @param x
|
||||
* @param z
|
||||
* @return
|
||||
*/
|
||||
boolean isChunkStored(int x, int z)
|
||||
{
|
||||
x &= 31;
|
||||
z &= 31;
|
||||
return chunks.get(x + z * 32);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TODO write javadoc (and add comment to explain numbers)
|
||||
*
|
||||
* @param x chunk x coordinate
|
||||
* @param z chunk z coordinate
|
||||
*/
|
||||
void storeChunk(int x, int z)
|
||||
{
|
||||
x &= 31;
|
||||
z &= 31;
|
||||
chunks.set(x + z * 32);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TODO write javadoc (and add comment to explain numbers)
|
||||
*
|
||||
* @param x
|
||||
* @param z
|
||||
*/
|
||||
void markChunkAsRestored(int x, int z)
|
||||
{
|
||||
x &= 31;
|
||||
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 a RegionChunkList, you should invoke this method.
|
||||
*
|
||||
* @throws IOException if the RandomAccessFile was not closed
|
||||
*/
|
||||
void close() throws IOException
|
||||
{
|
||||
readwrite.close();
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
package simpleWarBackup;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraft.server.v1_12_R1.RegionFile;
|
||||
|
||||
/**
|
||||
* TODO write this
|
||||
*/
|
||||
public class RegionFileCache
|
||||
{
|
||||
private static final Map<File, RegionFile> regionFiles = new HashMap<File, RegionFile>();
|
||||
|
||||
/**
|
||||
* 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.clearCace()}.
|
||||
*
|
||||
* @param backupFolder folder containing "region" folder
|
||||
* @param x chunk X
|
||||
* @param z chunk Z
|
||||
* @return
|
||||
*/
|
||||
public static synchronized RegionFile get(File backupFolder, int x, int z)
|
||||
{
|
||||
System.out.println("RegionFileCache.get has been called");//TODO
|
||||
x >>= 5; z >>= 5;
|
||||
File regionFolder = new File(backupFolder, "region");
|
||||
File regionFileFile = new File(regionFolder, "r." + x + "." + z + ".mca");
|
||||
RegionFile regionFile = regionFiles.get(regionFileFile);
|
||||
|
||||
if (regionFile != null)
|
||||
return regionFile;
|
||||
if (!regionFolder.exists())
|
||||
regionFolder.mkdirs();
|
||||
if (regionFiles.size() >= 16)
|
||||
clearCache();
|
||||
|
||||
regionFile = new RegionFile(regionFileFile);
|
||||
RegionFileCache.regionFiles.put(regionFileFile, 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.regionFiles.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.regionFiles.clear();
|
||||
}
|
||||
}
|
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 javadoc
|
||||
*/
|
||||
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 contains 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 write javadoc, explain what method a(...) does
|
||||
*
|
||||
* @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 write javadoc, explain what method a(...) does
|
||||
*
|
||||
* @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…
Reference in a new issue