Compare commits

...

11 commits

Author SHA1 Message Date
BuildTools
f14eb4693a removed test stuff to declutter 2018-09-16 20:51:42 +01:00
BuildTools
f69fb22f88 added constructor for selective backups 2018-09-13 13:31:40 +01:00
BuildTools
7a5668b1d7 Finished maketree() method in Backup ? 2018-09-13 13:09:00 +01:00
BuildTools
55ea36c165 still working on maketree() in Backup 2018-09-12 20:02:17 +01:00
BuildTools
1ce45d6635 Let this be over, please make it end 2018-09-07 21:55:32 +01:00
BuildTools
67691336e2 Added three new classes, with some methods
classes: Backup, RegionChunkList, NameCollisionException.
2018-08-06 00:50:47 -07:00
BuildTools
0dfa339856 created RegionChunkList 2018-04-14 10:59:49 +01:00
BuildTools
d40d7a5a6f check that chunk-to-be-restored actually has a backup
also, removed the “plan b” method of pasting backup chunks into a game
file. Instead, record which chunks were written and which weren’t.
Encountering an error, skip that chunk.
2018-04-08 20:20:23 +01:00
BuildTools
faf8bc98ff haven't commit in a while 2018-04-07 23:46:51 +01:00
BuildTools
748ed70f50 it worked... 2018-02-15 19:32:53 +00:00
BuildTools
fda2afbd49 "Restore" test, fails currently
added a restore chunks command for two test chunks. Currently does not
manage to write to the main world file.
2018-02-13 19:31:46 +00:00
16 changed files with 960 additions and 185 deletions

View file

@ -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
View file

@ -1 +1,2 @@
/bin/
/target/

View file

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

View file

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

View file

@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

47
pom.xml Normal file
View 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>

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

View file

@ -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; // to be serialized
DataOutputStream dataOutput; // serialized to here
NBTTagCompound chunkNBT;
DataOutputStream output;
/* 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);
}
}

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

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

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

@ -18,43 +18,50 @@ import org.bukkit.plugin.java.JavaPlugin;
public class Main extends JavaPlugin implements Listener
{
public void onEnable()
{
getCommand("testBackupChunk").setExecutor(this);
getCommand("testRestoreChunk").setExecutor(this);
// --------------------- STATIC ---------------------
File dataFolder = this.getDataFolder();
BackupIO.initialize(dataFolder,
new File(dataFolder,
"Backup Files"));
/**
* 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)
{
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;
}
}

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

View file

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

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

View file

@ -0,0 +1,8 @@
package simpleWarBackup.exceptions;
public class NameCollisionException extends Throwable
{
//TODO serialVersionUID?
//TODO write the damn class
}