"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:
parent
ffed682beb
commit
fda2afbd49
4 changed files with 312 additions and 113 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue