haven't commit in a while
This commit is contained in:
parent
748ed70f50
commit
faf8bc98ff
4 changed files with 314 additions and 95 deletions
|
@ -22,7 +22,14 @@ public class BackupIO
|
|||
{
|
||||
private static File pluginDir, backupsDir;
|
||||
|
||||
protected static void initialize(JavaPlugin plugin)
|
||||
/**
|
||||
* 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");
|
||||
|
@ -30,15 +37,25 @@ public class BackupIO
|
|||
|
||||
|
||||
/**
|
||||
* 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>
|
||||
*
|
||||
* 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 backup TODO
|
||||
* @param town
|
||||
* @param world
|
||||
* @param backup
|
||||
* @return
|
||||
*/
|
||||
private static File getBackupDir(CraftWorld world, String backup)
|
||||
private static File getDir(String backup, String town, CraftWorld world)
|
||||
{
|
||||
return new File(new File(backupsDir, world.getName()), backup);
|
||||
return new File(
|
||||
new File(
|
||||
new File(backupsDir, backup )
|
||||
, town )
|
||||
, world.getName() );
|
||||
}
|
||||
|
||||
|
||||
|
@ -51,20 +68,20 @@ public class BackupIO
|
|||
* @throws IOException
|
||||
*/
|
||||
public static void backup(
|
||||
CraftWorld world, String backup, CraftChunk... chunks)
|
||||
String backup, String town, CraftWorld world, CraftChunk... chunks)
|
||||
throws IOException
|
||||
{
|
||||
final World worldNMS = world.getHandle();
|
||||
Chunk chunkNMS;
|
||||
NBTTagCompound chunkNBT; // to be serialized
|
||||
|
||||
final File backupDir = getBackupDir(world, backup);
|
||||
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.
|
||||
* The internal class ChunkBuffer is defined within RegionFile, and
|
||||
* 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.
|
||||
*/
|
||||
|
@ -76,11 +93,11 @@ public class BackupIO
|
|||
|
||||
x = chunk.getX();
|
||||
z = chunk.getZ();
|
||||
regionFile = RegionFileCache.get(backupDir, x, z);
|
||||
regionFile = RegionFileCache.get(worldDir, x, z);
|
||||
chunkPipe = regionFile.b(x & 31, z & 31);
|
||||
|
||||
/* below serializes chunkNBT and writes the bytes to chunkPipe,
|
||||
* which then, on close(), writes those bytes into the RegionFile's
|
||||
/* 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.
|
||||
*/
|
||||
NBTCompressedStreamTools.a(chunkNBT, (DataOutput) chunkPipe);
|
||||
|
@ -94,10 +111,77 @@ public class BackupIO
|
|||
*
|
||||
* @param world
|
||||
* @param backup
|
||||
* @param chunks
|
||||
* @param chunks (chunk coords, larger half is z)
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private static void restoreWhenSafe(CraftWorld world, String backup, long[] chunks)
|
||||
private static void restore(
|
||||
String backup, String town, CraftWorld world, long... chunks)
|
||||
throws IOException
|
||||
{
|
||||
final File worldDir = getDir(backup, town, world);
|
||||
|
||||
int x, z;
|
||||
|
||||
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.
|
||||
*/
|
||||
for (long chunk : chunks)
|
||||
{
|
||||
x = (int) chunk;
|
||||
z = (int) ( chunk >> 32 );
|
||||
|
||||
source = RegionFileCache.get(worldDir, x, z);
|
||||
target = RegionFileCache.getFromMinecraft(world, x, z);
|
||||
dataInput = source.a(x & 31, z & 31);
|
||||
dataOutput = target.b(x & 31, z & 31);
|
||||
|
||||
length = RegionFileCache.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,
|
||||
* otherwise it will return the length value recorded in the file.
|
||||
*
|
||||
* Below are two approaches to writing bytes from the source to
|
||||
* the target. The first is faster, but we need to know the exact
|
||||
* length of the chunk's data to use this approach.
|
||||
*/
|
||||
if (length > 0)
|
||||
{
|
||||
buf = new byte[length];
|
||||
dataInput.readFully(buf);
|
||||
dataOutput.write(buf);
|
||||
}
|
||||
/* In the case of an error, i.e. length 0, plan B is to convert the
|
||||
* bytes into an NBTTagCompound, then serialize the NBTTagCompound
|
||||
* back into bytes at the target location. This approach lets the
|
||||
* native game handle the data as it normally would, using tools
|
||||
* built for the purpose, at the cost of some extra effort.
|
||||
*/
|
||||
else
|
||||
{
|
||||
NBTCompressedStreamTools.a(
|
||||
NBTCompressedStreamTools.a(dataInput),
|
||||
(java.io.DataOutput) dataOutput);
|
||||
}
|
||||
dataOutput.close(); //writes to the target file
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -108,26 +192,9 @@ public class BackupIO
|
|||
* @param world
|
||||
* @param backup
|
||||
*/
|
||||
private static void restoreWhenSafe(CraftWorld world, String backup)
|
||||
private static void restoreAll(CraftWorld world, String backup)
|
||||
{
|
||||
/*
|
||||
File worldDir = new File(backupsDir, world.getName());
|
||||
File backupDir = new File(worldDir, backup);
|
||||
File regionDir = new File(backupDir, "region");
|
||||
|
||||
if (!regionDir.exists() ||
|
||||
!regionDir.isDirectory())
|
||||
return;
|
||||
|
||||
String[] regionFileNames = regionDir.list();
|
||||
if (regionFileNames == null)
|
||||
return;
|
||||
|
||||
for (String regionFileName : regionFileNames)
|
||||
{
|
||||
//TODO BLAHAHAHABLHABLHBALJHBFLJDHBFKWYBFKUHEBF
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
|
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);
|
||||
}
|
||||
}
|
6
src/simpleWarBackup/RegionChunkList.java
Normal file
6
src/simpleWarBackup/RegionChunkList.java
Normal file
|
@ -0,0 +1,6 @@
|
|||
package simpleWarBackup;
|
||||
|
||||
public class RegionChunkList
|
||||
{
|
||||
|
||||
}
|
|
@ -20,7 +20,7 @@ import net.minecraft.server.v1_12_R1.World;
|
|||
*/
|
||||
public final class RegionFileCache
|
||||
{
|
||||
private static final int cap = 16; //capacity - how many regionFiles to keep cached
|
||||
private static final int cap = 32; //capacity - how many regionFiles to keep cached
|
||||
private static final Map<File, RegionFile> cache = new HashMap<File, RegionFile>();
|
||||
|
||||
/**
|
||||
|
@ -45,7 +45,7 @@ public final class RegionFileCache
|
|||
* that the cache contains fewer than 16 RegionFiles, and, finding 16 or more, calls
|
||||
* {@link #clearCache() RegionFileCache.clearCache()}.
|
||||
*
|
||||
* @param backupDir where to look for a "region" folder
|
||||
* @param backupDir parent folder containing the "region" folder
|
||||
* @param x global chunk coordinate x
|
||||
* @param z global chunk coordinate z
|
||||
* @return
|
||||
|
@ -66,7 +66,7 @@ public final class RegionFileCache
|
|||
|
||||
regionFile = new RegionFile(mcaFile);
|
||||
RegionFileCache.cache.put(mcaFile, regionFile);
|
||||
RegionFileCache.cacheRAF(mcaFile, regionFile);
|
||||
RegionFileCache.cacheChunkAccess(mcaFile, regionFile);
|
||||
return regionFile;
|
||||
}
|
||||
|
||||
|
@ -90,7 +90,7 @@ public final class RegionFileCache
|
|||
catch (IOException e) { e.printStackTrace(); }
|
||||
}
|
||||
RegionFileCache.cache.clear();
|
||||
RegionFileCache.cacheRAF.clear();
|
||||
RegionFileCache.cacheChunkAccess.clear();
|
||||
}
|
||||
|
||||
|
||||
|
@ -114,77 +114,35 @@ public final class RegionFileCache
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
/*=================================Chunk Access=================================*/
|
||||
|
||||
|
||||
|
||||
/*---------------------------------Chunk Access---------------------------------*/
|
||||
|
||||
/* RegionFiles keep their actual .mca files private, but those bytes can be useful
|
||||
* to read. The auxiliary cache below stores, for each cached RegionFile, a read-only
|
||||
* RandomAccessFile pointing to the .mca file, and an int[] storing the offset values
|
||||
* for each chunk's bytes within the file.
|
||||
/* 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> cacheRAF = new HashMap<RegionFile,
|
||||
ChunkAccess> cacheChunkAccess = new HashMap<RegionFile,
|
||||
ChunkAccess>();
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
public static class ChunkAccess
|
||||
{
|
||||
//private static Field offsetField = RegionFile.class.getDeclaredField("d");
|
||||
|
||||
final RandomAccessFile bytes;
|
||||
final int[] offsets;
|
||||
|
||||
ChunkAccess(RandomAccessFile bytes, int[] chunkOffsets)
|
||||
{
|
||||
this.bytes = bytes;
|
||||
this.offsets = chunkOffsets;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @param file
|
||||
* @param regionFile
|
||||
*/
|
||||
private static void cacheRAF(File file, RegionFile regionFile)
|
||||
private static void cacheChunkAccess(File file, RegionFile regionFile)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
cacheChunkAccess.put(regionFile, new ChunkAccess(regionFile));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @param regionFile
|
||||
* @return
|
||||
*/
|
||||
public static RandomAccessFile getRAF(RegionFile regionFile)
|
||||
catch (FileNotFoundException |
|
||||
IllegalAccessException |
|
||||
IllegalArgumentException e)
|
||||
{
|
||||
return null;//TODO
|
||||
e.printStackTrace();//TODO
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -196,8 +154,98 @@ public final class RegionFileCache
|
|||
* @param z
|
||||
* @return
|
||||
*/
|
||||
public static int getChunkByteLength(RegionFile regionFile, int x, int z)
|
||||
public static int getChunkLength(RegionFile regionFile, int x, int z)
|
||||
{
|
||||
return 0;//TODO
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue