Permalink
Browse files

LWC full backups are now creatable and savable (saves protections & t…

…he block+contents). NOT COMPATIBLE WITH DOUBLE CHESTS (yet). Restore via /lwc admin backup restore NAME. Still some bugs, so unless your database is empty you'll get duplicates :p
  • Loading branch information...
1 parent 94243b6 commit e30274b81e7092f62ce412bf18dd8bc24e96ab83 @Hidendra committed Feb 17, 2012
View
1 docs/backups/Format.md
@@ -37,6 +37,7 @@ By default, backup files are named using the naming format `MM-dd-yyyy-HHmm.lwc.
struct RestorableProtection {
int id; // ID in the database, e.g mysql
+ byte type; // protection type
short blockId;
string owner;
string world;
View
21 modules/core/src/main/java/com/griefcraft/modules/admin/AdminBackup.java
@@ -29,9 +29,11 @@
package com.griefcraft.modules.admin;
import com.griefcraft.io.Backup;
+import com.griefcraft.io.BackupManager;
import com.griefcraft.lwc.LWC;
import com.griefcraft.scripting.JavaModule;
import com.griefcraft.scripting.event.LWCCommandEvent;
+import com.griefcraft.util.StringUtil;
import org.bukkit.command.CommandSender;
public class AdminBackup extends JavaModule {
@@ -46,8 +48,8 @@ public void onCommand(LWCCommandEvent event) {
return;
}
- LWC lwc = event.getLWC();
- CommandSender sender = event.getSender();
+ final LWC lwc = event.getLWC();
+ final CommandSender sender = event.getSender();
String[] args = event.getArgs();
if (!args[0].equals("backup")) {
@@ -69,6 +71,21 @@ public void onCommand(LWCCommandEvent event) {
// Dumb code for now
Backup created = lwc.getBackupManager().createBackup();
sender.sendMessage("Backup is being created now.");
+ } else if (action.equals("restore")) {
+ if (args.length < 3) {
+ lwc.sendSimpleUsage(sender, "/lwc admin backup restore <BackupName>");
+ return;
+ }
+
+ final String backupName = StringUtil.join(args, 2);
+ sender.sendMessage("Restoring backup " + backupName);
+
+ lwc.getPlugin().getServer().getScheduler().scheduleAsyncDelayedTask(lwc.getPlugin(), new Runnable() {
+ public void run() {
+ BackupManager.Result result = lwc.getBackupManager().restoreBackup(backupName);
+ sender.sendMessage("Result: " + result);
+ }
+ });
}
}
View
112 src/main/java/com/griefcraft/io/Backup.java
@@ -49,16 +49,9 @@
public static final int CURRENT_REVISION = 1;
/**
- * The result for a backup operation
- */
- public enum Result {
- OK, FAIL
- }
-
- /**
* The operations the backup is allowed to perform
*/
- public enum Operation {
+ public enum OperationMode {
READ, WRITE
}
@@ -68,9 +61,9 @@
private final File file;
/**
- * The operation we are allowed to perform
+ * The operationMode we are allowed to perform
*/
- private final Operation operation;
+ private final OperationMode operationMode;
/**
* The flags we are using
@@ -97,21 +90,21 @@
*/
private DataOutputStream outputStream;
- public Backup(File file, Operation operation, EnumSet<BackupManager.Flag> flags) throws IOException {
+ public Backup(File file, OperationMode operationMode, EnumSet<BackupManager.Flag> flags) throws IOException {
this.file = file;
- this.operation = operation;
+ this.operationMode = operationMode;
this.flags = flags;
if (!file.exists()) {
- if (operation == Operation.READ) {
+ if (operationMode == OperationMode.READ) {
throw new UnsupportedOperationException("The backup could not be read");
} else {
file.createNewFile();
}
}
// Set some base data if we're writing
- if (operation == Operation.WRITE) {
+ if (operationMode == OperationMode.WRITE) {
revision = CURRENT_REVISION;
created = System.currentTimeMillis() / 1000;
}
@@ -120,49 +113,57 @@ public Backup(File file, Operation operation, EnumSet<BackupManager.Flag> flags)
boolean compression = flags.contains(BackupManager.Flag.COMPRESSION);
// create the stream we need
- if (operation == Operation.READ) {
+ if (operationMode == OperationMode.READ) {
FileInputStream fis = new FileInputStream(file);
inputStream = new DataInputStream(compression ? new GZIPInputStream(fis) : fis);
- } else if (operation == Operation.WRITE) {
+ } else if (operationMode == OperationMode.WRITE) {
FileOutputStream fos = new FileOutputStream(file);
outputStream = new DataOutputStream(compression ? new GZIPOutputStream(fos) : fos);
}
}
/**
- * Begin restoring this backup. This should be ran in a separate thread.
- * Any world calls are offloaded to the world thread using the scheduler. No world reads are done, only writes.
- *
- * @return
- */
- public Result restoreBackup() {
- throw new UnsupportedOperationException("Not yet supported");
- }
-
- /**
* Read an entity from the backup file
*
* @return
*/
protected Restorable readRestorable() throws IOException {
- if (operation != Operation.READ) {
+ if (operationMode != OperationMode.READ) {
throw new UnsupportedOperationException("READ is not allowed on this backup.");
}
// The object type
- int type = inputStream.read() & 0xFF;
+ int type = (byte) inputStream.read();
+
+ // EOF
+ if (type == -1) {
+ return null;
+ }
// TODO enum that shit yo
if (type == 0) { // Protection
-
+ RestorableProtection rprotection = new RestorableProtection();
+ rprotection.setId(inputStream.readInt());
+ rprotection.setProtectionType(inputStream.readByte());
+ rprotection.setBlockId(inputStream.readShort());
+ rprotection.setOwner(inputStream.readUTF());
+ rprotection.setWorld(inputStream.readUTF());
+ rprotection.setX(inputStream.readInt());
+ rprotection.setY(inputStream.readShort());
+ rprotection.setZ(inputStream.readInt());
+ rprotection.setData(inputStream.readUTF());
+ rprotection.setCreated(inputStream.readLong());
+ rprotection.setUpdated(inputStream.readLong());
+
+ return rprotection;
} else if (type == 1) { // Block
- RestorableBlock block = new RestorableBlock();
- block.setId(inputStream.readShort());
- block.setWorld(inputStream.readUTF());
- block.setX(inputStream.readInt());
- block.setY(inputStream.readShort());
- block.setZ(inputStream.readInt());
- block.setData(inputStream.read() & 0xFF);
+ RestorableBlock rblock = new RestorableBlock();
+ rblock.setId(inputStream.readShort());
+ rblock.setWorld(inputStream.readUTF());
+ rblock.setX(inputStream.readInt());
+ rblock.setY(inputStream.readShort());
+ rblock.setZ(inputStream.readInt());
+ rblock.setData(inputStream.read() & 0xFF);
int itemCount = inputStream.readShort();
for (int i = 0; i < itemCount; i++) {
@@ -176,14 +177,14 @@ protected Restorable readRestorable() throws IOException {
ItemStack itemStack = new ItemStack(itemId, amount, damage);
// add it to the block
- block.setSlot(slot, itemStack);
+ rblock.setSlot(slot, itemStack);
}
// Woo!
- return block;
+ return rblock;
}
- throw new UnsupportedOperationException("Not yet supported");
+ throw new UnsupportedOperationException("Read unknown type: " + type);
}
/**
@@ -192,7 +193,7 @@ protected Restorable readRestorable() throws IOException {
* @param restorable
*/
protected void writeRestorable(Restorable restorable) throws IOException {
- if (operation != Operation.WRITE) {
+ if (operationMode != OperationMode.WRITE) {
throw new UnsupportedOperationException("WRITE is not allowed on this backup.");
}
@@ -201,7 +202,19 @@ protected void writeRestorable(Restorable restorable) throws IOException {
// Write it
if (restorable.getType() == 0) { // Protection, also TODO ENUMSSSSSSSSSSS
-
+ RestorableProtection rprotection = (RestorableProtection) restorable;
+
+ outputStream.writeInt(rprotection.getId());
+ outputStream.writeByte(rprotection.getType());
+ outputStream.writeShort(rprotection.getBlockId());
+ outputStream.writeUTF(rprotection.getOwner());
+ outputStream.writeUTF(rprotection.getWorld());
+ outputStream.writeInt(rprotection.getX());
+ outputStream.writeShort(rprotection.getY());
+ outputStream.writeInt(rprotection.getZ());
+ outputStream.writeUTF(rprotection.getData());
+ outputStream.writeLong(rprotection.getCreated());
+ outputStream.writeLong(rprotection.getUpdated());
} else if (restorable.getType() == 1) { // Block, TODO DID I SAY TO DO THE ENUM YET??
RestorableBlock rblock = (RestorableBlock) restorable;
@@ -212,7 +225,7 @@ protected void writeRestorable(Restorable restorable) throws IOException {
outputStream.writeInt(rblock.getZ());
outputStream.write((byte) rblock.getData());
outputStream.writeShort(rblock.getItems().size());
-
+
// Write the items if there are any
for (Map.Entry<Integer, ItemStack> entry : rblock.getItems().entrySet()) {
int slot = entry.getKey();
@@ -223,9 +236,9 @@ protected void writeRestorable(Restorable restorable) throws IOException {
outputStream.writeShort(stack.getAmount());
outputStream.writeShort(stack.getDurability());
}
-
- outputStream.flush();
}
+
+ outputStream.flush();
}
/**
@@ -250,11 +263,16 @@ protected void writeHeader() throws IOException {
outputStream.write(new byte[10]); // reserved space
outputStream.flush();
}
-
+
+ /**
+ * Close the backup file
+ *
+ * @throws IOException
+ */
protected void close() throws IOException {
- if (operation == Operation.READ) {
+ if (operationMode == OperationMode.READ) {
inputStream.close();
- } else if (operation == Operation.WRITE) {
+ } else if (operationMode == OperationMode.WRITE) {
outputStream.close();
}
}
View
107 src/main/java/com/griefcraft/io/BackupManager.java
@@ -53,6 +53,13 @@
public class BackupManager {
/**
+ * The result for a backup operationMode
+ */
+ public enum Result {
+ OK, FAILURE
+ }
+
+ /**
* The folder where backups are stored at
*/
public static String BACKUP_FOLDER = "plugins/LWC/backups/";
@@ -75,7 +82,7 @@
/**
* The amount of protection block gets to batch at once
*/
- private static int BATCH_SIZE = 100;
+ private static int BATCH_SIZE = 250;
/**
* The folder backups are stored in
@@ -111,6 +118,97 @@ public BackupManager() {
}
/**
+ * Begin restoring a backup. This should be ran in a separate thread.
+ * Any world calls are offloaded to the world thread using the scheduler. No world reads are done, only writes.
+ *
+ * @param name
+ * @return OK if successful, otherwise FAILURE
+ */
+ public Result restoreBackup(String name) {
+ try {
+ Backup backup = loadBackup(name);
+
+ if (backup == null) {
+ return Result.FAILURE;
+ }
+
+ return restoreBackup(backup);
+ } catch (IOException e) {
+ System.out.println("[BackupManager] Caught: " + e.getMessage());
+ return Result.FAILURE;
+ }
+ }
+
+ /**
+ * Begin restoring a backup. This should be ran in a separate thread.
+ * Any world calls are offloaded to the world thread using the scheduler. No world reads are done, only writes.
+ *
+ * @param backup
+ * @return OK if successful, otherwise FAILURE
+ */
+ public Result restoreBackup(Backup backup) {
+ try {
+ // Read in the backup's header
+ backup.readHeader();
+
+ // begin restoring :)
+ Restorable restorable;
+ int count = 0;
+ int protectionCount = 0;
+ int blockCount = 0;
+ while ((restorable = backup.readRestorable()) != null) {
+ restorable.restore();
+
+ if (count % 2000 == 0) {
+ System.out.println("[Backup] Restored restorables: " + count);
+ }
+ count ++;
+
+ // TODO THIS IS HACKS :-( ALSO ENUM ENUM
+ if (restorable.getType() == 0) {
+ protectionCount ++;
+ } else if (restorable.getType() == 1) {
+ blockCount ++;
+ }
+ }
+
+ System.out.println(String.format("[BackupManager] Restored %d restorables. %d were protections, %d blocks.", count, protectionCount, blockCount));
+ return Result.OK;
+ } catch (IOException e) {
+ e.printStackTrace();
+ return Result.FAILURE;
+ }
+ }
+
+ /**
+ * Load a backup
+ *
+ * @param name
+ * @return
+ */
+ public Backup loadBackup(String name) throws IOException {
+ File file;
+
+ // Try to load the compressed version
+ file = new File(BACKUP_FOLDER, name + FILE_EXTENSION_COMPRESSED);
+
+ if (file.exists()) {
+ // Bingo
+ return new Backup(file, Backup.OperationMode.READ, EnumSet.of(Flag.COMPRESSION));
+ }
+
+ // Try uncompressed
+ file = new File(BACKUP_FOLDER, name + FILE_EXTENSION_UNCOMPRESSED);
+
+ if (file.exists()) {
+ return new Backup(file, Backup.OperationMode.READ, EnumSet.noneOf(Flag.class));
+ }
+
+ // Nothing :-(
+ return null;
+ }
+
+ /**
* Create a backup of the given objects.
* When this returns, it is not guaranteed that the backup is fully written to the disk.
*
@@ -128,7 +226,7 @@ public Backup createBackup(String name, final EnumSet<Flag> flags) {
// Our backup file
try {
- final Backup backup = new Backup(backupFile, Backup.Operation.WRITE, flags);
+ final Backup backup = new Backup(backupFile, Backup.OperationMode.WRITE, flags);
scheduler.scheduleAsyncDelayedTask(plugin, new Runnable() {
public void run() {
@@ -173,7 +271,10 @@ public void run() {
if (protections.size() != BATCH_SIZE) {
// Wait until we have BATCH_SIZE protections
protections.add(tprotection);
- continue;
+
+ if (protections.size() != totalProtections) {
+ continue;
+ }
}
// Get all of the blocks in the world
View
49 src/main/java/com/griefcraft/io/RestorableBlock.java
@@ -28,6 +28,10 @@
package com.griefcraft.io;
+import com.griefcraft.lwc.LWC;
+import org.bukkit.Bukkit;
+import org.bukkit.Server;
+import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.ContainerBlock;
@@ -79,7 +83,50 @@ public int getType() {
}
public void restore() {
- throw new UnsupportedOperationException("Not yet available.");
+ LWC lwc = LWC.getInstance();
+
+ lwc.getPlugin().getServer().getScheduler().scheduleSyncDelayedTask(lwc.getPlugin(), new Runnable() {
+ public void run() {
+ Server server = Bukkit.getServer();
+
+ // Get the world
+ World bworld = server.getWorld(world);
+
+ // Not found :-(
+ if (world == null) {
+ return;
+ }
+
+ // Get the block we want
+ Block block = bworld.getBlockAt(x, y, z);
+
+ // Begin screwing with shit :p
+ block.setTypeId(id);
+ block.setData((byte) data);
+
+ if (items.size() > 0) {
+ if (!(block.getState() instanceof ContainerBlock)) {
+ System.out.println(String.format("The block at [%d, %d, %d] has backed up items but no longer supports them. Why? %s", x, y, z, block.toString()));
+ }
+
+ // Get the block's inventory
+ Inventory inventory = ((ContainerBlock) block.getState()).getInventory();
+
+ // Set all of the items to it
+ for (Map.Entry<Integer, ItemStack> entry : items.entrySet()) {
+ int slot = entry.getKey();
+ ItemStack stack = entry.getValue();
+
+ if (stack == null) {
+ continue;
+ }
+
+ // Add it to the inventory
+ inventory.setItem(slot, stack);
+ }
+ }
+ }
+ });
}
/**
View
20 src/main/java/com/griefcraft/io/RestorableProtection.java
@@ -28,6 +28,7 @@
package com.griefcraft.io;
+import com.griefcraft.lwc.LWC;
import com.griefcraft.model.Protection;
import java.text.ParseException;
@@ -41,6 +42,11 @@
private int id;
/**
+ * The protection type
+ */
+ private int protectionType;
+
+ /**
* The block id
*/
private int blockId;
@@ -90,7 +96,10 @@ public int getType() {
}
public void restore() {
- throw new UnsupportedOperationException("Not yet available.");
+ LWC lwc = LWC.getInstance();
+ Protection protection = lwc.getPhysicalDatabase().registerProtection(blockId, Protection.Type.values()[protectionType],
+ world, owner, data, x, y, z);
+ // TODO fix the ID?
}
/**
@@ -107,6 +116,7 @@ public static RestorableProtection wrapProtection(Protection protection) {
try {
RestorableProtection rprotection = new RestorableProtection();
rprotection.id = protection.getId();
+ rprotection.protectionType = protection.getType().ordinal();
rprotection.blockId = protection.getBlockId();
rprotection.owner = protection.getOwner();
rprotection.world = protection.getWorld();
@@ -131,6 +141,14 @@ public int getId() {
public void setId(int id) {
this.id = id;
}
+
+ public int getProtectionType() {
+ return protectionType;
+ }
+
+ public void setProtectionType(int protectionType) {
+ this.protectionType = protectionType;
+ }
public int getBlockId() {
return blockId;

0 comments on commit e30274b

Please sign in to comment.