Update cloneWorld().

- Make it actually work.
- Support for unloaded worlds - saves memory!
- The Thread code seems unnecessary since the main thread just waits for it to finish, so I'm removing it.
- Added cloneWorld(String, String).
- Deprecate the old cloneWorld() method.
Closes #1436
Closes #1491

Due to some limitations, I have to temporarily load the old world in
order to properly clone. However, **no chunks are loaded during this
process.**
This commit is contained in:
JBYoshi 2015-10-15 18:07:44 -05:00 committed by Jeremy Wood
parent 762d8e3ebb
commit 528dc25fcb
5 changed files with 171 additions and 95 deletions

View File

@ -315,6 +315,12 @@
<version>1.0.9</version>
<type>jar</type>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>org.bukkit</groupId>
<artifactId>craftbukkit</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- End of Logging Dependency -->
<!-- Start of Test Dependencies -->

View File

@ -374,7 +374,7 @@ public class WorldProperties extends SerializationConfig {
this.flushPendingVPropChanges();
}
String getAlias() {
public String getAlias() {
return this.alias;
}

View File

@ -64,16 +64,30 @@ public interface MVWorldManager {
*
* @param oldName Name of world to be copied
* @param newName Name of world to be created
* @param generator The Custom generator plugin to use.
* @param generator The Custom generator plugin to use. Ignored.
* @return True if the world is copied successfully, false if not.
* @deprecated Use {@link #cloneWorld(String, String)} instead.
*/
@Deprecated
boolean cloneWorld(String oldName, String newName, String generator);
/**
* Remove the world from the Multiverse list, from the
* config and deletes the folder.
* Make a copy of a world.
*
* @param name The name of the world to remove
* @param oldName
* Name of world to be copied
* @param newName
* Name of world to be created
* @return True if the world is copied successfully, false if not.
*/
boolean cloneWorld(String oldName, String newName);
/**
* Remove the world from the Multiverse list, from the config and deletes
* the folder.
*
* @param name
* The name of the world to remove
* @return True if success, false if failure.
*/
boolean deleteWorld(String name);
@ -288,4 +302,16 @@ public interface MVWorldManager {
boolean regenWorld(String name, boolean useNewSeed, boolean randomSeed, String seed);
boolean isKeepingSpawnInMemory(World world);
/**
* Checks whether Multiverse knows about a provided unloaded world. This
* method will check the parameter against the alias mappings.
*
* @param name The name of the unloaded world
* @param includeLoaded The value to return if the world is loaded
*
* @return True if the world exists and is unloaded. False if the world
* does not exist. {@code includeLoaded} if the world exists and is loaded.
*/
boolean hasUnloadedWorld(String name, boolean includeLoaded);
}

View File

@ -7,15 +7,14 @@
package com.onarandombox.MultiverseCore.commands;
import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.api.MVWorldManager;
import com.pneumaticraft.commandhandler.CommandHandler;
import java.util.List;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.PermissionDefault;
import java.util.ArrayList;
import java.util.List;
import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.api.MVWorldManager;
/**
* Creates a clone of a world.
@ -34,27 +33,23 @@ public class CloneCommand extends MultiverseCommand {
this.addKey("mv clone");
this.addCommandExample("/mv clone " + ChatColor.GOLD + "world" + ChatColor.GREEN + " world_backup");
this.addCommandExample("/mv clone " + ChatColor.GOLD + "skyblock_pristine" + ChatColor.GREEN + " skyblock");
this.addCommandExample("To clone a world that uses a generator:");
this.addCommandExample("/mv clone " + ChatColor.GOLD + "CleanRoom"
+ ChatColor.GREEN + " CleanRoomCopy" + ChatColor.DARK_AQUA + " -g CleanRoomGenerator");
this.setPermission("multiverse.core.clone", "Clones a world.", PermissionDefault.OP);
this.worldManager = this.plugin.getMVWorldManager();
}
@Override
public void runCommand(CommandSender sender, List<String> args) {
Class<?>[] paramTypes = {String.class, String.class, String.class};
List<Object> objectArgs = new ArrayList<Object>();
objectArgs.add(args.get(0));
objectArgs.add(args.get(1));
objectArgs.add(CommandHandler.getFlag("-g", args));
if (!this.worldManager.isMVWorld(args.get(0))) {
String oldName = args.get(0);
if (!this.worldManager.hasUnloadedWorld(oldName, true)) {
// If no world was found, we can't clone.
sender.sendMessage("Sorry, Multiverse doesn't know about world " + args.get(0) + ", so we can't clone it!");
sender.sendMessage("Sorry, Multiverse doesn't know about world " + oldName + ", so we can't clone it!");
sender.sendMessage("Check the " + ChatColor.GREEN + "/mv list" + ChatColor.WHITE + " command to verify it is listed.");
return;
}
this.plugin.getCommandHandler().queueCommand(sender, "mvclone", "cloneWorld", objectArgs,
paramTypes, ChatColor.GREEN + "World Cloned!", ChatColor.RED + "World could NOT be cloned!");
if (this.plugin.getMVWorldManager().cloneWorld(oldName, args.get(1))) {
sender.sendMessage(ChatColor.GREEN + "World cloned!");
} else {
sender.sendMessage(ChatColor.RED + "World could NOT be cloned!");
}
}
}

View File

@ -7,30 +7,6 @@
package com.onarandombox.MultiverseCore.utils;
import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.MVWorld;
import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.WorldProperties;
import com.onarandombox.MultiverseCore.api.MVWorldManager;
import com.onarandombox.MultiverseCore.api.MultiverseWorld;
import com.onarandombox.MultiverseCore.api.SafeTTeleporter;
import com.onarandombox.MultiverseCore.api.WorldPurger;
import com.onarandombox.MultiverseCore.event.MVWorldDeleteEvent;
import com.onarandombox.MultiverseCore.exceptions.PropertyDoesNotExistException;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.World.Environment;
import org.bukkit.WorldCreator;
import org.bukkit.WorldType;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.Plugin;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
@ -45,7 +21,30 @@ import java.util.Stack;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.World.Environment;
import org.bukkit.WorldCreator;
import org.bukkit.WorldType;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.Plugin;
import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.MVWorld;
import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.WorldProperties;
import com.onarandombox.MultiverseCore.api.MVWorldManager;
import com.onarandombox.MultiverseCore.api.MultiverseWorld;
import com.onarandombox.MultiverseCore.api.SafeTTeleporter;
import com.onarandombox.MultiverseCore.api.WorldPurger;
import com.onarandombox.MultiverseCore.event.MVWorldDeleteEvent;
/**
* Public facing API to add/remove Multiverse worlds.
@ -93,15 +92,35 @@ public class WorldManager implements MVWorldManager {
/**
* {@inheritDoc}
* @deprecated Use {@link #cloneWorld(String, String)} instead.
*/
@Override
@Deprecated
public boolean cloneWorld(String oldName, String newName, String generator) {
// Make sure we don't already know about the new world.
if (this.isMVWorld(newName)) {
return false;
return this.cloneWorld(oldName, newName);
}
/**
* {@inheritDoc}
*/
@Override
public boolean cloneWorld(String oldName, String newName) {
// Make sure we already know about the old world and that we don't
// already know about the new world.
if (!this.worldsFromTheConfig.containsKey(oldName)) {
for (Map.Entry<String, WorldProperties> entry : this.worldsFromTheConfig.entrySet()) {
if (oldName.equals(entry.getValue().getAlias())) {
oldName = entry.getKey();
break;
}
}
if (!this.worldsFromTheConfig.containsKey(oldName)) {
Logging.warning("Old world '%s' does not exist", oldName);
return false;
}
}
// Make sure the old world is actually a world!
if (this.getUnloadedWorlds().contains(oldName) || !this.isMVWorld(oldName)) {
if (this.isMVWorld(newName)) {
Logging.warning("New world '%s' already exists", newName);
return false;
}
@ -110,63 +129,80 @@ public class WorldManager implements MVWorldManager {
// Make sure the new world doesn't exist outside of multiverse.
if (newWorldFile.exists()) {
Logging.warning("File for new world '%s' already exists", newName);
return false;
}
unloadWorld(oldName);
removePlayersFromWorld(oldName);
Logging.config("Copying data for world '%s'", oldName);
try {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
FileUtils.copyFolder(oldWorldFile, newWorldFile, Logger.getLogger("Minecraft"));
}
});
t.start();
try {
t.join();
} catch (InterruptedException e) {
// do nothing
// Load the old world... but just the metadata.
boolean wasJustLoaded = false;
boolean wasLoadSpawn = false;
if (this.plugin.getServer().getWorld(oldName) == null) {
wasJustLoaded = true;
WorldProperties props = this.worldsFromTheConfig.get(oldName);
wasLoadSpawn = props.isKeepingSpawnInMemory();
if (wasLoadSpawn) {
// No chunks please.
props.setKeepSpawnInMemory(false);
}
File uidFile = new File(newWorldFile, "uid.dat");
uidFile.delete();
} catch (NullPointerException e) {
e.printStackTrace();
if (!this.loadWorld(oldName)) {
return false;
}
this.plugin.getServer().getWorld(oldName).setAutoSave(false);
}
// Grab a bit of metadata from the old world.
MVWorld oldWorld = (MVWorld) getMVWorld(oldName);
Environment environment = oldWorld.getEnvironment();
String seedString = oldWorld.getSeed() + "";
WorldType worldType = oldWorld.getWorldType();
Boolean generateStructures = oldWorld.getCBWorld().canGenerateStructures();
String generator = oldWorld.getGenerator();
boolean useSpawnAdjust = oldWorld.getAdjustSpawn();
// Don't need the loaded world anymore.
if (wasJustLoaded) {
this.unloadWorld(oldName, true);
oldWorld = null;
if (wasLoadSpawn) {
this.worldsFromTheConfig.get(oldName).setKeepSpawnInMemory(true);
}
}
boolean wasAutoSave = false;
if (oldWorld != null && oldWorld.getCBWorld().isAutoSave()) {
wasAutoSave = true;
Logging.config("Saving world '%s'", oldName);
oldWorld.getCBWorld().setAutoSave(false);
oldWorld.getCBWorld().save();
}
Logging.config("Copying files for world '%s'", oldName);
if (!FileUtils.copyFolder(oldWorldFile, newWorldFile, Logging.getLogger())) {
Logging.warning("Failed to copy files for world '%s', see the log info", newName);
return false;
}
Logging.fine("Kind of copied stuff");
if (oldWorld != null && wasAutoSave) {
oldWorld.getCBWorld().setAutoSave(true);
}
WorldCreator worldCreator = new WorldCreator(newName);
Logging.fine("Started to copy settings");
worldCreator.copy(this.getMVWorld(oldName).getCBWorld());
Logging.fine("Copied lots of settings");
File uidFile = new File(newWorldFile, "uid.dat");
if (uidFile.exists() && !uidFile.delete()) {
Logging.warning("Failed to delete unique ID file for world '%s'", newName);
return false;
}
boolean useSpawnAdjust = this.getMVWorld(oldName).getAdjustSpawn();
Logging.fine("Copied more settings");
Environment environment = worldCreator.environment();
Logging.fine("Copied most settings");
if (newWorldFile.exists()) {
Logging.fine("Succeeded at copying stuff");
if (this.addWorld(newName, environment, null, null, null, generator, useSpawnAdjust)) {
Logging.fine("Succeeded at copying files");
if (this.addWorld(newName, environment, seedString, worldType, generateStructures, generator, useSpawnAdjust)) {
// getMVWorld() doesn't actually return an MVWorld
Logging.fine("Succeeded at importing stuff");
Logging.fine("Succeeded at importing world");
MVWorld newWorld = (MVWorld) this.getMVWorld(newName);
MVWorld oldWorld = (MVWorld) this.getMVWorld(oldName);
newWorld.copyValues(oldWorld);
try {
// don't keep the alias the same -- that would be useless
newWorld.setPropertyValue("alias", newName);
} catch (PropertyDoesNotExistException e) {
// this should never happen
throw new RuntimeException(e);
}
newWorld.copyValues(this.worldsFromTheConfig.get(oldName));
// don't keep the alias the same -- that would be useless
newWorld.setAlias(null);
return true;
}
}
Logging.warning("Failed to copy files for world '%s', see the log info", newName);
return false;
}
@ -846,4 +882,17 @@ public class WorldManager implements MVWorldManager {
public FileConfiguration getConfigWorlds() {
return this.configWorlds;
}
@Override
public boolean hasUnloadedWorld(String name, boolean includeLoaded) {
if (getMVWorld(name) != null) {
return includeLoaded;
}
for (Map.Entry<String, WorldProperties> entry : this.worldsFromTheConfig.entrySet()) {
if (name.equals(entry.getKey()) || name.equals(entry.getValue().getAlias())) {
return true;
}
}
return false;
}
}