refactor of single map and poster map => poster map

This commit is contained in:
Vlammar 2022-07-13 16:37:41 +02:00
parent c18ca1448d
commit fccdf4e5de
13 changed files with 119 additions and 1097 deletions

View File

@ -123,79 +123,74 @@ public class NewCommand extends IoMCommand {
throwInvalidArgument(I.t("Invalid URL."));
return;
}
boolean isPlayer = sender != null;
// TODO Add a per-player toggle for the GUI.
if (args.length >= 2) {
ImageRendererExecutor.renderAndNotify(url, scaling, player.getUniqueId(), width, height);
} else {
if (args.length < 2 && isPlayer) {
Gui.open(player, new RenderGui(url));
}
//I try to test if the gui is run correctly
//keep the following as a fallback and for cmd made by console
/*
if (args.length >= 2) {
if (args.length >= 3) {
try {
if (args.length >= 4) {
width = Integer.parseInt(args[2]);
height = Integer.parseInt(args[3]);
} else {
String[] size;
if (args[2].contains("*") && !args[2].contains("x")) {
size = args[2].split("\\*");
width = Integer.parseInt(size[0]);
height = Integer.parseInt(size[1]);
}
if (!args[2].contains("*") && args[2].contains("x")) {
size = args[2].split("x");
width = Integer.parseInt(size[0]);
height = Integer.parseInt(size[1]);
}
}
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
throwInvalidArgument(I.t("resize dimension as to be in format <n m> or <nxm> or <n*m>."));
return;
}
}
scaling = resizeMode();
}
if (width < 0 || height < 0) {
throwInvalidArgument(I.t("You need to specify a valid size. e.g. resize 4 5"));
return;
}
try {
ActionBar.sendPermanentMessage(player, ChatColor.DARK_GREEN + I.t("Rendering..."));
ImageRendererExecutor
.render(url, scaling, player.getUniqueId(), width, height, new WorkerCallback<ImageMap>() {
@Override
public void finished(ImageMap result) {
ActionBar.removeMessage(player);
MessageSender
.sendActionBarMessage(player, ChatColor.DARK_GREEN + I.t("Rendering finished!"));
if (result.give(player)
&& (result instanceof PosterMap && !((PosterMap) result).hasColumnData())) {
info(I.t("The rendered map was too big to fit in your inventory."));
info(I.t("Use '/maptool getremaining' to get the remaining maps."));
} else {
//ImageRendererExecutor.renderAndNotify(url, scaling, player.getUniqueId(), width, height);
if (args.length >= 2) {
if (args.length >= 3) {
try {
if (args.length >= 4) {
width = Integer.parseInt(args[2]);
height = Integer.parseInt(args[3]);
} else {
String[] size;
if (args[2].contains("*") && !args[2].contains("x")) {
size = args[2].split("\\*");
width = Integer.parseInt(size[0]);
height = Integer.parseInt(size[1]);
}
if (!args[2].contains("*") && args[2].contains("x")) {
size = args[2].split("x");
width = Integer.parseInt(size[0]);
height = Integer.parseInt(size[1]);
}
}
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
throwInvalidArgument(I.t("resize dimension as to be in format <n m> or <nxm> or <n*m>."));
return;
}
}
scaling = resizeMode();
}
if (width < 0 || height < 0) {
throwInvalidArgument(I.t("You need to specify a valid size. e.g. resize 4 5"));
return;
}
try {
ActionBar.sendPermanentMessage(player, ChatColor.DARK_GREEN + I.t("Rendering..."));
ImageRendererExecutor
.render(url, scaling, player.getUniqueId(), width, height, new WorkerCallback<ImageMap>() {
@Override
public void finished(ImageMap result) {
ActionBar.removeMessage(player);
MessageSender
.sendActionBarMessage(player,
ChatColor.DARK_GREEN + I.t("Rendering finished!"));
@Override
public void errored(Throwable exception) {
player.sendMessage(I.t("{ce}Map rendering failed: {0}", exception.getMessage()));
if (result.give(player)
&& (result instanceof PosterMap && !((PosterMap) result).hasColumnData())) {
info(I.t("The rendered map was too big to fit in your inventory."));
info(I.t("Use '/maptool getremaining' to get the remaining maps."));
}
}
PluginLogger.warning("Rendering from {0} failed: {1}: {2}",
player.getName(),
exception.getClass().getCanonicalName(),
exception.getMessage());
}
});
} finally {
ActionBar.removeMessage(player);
@Override
public void errored(Throwable exception) {
player.sendMessage(I.t("{ce}Map rendering failed: {0}", exception.getMessage()));
PluginLogger.warning("Rendering from {0} failed: {1}: {2}",
player.getName(),
exception.getClass().getCanonicalName(),
exception.getMessage());
}
});
} finally {
ActionBar.removeMessage(player);
}
}
*/
}
@Override

