Migration preparation done.

* NEW: Added Migration worker.
* NEW: Added the maptool-migration op-only command.
* NEW: Added pre-v3 map files loading support.
* NEW: Implemented v3 migration preparation.
* NEW: Commands : added support for runtime permission checking.
* NEW: Worker threads are now named based upon their worker's name.
* BUG: Fix MapItemManager exiting if it has not been properly initialized.
This commit is contained in:
Prokopyl 2015-04-09 22:32:51 +02:00
parent aa27636962
commit 90dd9eb24d
14 changed files with 974 additions and 18 deletions

View File

@ -23,6 +23,7 @@ import fr.moribus.imageonmap.image.ImageIOExecutor;
import fr.moribus.imageonmap.image.ImageRendererExecutor;
import fr.moribus.imageonmap.image.MapInitEvent;
import fr.moribus.imageonmap.map.MapManager;
import fr.moribus.imageonmap.migration.Migrator;
import fr.moribus.imageonmap.migration.V3Migrator;
import fr.moribus.imageonmap.ui.MapItemManager;
import java.io.File;
@ -82,6 +83,7 @@ public final class ImageOnMap extends JavaPlugin
Commands.init(this);
MapInitEvent.init(this);
MapItemManager.init();
Migrator.startWorker();
}
@Override
@ -91,6 +93,7 @@ public final class ImageOnMap extends JavaPlugin
ImageRendererExecutor.stop();
MapManager.exit();
MapItemManager.exit();
Migrator.stopWorker();
}
private File checkPluginDirectory(File primaryFile, File... alternateFiles) throws IOException

View File

@ -65,12 +65,18 @@ abstract public class Command
return null;
}
public boolean hasPermission(CommandSender sender)
{
if(!commandGroup.getPermission().hasPermission(sender)) return false;
return canExecute(sender);
}
public void execute(CommandSender sender, String[] args)
{
this.sender = sender; this.args = args;
try
{
if(!canExecute(sender))
if(!hasPermission(sender))
throw new CommandException(this, Reason.SENDER_NOT_AUTHORIZED);
run();
}
@ -177,16 +183,26 @@ abstract public class Command
///////////// Methods for command execution /////////////
protected void info(String message)
static protected void info(CommandSender sender, String message)
{
sender.sendMessage("§7" + message);
}
protected void warning(String message)
protected void info(String message)
{
info(sender, message);
}
static protected void warning(CommandSender sender, String message)
{
sender.sendMessage("§c" + message);
}
protected void warning(String message)
{
info(sender, message);
}
protected void error(String message) throws CommandException
{
throw new CommandException(this, Reason.COMMAND_ERROR, message);

View File

@ -0,0 +1,55 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.commands;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
public abstract class CommandPermission
{
abstract public boolean hasPermission(CommandSender sender);
static public final CommandPermission OP_ONLY = new CommandPermission()
{
@Override
public boolean hasPermission(CommandSender sender)
{
return sender.isOp();
}
};
static public CommandPermission bukkitPermission(final String permission)
{
return new CommandPermission()
{
@Override
public boolean hasPermission(CommandSender sender)
{
return sender.hasPermission(permission);
}
};
}
static public CommandPermission bukkitPermission(Plugin plugin, String permission)
{
final String permissionName = plugin.getName().toLowerCase()
+ "." + permission.toLowerCase();
return bukkitPermission(permissionName);
}
}

View File

@ -18,8 +18,10 @@
package fr.moribus.imageonmap.commands;
import fr.moribus.imageonmap.ImageOnMap;
import fr.moribus.imageonmap.PluginLogger;
import fr.moribus.imageonmap.commands.maptool.*;
import fr.moribus.imageonmap.commands.migration.*;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
@ -48,16 +50,20 @@ public enum Commands implements TabCompleter, CommandExecutor
DeleteNoConfirmCommand.class,
GetRemainingCommand.class
),
TOMAP(MAPTOOL, NewCommand.class, "tomap");
TOMAP(MAPTOOL, NewCommand.class, "tomap"),
MAPTOOL_MIGRATION(new String[]{"maptool-migration"}, CommandPermission.OP_ONLY,
StartCommand.class
);
static private JavaPlugin plugin;
static private final Commands[] commandGroups = Commands.class.getEnumConstants();
private final Commands shortcutCommandGroup;
private final String[] names;
private final Class<? extends Command>[] commandsClasses;
private final ArrayList<Command> commands = new ArrayList<>();
private final HashMap<String, String> commandsDescriptions = new HashMap<>();
private final CommandPermission commandPermission;
private String description = "";
private Commands(Commands shortcutCommandGroup, Class<? extends Command> commandClass, String ... names)
@ -65,16 +71,23 @@ public enum Commands implements TabCompleter, CommandExecutor
this.names = names;
this.commandsClasses = new Class[]{commandClass};
this.shortcutCommandGroup = shortcutCommandGroup;
this.commandPermission = shortcutCommandGroup.getPermission();
initCommands();
}
private Commands(String[] names, CommandPermission permission, Class<? extends Command> ... commandsClasses)
{
this.names = names;
this.commandsClasses = commandsClasses;
this.shortcutCommandGroup = null;
this.commandPermission = permission;
initDescriptions();
initCommands();
}
private Commands(String[] names, Class<? extends Command> ... commandsClasses)
{
this.names = names;
this.commandsClasses = commandsClasses;
this.shortcutCommandGroup = null;
initDescriptions();
initCommands();
this(names, CommandPermission.bukkitPermission(ImageOnMap.getPlugin(), names[0]), commandsClasses);
}
private void initDescriptions()
@ -328,5 +341,6 @@ public enum Commands implements TabCompleter, CommandExecutor
public String getDescription(String commandName) { return commandsDescriptions.get(commandName); }
public boolean isShortcutCommand() { return shortcutCommandGroup != null; }
public Commands getShortcutCommandGroup() { return shortcutCommandGroup; }
public CommandPermission getPermission() { return commandPermission; }
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.commands.migration;
import fr.moribus.imageonmap.commands.*;
import fr.moribus.imageonmap.migration.Migrator;
import fr.moribus.imageonmap.worker.WorkerCallback;
import org.bukkit.command.CommandSender;
@CommandInfo(name = "start")
public class StartCommand extends Command
{
public StartCommand(Commands commandGroup) {
super(commandGroup);
}
@Override
protected void run() throws CommandException
{
final CommandSender cmdSender = sender;
if(Migrator.isMigrationRunning())
{
error("A migration process is already running. Check console for details.");
}
else
{
Migrator.runMigration(new WorkerCallback<Void>()
{
@Override
public void finished(Void result)
{
info(cmdSender, "Migration finished. See console for details.");
}
@Override
public void errored(Throwable exception)
{
warning(cmdSender, "Migration ended unexpectedly. See console for details.");
}
});
info("Migration started. See console for details.");
}
}
}

