"Restore" test, fails currently

added a restore chunks command for two test chunks. Currently does not
manage to write to the main world file.
This commit is contained in:
BuildTools 2018-02-13 19:31:46 +00:00
parent ffed682beb
commit fda2afbd49
4 changed files with 312 additions and 113 deletions

View file

@ -1,113 +1,180 @@
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 java.io.RandomAccessFile;
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 org.bukkit.plugin.java.JavaPlugin;
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;
public class BackupIO
{
private static File dataFolder, backupsFolder;
private static File pluginDir, backupsDir;
public static final HashMap<World, HashMap<String, File>> backups
= new HashMap<World, HashMap<String, File>>();
/**
* TODO
*
* @param dataFolder
* @param backupsFolder
*/
public static void initialize(File dataFolder, File backupsFolder)
protected static void initialize(JavaPlugin plugin)
{
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);
}
}
}
pluginDir = plugin.getDataFolder();
backupsDir = new File(pluginDir, "Backup Files");
}
/**
* TODO
*
* @param backupFolder
* @param chunkX
* @param chunkZ
* @param world
* @param backup
* @return
*/
public static DataOutputStream chunkDataOutputStream(File backupFolder, int chunkX, int chunkZ)
private static File getBackupDir(CraftWorld world, String backup)
{
return RegionFileCache.get(backupFolder, chunkX, chunkZ) // get(...) returns a RegionFile
.b(chunkX & 31, chunkZ & 31); // b(...) returns a DataOutputStream
return new File(new File(backupsDir, world.getName()), backup);
}
/**
* 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(
CraftWorld world, String backup, CraftChunk... 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();
final World worldNMS = world.getHandle();
Chunk chunkNMS;
NBTTagCompound chunkNBT; // to be serialized
NBTTagCompound chunkNBT;
DataOutputStream output;
final File backupDir = getBackupDir(world, backup);
int x, z;
RegionFile regionFile;
DataOutputStream chunkPipe; // serialized to this
for (Chunk chunk : chunks)
/* chunkPipe is a series of data outputs pointing to a ChunkBuffer.
* The internal class ChunkBuffer is defined within RegionFile, and
* extends ByteArrayOutputStream. It holds a byte[] buffer and two
* int values representing the chunk's coordinates.
*/
for (CraftChunk chunk : chunks)
{
chunkNBT = ChunkNBTWriter.write(((CraftChunk) chunk).getHandle(), worldNMS);
output = BackupIO.chunkDataOutputStream(folder, chunk.getX(), chunk.getZ());
chunkNMS = chunk.getHandle();
chunkNBT = ChunkNBTTools.save(chunkNMS, worldNMS);
NBTCompressedStreamTools.a(chunkNBT, (DataOutput) output);
output.close();
x = chunk.getX();
z = chunk.getZ();
regionFile = RegionFileCache.get(backupDir, 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
* internal RandomAccessFile, which points to the actual .mca file.
*/
NBTCompressedStreamTools.a(chunkNBT, (DataOutput) chunkPipe);
chunkPipe.close();
}
}
/**
* TODO
*
* @param world
* @param backup
* @param chunks
*/
private static void restoreWhenSafe(CraftWorld world, String backup, long[] chunks)
{
}
/**
* TODO
*
* @param world
* @param backup
*/
private static void restoreWhenSafe(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
}
*/
}
public static boolean restoreTest(int x, int z) throws IOException
{
CraftWorld world = (CraftWorld) Bukkit.getWorld("world");
File worldDir = new File(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);
DataInputStream dataInput = regionFileSource.a(x & 31, z & 31);
DataOutputStream dataOutput = regionFileTarget.b(x & 31, z & 31);
/* Look in RegionFile to find how to get length.
* Length is recorded in the first 4 bytes of each
* chunk's data, in the body of the region file.
*
* Read header for chunk data location, seek to
* that location and read first 4 bytes. This is
* the length for byte[] buf below.
*/
/*int length;
{
RandomAccessFile raf = RegionFileCache.getRAF(regionFileSource);
if (raf == null) return false;
...
}
byte[] buf = new byte[length];
dataInput.readFully(buf);
dataOutput.write(buf);
dataOutput.close();*/
if (dataInput == null)
return false;
NBTCompressedStreamTools.a(
NBTCompressedStreamTools.a(dataInput),
(java.io.DataOutput) dataOutput);
return true;
}
}

View file

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

View file