View File

@ -39,7 +39,6 @@ package fr.moribus.imageonmap.gui;
import fr.moribus.imageonmap.Permissions;
import fr.moribus.imageonmap.map.ImageMap;
import fr.moribus.imageonmap.map.PosterMap;
import fr.moribus.imageonmap.map.SingleMap;
import fr.moribus.imageonmap.ui.MapItemManager;
import fr.zcraft.quartzlib.components.gui.ExplorerGui;
import fr.zcraft.quartzlib.components.gui.Gui;
@ -105,9 +104,7 @@ public class MapDetailGui extends ExplorerGui<Integer> {
return null;
}
if (map instanceof SingleMap) {
return MapItemManager.createMapItem((SingleMap) map, true);
} else if (map instanceof PosterMap) {
if (map instanceof PosterMap) {
return MapItemManager.createMapItem((PosterMap) map, x, y);
}
@ -124,15 +121,11 @@ public class MapDetailGui extends ExplorerGui<Integer> {
return MapItemManager.createMapItem(poster, poster.getIndex(mapId));
}
@Override
protected ItemStack getEmptyViewItem() {
if (map instanceof SingleMap) {
return getViewItem(0, 0);
} else {
/* @Override
protected ItemStack getEmptyViewItem() {
return super.getEmptyViewItem();
}
}
*/
@Override
protected void onUpdate() {
/// Title of the map details GUI

View File

@ -41,7 +41,6 @@ import fr.moribus.imageonmap.PluginConfiguration;
import fr.moribus.imageonmap.map.ImageMap;
import fr.moribus.imageonmap.map.MapManager;
import fr.moribus.imageonmap.map.PosterMap;
import fr.moribus.imageonmap.map.SingleMap;
import fr.moribus.imageonmap.ui.MapItemManager;
import fr.moribus.imageonmap.ui.SplatterMapManager;
import fr.zcraft.quartzlib.components.gui.ExplorerGui;
@ -72,19 +71,14 @@ public class MapListGui extends ExplorerGui<ImageMap> {
@Override
protected ItemStack getViewItem(ImageMap map) {
String mapDescription;
if (map instanceof SingleMap) {
/// Displayed subtitle description of a single map on the list GUI
mapDescription = I.tl(getPlayerLocale(), "{white}Single map");
PosterMap poster = (PosterMap) map;
if (poster.hasColumnData()) {
/// Displayed subtitle description of a poster map on the list GUI (columns × rows in english)
mapDescription = I.tl(getPlayerLocale(), "{white}Poster map ({0} × {1})", poster.getColumnCount(),
poster.getRowCount());
} else {
PosterMap poster = (PosterMap) map;
if (poster.hasColumnData()) {
/// Displayed subtitle description of a poster map on the list GUI (columns × rows in english)
mapDescription = I.tl(getPlayerLocale(), "{white}Poster map ({0} × {1})", poster.getColumnCount(),
poster.getRowCount());
} else {
/// Displayed subtitle description of a poster map without column data on the list GUI
mapDescription = I.tl(getPlayerLocale(), "{white}Poster map ({0} parts)", poster.getMapCount());
}
/// Displayed subtitle description of a poster map without column data on the list GUI
mapDescription = I.tl(getPlayerLocale(), "{white}Poster map ({0} parts)", poster.getMapCount());
}
ItemStackBuilder builder = new ItemStackBuilder(Material.FILLED_MAP)
@ -147,9 +141,7 @@ public class MapListGui extends ExplorerGui<ImageMap> {
return null;
}
if (map instanceof SingleMap) {
return MapItemManager.createMapItem(map.getMapsIDs()[0], map.getName(), false, true);
} else if (map instanceof PosterMap) {
if (map instanceof PosterMap) {
PosterMap poster = (PosterMap) map;
if (poster.hasColumnData()) {

View File

@ -65,7 +65,6 @@ import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
@WorkerAttributes(name = "Image Renderer", queriesMainThread = true)
public class ImageRendererExecutor extends Worker {
public static void renderAndNotify(final URL url, final ImageUtils.ScalingType scaling, final UUID playerUUID,
@ -189,14 +188,10 @@ public class ImageRendererExecutor extends Worker {
// Limits are in place and the player does NOT have rights to avoid them.
checkSizeLimit(playerUUID, image);
final BufferedImage resizedImage;
if (scaling != ImageUtils.ScalingType.NONE && height <= 1 && width <= 1) {
resizedImage = scaling.resize(image, ImageMap.WIDTH * width, ImageMap.HEIGHT * height);
image.flush();//Safe to free
return renderSingle(resizedImage, playerUUID);
}
resizedImage =
scaling.resize(image, ImageMap.WIDTH * width, ImageMap.HEIGHT * height);
resizedImage = scaling.resize(image, ImageMap.WIDTH * width, ImageMap.HEIGHT * height);
image.flush();//Safe to free
return renderPoster(resizedImage, playerUUID);
}
}, callback);
@ -249,27 +244,6 @@ public class ImageRendererExecutor extends Worker {
});
}
private static ImageMap renderSingle(final BufferedImage image, final UUID playerUUID) throws Throwable {
MapManager.checkMapLimit(1, playerUUID);
final Future<Integer> futureMapID = submitToMainThread(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return MapManager.getNewMapsIds(1)[0];
}
});
final int mapID = futureMapID.get();
ImageIOExecutor.saveImage(mapID, image);
submitToMainThread(new Callable<Void>() {
@Override
public Void call() throws Exception {
Renderer.installRenderer(image, mapID);
return null;
}
});
return MapManager.createMap(playerUUID, mapID);
}
private static ImageMap renderPoster(final BufferedImage image, final UUID playerUUID) throws Throwable {
final PosterImage poster = new PosterImage(image);
final int mapCount = poster.getImagesCount();

View File

@ -91,7 +91,7 @@ public abstract class ImageMap implements ConfigurationSerializable {
}
public static ImageMap fromConfig(Map<String, Object> map, UUID userUUID) throws InvalidConfigurationException {
Type mapType;
Type mapType;//TODO refactor this
try {
mapType = Type.valueOf((String) map.get("type"));
} catch (ClassCastException ex) {
@ -100,7 +100,6 @@ public abstract class ImageMap implements ConfigurationSerializable {
switch (mapType) {
case SINGLE:
return new SingleMap(map, userUUID);
case POSTER:
return new PosterMap(map, userUUID);
default:

View File

@ -102,8 +102,10 @@ public abstract class MapManager {
}
public static ImageMap createMap(UUID playerUUID, int mapID) throws MapManagerException {
ImageMap newMap = new SingleMap(playerUUID, mapID);
addMap(newMap);
//ImageMap newMap = new SingleMap(playerUUID, mapID);
int[] ids = new int[] {mapID};
ImageMap newMap = new PosterMap(playerUUID, ids, 1, 1);
addMap(newMap);//TODO refactor this
return newMap;
}
@ -111,7 +113,8 @@ public abstract class MapManager {
ImageMap newMap;
if (image.getImagesCount() == 1) {
newMap = new SingleMap(playerUUID, mapsIDs[0]);
newMap = new PosterMap(playerUUID, mapsIDs, 1, 1);//TODO refactor this
//newMap = new SingleMap(playerUUID, mapsIDs[0]);
} else {
newMap = new PosterMap(playerUUID, mapsIDs, image.getColumns(), image.getLines());
}

View File

@ -1,82 +0,0 @@
/*
* Copyright or © or Copr. Moribus (2013)
* Copyright or © or Copr. ProkopyL <prokopylmc@gmail.com> (2015)
* Copyright or © or Copr. Amaury Carrade <amaury@carrade.eu> (2016 2022)
* Copyright or © or Copr. Vlammar <valentin.jabre@gmail.com> (2019 2022)
*
* This software is a computer program whose purpose is to allow insertion of
* custom images in a Minecraft world.
*
* This software is governed by the CeCILL license under French law and
* abiding by the rules of distribution of free software. You can use,
* modify and/ or redistribute the software under the terms of the CeCILL
* license as circulated by CEA, CNRS and INRIA at the following URL
* "http://www.cecill.info".
*
* As a counterpart to the access to the source code and rights to copy,
* modify and redistribute granted by the license, users are provided only
* with a limited warranty and the software's author, the holder of the
* economic rights, and the successive licensors have only limited
* liability.
*
* In this respect, the user's attention is drawn to the risks associated
* with loading, using, modifying and/or developing or reproducing the
* software by the user in light of its specific status of free software,
* that may mean that it is complicated to manipulate, and that also
* therefore means that it is reserved for developers and experienced
* professionals having in-depth computer knowledge. Users are therefore
* encouraged to load and test the software's suitability as regards their
* requirements in conditions enabling the security of their systems and/or
* data to be ensured and, more generally, to use and operate it in the
* same conditions as regards security.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL license and that you accept its terms.
*/
package fr.moribus.imageonmap.map;
import java.util.Map;
import java.util.UUID;
import org.bukkit.configuration.InvalidConfigurationException;
public class SingleMap extends ImageMap {
protected final int mapID;
public SingleMap(UUID ownerUUID, int mapID, String id, String name) {
super(ownerUUID, Type.SINGLE, id, name);
this.mapID = mapID;
}
public SingleMap(UUID ownerUUID, int mapID) {
this(ownerUUID, mapID, null, null);
}
public SingleMap(Map<String, Object> map, UUID userUUID) throws InvalidConfigurationException {
super(map, userUUID, Type.SINGLE);
mapID = getFieldValue(map, "mapID");
}
@Override
public int[] getMapsIDs() {
return new int[] {mapID};
}
@Override
public boolean managesMap(int mapID) {
return this.mapID == mapID;
}
/* ====== Serialization methods ====== */
@Override
public int getMapCount() {
return 1;
}
@Override
protected void postSerialize(Map<String, Object> map) {
map.put("mapID", mapID);
}
}

View File

@ -36,7 +36,6 @@
package fr.moribus.imageonmap.migration;
import fr.moribus.imageonmap.ImageOnMap;
import fr.zcraft.quartzlib.components.i18n.I;
import fr.zcraft.quartzlib.tools.PluginLogger;
@ -49,7 +48,7 @@ public class MigratorExecutor {
PluginLogger.error(I.t("Migration is already running."));
return;
}
migratorThread = new Thread(new V3Migrator(ImageOnMap.getPlugin()), "ImageOnMap-Migration");
//migratorThread = new Thread(new V3Migrator(ImageOnMap.getPlugin()), "ImageOnMap-Migration");
migratorThread.start();
}

View File

@ -1,97 +0,0 @@
/*
* Copyright or © or Copr. Moribus (2013)
* Copyright or © or Copr. ProkopyL <prokopylmc@gmail.com> (2015)
* Copyright or © or Copr. Amaury Carrade <amaury@carrade.eu> (2016 2022)
* Copyright or © or Copr. Vlammar <valentin.jabre@gmail.com> (2019 2022)
*
* This software is a computer program whose purpose is to allow insertion of
* custom images in a Minecraft world.
*
* This software is governed by the CeCILL license under French law and
* abiding by the rules of distribution of free software. You can use,
* modify and/ or redistribute the software under the terms of the CeCILL
* license as circulated by CEA, CNRS and INRIA at the following URL
* "http://www.cecill.info".
*
* As a counterpart to the access to the source code and rights to copy,
* modify and redistribute granted by the license, users are provided only
* with a limited warranty and the software's author, the holder of the
* economic rights, and the successive licensors have only limited
* liability.
*
* In this respect, the user's attention is drawn to the risks associated
* with loading, using, modifying and/or developing or reproducing the
* software by the user in light of its specific status of free software,
* that may mean that it is complicated to manipulate, and that also
* therefore means that it is reserved for developers and experienced
* professionals having in-depth computer knowledge. Users are therefore
* encouraged to load and test the software's suitability as regards their
* requirements in conditions enabling the security of their systems and/or
* data to be ensured and, more generally, to use and operate it in the
* same conditions as regards security.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL license and that you accept its terms.
*/
package fr.moribus.imageonmap.migration;
import fr.moribus.imageonmap.map.ImageMap;
import fr.moribus.imageonmap.map.MapManager;
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 {
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 ImageMap toImageMap(UUID userUUID) {
return new SingleMap(userUUID, mapId, null, mapName);
}
public void serialize(Configuration configuration) {
ArrayList<String> data = new ArrayList<>();
data.add(Short.toString(mapId));
data.add(mapName);
data.add(userName);
configuration.set(mapName, data);
}
public boolean isMapValid() {
return MapManager.mapIdExists(mapId);
}
public short getMapId() {
return mapId;
}
public String getUserName() {
return userName;
}
}

View File

@ -1,127 +0,0 @@
/*
* Copyright or © or Copr. Moribus (2013)
* Copyright or © or Copr. ProkopyL <prokopylmc@gmail.com> (2015)
* Copyright or © or Copr. Amaury Carrade <amaury@carrade.eu> (2016 2022)
* Copyright or © or Copr. Vlammar <valentin.jabre@gmail.com> (2019 2022)
*
* This software is a computer program whose purpose is to allow insertion of
* custom images in a Minecraft world.
*
* This software is governed by the CeCILL license under French law and
* abiding by the rules of distribution of free software. You can use,
* modify and/ or redistribute the software under the terms of the CeCILL
* license as circulated by CEA, CNRS and INRIA at the following URL
* "http://www.cecill.info".
*
* As a counterpart to the access to the source code and rights to copy,
* modify and redistribute granted by the license, users are provided only
* with a limited warranty and the software's author, the holder of the
* economic rights, and the successive licensors have only limited
* liability.
*
* In this respect, the user's attention is drawn to the risks associated
* with loading, using, modifying and/or developing or reproducing the
* software by the user in light of its specific status of free software,
* that may mean that it is complicated to manipulate, and that also
* therefore means that it is reserved for developers and experienced
* professionals having in-depth computer knowledge. Users are therefore
* encouraged to load and test the software's suitability as regards their
* requirements in conditions enabling the security of their systems and/or
* data to be ensured and, more generally, to use and operate it in the
* same conditions as regards security.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL license and that you accept its terms.
*/
package fr.moribus.imageonmap.migration;
import fr.moribus.imageonmap.map.ImageMap;
import fr.moribus.imageonmap.map.MapManager;
import fr.moribus.imageonmap.map.PosterMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.InvalidConfigurationException;
class OldSavedPoster {
private final String userName;
private final String posterName;
private final short[] mapsIds;
public OldSavedPoster(Object rawData, String key) throws InvalidConfigurationException {
posterName = key;
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 (short mapsId : mapsIds) {
if (mapsId == mapId) {
return true;
}
}
return false;
}
public ImageMap toImageMap(UUID userUUID) {
// Converts the maps IDs to int as MC 1.13.2+ uses integer ids
final int[] mapsIdsInt = new int[mapsIds.length];
Arrays.setAll(mapsIdsInt, i -> mapsIds[i]);
return new PosterMap(userUUID, mapsIdsInt, null, "poster", 0, 0);
}
public void serialize(Configuration configuration) {
ArrayList<String> data = new ArrayList<>();
data.add(userName);
for (short mapId : mapsIds) {
data.add(Short.toString(mapId));
}
configuration.set(posterName, data);
}
public boolean isMapValid() {
for (short mapId : mapsIds) {
if (!MapManager.mapIdExists(mapId)) {
return false;
}
}
return true;
}
public String getUserName() {
return userName;
}
public short[] getMapsIds() {
return mapsIds;
}
}

View File

@ -1,619 +0,0 @@
/*
* Copyright or © or Copr. Moribus (2013)
* Copyright or © or Copr. ProkopyL <prokopylmc@gmail.com> (2015)
* Copyright or © or Copr. Amaury Carrade <amaury@carrade.eu> (2016 2022)
* Copyright or © or Copr. Vlammar <valentin.jabre@gmail.com> (2019 2022)
*
* This software is a computer program whose purpose is to allow insertion of
* custom images in a Minecraft world.
*
* This software is governed by the CeCILL license under French law and
* abiding by the rules of distribution of free software. You can use,
* modify and/ or redistribute the software under the terms of the CeCILL
* license as circulated by CEA, CNRS and INRIA at the following URL
* "http://www.cecill.info".
*
* As a counterpart to the access to the source code and rights to copy,
* modify and redistribute granted by the license, users are provided only
* with a limited warranty and the software's author, the holder of the
* economic rights, and the successive licensors have only limited
* liability.
*
* In this respect, the user's attention is drawn to the risks associated
* with loading, using, modifying and/or developing or reproducing the
* software by the user in light of its specific status of free software,
* that may mean that it is complicated to manipulate, and that also
* therefore means that it is reserved for developers and experienced
* professionals having in-depth computer knowledge. Users are therefore
* encouraged to load and test the software's suitability as regards their
* requirements in conditions enabling the security of their systems and/or
* data to be ensured and, more generally, to use and operate it in the
* same conditions as regards security.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL license and that you accept its terms.
*/
package fr.moribus.imageonmap.migration;
import fr.moribus.imageonmap.ImageOnMap;
import fr.moribus.imageonmap.map.MapManager;
import fr.zcraft.quartzlib.components.i18n.I;
import fr.zcraft.quartzlib.tools.PluginLogger;
import fr.zcraft.quartzlib.tools.mojang.UUIDFetcher;
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.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
/**
* This class represents and executes the ImageOnMap v3.x migration process
*/
public class V3Migrator implements Runnable {
/**
* The name of the former images directory
*/
private static final String OLD_IMAGES_DIRECTORY_NAME = "Image";
/**
* The name of the former file that contained all the maps definitions (including posters)
*/
private static final String OLD_MAPS_FILE_NAME = "map.yml";
/**
* The name of the former file that contained all the posters definitions
*/
private static 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
*/
private static 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
*/
private static final String BACKUPS_POSTV3_DIRECTORY_NAME = "backups_post-v3";
/**
* The plugin that is running the migration
*/
private final ImageOnMap plugin;
/**
* 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 ArrayDeque<OldSavedPoster> postersToMigrate;
/**
* The list of all the single maps to migrate
*/
private final ArrayDeque<OldSavedMap> mapsToMigrate;
/**
* The set of all the user names to retreive the UUID from Mojang
*/
private final HashSet<String> userNamesToFetch;
/**
* 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 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 ArrayDeque<>();
mapsToMigrate = new ArrayDeque<>();
userNamesToFetch = new HashSet<>();
}
/**
* Returns the former images directory of a given plugin
*
* @param plugin The plugin.
* @return the corresponding 'Image' directory
*/
public static File getOldImagesDirectory(Plugin plugin) {
return new File(plugin.getDataFolder(), OLD_IMAGES_DIRECTORY_NAME);
}
/**
* 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
*/
private static 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.");
}
}
/* ****** Actions ***** */
/**
* 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
**/
private static 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();
}
/**
* Executes the full migration
*/
private void migrate() {
try {
if (!spotFilesToMigrate()) {
return;
}
if (checkForExistingBackups()) {
return;
}
if (!loadOldFiles()) {
return;
}
backupMapData();
fetchUUIDs();
if (!fetchMissingUUIDs()) {
return;
}
} catch (Exception ex) {
PluginLogger.error(I.t("Error while preparing migration"));
PluginLogger.error(I.t("Aborting migration. No change has been made."), ex);
return;
}
try {
mergeMapData();
saveChanges();
cleanup();
} catch (Exception ex) {
PluginLogger.error(I.t("Error while migrating"), ex);
PluginLogger.error(I.t("Aborting migration. Some changes may already have been made."));
PluginLogger.error(I.t(
"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."));
}
}
/**
* 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() {
PluginLogger.info(I.t("Looking for configuration files to migrate..."));
if (!oldPostersFile.exists()) {
oldPostersFile = null;
} else {
PluginLogger.info(I.t("Detected former posters file {0}", OLD_POSTERS_FILE_NAME));
}
if (!oldMapsFile.exists()) {
oldMapsFile = null;
} else {
PluginLogger.info(I.t("Detected former maps file {0}", OLD_MAPS_FILE_NAME));
}
if (oldPostersFile == null && oldMapsFile == null) {
PluginLogger.info(I.t("There is nothing to migrate. Stopping."));
return false;
} else {
PluginLogger.info(I.t("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)) {
PluginLogger.error(I.t("Backup directories already exists."));
PluginLogger.error(I.t("This means that a migration has already been done,"
+ " or may not have ended well."));
PluginLogger.error(I.t(
"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 {
PluginLogger.info(I.t("Backing up map data before migrating..."));
if (!backupsPrev3Directory.exists()) {
backupsPrev3Directory.mkdirs();
}
if (!backupsPostv3Directory.exists()) {
backupsPostv3Directory.mkdirs();
}
if (oldMapsFile != null && oldMapsFile.exists()) {
File oldMapsFileBackup = new File(backupsPrev3Directory, oldMapsFile.getName());
verifiedBackupCopy(oldMapsFile, oldMapsFileBackup);
}
if (oldPostersFile != null && 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);
}
PluginLogger.info(I.t("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() {
if (oldPostersFile != null) {
FileConfiguration oldPosters = YamlConfiguration.loadConfiguration(oldPostersFile);
OldSavedPoster oldPoster;
for (String key : oldPosters.getKeys(false)) {
if ("IdCount".equals(key)) {
continue;
}
try {
oldPoster = new OldSavedPoster(oldPosters.get(key), key);
postersToMigrate.add(oldPoster);
if (!userNamesToFetch.contains(oldPoster.getUserName())) {
userNamesToFetch.add(oldPoster.getUserName());
}
} catch (InvalidConfigurationException ex) {
PluginLogger.warning("Could not read poster data for key {0}", ex, key);
}
}
}
if (oldMapsFile != null) {
FileConfiguration oldMaps = YamlConfiguration.loadConfiguration(oldMapsFile);
OldSavedMap oldMap;
for (String key : oldMaps.getKeys(false)) {
try {
if ("IdCount".equals(key)) {
continue;
}
oldMap = new OldSavedMap(oldMaps.get(key));
if (!posterContains(oldMap)) {
mapsToMigrate.add(oldMap);
}
if (!userNamesToFetch.contains(oldMap.getUserName())) {
userNamesToFetch.add(oldMap.getUserName());
}
} catch (InvalidConfigurationException ex) {
PluginLogger.warning("Could not read poster data for key '{0}'", ex, key);
}
}
}
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 {
PluginLogger.info(I.t("Fetching UUIDs from Mojang..."));
try {
usersUUIDs = UUIDFetcher.fetch(new ArrayList<String>(userNamesToFetch));
} catch (IOException ex) {
PluginLogger.error(I.t("An error occurred while fetching the UUIDs from Mojang"), ex);
throw ex;
} catch (InterruptedException ex) {
PluginLogger.error(I.t("The migration worker has been interrupted"), ex);
throw ex;
}
PluginLogger.info(I.tn("Fetching done. {0} UUID have been retrieved.",
"Fetching done. {0} UUIDs have been retrieved.", usersUUIDs.size()));
}
/**
* Fetches the UUIDs that could not be retrieved via Mojang's standard API
*
* @return true if at least one UUID has been retrieved, false otherwise
*/
private boolean fetchMissingUUIDs() throws IOException, InterruptedException {
if (usersUUIDs.size() == userNamesToFetch.size()) {
return true;
}
int remainingUsersCount = userNamesToFetch.size() - usersUUIDs.size();
PluginLogger.info(I.tn("Mojang did not find UUIDs for {0} player at the current time.",
"Mojang did not find UUIDs for {0} players at the current time.", remainingUsersCount));
PluginLogger.info(I.t("The Mojang servers limit requests rate at one per second, this may take some time..."));
try {
UUIDFetcher.fetchRemaining(userNamesToFetch, usersUUIDs);
} catch (IOException ex) {
PluginLogger.error(I.t("An error occurred while fetching the UUIDs from Mojang"));
throw ex;
} catch (InterruptedException ex) {
PluginLogger.error(I.t("The migration worker has been interrupted"));
throw ex;
}
if (usersUUIDs.size() != userNamesToFetch.size()) {
PluginLogger.warning(I.tn("Mojang did not find player data for {0} player",
"Mojang did not find player data for {0} players",
userNamesToFetch.size() - usersUUIDs.size()));
PluginLogger.warning(I.t("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) {
PluginLogger.info(I.t("Mojang could not find any of the registered players."));
PluginLogger.info(I.t("There is nothing to migrate. Stopping."));
return false;
}
return true;
}
private void mergeMapData() {
PluginLogger.info(I.t("Merging map data..."));
ArrayDeque<OldSavedMap> remainingMaps = new ArrayDeque<>();
ArrayDeque<OldSavedPoster> remainingPosters = new ArrayDeque<>();
ArrayDeque<Integer> missingMapIds = new ArrayDeque<>();
UUID playerUUID;
OldSavedMap map;
while (!mapsToMigrate.isEmpty()) {
map = mapsToMigrate.pop();
playerUUID = usersUUIDs.get(map.getUserName());
if (playerUUID == null) {
remainingMaps.add(map);
} else if (!map.isMapValid()) {
missingMapIds.add((int) map.getMapId());
} 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 if (!poster.isMapValid()) {
missingMapIds.addAll(Arrays.stream(ArrayUtils.toObject(poster.getMapsIds())).map(id -> (int) id)
.collect(Collectors.toList()));
} else {
MapManager.insertMap(poster.toImageMap(playerUUID));
}
}
postersToMigrate.addAll(remainingPosters);
if (!missingMapIds.isEmpty()) {
PluginLogger.warning(I.tn("{0} registered minecraft map is missing from the save.",
"{0} registered minecraft maps are missing from the save.", missingMapIds.size()));
PluginLogger.warning(
I.t("These maps will not be migrated,"
+ " but this could mean the save has been altered or corrupted."));
PluginLogger.warning(I.t("The following maps are missing : {0} ",
StringUtils.join(missingMapIds, ',')));
}
}
/* ****** Utils ***** */
private void saveChanges() {
PluginLogger.info(I.t("Saving changes..."));
MapManager.save();
}
private void cleanup() throws IOException {
PluginLogger.info(I.t("Cleaning up old data files..."));
//Cleaning maps file
if (oldMapsFile != null) {
if (mapsToMigrate.isEmpty()) {
PluginLogger.info(I.t("Deleting old map data file..."));
oldMapsFile.delete();
} else {
PluginLogger.info(I.tn("{0} map could not be migrated.", "{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(I.t("Deleting old poster data file..."));
oldPostersFile.delete();
} else {
PluginLogger.info(I.tn("{0} poster could not be migrated.", "{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(I.t("Data that has not been migrated will be kept in the old data files."));
}
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
*/
@Override
public void run() {
setRunning(true);
migrate();
setRunning(false);
}
}

View File

@ -40,7 +40,6 @@ import fr.moribus.imageonmap.Permissions;
import fr.moribus.imageonmap.map.ImageMap;
import fr.moribus.imageonmap.map.MapManager;
import fr.moribus.imageonmap.map.PosterMap;
import fr.moribus.imageonmap.map.SingleMap;
import fr.zcraft.quartzlib.components.i18n.I;
import fr.zcraft.quartzlib.core.QuartzLib;
import fr.zcraft.quartzlib.tools.items.ItemStackBuilder;
@ -88,15 +87,10 @@ public class MapItemManager implements Listener {
public static boolean give(Player player, ImageMap map) {
if (map instanceof PosterMap) {
return give(player, (PosterMap) map);
} else if (map instanceof SingleMap) {
return give(player, (SingleMap) map);
}
return false;
}
public static boolean give(Player player, SingleMap map) {
return give(player, createMapItem(map, true));
}
public static boolean give(Player player, PosterMap map) {
if (!map.hasColumnData()) {
@ -141,14 +135,6 @@ public class MapItemManager implements Listener {
return givenItemsCount;
}
public static ItemStack createMapItem(SingleMap map) {
return createMapItem(map.getMapsIDs()[0], map.getName(), false);
}
public static ItemStack createMapItem(SingleMap map, boolean goldTitle) {
return createMapItem(map.getMapsIDs()[0], map.getName(), false, goldTitle);
}
public static ItemStack createMapItem(PosterMap map, int index) {
return createMapItem(map.getMapIdAt(index), getMapTitle(map, index), true);
}
@ -163,17 +149,24 @@ public class MapItemManager implements Listener {
public static ItemStack createMapItem(int mapID, String text, boolean isMapPart, boolean goldTitle) {
ItemStack mapItem;
if (goldTitle) {
if (text == "") {
mapItem = new ItemStackBuilder(Material.FILLED_MAP)
.title(ChatColor.GOLD, text)
.hideAllAttributes()
.item();
} else {
mapItem = new ItemStackBuilder(Material.FILLED_MAP)
.title(text)
.hideAllAttributes()
.item();
if (goldTitle) {
mapItem = new ItemStackBuilder(Material.FILLED_MAP)
.title(ChatColor.GOLD, text)
.hideAllAttributes()
.item();
} else {
mapItem = new ItemStackBuilder(Material.FILLED_MAP)
.title(text)
.hideAllAttributes()
.item();
}
}
final MapMeta meta = (MapMeta) mapItem.getItemMeta();
meta.setMapId(mapID);
meta.setColor(isMapPart ? Color.LIME : Color.GREEN);
@ -193,17 +186,14 @@ public class MapItemManager implements Listener {
private static String getMapTitle(ItemStack item) {
ImageMap map = MapManager.getMap(item);
if (map instanceof SingleMap) {
return map.getName();
} else {
PosterMap poster = (PosterMap) map;
int index = poster.getIndex(MapManager.getMapIdFromItemStack(item));
if (poster.hasColumnData()) {
return getMapTitle(poster, poster.getRowAt(index), poster.getColumnAt(index));
}
return getMapTitle(poster, index);
PosterMap poster = (PosterMap) map;
int index = poster.getIndex(MapManager.getMapIdFromItemStack(item));
if (poster.hasColumnData()) {
return getMapTitle(poster, poster.getRowAt(index), poster.getColumnAt(index));
}
return getMapTitle(poster, index);
//}
}
//
@ -296,19 +286,19 @@ public class MapItemManager implements Listener {
return;
}
if (Permissions.REMOVE_SPLATTER_MAP.grantedTo(player)) {
if (player.isSneaking()) {
PosterMap poster = SplatterMapManager.removeSplatterMap(frame, player);
if (poster != null) {
event.setCancelled(true);
if (Permissions.REMOVE_SPLATTER_MAP.grantedTo(player) && player.isSneaking()) {
if (player.getGameMode() != GameMode.CREATIVE
|| !SplatterMapManager.hasSplatterMap(player, poster)) {
poster.give(player);
}
return;
PosterMap poster = SplatterMapManager.removeSplatterMap(frame, player);
if (poster != null) {
event.setCancelled(true);
if (player.getGameMode() != GameMode.CREATIVE
|| !SplatterMapManager.hasSplatterMap(player, poster)) {
poster.give(player);
}
return;
}
}
if (!MapManager.managesMap(frame.getItem())) {

View File

@ -57,6 +57,7 @@ import org.bukkit.Material;
import org.bukkit.Rotation;
import org.bukkit.attribute.AttributeModifier;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Item;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerInteractEntityEvent;
@ -66,7 +67,7 @@ import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.MapMeta;
//TODO rework splatter effect, using ID is far more stable than nbt tags.
// To update when adding small picture previsualization.
// To update when adding small picture snapshot.
public abstract class SplatterMapManager {
private SplatterMapManager() {
}
@ -318,8 +319,9 @@ public abstract class SplatterMapManager {
RunTask.later(() -> {
addPropertiesToFrames(player, frame);
frame.setItem(
new ItemStackBuilder(Material.FILLED_MAP).nbt(ImmutableMap.of("map", id)).craftItem());
ItemStack item = MapItemManager.createMapItem(id, "", true, false);
frame.setItem(item);
}, 5L);