Migration is now fully functional !

* NEW: Refactored PluginLogger for multi-thread usage and message formatting.
* NEW: Posters and maps that could not be migrated are now saved back
  to their original files.
* NEW: Moved the maptool-migration command to the /maptool migrate subcommand.
* NEW: PosterMap can now be used without having row/column count information.
* NEW: Added a detailed help message for the migration command.
* BUG: Fixed UUIDFetcher's HTTP GET request sending.
* BUG: Fixed Name->UUID time-specific matching.
* OPT: Removed the CommandPermission structure, it is actually useless.
* OPT: Renamed Migrator to MigratorExecutor.
This commit is contained in:
Prokopyl 2015-04-12 01:28:05 +02:00
parent 6725c15002
commit 2a49d94d1e
26 changed files with 526 additions and 336 deletions

View File

@ -23,7 +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.MigratorExecutor;
import fr.moribus.imageonmap.migration.V3Migrator;
import fr.moribus.imageonmap.ui.MapItemManager;
import java.io.File;
@ -61,6 +61,7 @@ public final class ImageOnMap extends JavaPlugin
@Override
public void onEnable()
{
PluginLogger.init(this);
// Creating the images and maps directories if necessary
try
{
@ -69,7 +70,7 @@ public final class ImageOnMap extends JavaPlugin
}
catch(IOException ex)
{
PluginLogger.LogError("FATAL : " + ex.getMessage(), null);
PluginLogger.error("FATAL : " + ex.getMessage(), null);
this.setEnabled(false);
return;
}
@ -83,7 +84,6 @@ public final class ImageOnMap extends JavaPlugin
Commands.init(this);
MapInitEvent.init(this);
MapItemManager.init();
Migrator.startWorker();
}
@Override
@ -93,7 +93,8 @@ public final class ImageOnMap extends JavaPlugin
ImageRendererExecutor.stop();
MapManager.exit();
MapItemManager.exit();
Migrator.stopWorker();
MigratorExecutor.waitForMigration();
PluginLogger.exit();
}
private File checkPluginDirectory(File primaryFile, File... alternateFiles) throws IOException

View File

@ -65,7 +65,7 @@ public class MetricsLite
}
catch (IOException e)
{
PluginLogger.LogError("Failed to start MetricsLite", e);
PluginLogger.error("Failed to start MetricsLite", e);
}
}
@ -197,7 +197,7 @@ public class MetricsLite
firstPost = false;
} catch (IOException e) {
if (debug) {
PluginLogger.LogWarning("[Metrics] ", e);
PluginLogger.warning("[Metrics] ", e);
}
}
}
@ -219,7 +219,7 @@ public class MetricsLite
configuration.load(getConfigFile());
} catch (IOException | InvalidConfigurationException ex) {
if (debug) {
PluginLogger.LogInfo("[Metrics] " + ex.getMessage());
PluginLogger.info("[Metrics] " + ex.getMessage());
}
return true;
}
@ -408,7 +408,7 @@ public class MetricsLite
gzos = new GZIPOutputStream(baos);
gzos.write(input.getBytes("UTF-8"));
} catch (IOException e) {
PluginLogger.LogError("MetricsLite GZIP error : ", e);
PluginLogger.error("MetricsLite GZIP error : ", e);
} finally {
if (gzos != null) try {
gzos.close();

View File

@ -18,33 +18,112 @@
package fr.moribus.imageonmap;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import org.bukkit.plugin.Plugin;
abstract public class PluginLogger
{
static private Plugin plugin;
static private Thread mainThread;
static private HashMap<Thread, PluginThreadLogger> loggers;
static public void init(Plugin plugin)
{
PluginLogger.plugin = plugin;
mainThread = Thread.currentThread();
loggers = new HashMap<>();
}
static public void exit()
{
plugin = null;
mainThread = null;
loggers = null;
}
static public void log(Level level, String message, Throwable ex)
{
getLogger().log(level, message, ex);
}
static public void log(Level level, String message, Object...args)
{
getLogger().log(level, message, args);
}
static public void log(Level level, String message, Throwable ex, Object... args)
{
log(level, message + " : " + ex.getMessage(), args);
}
static public void info(String message, Object...args)
{
log(Level.INFO, message, args);
}
static public void warning(String message, Object... args)
{
log(Level.WARNING, message, args);
}
static public void warning(String message, Throwable ex)
{
warning(message + " : " + ex.getMessage());
}
static public void error(String message)
{
log(Level.SEVERE, message);
}
static public void error(String message, Throwable ex)
{
log(Level.SEVERE, message, ex);
}
static public void error(String message, Throwable ex, Object... args)
{
log(Level.SEVERE, message, ex, args);
}
static private Logger getLogger()
{
return ImageOnMap.getPlugin().getLogger();
Thread currentThread = Thread.currentThread();
if(currentThread.equals(mainThread)) return plugin.getLogger();
return getLogger(currentThread);
}
static public void LogInfo(String message)
static private Logger getLogger(Thread thread)
{
getLogger().log(Level.INFO, message);
PluginThreadLogger logger = loggers.get(thread);
if(logger == null)
{
logger = new PluginThreadLogger(thread);
loggers.put(thread, logger);
}
return logger;
}
static public void LogWarning(String message)
static private class PluginThreadLogger extends Logger
{
getLogger().log(Level.WARNING, message);
}
static public void LogWarning(String message, Throwable ex)
{
getLogger().log(Level.WARNING, message, ex);
}
static public void LogError(String message, Throwable ex)
{
getLogger().log(Level.SEVERE, message, ex);
private final String loggerName;
public PluginThreadLogger(Thread thread)
{
super(plugin.getClass().getCanonicalName(), null);
setParent(plugin.getLogger());
setLevel(Level.ALL);
loggerName = "[" + thread.getName() + "] ";
}
@Override
public void log(LogRecord logRecord)
{
logRecord.setMessage(loggerName + logRecord.getMessage());
super.log(logRecord);
}
}
}

View File

@ -65,18 +65,12 @@ 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(!hasPermission(sender))
if(!canExecute(sender))
throw new CommandException(this, Reason.SENDER_NOT_AUTHORIZED);
run();
}

View File

@ -65,7 +65,7 @@ public class CommandException extends Exception
case SENDER_NOT_AUTHORIZED:
return "You do not have the permission to use this command.";
default:
PluginLogger.LogWarning("Unknown CommandException caught", this);
PluginLogger.warning("Unknown CommandException caught", this);
return "An unknown error suddenly happened.";
}
}

View File

@ -1,55 +0,0 @@
/*
* 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,10 +18,9 @@
package fr.moribus.imageonmap.commands;
import fr.moribus.imageonmap.ImageOnMap;
import fr.moribus.imageonmap.commands.maptool.MigrateCommand;
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;
@ -36,10 +35,7 @@ import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.plugin.java.JavaPlugin;
/**
*
* @author Prokopyl<prokopylmc@gmail.com>
*/
public enum Commands implements TabCompleter, CommandExecutor
{
MAPTOOL(new String[]{"maptool"},
@ -48,22 +44,17 @@ public enum Commands implements TabCompleter, CommandExecutor
GetCommand.class,
DeleteConfirmCommand.class,
DeleteNoConfirmCommand.class,
GetRemainingCommand.class
GetRemainingCommand.class,
MigrateCommand.class
),
TOMAP(MAPTOOL, NewCommand.class, "tomap"),
MAPTOOL_MIGRATION(new String[]{"maptool-migration"}, CommandPermission.OP_ONLY,
StartCommand.class
);
TOMAP(MAPTOOL, NewCommand.class, "tomap");
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)
@ -71,23 +62,16 @@ 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, CommandPermission.bukkitPermission(ImageOnMap.getPlugin(), names[0]), commandsClasses);
this.names = names;
this.commandsClasses = commandsClasses;
this.shortcutCommandGroup = null;
initDescriptions();
initCommands();
}
private void initDescriptions()
@ -96,7 +80,7 @@ public enum Commands implements TabCompleter, CommandExecutor
InputStream stream = getClass().getClassLoader().getResourceAsStream(fileName);
if(stream == null)
{
PluginLogger.LogWarning("Could not load description file for the " + getUsualName() + " command");
PluginLogger.warning("Could not load description file for the " + getUsualName() + " command");
return;
}
@ -154,7 +138,7 @@ public enum Commands implements TabCompleter, CommandExecutor
}
catch (Exception ex)
{
PluginLogger.LogWarning("Exception while initializing command", ex);
PluginLogger.warning("Exception while initializing command", ex);
}
}
@ -341,6 +325,5 @@ 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

@ -73,25 +73,27 @@ public class HelpCommand extends Command
if(!command.canExecute(sender))
warning("You do not have the permission to use this command.");
sender.sendMessage("§l§6 ||== ImageOnMap help ==||\n" +
"§l§6 |Usage : §r" + command.getUsageString());
String message = "§l§6 ||== ImageOnMap help ==||\n" +
"§l§6 |Usage : §r" + command.getUsageString();
try
{
String help = getHelpText(command);
if(help.isEmpty())
{
sender.sendMessage(message);
warning("There is no help message for this command.");
}
else
{
sender.sendMessage(help);
sender.sendMessage(message + "\n" + help);
}
}
catch(IOException ex)
{
sender.sendMessage(message);
warning("Could not read help for this command.");
PluginLogger.LogWarning("Could not read help for the command : " + command.getName(), ex);
PluginLogger.warning("Could not read help for the command : " + command.getName(), ex);
}
}

View File

@ -48,7 +48,7 @@ public class DeleteNoConfirmCommand extends Command
}
catch (MapManagerException ex)
{
PluginLogger.LogWarning("A non-existent map was requested to be deleted", ex);
PluginLogger.warning("A non-existent map was requested to be deleted", ex);
warning("This map does not exist.");
}
}

View File

@ -16,17 +16,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.commands.migration;
package fr.moribus.imageonmap.commands.maptool;
import fr.moribus.imageonmap.commands.*;
import fr.moribus.imageonmap.migration.Migrator;
import fr.moribus.imageonmap.worker.WorkerCallback;
import fr.moribus.imageonmap.migration.MigratorExecutor;
import org.bukkit.command.CommandSender;
@CommandInfo(name = "start")
public class StartCommand extends Command
@CommandInfo(name = "migrate")
public class MigrateCommand extends Command
{
public StartCommand(Commands commandGroup) {
public MigrateCommand(Commands commandGroup) {
super(commandGroup);
}
@ -34,28 +33,20 @@ public class StartCommand extends Command
protected void run() throws CommandException
{
final CommandSender cmdSender = sender;
if(Migrator.isMigrationRunning())
if(MigratorExecutor.isRunning())
{
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.");
MigratorExecutor.migrate();
}
}
@Override
public boolean canExecute(CommandSender sender)
{
return sender.isOp();
}
}

View File

@ -79,7 +79,7 @@ public class NewCommand extends Command
public void errored(Throwable exception)
{
player.sendMessage("§cMap rendering failed : " + exception.getMessage());
PluginLogger.LogWarning("Rendering from '" + player.getName() + "' failed", exception);
PluginLogger.warning("Rendering from '{0}' failed", exception, player.getName());
}
});
}

View File

@ -170,7 +170,6 @@ public class ImageRendererExecutor extends Worker
int x, y;
x = (destinationW - finalW) / 2;
y = (destinationH - finalH) / 2;
PluginLogger.LogInfo(finalW + " " + finalH + " : " + x + " " + y);
BufferedImage newImage = new BufferedImage(destinationW, destinationH, BufferedImage.TYPE_INT_ARGB);

View File

@ -111,6 +111,11 @@ abstract public class MapManager
getPlayerMapStore(map.getUserUUID()).addMap(map);
}
static public void insertMap(ImageMap map)
{
getPlayerMapStore(map.getUserUUID()).insertMap(map);
}
static public void deleteMap(ImageMap map) throws MapManagerException
{
getPlayerMapStore(map.getUserUUID()).deleteMap(map);

View File

@ -69,6 +69,11 @@ public class PlayerMapStore implements ConfigurationSerializable
public synchronized void addMap(ImageMap map) throws MapManagerException
{
checkMapLimit(map);
insertMap(map);
}
public synchronized void insertMap(ImageMap map)
{
_addMap(map);
notifyModification();
}
@ -201,15 +206,15 @@ public class PlayerMapStore implements ConfigurationSerializable
}
catch(InvalidConfigurationException ex)
{
PluginLogger.LogWarning("Could not load map data : " + ex.getMessage());
PluginLogger.warning("Could not load map data : ", ex);
}
}
try { checkMapLimit(0); }
catch(MapManagerException ex)
{
PluginLogger.LogWarning("Map limit exceeded for player " + playerUUID.toString() +
" (" + mapList.size() + " maps loaded).");
PluginLogger.warning("Map limit exceeded for player '{0}' ({1} maps loaded)",
playerUUID.toString(),mapList.size());
}
}
@ -246,9 +251,8 @@ public class PlayerMapStore implements ConfigurationSerializable
}
catch (IOException ex)
{
PluginLogger.LogError("Could not save maps file for player " + playerUUID.toString(), ex);
PluginLogger.error("Could not save maps file for player '{0}'", ex, playerUUID.toString());
}
PluginLogger.LogInfo("Saving maps file for " + playerUUID.toString());
synchronized(this) {modified = false;}
}
}

