haven't commit in a while

This commit is contained in:
BuildTools 2018-04-07 23:46:51 +01:00
parent 748ed70f50
commit faf8bc98ff
4 changed files with 314 additions and 95 deletions

View file

@ -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
}
*/
}

View 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);
}
}

View file

@ -0,0 +1,6 @@
package simpleWarBackup;
public class RegionChunkList
{
}

View file

@ -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,55 +114,16 @@ 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>();
/**
* 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;
}
}
ChunkAccess> cacheChunkAccess = new HashMap<RegionFile,
ChunkAccess>();
/**
* TODO
@ -170,21 +131,18 @@ public final class RegionFileCache
* @param file
* @param regionFile
*/
private static void cacheRAF(File file, RegionFile regionFile)
private static void cacheChunkAccess(File file, RegionFile regionFile)
{
}
/**
* TODO
*
* @param regionFile
* @return
*/
public static RandomAccessFile getRAF(RegionFile regionFile)
{
return null;//TODO
try
{
cacheChunkAccess.put(regionFile, new ChunkAccess(regionFile));
}
catch (FileNotFoundException |
IllegalAccessException |
IllegalArgumentException e)
{
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);
}
}
}