@ -23,37 +23,56 @@ public class Main extends JavaPlugin implements Listener
getCommand("testBackupChunk").setExecutor(this);
getCommand("testRestoreChunk").setExecutor(this);
File dataFolder = this.getDataFolder();
BackupIO.initialize(dataFolder,
new File(dataFolder,
"Backup Files"));
BackupIO.initialize(this);
}
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
if (!(sender instanceof Player)) return false;
if (!sender.getName().equals("iie")) return false;
sender.sendMessage("some command received");
Location loc = ((Player) sender).getLocation();
World world = loc.getWorld();
Chunk chunk = loc.getChunk();
World world = loc.getWorld();
Chunk chunk = loc.getChunk();
//test-chunks to be test-restored
int[][] xz = {{187, 187},
{40 , 40 }};
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();
}
try { BackupIO.backup((CraftWorld) world, "test", (CraftChunk) chunk); }
catch (IOException e) { e.printStackTrace(); }
}
else if (label.equals("testRestoreChunk"))
else if (command.getName().equals("testRestoreChunk"))
{
sender.sendMessage("testRestoreChunk command received");
for (int[] coords : xz)
{
if (world.isChunkLoaded(coords[0], coords[1]))
{
sender.sendMessage("chunk " + coords[0] + ", " + coords[1] + " is still loaded");
return false;
}
}
{
sender.sendMessage("chunk is not loaded");
try
{
for (int[] coords : xz)
{
sender.sendMessage(
BackupIO.restoreTest(coords[0], coords[1]) ? "restore worked" :
"restore failed");
}
}
catch (IOException e)
{
e.printStackTrace();
sender.sendMessage("IOException, restore failed");
}
}
}
return true;
}

View file

@ -1,49 +1,71 @@
package simpleWarBackup;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
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 class RegionFileCache
public final class RegionFileCache
{
private static final Map<File, RegionFile> regionFiles = new HashMap<File, RegionFile>();
private static final int cap = 16; //capacity - how many regionFiles to keep cached
private static final Map<File, RegionFile> cache = 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()}.
* 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 backupFolder folder containing "region" folder
* @param x chunk X
* @param z chunk Z
* @param x global chunk coordinate x
* @param z global chunk coordinate z
* @return
*/
public static synchronized RegionFile get(File backupFolder, int x, int z)
public static String path(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);
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 where to look for a "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 (!regionFolder.exists())
regionFolder.mkdirs();
if (regionFiles.size() >= 16)
if (!regionDir.exists())
regionDir.mkdirs();
if (cache.size() >= cap)
clearCache();
regionFile = new RegionFile(regionFileFile);
RegionFileCache.regionFiles.put(regionFileFile, regionFile);
regionFile = new RegionFile(mcaFile);
RegionFileCache.cache.put(mcaFile, regionFile);
RegionFileCache.cacheRAF(mcaFile, regionFile);
return regionFile;
}
@ -54,7 +76,7 @@ public class RegionFileCache
*/
public static synchronized void clearCache()
{
Iterator<RegionFile> iterator = RegionFileCache.regionFiles.values().iterator();
Iterator<RegionFile> iterator = RegionFileCache.cache.values().iterator();
/* regionFile.c() closes the private RandomAccessFile
* inside the regionFile object, and that's all it does.
@ -66,6 +88,97 @@ public class RegionFileCache
try { if (regionFile != null) regionFile.c(); }
catch (IOException e) { e.printStackTrace(); }
}
RegionFileCache.regionFiles.clear();
RegionFileCache.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);
}
/*-------------------------------RandomAccessFiles-------------------------------*/
/* 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.
*/
private static Map<RegionFile,
RandomAccessFile> cacheRAF = new HashMap<RegionFile,
RandomAccessFile>();
/**
* TODO
*
* @param file
* @param regionFile
*/
private static void cacheRAF(File file, RegionFile regionFile)
{
try { cacheRAF.put(regionFile, new RandomAccessFile(file, "r")); }
catch (FileNotFoundException e) { e.printStackTrace(); }
}
/**
* TODO
*
* @param regionFile
* @return
*/
public static RandomAccessFile getRAF(RegionFile regionFile)
{
return cacheRAF.get(regionFile);
}
/**
* TODO
*
* @param regionFile
* @param x
* @param z
* @return
*/
public static int getChunkByteLength(RegionFile regionFile, int x, int z)
{
return 0;//TODO
}
}