View File

@ -33,16 +33,13 @@ public class PosterMap extends ImageMap
{
super(userUUID, Type.POSTER, id, name);
this.mapsIDs = mapsIDs;
this.columnCount = columnCount;
this.rowCount = rowCount;
this.columnCount = Math.max(columnCount, 0);
this.rowCount = Math.max(rowCount, 0);
}
public PosterMap(UUID userUUID, short[] mapsIDs, int columnCount, int rowCount)
{
super(userUUID, Type.POSTER, null, null);
this.mapsIDs = mapsIDs;
this.columnCount = columnCount;
this.rowCount = rowCount;
this(userUUID, mapsIDs, null, null, columnCount, rowCount);
}
@Override
@ -89,11 +86,19 @@ public class PosterMap extends ImageMap
/* ====== Getters & Setters ====== */
/**
* Returns the amount of columns in the poster map
* @return The number of columns, or 0 if this data is missing
*/
public int getColumnCount()
{
return columnCount;
}
/**
* Returns the amount of rows in the poster map
* @return The number of rows, or 0 if this data is missing
*/
public int getRowCount()
{
return rowCount;
@ -101,13 +106,20 @@ public class PosterMap extends ImageMap
public int getColumnAt(int i)
{
if(columnCount == 0) return 0;
return (i % columnCount) + 1;
}
public int getRowAt(int i)
{
if(columnCount == 0) return 0;
return (i / columnCount) + 1;
}
public boolean hasColumnData()
{
return rowCount != 0 && columnCount != 0;
}
@Override
public int getMapCount()

View File

@ -26,10 +26,15 @@ public class SingleMap extends ImageMap
{
protected final short mapID;
public SingleMap(UUID ownerUUID, short mapID, String id, String name)
{
super(ownerUUID, Type.SINGLE, id, name);
this.mapID = mapID;
}
public SingleMap(UUID ownerUUID, short mapID)
{
super(ownerUUID, Type.SINGLE);
this.mapID = mapID;
this(ownerUUID, mapID, null, null);
}
@Override

View File

@ -1,74 +0,0 @@
/*
* 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 fr.moribus.imageonmap.ImageOnMap;
import fr.moribus.imageonmap.PluginLogger;
public class MigratorExecutor
{
static private Thread migratorThread;
static public void migrate()
{
if(isRunning())
{
PluginLogger.error("Migration is already running.");
return;
}
migratorThread = new Thread(new V3Migrator(ImageOnMap.getPlugin()), "ImageOnMap-Migration");
migratorThread.start();
}
static public boolean isRunning()
{
return migratorThread != null && migratorThread.isAlive();
}
static public void waitForMigration()
{
if(isRunning())
{
PluginLogger.info("Waiting for migration to finish ...");
try
{
migratorThread.join();
}
catch(InterruptedException ex)
{
PluginLogger.error("Migration thread has been interrupted while wating to finish. It may not have ended correctly.");
}
}
}
}

View File

@ -18,7 +18,12 @@
package fr.moribus.imageonmap.migration;
import fr.moribus.imageonmap.map.ImageMap;
import fr.moribus.imageonmap.map.SingleMap;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.InvalidConfigurationException;
class OldSavedMap
@ -54,6 +59,20 @@ class OldSavedMap
userName = data.get(2);
}
public ImageMap toImageMap(UUID userUUID)
{
return new SingleMap(userUUID, mapId, null, mapName);
}
public void serialize(Configuration configuration)
{
ArrayList<String> data = new ArrayList<String>();
data.add(Short.toString(mapId));
data.add(mapName);
data.add(userName);
configuration.set(mapName, data);
}
public short getMapId() {return mapId;}
public String getUserName() {return userName;}
}

View File

@ -18,16 +18,23 @@
package fr.moribus.imageonmap.migration;
import fr.moribus.imageonmap.map.ImageMap;
import fr.moribus.imageonmap.map.PosterMap;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.InvalidConfigurationException;
class OldSavedPoster
{
private String userName;
private short[] mapsIds;
private final String userName;
private final String posterName;
private final short[] mapsIds;
public OldSavedPoster(Object rawData) throws InvalidConfigurationException
public OldSavedPoster(Object rawData, String key) throws InvalidConfigurationException
{
posterName = key;
List<String> data;
try
{
@ -68,5 +75,24 @@ class OldSavedPoster
return false;
}
public ImageMap toImageMap(UUID userUUID)
{
return new PosterMap(userUUID, mapsIds, null, "poster", 0, 0);
}
public void serialize(Configuration configuration)
{
ArrayList<String> data = new ArrayList<String>();
data.add(userName);
for(short mapId : mapsIds)
{
data.add(Short.toString(mapId));
}
configuration.set(posterName, data);
}
public String getUserName() {return userName;}
}

View File

@ -142,7 +142,7 @@ abstract public class UUIDFetcher
for(String name : remainingNames)
{
user = fetchOriginalUUID(name);
uuids.put(user.name, user.uuid);
uuids.put(name, user.uuid);
Thread.sleep(timeBetweenRequests);
}
@ -151,7 +151,6 @@ abstract public class UUIDFetcher
static private User fetchOriginalUUID(String name) throws IOException
{
HttpURLConnection connection = getGETConnection(TIMED_PROFILE_URL + name + "?at=" + NAME_CHANGE_TIMESTAMP);
sendRequest(connection);
JSONObject object;
@ -187,16 +186,11 @@ abstract public class UUIDFetcher
connection.setRequestMethod("GET");
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
return connection;
}
static private void sendRequest(HttpURLConnection connection) throws IOException
{
OutputStream stream = connection.getOutputStream();
stream.flush();
stream.close();
}
private static void writeBody(HttpURLConnection connection, List<String> names) throws IOException
{
OutputStream stream = connection.getOutputStream();
@ -208,9 +202,9 @@ abstract public class UUIDFetcher
private static Object readResponse(HttpURLConnection connection) throws IOException, ParseException
{
return new JSONParser().parse(new InputStreamReader(connection.getInputStream()));
return new JSONParser().parse(new InputStreamReader(connection.getInputStream()));
}
private static UUID fromMojangUUID(String id) //Mojang sends string UUIDs without dashes ...
{
return UUID.fromString(id.substring(0, 8) + "-" + id.substring(8, 12) + "-" +

View File

@ -19,6 +19,8 @@
package fr.moribus.imageonmap.migration;
import fr.moribus.imageonmap.ImageOnMap;
import fr.moribus.imageonmap.PluginLogger;
import fr.moribus.imageonmap.map.MapManager;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@ -28,6 +30,7 @@ import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Map;
@ -42,7 +45,7 @@ import org.bukkit.plugin.Plugin;
/**
* This class represents and executes the ImageOnMap v3.x migration process
*/
public class V3Migrator
public class V3Migrator implements Runnable
{
/**
* The name of the former images directory
@ -111,12 +114,12 @@ public class V3Migrator
/**
* The list of all the posters to migrate
*/
private final ArrayList<OldSavedPoster> postersToMigrate;
private final ArrayDeque<OldSavedPoster> postersToMigrate;
/**
* The list of all the single maps to migrate
*/
private final ArrayList<OldSavedMap> mapsToMigrate;
private final ArrayDeque<OldSavedMap> mapsToMigrate;
/**
* The set of all the user names to retreive the UUID from Mojang
@ -145,8 +148,8 @@ public class V3Migrator
backupsPrev3Directory = new File(dataFolder, BACKUPS_PREV3_DIRECTORY_NAME);
backupsPostv3Directory = new File(dataFolder, BACKUPS_POSTV3_DIRECTORY_NAME);
postersToMigrate = new ArrayList<>();
mapsToMigrate = new ArrayList<>();
postersToMigrate = new ArrayDeque<>();
mapsToMigrate = new ArrayDeque<>();
userNamesToFetch = new HashSet<>();
}
@ -166,21 +169,22 @@ public class V3Migrator
}
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);
PluginLogger.error("Error while preparing migration");
PluginLogger.error("Aborting migration. No change has been made.", ex);
return;
}
try
{
mergeMapData();
saveChanges();
cleanup();
}
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.");
PluginLogger.error("Error while migrating", ex);
PluginLogger.error("Aborting migration. Some changes may already have been made.");
PluginLogger.error("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);
}
@ -194,22 +198,22 @@ public class V3Migrator
*/
private boolean spotFilesToMigrate()
{
logInfo("Looking for configuration files to migrate ...");
PluginLogger.info("Looking for configuration files to migrate ...");
if(!oldPostersFile.exists()) oldPostersFile = null;
else logInfo("Detected former posters file " + OLD_POSTERS_FILE_NAME);
else PluginLogger.info("Detected former posters file {0}", OLD_POSTERS_FILE_NAME);
if(!oldMapsFile.exists()) oldMapsFile = null;
else logInfo("Detected former maps file " + OLD_POSTERS_FILE_NAME);
else PluginLogger.info("Detected former maps file {0}", OLD_MAPS_FILE_NAME);
if(oldPostersFile == null && oldMapsFile == null)
{
logInfo("There is nothing to migrate. Stopping.");
PluginLogger.info("There is nothing to migrate. Stopping.");
return false;
}
else
{
logInfo("Done.");
PluginLogger.info("Done.");
return true;
}
}
@ -223,9 +227,9 @@ public class V3Migrator
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.");
PluginLogger.error("Backup directories already exists.");
PluginLogger.error("This means that a migration has already been done, or may not have ended well.");
PluginLogger.error("To start a new migration, you must move away the backup directories so they are not overwritten.");
return true;
}
@ -238,18 +242,18 @@ public class V3Migrator
*/
private void backupMapData() throws IOException
{
logInfo("Backing up map data before migrating ...");
PluginLogger.info("Backing up map data before migrating ...");
if(!backupsPrev3Directory.exists()) backupsPrev3Directory.mkdirs();
if(!backupsPostv3Directory.exists()) backupsPostv3Directory.mkdirs();
if(oldMapsFile.exists())
if(oldMapsFile != null && oldMapsFile.exists())
{
File oldMapsFileBackup = new File(backupsPrev3Directory, oldMapsFile.getName());
verifiedBackupCopy(oldMapsFile, oldMapsFileBackup);
}
if(oldPostersFile.exists())
if(oldPostersFile != null && oldPostersFile.exists())
{
File oldPostersFileBackup = new File(backupsPrev3Directory, oldPostersFile.getName());
verifiedBackupCopy(oldPostersFile, oldPostersFileBackup);
@ -262,7 +266,7 @@ public class V3Migrator
verifiedBackupCopy(mapFile, backupFile);
}
logInfo("Backup complete.");
PluginLogger.info("Backup complete.");
}
/**
@ -287,37 +291,45 @@ public class V3Migrator
*/
private boolean loadOldFiles()
{
FileConfiguration oldPosters = YamlConfiguration.loadConfiguration(oldPostersFile);
OldSavedPoster oldPoster;
for(String key : oldPosters.getKeys(false))
if(oldPostersFile != null)
{
try
FileConfiguration oldPosters = YamlConfiguration.loadConfiguration(oldPostersFile);
OldSavedPoster oldPoster;
for(String key : oldPosters.getKeys(false))
{
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);
if("IdCount".equals(key)) continue;
try
{
oldPoster = new OldSavedPoster(oldPosters.get(key), key);
userNamesToFetch.add(oldPoster.getUserName());
postersToMigrate.add(oldPoster);
}
catch(InvalidConfigurationException ex)
{
PluginLogger.warning("Could not read poster data for key '{0}'", ex, key);
}
}
}
FileConfiguration oldMaps = YamlConfiguration.loadConfiguration(oldMapsFile);
OldSavedMap oldMap;
for(String key : oldMaps.getKeys(false))
if(oldMapsFile != null)
{
try
FileConfiguration oldMaps = YamlConfiguration.loadConfiguration(oldMapsFile);
OldSavedMap oldMap;
for(String key : oldMaps.getKeys(false))
{
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);
try
{
if("IdCount".equals(key)) continue;
oldMap = new OldSavedMap(oldMaps.get(key));
if(!posterContains(oldMap)) mapsToMigrate.add(oldMap);
}
catch(InvalidConfigurationException ex)
{
PluginLogger.warning("Could not read poster data for key '{0}'", ex, key);
}
}
}
@ -331,22 +343,22 @@ public class V3Migrator
*/
private void fetchUUIDs() throws IOException, InterruptedException
{
logInfo("Fetching UUIDs from Mojang ...");
PluginLogger.info("Fetching UUIDs from Mojang ...");
try
{
usersUUIDs = UUIDFetcher.fetch(new ArrayList<String>(userNamesToFetch));
}
catch(IOException ex)
{
logError("An error occured while fetching the UUIDs from Mojang", ex);
PluginLogger.error("An error occured while fetching the UUIDs from Mojang", ex);
throw ex;
}
catch(InterruptedException ex)
{
logError("The migration worker has been interrupted", ex);
PluginLogger.error("The migration worker has been interrupted", ex);
throw ex;
}
logInfo("Fetching done. " + usersUUIDs.size() + " UUIDs have been retreived.");
PluginLogger.info("Fetching done. {0} UUIDs have been retreived.", usersUUIDs.size());
}
/**
@ -357,8 +369,8 @@ public class V3Migrator
{
if(usersUUIDs.size() == userNamesToFetch.size()) return true;
int remainingUsersCount = userNamesToFetch.size() - usersUUIDs.size();
logInfo("Mojang did not find UUIDs for "+remainingUsersCount+" players.");
logInfo("The Mojang servers limit requests rate at one per second, this may take some time...");
PluginLogger.info("Mojang did not find UUIDs for {0} players at the current time.", remainingUsersCount);
PluginLogger.info("The Mojang servers limit requests rate at one per second, this may take some time...");
try
{
@ -366,51 +378,143 @@ public class V3Migrator
}
catch(IOException ex)
{
logError("An error occured while fetching the UUIDs from Mojang", ex);
PluginLogger.error("An error occured while fetching the UUIDs from Mojang");
throw ex;
}
catch(InterruptedException ex)
{
logError("The migration worker has been interrupted", ex);
PluginLogger.error("The migration worker has been interrupted");
throw ex;
}
if(usersUUIDs.size() != userNamesToFetch.size())
{
PluginLogger.warning("Mojang did not find player data for {0} players",
userNamesToFetch.size() - usersUUIDs.size());
PluginLogger.warning("The following players do not exist or do not have paid accounts :");
String missingUsersList = "";
for(String user : userNamesToFetch)
{
if(!usersUUIDs.containsKey(user)) missingUsersList += user + ",";
}
missingUsersList = missingUsersList.substring(0, missingUsersList.length());
PluginLogger.info(missingUsersList);
}
if(usersUUIDs.size() <= 0)
{
logInfo("Mojang could not find any of the registered players.");
logInfo("There is nothing to migrate. Stopping.");
PluginLogger.info("Mojang could not find any of the registered players.");
PluginLogger.info("There is nothing to migrate. Stopping.");
return false;
}
return true;
}
private void mergeMapData()
{
PluginLogger.info("Merging map data ...");
ArrayDeque<OldSavedMap> remainingMaps = new ArrayDeque<>();
ArrayDeque<OldSavedPoster> remainingPosters = new ArrayDeque<>();
UUID playerUUID;
OldSavedMap map;
while(!mapsToMigrate.isEmpty())
{
map = mapsToMigrate.pop();
playerUUID = usersUUIDs.get(map.getUserName());
if(playerUUID == null)
{
remainingMaps.add(map);
}
else
{
MapManager.insertMap(map.toImageMap(playerUUID));
}
}
mapsToMigrate.addAll(remainingMaps);
OldSavedPoster poster;
while(!postersToMigrate.isEmpty())
{
poster = postersToMigrate.pop();
playerUUID = usersUUIDs.get(poster.getUserName());
if(playerUUID == null)
{
remainingPosters.add(poster);
}
else
{
MapManager.insertMap(poster.toImageMap(playerUUID));
}
}
postersToMigrate.addAll(remainingPosters);
}
private void saveChanges()
{
PluginLogger.info("Saving changes ...");
MapManager.save();
}
private void cleanup() throws IOException
{
PluginLogger.info("Cleaning up old data files ...");
//Cleaning maps file
if(oldMapsFile != null)
{
if(mapsToMigrate.isEmpty())
{
PluginLogger.info("Deleting old map data file ...");
oldMapsFile.delete();
}
else
{
PluginLogger.info("{0} maps could not be migrated.", mapsToMigrate.size());
YamlConfiguration mapConfig = new YamlConfiguration();
mapConfig.set("IdCount", mapsToMigrate.size());
for(OldSavedMap map : mapsToMigrate)
{
map.serialize(mapConfig);
}
mapConfig.save(oldMapsFile);
}
}
//Cleaning posters file
if(oldPostersFile != null)
{
if(postersToMigrate.isEmpty())
{
PluginLogger.info("Deleting old poster data file ...");
oldPostersFile.delete();
}
else
{
PluginLogger.info("{0} posters could not be migrated.", postersToMigrate.size());
YamlConfiguration posterConfig = new YamlConfiguration();
posterConfig.set("IdCount", postersToMigrate.size());
for(OldSavedPoster poster : postersToMigrate)
{
poster.serialize(posterConfig);
}
posterConfig.save(oldPostersFile);
}
}
PluginLogger.info("Data that has not been migrated will be kept in the old data files.");
}
/* ****** 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;
@ -424,6 +528,7 @@ public class V3Migrator
/**
* Executes the full migration, and defines the running status of the migration
*/
@Override
public void run()
{
setRunning(true);

View File

@ -64,12 +64,20 @@ public class MapItemManager implements Listener
short[] mapsIDs = map.getMapsIDs();
boolean inventoryFull = false;
String mapName;
for(int i = 0, c = mapsIDs.length; i < c; i++)
{
inventoryFull = give(player,
createMapItem(mapsIDs[i], map.getName() +
if(map.hasColumnData())
{
mapName = map.getName() +
" (row " + map.getRowAt(i) +
", column " + map.getColumnAt(i) + ")")) || inventoryFull;
", column " + map.getColumnAt(i) + ")";
}
else
{
mapName = map.getName();
}
inventoryFull = give(player, createMapItem(mapsIDs[i], mapName)) || inventoryFull;
}
return inventoryFull;

View File

@ -48,7 +48,7 @@ public abstract class Worker
{
if(thread != null && thread.isAlive())
{
PluginLogger.LogWarning("Restarting " + name + " thread.");
PluginLogger.warning("Restarting '{0}' thread.", name);
exit();
}
callbackManager.init();
@ -118,7 +118,7 @@ public abstract class Worker
private Thread createThread()
{
return new Thread("ImageOnMap " + name)
return new Thread("ImageOnMap-" + name)
{
@Override
public void run()

View File

@ -0,0 +1,36 @@
Migrates the Map database to the new V3.x format, that uses UUIDs
instead of player names to designate players (among other improvements).
Migration runs in a separate thread, therefore its progress can only be
watched from the server console.
The migration will run the following steps :
- Checking if there are files to migrate. If not, the migration stops.
- Checking if there are backups from a previous migration.
If there are, the migration stops.
- Loading the old map and poster data to memory.
- Backing up old files and new files, to the backups_pre-v3 and
backups_post-v3 subdirectories respectively.
Backup's integrity are chacked using file size and SHA1 checksum.
If integrity could not be proved, the migration stops.
- Retreiving the UUIDs of the players from Mojang's servers.
- Checking if some UUIDs could not be retreived.
If there are, this means some of your players may have changed names
before the migration started. The plugin will therefore try to retreive
them specifying a time, back when usernames could not be changed.
If some names could still not be matched to their UUIDs, then these are
probably non-paid accounts.
If no UUID has been retreived at all, the migration stops.
§c--- From this step, changes to disk will be made, and you will have to use§r
§c--- backups if you want to revert back from before the migration started.§r
- Merging the old map data with the new one, if there is any
(which can be the case if your player started to use newer
versions of ImageOnMap before the migration started).
- Saving all this merged map data to disk.
- Removing the old map data from their former files, leaving only the data
that could not be migrated due to usernames that could not be matched to
their UUIDs.
Original data is still present in the appropriate backup directory,
just in case.
Note that this plugin will NEVER delete nor overwrite any backup directory.
Moving or deleting these backups is left to the administrator's responsibility.

View File

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