mirror of
https://github.com/zDevelopers/ImageOnMap.git
synced 2025-02-13 01:51:19 +01:00
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:
parent
aa27636962
commit
90dd9eb24d
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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; }
|
||||
|
||||
}
|
||||
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
74
src/main/java/fr/moribus/imageonmap/migration/Migrator.java
Normal file
74
src/main/java/fr/moribus/imageonmap/migration/Migrator.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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;}
|
||||
}
|
@ -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;}
|
||||
}
|
119
src/main/java/fr/moribus/imageonmap/migration/UUIDFetcher.java
Normal file
119
src/main/java/fr/moribus/imageonmap/migration/UUIDFetcher.java
Normal 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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ public class MapItemManager implements Listener
|
||||
|
||||
static public void exit()
|
||||
{
|
||||
mapItemCache.clear();
|
||||
if(mapItemCache != null) mapItemCache.clear();
|
||||
mapItemCache = null;
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user