View File

@ -29,9 +29,17 @@ public class PosterMap extends ImageMap
protected final int columnCount;
protected final int rowCount;
public PosterMap(UUID userUUID, short[] mapsIDs, String id, String name, int columnCount, int rowCount)
{
super(userUUID, Type.POSTER, id, name);
this.mapsIDs = mapsIDs;
this.columnCount = columnCount;
this.rowCount = rowCount;
}
public PosterMap(UUID userUUID, short[] mapsIDs, int columnCount, int rowCount)
{
super(userUUID, Type.POSTER);
super(userUUID, Type.POSTER, null, null);
this.mapsIDs = mapsIDs;
this.columnCount = columnCount;
this.rowCount = rowCount;

View File

@ -0,0 +1,74 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.migration;
import fr.moribus.imageonmap.ImageOnMap;
import fr.moribus.imageonmap.worker.Worker;
import fr.moribus.imageonmap.worker.WorkerCallback;
import fr.moribus.imageonmap.worker.WorkerRunnable;
public class Migrator extends Worker
{
static private Migrator instance;
static private V3Migrator migrator;
static public void startWorker()
{
if(instance != null) stopWorker();
instance = new Migrator();
instance.init();
}
static public void stopWorker()
{
instance.exit();
instance = null;
}
private Migrator()
{
super("Migration");
}
static public boolean isMigrationStarted()
{
return migrator != null;
}
static public boolean isMigrationRunning()
{
return migrator != null && migrator.isRunning();
}
static public boolean runMigration(WorkerCallback<Void> callback)
{
if(isMigrationRunning()) return false;
if(!isMigrationStarted()) migrator = new V3Migrator(ImageOnMap.getPlugin());
instance.submitQuery(new WorkerRunnable<Void>()
{
@Override
public Void run() throws Throwable
{
migrator.run();
return null;
}
}, callback);
return true;
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.migration;
import java.util.List;
import org.bukkit.configuration.InvalidConfigurationException;
class OldSavedMap
{
private final short mapId;
private final String mapName;
private final String userName;
public OldSavedMap(Object rawData) throws InvalidConfigurationException
{
List<String> data;
try
{
data = (List<String>) rawData;
}
catch(ClassCastException ex)
{
throw new InvalidConfigurationException("Invalid map data : " + ex.getMessage());
}
if(data.size() < 3)
throw new InvalidConfigurationException("Map data too short (given : " + data.size() + ", expected 3)");
try
{
mapId = Short.parseShort(data.get(0));
}
catch(NumberFormatException ex)
{
throw new InvalidConfigurationException("Invalid map ID : " + ex.getMessage());
}
mapName = data.get(1);
userName = data.get(2);
}
public short getMapId() {return mapId;}
public String getUserName() {return userName;}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.migration;
import java.util.List;
import org.bukkit.configuration.InvalidConfigurationException;
class OldSavedPoster
{
private String userName;
private short[] mapsIds;
public OldSavedPoster(Object rawData) throws InvalidConfigurationException
{
List<String> data;
try
{
data = (List<String>) rawData;
}
catch(ClassCastException ex)
{
throw new InvalidConfigurationException("Invalid map data : " + ex.getMessage());
}
if(data.size() < 2)
throw new InvalidConfigurationException("Poster data too short (given : " + data.size() + ", expected at least 2)");
userName = data.get(0);
mapsIds = new short[data.size() - 1];
for(int i = 1, c = data.size(); i < c; i++)
{
try
{
mapsIds[i - 1] = Short.parseShort(data.get(i));
}
catch(NumberFormatException ex)
{
throw new InvalidConfigurationException("Invalid map ID : " + ex.getMessage());
}
}
}
public boolean contains(OldSavedMap map)
{
short mapId = map.getMapId();
for(int i = 0, c = mapsIds.length; i < c; i++)
{
if(mapsIds[i] == mapId) return true;
}
return false;
}
public String getUserName() {return userName;}
}

View File

@ -0,0 +1,119 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.migration;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
abstract public class UUIDFetcher
{
static private final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft"; //The URI of the name->UUID API from Mojang
static private final JSONParser jsonParser = new JSONParser();
static public Map<String, UUID> fetch(List<String> names) throws IOException
{
Map<String, UUID> uuidMap = new HashMap<String, UUID>();
HttpURLConnection connection = createConnection();
writeBody(connection, names);
JSONArray array;
try
{
array = (JSONArray) jsonParser.parse(new InputStreamReader(connection.getInputStream()));
}
catch(ParseException ex)
{
throw new IOException("Invalid response from server, unable to parse received JSON : " + ex.toString());
}
for (Object profile : array)
{
JSONObject jsonProfile = (JSONObject) profile;
String id = (String) jsonProfile.get("id");
String name = (String) jsonProfile.get("name");
uuidMap.put(name, fromMojangUUID(id));
}
return uuidMap;
}
static public Map<String, UUID> fetch(List<String> names, int limitByRequest) throws IOException, InterruptedException
{
Map<String, UUID> UUIDs = new HashMap<String, UUID>();
int requests = (names.size() / limitByRequest) + 1;
List<String> tempNames;
Map<String, UUID> tempUUIDs;
for(int i = 0; i < requests; i++)
{
tempNames = names.subList(limitByRequest * i, Math.min((limitByRequest * (i+1)) - 1, names.size()));
tempUUIDs = fetch(tempNames);
UUIDs.putAll(tempUUIDs);
Thread.sleep(400);
}
return UUIDs;
}
private static HttpURLConnection createConnection() throws IOException
{
URL url = new URL(PROFILE_URL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
return connection;
}
private static void writeBody(HttpURLConnection connection, List<String> names) throws IOException
{
OutputStream stream = connection.getOutputStream();
String body = JSONArray.toJSONString(names);
stream.write(body.getBytes());
stream.flush();
stream.close();
}
private static UUID fromMojangUUID(String id) //Mojang sends string UUIDs without dashes ...
{
return UUID.fromString(id.substring(0, 8) + "-" + id.substring(8, 12) + "-" +
id.substring(12, 16) + "-" + id.substring(16, 20) + "-" +
id.substring(20, 32));
}
}

View File

@ -18,15 +18,489 @@
package fr.moribus.imageonmap.migration;
import fr.moribus.imageonmap.ImageOnMap;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
public class V3Migrator
/**
* This class represents and executes the ImageOnMap v3.x migration process
*/
public class V3Migrator
{
/**
* The name of the former images directory
*/
static private final String OLD_IMAGES_DIRECTORY_NAME = "Image";
/**
* The name of the former file that contained all the maps definitions (including posters)
*/
static private final String OLD_MAPS_FILE_NAME = "map.yml";
/**
* The name of the former file that contained all the posters definitions
*/
static private final String OLD_POSTERS_FILE_NAME = "poster.yml";
/**
* The name of the backup directory that will contain the pre-v3 files that
* were present before the migration started
*/
static private final String BACKUPS_PREV3_DIRECTORY_NAME = "backups_pre-v3";
/**
* The name of the backup directory that will contain the post-v3 files that
* were present before the migration started
*/
static private final String BACKUPS_POSTV3_DIRECTORY_NAME = "backups_post-v3";
/**
* The maximal amount of usernames to send to mojang per request
* This allows not to overload mojang's service with too many usernames at a time
*/
static private final int MOJANG_USERNAMES_PER_REQUEST = 100;
/**
* Returns the former images directory of a given plugin
* @param plugin The plugin.
* @return the corresponding 'Image' directory
*/
static public File getOldImagesDirectory(Plugin plugin)
{
return new File(plugin.getDataFolder(), OLD_IMAGES_DIRECTORY_NAME);
}
/**
* The plugin that is running the migration
*/
private final ImageOnMap plugin;
/**
* The former file that contained all the posters definitions
*/
private File oldPostersFile;
/**
* The former file that contained all the maps definitions (including posters)
*/
private File oldMapsFile;
/**
* The backup directory that will contain the pre-v3 files that
* were present before the migration started
*/
private final File backupsPrev3Directory;
/**
* The backup directory that will contain the post-v3 files that
* were present before the migration started
*/
private final File backupsPostv3Directory;
/**
* The list of all the posters to migrate
*/
private final ArrayList<OldSavedPoster> postersToMigrate;
/**
* The list of all the single maps to migrate
*/
private final ArrayList<OldSavedMap> mapsToMigrate;
/**
* The set of all the user names to retreive the UUID from Mojang
*/
private final HashSet<String> userNamesToFetch;
/**
* The map of all the usernames and their corresponding UUIDs
*/
private Map<String, UUID> usersUUIDs;
/**
* Defines if the migration process is currently running
*/
private boolean isRunning = false;
public V3Migrator(ImageOnMap plugin)
{
this.plugin = plugin;
File dataFolder = plugin.getDataFolder();
oldPostersFile = new File(dataFolder, OLD_POSTERS_FILE_NAME);
oldMapsFile = new File(dataFolder, OLD_MAPS_FILE_NAME);
backupsPrev3Directory = new File(dataFolder, BACKUPS_PREV3_DIRECTORY_NAME);
backupsPostv3Directory = new File(dataFolder, BACKUPS_POSTV3_DIRECTORY_NAME);
postersToMigrate = new ArrayList<>();
mapsToMigrate = new ArrayList<>();
userNamesToFetch = new HashSet<>();
}
/**
* Executes the full migration
*/
private void migrate()
{
try
{
if(!spotFilesToMigrate()) return;
if(checkForExistingBackups()) return;
if(!loadOldFiles()) return;
backupMapData();
loadOldFiles();
fetchUUIDs();
if(!checkMissingUUIDs()) return;
}
catch(Exception ex)
{
logError("Error while preparing migration", ex);
logError("Aborting migration. No change has been made.");
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
return;
}
try
{
}
catch(Exception ex)
{
logError("Error while migrating", ex);
logError("Aborting migration. Some changes may already have been made.");
logError("Before trying to migrate again, you must recover player files from the backups, and then move the backups away from the plugin directory to avoid overwriting them.");
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
}
}
/* ****** Actions ***** */
/**
* Checks if there is any of the former files to be migrated
* @return true if any former map or poster file exists, false otherwise
*/
private boolean spotFilesToMigrate()
{
logInfo("Looking for configuration files to migrate ...");
if(!oldPostersFile.exists()) oldPostersFile = null;
else logInfo("Detected former posters file " + OLD_POSTERS_FILE_NAME);
if(!oldMapsFile.exists()) oldMapsFile = null;
else logInfo("Detected former maps file " + OLD_POSTERS_FILE_NAME);
if(oldPostersFile == null && oldMapsFile == null)
{
logInfo("There is nothing to migrate. Stopping.");
return false;
}
else
{
logInfo("Done.");
return true;
}
}
/**
* Checks if any existing backup directories exists
* @return true if a non-empty backup directory exists, false otherwise
*/
private boolean checkForExistingBackups()
{
if((backupsPrev3Directory.exists() && backupsPrev3Directory.list().length == 0)
|| (backupsPostv3Directory.exists() && backupsPostv3Directory.list().length == 0))
{
logError("Backup directories already exists.");
logError("This means that a migration has already been done, or may not have ended well.");
logError("To start a new migration, you must move away the backup directories so they are not overwritten.");
return true;
}
return false;
}
/**
* Creates backups of the former map files, and of the existing map stores
* @throws IOException
*/
private void backupMapData() throws IOException
{
logInfo("Backing up map data before migrating ...");
if(!backupsPrev3Directory.exists()) backupsPrev3Directory.mkdirs();
if(!backupsPostv3Directory.exists()) backupsPostv3Directory.mkdirs();
if(oldMapsFile.exists())
{
File oldMapsFileBackup = new File(backupsPrev3Directory, oldMapsFile.getName());
verifiedBackupCopy(oldMapsFile, oldMapsFileBackup);
}
if(oldPostersFile.exists())
{
File oldPostersFileBackup = new File(backupsPrev3Directory, oldPostersFile.getName());
verifiedBackupCopy(oldPostersFile, oldPostersFileBackup);
}
File backupFile;
for(File mapFile : plugin.getMapsDirectory().listFiles())
{
backupFile = new File(backupsPostv3Directory, mapFile.getName());
verifiedBackupCopy(mapFile, backupFile);
}
logInfo("Backup complete.");
}
/**
* An utility function to check if a map is actually part of a loaded poster
* @param map The single map.
* @return true if the map is part of a poster, false otherwise
*/
private boolean posterContains(OldSavedMap map)
{
for(OldSavedPoster poster : postersToMigrate)
{
if(poster.contains(map)) return true;
}
return false;
}
/**
* Loads the former files into the corresponding arrays
* Also fetches the names of all the users that have maps
* @return true if any of the files contained readable map data, false otherwise
*/
private boolean loadOldFiles()
{
FileConfiguration oldPosters = YamlConfiguration.loadConfiguration(oldPostersFile);
OldSavedPoster oldPoster;
for(String key : oldPosters.getKeys(false))
{
try
{
oldPoster = new OldSavedPoster(oldPosters.get(key));
userNamesToFetch.add(oldPoster.getUserName());
postersToMigrate.add(oldPoster);
}
catch(InvalidConfigurationException ex)
{
logWarning("Could not read poster data for key " + key, ex);
}
}
FileConfiguration oldMaps = YamlConfiguration.loadConfiguration(oldMapsFile);
OldSavedMap oldMap;
for(String key : oldMaps.getKeys(false))
{
try
{
oldMap = new OldSavedMap(oldMaps.get(key));
if(!posterContains(oldMap)) mapsToMigrate.add(oldMap);
}
catch(InvalidConfigurationException ex)
{
logWarning("Could not read poster data for key " + key, ex);
}
}
return (postersToMigrate.size() > 0) || (mapsToMigrate.size() > 0);
}
/**
* Fetches all the needed UUIDs from Mojang's UUID conversion service
* @throws IOException if the fetcher could not connect to Mojang's servers
* @throws InterruptedException if the thread was interrupted while fetching UUIDs
*/
private void fetchUUIDs() throws IOException, InterruptedException
{
logInfo("Fetching UUIDs from Mojang ...");
try
{
usersUUIDs = UUIDFetcher.fetch(new ArrayList<String>(userNamesToFetch), MOJANG_USERNAMES_PER_REQUEST);
}
catch(IOException ex)
{
logError("An error occured while fetching the UUIDs from Mojang", ex);
throw ex;
}
catch(InterruptedException ex)
{
logError("The migration worker has been interrupted", ex);
throw ex;
}
logInfo("Fetching done. " + usersUUIDs.size() + " UUIDs have been retreived.");
}
/**
* Check if any UUID has been retreived.
* @return true if at least one UUID has been retreived, false otherwise
*/
private boolean checkMissingUUIDs()
{
if(usersUUIDs.size() == userNamesToFetch.size()) return true;
logInfo("Mojang did not find UUIDs for all the registered players.");
logInfo("This means some of the users do not actually exist, or they have changed names before migrating.");
if(usersUUIDs.size() <= 0)
{
logInfo("Mojang could not find any of the registered players.");
logInfo("There is nothing to migrate. Stopping.");
return false;
}
String missingUsersList = "";
for(String user : userNamesToFetch)
{
if(!usersUUIDs.containsKey(user)) missingUsersList += user + ",";
}
missingUsersList = missingUsersList.substring(0, missingUsersList.length());
logInfo("Here are the missing players : " + missingUsersList);
return true;
}
/* ****** Utils ***** */
static public void logInfo(String message)
{
System.out.println("[ImageOnMap-Migration][INFO] " + message);
}
static public void logWarning(String message)
{
System.err.println("[ImageOnMap-Migration][WARN] " + message);
}
static public void logWarning(String message, Exception ex)
{
logWarning(message + " : " + ex.getMessage());
}
static public void logError(String message)
{
System.err.println("[ImageOnMap-Migration][ERROR] " + message);
}
static public void logError(String message, Exception ex)
{
logError(message + " : " + ex.getMessage());
}
public synchronized boolean isRunning()
{
return isRunning;
}
private synchronized void setRunning(boolean running)
{
this.isRunning = running;
}
/**
* Executes the full migration, and defines the running status of the migration
*/
public void run()
{
setRunning(true);
migrate();
setRunning(false);
}
/**
* Makes a standard file copy, and checks the integrity of the destination
* file after the copy
* @param sourceFile The file to copy
* @param destinationFile The destination file
* @throws IOException If the copy failed, if the integrity check failed, or if the destination file already exists
*/
static private void verifiedBackupCopy(File sourceFile, File destinationFile) throws IOException
{
if(destinationFile.exists())
throw new IOException("Backup copy failed : destination file ("+destinationFile.getName()+") already exists.");
long sourceSize = sourceFile.length();
String sourceCheckSum = fileCheckSum(sourceFile, "SHA1");
Path sourcePath = Paths.get(sourceFile.getAbsolutePath());
Path destinationPath = Paths.get(destinationFile.getAbsolutePath());
Files.copy(sourcePath, destinationPath, StandardCopyOption.REPLACE_EXISTING);
long destinationSize = destinationFile.length();
String destinationCheckSum = fileCheckSum(destinationFile, "SHA1");
if(sourceSize != destinationSize || !sourceCheckSum.equals(destinationCheckSum))
{
throw new IOException("Backup copy failed : source and destination files ("+sourceFile.getName()+") differ after copy.");
}
}
/**
* Calculates the checksum of a given file
* @param file The file to calculate the checksum of
* @param algorithmName The name of the algorithm to use
* @return The resulting checksum in hexadecimal format
* @throws IOException
*/
static private String fileCheckSum(File file, String algorithmName) throws IOException
{
MessageDigest instance;
try
{
instance = MessageDigest.getInstance(algorithmName);
}
catch(NoSuchAlgorithmException ex)
{
throw new IOException("Could not check file integrity because of NoSuchAlgorithmException : " + ex.getMessage());
}
FileInputStream inputStream = new FileInputStream(file);
byte[] data = new byte[1024];
int read = 0;
while((read = inputStream.read(data)) != -1)
{
instance.update(data);
}
byte[] hashBytes = instance.digest();
StringBuilder buffer = new StringBuilder();
char hexChar;
for(int i = 0; i < hashBytes.length; i++)
{
hexChar = Integer.toHexString((hashBytes[i] & 0xff) + 0x100).charAt(0);
buffer.append(hexChar);
}
return buffer.toString();
}
}

View File

@ -43,7 +43,7 @@ public class MapItemManager implements Listener
static public void exit()
{
mapItemCache.clear();
if(mapItemCache != null) mapItemCache.clear();
mapItemCache = null;
}

View File

@ -44,7 +44,7 @@ public abstract class Worker
this.mainThreadExecutor = runMainThreadExecutor ? new WorkerMainThreadExecutor(name) : null;
}
public void init()
protected void init()
{
if(thread != null && thread.isAlive())
{
@ -57,7 +57,7 @@ public abstract class Worker
thread.start();
}
public void exit()
protected void exit()
{
thread.interrupt();
callbackManager.exit();
@ -118,7 +118,7 @@ public abstract class Worker
private Thread createThread()
{
return new Thread()
return new Thread("ImageOnMap " + name)
{
@Override
public void run()

View File

@ -8,8 +8,9 @@ commands:
usage: /<command> [URL]
maptool:
description: manage maps
usage: /<command>
maptool-migration:
description: Manages the ImageOnMap migrations
permission: op
permissions:
imageonmap.userender:
description: Allows you to use /tomap