Implement UUID migration.

This commit is contained in:
sk89q 2014-08-02 21:03:12 -07:00
parent d87a335e85
commit fafd3aba6a
8 changed files with 377 additions and 28 deletions

View File

@ -102,6 +102,8 @@ public class ConfigurationManager {
public boolean usePlayerTeleports;
public boolean deopOnJoin;
public boolean blockInGameOp;
public boolean migrateRegionsToUuid;
public boolean keepUnresolvedNames;
public Map<String, String> hostKeys = new HashMap<String, String>();
/**
@ -142,6 +144,8 @@ public void load() {
config.removeProperty("suppress-tick-sync-warnings");
useRegionsScheduler = config.getBoolean("regions.use-scheduler", true);
migrateRegionsToUuid = config.getBoolean("regions.uuid-migration.perform-on-next-start", true);
keepUnresolvedNames = config.getBoolean("regions.uuid-migration.keep-names-that-lack-uuids", true);
useRegionsCreatureSpawnEvent = config.getBoolean("regions.use-creature-spawn-event", true);
autoGodMode = config.getBoolean("auto-invincible", config.getBoolean("auto-invincible-permission", false));
config.removeProperty("auto-invincible-permission");
@ -177,10 +181,6 @@ public void load() {
}
config.setHeader(CONFIG_HEADER);
if (!config.save()) {
plugin.getLogger().severe("Error saving configuration!");
}
}
/**
@ -190,6 +190,13 @@ public void unload() {
worlds.clear();
}
public void disableUuidMigration() {
config.setProperty("regions.uuid-migration.perform-on-next-start", false);
if (!config.save()) {
plugin.getLogger().severe("Error saving configuration!");
}
}
/**
* Get the configuration for a world.
*

View File

@ -42,10 +42,14 @@ public boolean mayForceLoadRegions() {
public boolean mayForceSaveRegions() {
return hasPluginPermission("region.save");
}
public boolean mayMigrateRegionStore() {
return hasPluginPermission("region.migratedb");
}
public boolean mayMigrateRegionNames() {
return hasPluginPermission("region.migrateuuid");
}
public boolean mayDefine() {
return hasPluginPermission("region.define");

View File

@ -52,6 +52,9 @@
import com.sk89q.worldguard.internal.listener.ChestProtectionListener;
import com.sk89q.worldguard.internal.listener.RegionProtectionListener;
import com.sk89q.worldguard.protection.GlobalRegionManager;
import com.sk89q.worldguard.protection.databases.ProtectionDatabaseException;
import com.sk89q.worldguard.protection.databases.migrator.MigrationException;
import com.sk89q.worldguard.protection.databases.migrator.UUIDMigrator;
import com.sk89q.worldguard.protection.databases.util.UnresolvedNamesException;
import com.sk89q.worldguard.protection.managers.RegionManager;
import com.sk89q.worldguard.util.FatalConfigurationLoadingException;
@ -154,6 +157,21 @@ public void run() {
// Need to create the plugins/WorldGuard folder
getDataFolder().mkdirs();
File cacheDir = new File(getDataFolder(), "cache");
cacheDir.mkdirs();
try {
profileCache = new SQLiteCache(new File(cacheDir, "profiles.sqlite"));
} catch (IOException e) {
getLogger().log(Level.WARNING, "Failed to initialize SQLite profile cache");
profileCache = new HashMapCache();
}
profileService = new CacheForwardingService(
new CombinedProfileService(
BukkitPlayerService.getInstance(),
HttpRepositoryService.forMinecraft()),
profileCache);
PermissionsResolverManager.initialize(this);
// This must be done before configuration is loaded
@ -162,10 +180,24 @@ public void run() {
try {
// Load the configuration
configuration.load();
getLogger().info("Loading region data...");
globalRegionManager.preload();
migrateRegionUniqueIds(); // Migrate to UUIDs
} catch (FatalConfigurationLoadingException e) {
e.printStackTrace();
getLogger().log(Level.WARNING, "Encountered fatal error while loading configuration", e);
getServer().shutdown();
getLogger().log(Level.WARNING, "\n" +
"******************************************************\n" +
"* Failed to load WorldGuard configuration!\n" +
"* \n" +
"* Shutting down the server just in case...\n" +
"* \n" +
"* The error should be printed above this message. If you can't\n" +
"* figure out the problem, ask us on our forums:\n" +
"* http://forum.enginehub.org\n" +
"******************************************************\n");
}
// Migrate regions after the regions were loaded because
@ -207,21 +239,6 @@ public void run() {
}
worldListener.registerEvents();
File cacheDir = new File(getDataFolder(), "cache");
cacheDir.mkdirs();
try {
profileCache = new SQLiteCache(new File(cacheDir, "profiles.sqlite"));
} catch (IOException e) {
getLogger().log(Level.WARNING, "Failed to initialize SQLite profile cache");
profileCache = new HashMapCache();
}
profileService = new CacheForwardingService(
new CombinedProfileService(
BukkitPlayerService.getInstance(),
HttpRepositoryService.forMinecraft()),
profileCache);
if (!configuration.hasCommandBookGodMode()) {
// Check god mode for existing players, if any
for (Player player : getServer().getOnlinePlayers()) {
@ -260,6 +277,35 @@ public void onDisable() {
this.getServer().getScheduler().cancelTasks(this);
}
private void migrateRegionUniqueIds() {
try {
if (configuration.migrateRegionsToUuid) {
UUIDMigrator migrator = new UUIDMigrator(profileService, getLogger());
migrator.readConfiguration(configuration);
List<RegionManager> managers = globalRegionManager.getLoaded();
// Try migration
if (migrator.migrate(managers)) {
getLogger().info("Now saving regions... this may take a while.");
for (RegionManager manager : managers) {
manager.save();
}
getLogger().info(
"Regions saved after UUID migration! This won't happen again unless " +
"you change the relevant configuration option in WorldGuard's config.");
configuration.disableUuidMigration();
}
}
} catch (MigrationException e) {
getLogger().log(Level.WARNING, "Failed to migrate regions to use UUIDs instead of player names", e);
} catch (ProtectionDatabaseException e) {
getLogger().log(Level.WARNING, "Failed to save regions after UUID conversion", e);
}
}
@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
try {

View File

@ -34,14 +34,17 @@
import com.sk89q.worldedit.bukkit.selections.Polygonal2DSelection;
import com.sk89q.worldedit.bukkit.selections.Selection;
import com.sk89q.worldguard.LocalPlayer;
import com.sk89q.worldguard.bukkit.LoggerToChatHandler;
import com.sk89q.worldguard.bukkit.RegionPermissionModel;
import com.sk89q.worldguard.bukkit.WorldConfiguration;
import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
import com.sk89q.worldguard.protection.ApplicableRegionSet;
import com.sk89q.worldguard.protection.databases.ProtectionDatabaseException;
import com.sk89q.worldguard.protection.databases.RegionDBUtil;
import com.sk89q.worldguard.protection.databases.migrator.AbstractDatabaseMigrator;
import com.sk89q.worldguard.protection.databases.migrator.MigrationException;
import com.sk89q.worldguard.protection.databases.migrator.MigratorKey;
import com.sk89q.worldguard.protection.databases.migrator.UUIDMigrator;
import com.sk89q.worldguard.protection.flags.DefaultFlag;
import com.sk89q.worldguard.protection.flags.Flag;
import com.sk89q.worldguard.protection.flags.InvalidFlagFormat;
@ -65,6 +68,8 @@
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Implements the /region commands for WorldGuard.
@ -1199,6 +1204,61 @@ public void migrateDB(CommandContext args, CommandSender sender) throws CommandE
"If you wish to use the destination format as your new backend, please update your config and reload WorldGuard.");
}
/**
* Migrate the region databases to use UUIDs rather than name.
*
* @param args the arguments
* @param sender the sender
* @throws CommandException any error
*/
@Command(aliases = {"migrateuuid"},
desc = "Migrate loaded databases to use UUIDs", max = 0)
public void migrateUuid(CommandContext args, CommandSender sender) throws CommandException {
// Check permissions
if (!getPermissionModel(sender).mayMigrateRegionNames()) {
throw new CommandPermissionsException();
}
LoggerToChatHandler handler = null;
Logger minecraftLogger = null;
if (sender instanceof Player) {
handler = new LoggerToChatHandler(sender);
handler.setLevel(Level.ALL);
minecraftLogger = Logger.getLogger("Minecraft");
minecraftLogger.addHandler(handler);
}
try {
UUIDMigrator migrator = new UUIDMigrator(plugin.getProfileService(), plugin.getLogger());
migrator.readConfiguration(plugin.getGlobalStateManager());
List<RegionManager> managers = plugin.getGlobalRegionManager().getLoaded();
// Try migration
if (migrator.migrate(managers)) {
sender.sendMessage(ChatColor.YELLOW + "Now saving regions... this may take a while.");
for (RegionManager manager : managers) {
manager.save();
}
sender.sendMessage(ChatColor.YELLOW + "Migration complete!");
} else {
sender.sendMessage(ChatColor.YELLOW + "There were no names to migrate.");
}
} catch (ProtectionDatabaseException e) {
plugin.getLogger().log(Level.WARNING, "Failed to save", e);
throw new CommandException("Error encountered while saving: " + e.getMessage());
} catch (MigrationException e) {
plugin.getLogger().log(Level.WARNING, "Failed to migrate", e);
throw new CommandException("Error encountered while migrating: " + e.getMessage());
} finally {
if (minecraftLogger != null) {
minecraftLogger.removeHandler(handler);
}
}
}
/**
* Teleport to a region
*

View File

@ -39,8 +39,46 @@
*/
public class DefaultDomain implements Domain {
private final PlayerDomain playerDomain = new PlayerDomain();
private final GroupDomain groupDomain = new GroupDomain();
private PlayerDomain playerDomain = new PlayerDomain();
private GroupDomain groupDomain = new GroupDomain();
/**
* Get the domain that holds the players.
*
* @return a domain
*/
public PlayerDomain getPlayerDomain() {
return playerDomain;
}
/**
* Set a new player domain.
*
* @param playerDomain a domain
*/
public void setPlayerDomain(PlayerDomain playerDomain) {
checkNotNull(playerDomain);
this.playerDomain = playerDomain;
}
/**
* Set the domain that holds the groups.
*
* @return a domain
*/
public GroupDomain getGroupDomain() {
return groupDomain;
}
/**
* Set a new group domain.
*
* @param groupDomain a domain
*/
public void setGroupDomain(GroupDomain groupDomain) {
checkNotNull(groupDomain);
this.groupDomain = groupDomain;
}
/**
* Add the given player to the domain, identified by the player's name.

View File

@ -25,9 +25,9 @@
import com.sk89q.worldguard.bukkit.WorldConfiguration;
import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
import com.sk89q.worldguard.protection.databases.MySQLDatabase;
import com.sk89q.worldguard.protection.databases.YAMLDatabase;
import com.sk89q.worldguard.protection.databases.ProtectionDatabase;
import com.sk89q.worldguard.protection.databases.ProtectionDatabaseException;
import com.sk89q.worldguard.protection.databases.YAMLDatabase;
import com.sk89q.worldguard.protection.flags.StateFlag;
import com.sk89q.worldguard.protection.managers.PRTreeRegionManager;
import com.sk89q.worldguard.protection.managers.RegionManager;
@ -38,7 +38,10 @@
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
@ -247,6 +250,21 @@ public RegionManager get(World world) {
return manager;
}
/**
* Get a list of loaded region managers.
*
* @return an unmodifiable list
*/
public List<RegionManager> getLoaded() {
List<RegionManager> list = new ArrayList<RegionManager>();
for (RegionManager manager : managers.values()) {
if (manager != null) {
list.add(manager);
}
}
return Collections.unmodifiableList(list);
}
/**
* Returns whether the player can bypass.
*

View File

@ -19,14 +19,14 @@
package com.sk89q.worldguard.protection.databases.migrator;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import com.sk89q.worldguard.protection.databases.ProtectionDatabase;
import com.sk89q.worldguard.protection.databases.ProtectionDatabaseException;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public abstract class AbstractDatabaseMigrator implements DatabaseMigrator {
private static HashMap<MigratorKey, Class<? extends AbstractDatabaseMigrator>> migrators =

View File

@ -0,0 +1,176 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldGuard team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldguard.protection.databases.migrator;
import com.google.common.base.Predicate;
import com.sk89q.squirrelid.Profile;
import com.sk89q.squirrelid.resolver.ProfileService;
import com.sk89q.worldguard.bukkit.ConfigurationManager;
import com.sk89q.worldguard.domains.DefaultDomain;
import com.sk89q.worldguard.domains.PlayerDomain;
import com.sk89q.worldguard.protection.managers.RegionManager;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.google.common.base.Preconditions.checkNotNull;
public class UUIDMigrator {
private static final int LOG_DELAY = 5000;
private final Timer timer = new Timer();
private final ProfileService profileService;
private final Logger logger;
private final ConcurrentMap<String, UUID> resolvedNames = new ConcurrentHashMap<String, UUID>();
private final Set<String> unresolvedNames = new HashSet<String>();
private boolean keepUnresolvedNames = true;
public UUIDMigrator(ProfileService profileService, Logger logger) {
checkNotNull(profileService);
checkNotNull(logger);
this.profileService = profileService;
this.logger = logger;
}
public boolean migrate(Collection<RegionManager> managers) throws MigrationException {
Set<String> names = new HashSet<String>();
// Name scan pass
logger.log(Level.INFO, "UUID migrator: Gathering names to convert...");
for (RegionManager manager : managers) {
scanForNames(manager, names);
}
if (names.isEmpty()) {
logger.log(Level.INFO, "UUID migrator: No names to convert!");
return false;
}
TimerTask resolvedTask = new ResolvedNamesTimerTask();
try {
timer.schedule(resolvedTask, LOG_DELAY, LOG_DELAY);
logger.log(Level.INFO, "UUID migrator: Resolving " + names.size() + " name(s) into UUIDs... this may take a while.");
profileService.findAllByName(names, new Predicate<Profile>() {
@Override
public boolean apply(Profile profile) {
resolvedNames.put(profile.getName().toLowerCase(), profile.getUniqueId());
return true;
}
});
} catch (IOException e) {
throw new MigrationException("The name -> UUID service failed", e);
} catch (InterruptedException e) {
throw new MigrationException("The migration was interrupted");
} finally {
resolvedTask.cancel();
}
logger.log(Level.INFO, "UUID migrator: UUIDs resolved... now migrating all regions to UUIDs where possible...");
for (RegionManager manager : managers) {
convertToUniqueIds(manager);
}
if (!unresolvedNames.isEmpty()) {
if (keepUnresolvedNames) {
logger.log(Level.WARNING,
"UUID migrator: Some member and owner names do not seem to exist or own Minecraft so they " +
"could not be converted into UUIDs. They have been left as names, but the conversion can " +
"be re-run with 'keep-names-that-lack-uuids' set to false in the configuration in " +
"order to remove these names. Leaving the names means that someone can register with one of " +
"these names in the future and become that player.");
} else {
logger.log(Level.WARNING,
"UUID migrator: Some member and owner names do not seem to exist or own Minecraft so they " +
"could not be converted into UUIDs. These names have been removed.");
}
}
logger.log(Level.INFO, "UUID migrator: Migration finished!");
return true;
}
private void scanForNames(RegionManager manager, Set<String> target) {
for (ProtectedRegion region : manager.getRegions().values()) {
target.addAll(region.getOwners().getPlayers());
target.addAll(region.getMembers().getPlayers());
}
}
private void convertToUniqueIds(RegionManager manager) {
for (ProtectedRegion region : manager.getRegions().values()) {
convertToUniqueIds(region.getOwners());
convertToUniqueIds(region.getMembers());
}
}
private void convertToUniqueIds(DefaultDomain domain) {
PlayerDomain playerDomain = new PlayerDomain();
for (UUID uuid : domain.getUniqueIds()) {
playerDomain.addPlayer(uuid);
}
for (String name : domain.getPlayers()) {
UUID uuid = resolvedNames.get(name.toLowerCase());
if (uuid != null) {
playerDomain.addPlayer(uuid);
} else {
if (keepUnresolvedNames) {
playerDomain.addPlayer(name);
}
unresolvedNames.add(name);
}
}
domain.setPlayerDomain(playerDomain);
}
public boolean isKeepUnresolvedNames() {
return keepUnresolvedNames;
}
public void setKeepUnresolvedNames(boolean keepUnresolvedNames) {
this.keepUnresolvedNames = keepUnresolvedNames;
}
public void readConfiguration(ConfigurationManager config) {
setKeepUnresolvedNames(config.keepUnresolvedNames);
}
private class ResolvedNamesTimerTask extends TimerTask {
@Override
public void run() {
logger.info("UUID migrator: UUIDs have been found for " + resolvedNames.size() + " name(s)...");
}
}
}