From 915f9d960817a1c697ba59da5ac35a0222274213 Mon Sep 17 00:00:00 2001 From: "Blue (Lukas Rieger)" Date: Sun, 14 Nov 2021 14:18:31 +0100 Subject: [PATCH] Add SQL-Storage and add storage configs --- .../bluemap/common/BlueMapService.java | 242 ++++-- .../common/MissingResourcesException.java | 9 +- .../bluemap/common/plugin/Plugin.java | 80 +- .../common/plugin/commands/Commands.java | 2 +- .../common/rendermanager/MapPurgeTask.java | 2 +- .../common/web/FileRequestHandler.java | 173 ++--- .../common/web/MapStorageRequestHandler.java | 219 ++++++ .../bluemap/common/web/WebSettings.java | 2 +- .../bluemap/config/storages/file.conf | 20 + .../bluemap/config/storages/sql.conf | 39 + BlueMapCore/build.gradle | 1 + .../bluemap/core/config/ConfigManager.java | 128 ++-- .../core/config/ConfigurationException.java | 83 ++ .../core/config/old/ConfigManager.java | 162 ++++ .../core/config/{ => old}/CoreConfig.java | 5 +- .../core/config/{ => old}/MapConfig.java | 25 +- .../core/config/{ => old}/Placeholder.java | 2 +- .../core/config/{ => old}/RenderConfig.java | 7 +- .../config/{ => old}/WebServerConfig.java | 24 +- .../core/config/storage/FileConfig.java | 51 ++ .../core/config/storage/SQLConfig.java | 81 ++ .../storage/StorageConfig.java} | 18 +- .../bluecolored/bluemap/core/map/BmMap.java | 10 +- .../core/map/lowres/LowresModelManager.java | 5 +- .../core/storage/CompressedInputStream.java | 73 ++ .../bluemap/core/storage/Compression.java | 63 +- .../bluemap/core/storage/MetaType.java | 29 +- .../bluemap/core/storage/Storage.java | 20 +- .../bluemap/core/storage/StorageType.java | 55 ++ .../bluemap/core/storage/TileData.java | 14 +- .../bluemap/core/storage/TileType.java | 11 + .../core/storage/{ => file}/FileStorage.java | 79 +- .../bluemap/core/storage/sql/SQLStorage.java | 718 ++++++++++++++++++ .../bluemap/core/util/AtomicFileHelper.java | 38 - .../bluemap/core/util/MappableFunction.java | 48 ++ .../bluemap/core/util/WrappedInputStream.java | 82 ++ .../core/util/WrappedOutputStream.java | 87 +++ .../core/webserver/HttpConnection.java | 7 +- .../bluemap/core/webserver/HttpResponse.java | 3 +- .../bluecolored/bluemap/cli/BlueMapCLI.java | 27 +- .../de/bluecolored/bluemap/render.conf | 11 +- .../de/bluecolored/bluemap/render.conf | 11 +- .../de/bluecolored/bluemap/render.conf | 11 +- .../de/bluecolored/bluemap/render.conf | 11 +- .../de/bluecolored/bluemap/render.conf | 11 +- .../de/bluecolored/bluemap/render.conf | 11 +- .../de/bluecolored/bluemap/render.conf | 11 +- .../de/bluecolored/bluemap/render.conf | 11 +- 48 files changed, 2343 insertions(+), 489 deletions(-) create mode 100644 BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/MapStorageRequestHandler.java create mode 100644 BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/storages/file.conf create mode 100644 BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/storages/sql.conf create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/ConfigurationException.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/old/ConfigManager.java rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/{ => old}/CoreConfig.java (92%) rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/{ => old}/MapConfig.java (85%) rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/{ => old}/Placeholder.java (97%) rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/{ => old}/RenderConfig.java (88%) rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/{ => old}/WebServerConfig.java (73%) create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/storage/FileConfig.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/storage/SQLConfig.java rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/{util/ThrowingRunnable.java => config/storage/StorageConfig.java} (73%) create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/CompressedInputStream.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/StorageType.java rename BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/ThrowingFunction.java => BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/TileData.java (83%) rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/{ => file}/FileStorage.java (65%) create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLStorage.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MappableFunction.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WrappedInputStream.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WrappedOutputStream.java diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java index d237bfe1..81cf6bc6 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java @@ -28,21 +28,30 @@ import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface; import de.bluecolored.bluemap.common.web.WebSettings; import de.bluecolored.bluemap.core.MinecraftVersion; -import de.bluecolored.bluemap.core.config.*; +import de.bluecolored.bluemap.core.config.ConfigManager; +import de.bluecolored.bluemap.core.config.ConfigurationException; +import de.bluecolored.bluemap.core.config.old.CoreConfig; +import de.bluecolored.bluemap.core.config.old.MapConfig; +import de.bluecolored.bluemap.core.config.old.RenderConfig; +import de.bluecolored.bluemap.core.config.old.WebServerConfig; +import de.bluecolored.bluemap.core.config.storage.StorageConfig; import de.bluecolored.bluemap.core.debug.DebugDump; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.map.BmMap; import de.bluecolored.bluemap.core.mca.MCAWorld; import de.bluecolored.bluemap.core.resourcepack.ParseResourceException; import de.bluecolored.bluemap.core.resourcepack.ResourcePack; -import de.bluecolored.bluemap.core.storage.FileStorage; import de.bluecolored.bluemap.core.storage.Storage; import de.bluecolored.bluemap.core.world.World; import org.apache.commons.io.FileUtils; +import org.spongepowered.configurate.ConfigurationNode; import java.io.File; import java.io.IOException; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; /** @@ -56,15 +65,19 @@ public class BlueMapService { private final ThrowingFunction worldNameProvider; private final ConfigManager configManager; + private final de.bluecolored.bluemap.core.config.old.ConfigManager configManagerOld; private CoreConfig coreConfig; private RenderConfig renderConfig; private WebServerConfig webServerConfig; + private final Map storages; + private ResourcePack resourcePack; private Map worlds; private Map maps; + private Map mapStorages; public BlueMapService(MinecraftVersion minecraftVersion, File configFolder) { this.minecraftVersion = minecraftVersion; @@ -82,7 +95,10 @@ public BlueMapService(MinecraftVersion minecraftVersion, File configFolder) { this.worldNameProvider = uuid -> null; - configManager = new ConfigManager(); + this.storages = new HashMap<>(); + + configManagerOld = new de.bluecolored.bluemap.core.config.old.ConfigManager(); + configManager = new ConfigManager(this.configFolder.toPath()); } public BlueMapService(MinecraftVersion minecraftVersion, ServerInterface serverInterface) { @@ -91,47 +107,76 @@ public BlueMapService(MinecraftVersion minecraftVersion, ServerInterface serverI this.worldUUIDProvider = serverInterface::getUUIDForWorld; this.worldNameProvider = serverInterface::getWorldName; - this.configManager = new ConfigManager(); + this.storages = new HashMap<>(); + + this.configManagerOld = new de.bluecolored.bluemap.core.config.old.ConfigManager(); + configManager = new ConfigManager(this.configFolder.toPath()); } - public synchronized void createOrUpdateWebApp(boolean force) throws IOException { + public synchronized void createOrUpdateWebApp(boolean force) throws ConfigurationException { WebFilesManager webFilesManager = new WebFilesManager(getRenderConfig().getWebRoot()); if (force || webFilesManager.needsUpdate()) { - webFilesManager.updateFiles(); + try { + webFilesManager.updateFiles(); + } catch (IOException ex) { + throw new ConfigurationException("Failed to update web-app files!", ex); + } } } - public synchronized WebSettings updateWebAppSettings() throws IOException, InterruptedException { - WebSettings webSettings = new WebSettings(new File(getRenderConfig().getWebRoot(), "data" + File.separator + "settings.json")); - webSettings.set(getRenderConfig().isUseCookies(), "useCookies"); - webSettings.set(getRenderConfig().isEnableFreeFlight(), "freeFlightEnabled"); - webSettings.setAllMapsEnabled(false); - for (BmMap map : getMaps().values()) { - webSettings.setMapEnabled(true, map.getId()); - webSettings.setFrom(map); - } - int ordinal = 0; - for (MapConfig map : getRenderConfig().getMapConfigs()) { - if (!getMaps().containsKey(map.getId())) continue; //don't add not loaded maps - webSettings.setOrdinal(ordinal++, map.getId()); - webSettings.setFrom(map); - } - webSettings.save(); + public synchronized WebSettings updateWebAppSettings() throws ConfigurationException, InterruptedException { + try { + WebSettings webSettings = new WebSettings(new File(getRenderConfig().getWebRoot(), + "data" + File.separator + "settings.json")); - return webSettings; + webSettings.set(getRenderConfig().isUseCookies(), "useCookies"); + webSettings.set(getRenderConfig().isEnableFreeFlight(), "freeFlightEnabled"); + webSettings.setAllMapsEnabled(false); + for (BmMap map : getMaps().values()) { + webSettings.setMapEnabled(true, map.getId()); + webSettings.setFrom(map); + } + int ordinal = 0; + for (MapConfig map : getRenderConfig().getMapConfigs()) { + if (!getMaps().containsKey(map.getId())) continue; //don't add not loaded maps + webSettings.setOrdinal(ordinal++, map.getId()); + webSettings.setFrom(map); + } + webSettings.save(); + + return webSettings; + } catch (IOException ex) { + throw new ConfigurationException("Failed to update web-app settings!", ex); + } } - public synchronized Map getWorlds() throws IOException, InterruptedException { + public synchronized Map getWorlds() throws ConfigurationException, InterruptedException { if (worlds == null) loadWorldsAndMaps(); return worlds; } - public synchronized Map getMaps() throws IOException, InterruptedException { + public synchronized Map getMaps() throws ConfigurationException, InterruptedException { if (maps == null) loadWorldsAndMaps(); return maps; } - private synchronized void loadWorldsAndMaps() throws IOException, InterruptedException { + public synchronized Map getMapStorages() throws ConfigurationException { + if (mapStorages == null) { + mapStorages = new HashMap<>(); + if (maps == null) { + for (MapConfig mapConfig : getRenderConfig().getMapConfigs()) { + mapStorages.put(mapConfig.getId(), getStorage(mapConfig.getStorage())); + } + } else { + for (BmMap map : maps.values()) { + mapStorages.put(map.getId(), map.getStorage()); + } + } + } + return mapStorages; + } + + private synchronized void loadWorldsAndMaps() throws ConfigurationException, InterruptedException { maps = new HashMap<>(); worlds = new HashMap<>(); @@ -141,7 +186,7 @@ private synchronized void loadWorldsAndMaps() throws IOException, InterruptedExc File worldFolder = new File(mapConfig.getWorldPath()); if (!worldFolder.exists() || !worldFolder.isDirectory()) { - Logger.global.logWarning("Failed to load map '" + id + "': '" + worldFolder.getCanonicalPath() + "' does not exist or is no directory!"); + Logger.global.logWarning("Failed to load map '" + id + "': '" + worldFolder.getAbsolutePath() + "' does not exist or is no directory!"); continue; } @@ -158,36 +203,85 @@ private synchronized void loadWorldsAndMaps() throws IOException, InterruptedExc try { world = MCAWorld.load(worldFolder.toPath(), worldUUID, worldNameProvider.apply(worldUUID), mapConfig.getWorldSkyLight(), mapConfig.isIgnoreMissingLightData()); worlds.put(worldUUID, world); - } catch (MissingResourcesException e) { - throw e; // rethrow this to stop loading and display resource-missing message } catch (IOException e) { Logger.global.logError("Failed to load map '" + id + "'!", e); continue; } } - Storage storage = new FileStorage( - getRenderConfig().getWebRoot().toPath().resolve("data"), - mapConfig.getCompression() - ); + Storage storage = getStorage(mapConfig.getStorage()); + try { + BmMap map = new BmMap( + id, + name, + world, + storage, + getResourcePack(), + mapConfig + ); - BmMap map = new BmMap( - id, - name, - world, - storage, - getResourcePack(), - mapConfig - ); - - maps.put(id, map); + maps.put(id, map); + } catch (IOException ex) { + Logger.global.logError("Failed to load map '" + id + "'!", ex); + } } worlds = Collections.unmodifiableMap(worlds); maps = Collections.unmodifiableMap(maps); } - public synchronized ResourcePack getResourcePack() throws IOException, InterruptedException { + public synchronized Storage getStorage(String id) throws ConfigurationException { + Storage storage = storages.get(id); + + if (storage == null) { + storage = loadStorage(id); + storages.put(id, storage); + } + + return storage; + } + + private synchronized Storage loadStorage(String id) throws ConfigurationException { + Logger.global.logInfo("Loading storage '" + id + "'..."); + + Path storageFolder = Paths.get("storages"); + Path storageConfigFolder = configManager.getConfigRoot().resolve(storageFolder); + + if (!Files.exists(storageConfigFolder)){ + try { + Files.createDirectories(storageConfigFolder); + + Files.copy( + Objects.requireNonNull(BlueMapService.class + .getResourceAsStream("/de/bluecolored/bluemap/config/storages/file.conf")), + storageConfigFolder.resolve("file.conf") + ); + Files.copy( + Objects.requireNonNull(BlueMapService.class + .getResourceAsStream("/de/bluecolored/bluemap/config/storages/sql.conf")), + storageConfigFolder.resolve("sql.conf") + ); + } catch (IOException | NullPointerException ex) { + Logger.global.logWarning("Failed to create default storage-configuration-files: " + ex); + } + } + + try { + ConfigurationNode node = configManager.loadConfig(storageFolder.resolve(id)); + StorageConfig storageConfig = Objects.requireNonNull(node.get(StorageConfig.class)); + Storage storage = storageConfig.getStorageType().create(node); + storage.initialize(); + return storage; + } catch (Exception ex) { + throw new ConfigurationException( + "BlueMap tried to create the storage '" + id + "' but something went wrong.\n" + + "Check if that storage is configured correctly.", + ex + ); + } + } + + public synchronized ResourcePack getResourcePack() throws ConfigurationException, InterruptedException { if (resourcePack == null) { File defaultResourceFile = new File(getCoreConfig().getDataFolder(), "minecraft-client-" + minecraftVersion.getResource().getVersion().getVersionString() + ".jar"); File resourceExtensionsFile = new File(getCoreConfig().getDataFolder(), "resourceExtensions.zip"); @@ -195,7 +289,15 @@ public synchronized ResourcePack getResourcePack() throws IOException, Interrupt File textureExportFile = new File(getRenderConfig().getWebRoot(), "data" + File.separator + "textures.json"); File resourcePackFolder = new File(configFolder, "resourcepacks"); - FileUtils.forceMkdir(resourcePackFolder); + try { + FileUtils.forceMkdir(resourcePackFolder); + } catch (IOException ex) { + throw new ConfigurationException( + "BlueMap failed to create this folder:\n" + + resourcePackFolder + "\n" + + "Does BlueMap has sufficient permissions?", + ex); + } if (!defaultResourceFile.exists()) { if (getCoreConfig().isDownloadAccepted()) { @@ -205,8 +307,8 @@ public synchronized ResourcePack getResourcePack() throws IOException, Interrupt Logger.global.logInfo("Downloading " + minecraftVersion.getResource().getClientUrl() + " to " + defaultResourceFile + " ..."); FileUtils.forceMkdirParent(defaultResourceFile); FileUtils.copyURLToFile(new URL(minecraftVersion.getResource().getClientUrl()), defaultResourceFile, 10000, 10000); - } catch (IOException e) { - throw new IOException("Failed to download resources!", e); + } catch (IOException ex) { + throw new ConfigurationException("Failed to download resources!", ex); } } else { @@ -216,12 +318,21 @@ public synchronized ResourcePack getResourcePack() throws IOException, Interrupt Logger.global.logInfo("Loading resources..."); - if (resourceExtensionsFile.exists()) FileUtils.forceDelete(resourceExtensionsFile); - FileUtils.forceMkdirParent(resourceExtensionsFile); - URL resourceExtensionsUrl = Objects.requireNonNull( - Plugin.class.getResource("/de/bluecolored/bluemap/" + minecraftVersion.getResource().getResourcePrefix() + "/resourceExtensions.zip") - ); - FileUtils.copyURLToFile(resourceExtensionsUrl, resourceExtensionsFile, 10000, 10000); + try { + if (resourceExtensionsFile.exists()) FileUtils.forceDelete(resourceExtensionsFile); + FileUtils.forceMkdirParent(resourceExtensionsFile); + URL resourceExtensionsUrl = Objects.requireNonNull( + Plugin.class.getResource( + "/de/bluecolored/bluemap/" + minecraftVersion.getResource().getResourcePrefix() + + "/resourceExtensions.zip") + ); + FileUtils.copyURLToFile(resourceExtensionsUrl, resourceExtensionsFile, 10000, 10000); + } catch (IOException ex) { + throw new ConfigurationException( + "Failed to create resourceExtensions.zip!\n" + + "Does BlueMap has sufficient write permissions?", + ex); + } //find more resource packs File[] resourcePacks = resourcePackFolder.listFiles(); @@ -238,8 +349,8 @@ public synchronized ResourcePack getResourcePack() throws IOException, Interrupt if (textureExportFile.exists()) resourcePack.loadTextureFile(textureExportFile); resourcePack.load(resources); resourcePack.saveTextureFile(textureExportFile); - } catch (ParseResourceException e) { - throw new IOException("Failed to parse resources!", e); + } catch (IOException | ParseResourceException e) { + throw new ConfigurationException("Failed to parse resources!", e); } } @@ -247,17 +358,18 @@ public synchronized ResourcePack getResourcePack() throws IOException, Interrupt return resourcePack; } - public synchronized ConfigManager getConfigManager() { - return configManager; + @Deprecated + public synchronized de.bluecolored.bluemap.core.config.old.ConfigManager getConfigManagerOld() { + return configManagerOld; } public File getCoreConfigFile() { return new File(configFolder, "core.conf"); } - public synchronized CoreConfig getCoreConfig() throws IOException { + public synchronized CoreConfig getCoreConfig() throws ConfigurationException { if (coreConfig == null) { - coreConfig = new CoreConfig(configManager.loadOrCreate( + coreConfig = new CoreConfig(configManagerOld.loadOrCreate( getCoreConfigFile(), Plugin.class.getResource("/de/bluecolored/bluemap/core.conf"), Plugin.class.getResource("/de/bluecolored/bluemap/core-defaults.conf"), @@ -273,9 +385,9 @@ public File getRenderConfigFile() { return new File(configFolder, "render.conf"); } - public synchronized RenderConfig getRenderConfig() throws IOException { + public synchronized RenderConfig getRenderConfig() throws ConfigurationException { if (renderConfig == null) { - renderConfig = new RenderConfig(configManager.loadOrCreate( + renderConfig = new RenderConfig(configManagerOld.loadOrCreate( getRenderConfigFile(), Plugin.class.getResource("/de/bluecolored/bluemap/render.conf"), Plugin.class.getResource("/de/bluecolored/bluemap/render-defaults.conf"), @@ -291,9 +403,9 @@ public File getWebServerConfigFile() { return new File(configFolder, "webserver.conf"); } - public synchronized WebServerConfig getWebServerConfig() throws IOException { + public synchronized WebServerConfig getWebServerConfig() throws ConfigurationException { if (webServerConfig == null) { - webServerConfig = new WebServerConfig(configManager.loadOrCreate( + webServerConfig = new WebServerConfig(configManagerOld.loadOrCreate( getWebServerConfigFile(), Plugin.class.getResource("/de/bluecolored/bluemap/webserver.conf"), Plugin.class.getResource("/de/bluecolored/bluemap/webserver-defaults.conf"), @@ -309,4 +421,8 @@ public File getConfigFolder() { return configFolder; } + private interface ThrowingFunction { + R apply(T t) throws E; + } + } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/MissingResourcesException.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/MissingResourcesException.java index 33879928..087e6379 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/MissingResourcesException.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/MissingResourcesException.java @@ -24,8 +24,13 @@ */ package de.bluecolored.bluemap.common; -import java.io.IOException; +import de.bluecolored.bluemap.core.config.ConfigurationException; -public class MissingResourcesException extends IOException { +public class MissingResourcesException extends ConfigurationException { private static final long serialVersionUID = 2084565069965755048L; + + public MissingResourcesException() { + super("BlueMap is missing important resources!\n" + + "You must accept the required file download in order for BlueMap to work!"); + } } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java index 51df7997..d6086698 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java @@ -35,11 +35,13 @@ import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask; import de.bluecolored.bluemap.common.rendermanager.RenderManager; import de.bluecolored.bluemap.common.web.FileRequestHandler; +import de.bluecolored.bluemap.common.web.MapStorageRequestHandler; import de.bluecolored.bluemap.core.BlueMap; import de.bluecolored.bluemap.core.MinecraftVersion; -import de.bluecolored.bluemap.core.config.CoreConfig; -import de.bluecolored.bluemap.core.config.RenderConfig; -import de.bluecolored.bluemap.core.config.WebServerConfig; +import de.bluecolored.bluemap.core.config.ConfigurationException; +import de.bluecolored.bluemap.core.config.old.CoreConfig; +import de.bluecolored.bluemap.core.config.old.RenderConfig; +import de.bluecolored.bluemap.core.config.old.WebServerConfig; import de.bluecolored.bluemap.core.debug.DebugDump; import de.bluecolored.bluemap.core.debug.StateDumper; import de.bluecolored.bluemap.core.logger.Logger; @@ -47,6 +49,7 @@ import de.bluecolored.bluemap.core.metrics.Metrics; import de.bluecolored.bluemap.core.resourcepack.ParseResourceException; import de.bluecolored.bluemap.core.util.FileUtils; +import de.bluecolored.bluemap.core.util.MappableFunction; import de.bluecolored.bluemap.core.webserver.HttpRequestHandler; import de.bluecolored.bluemap.core.webserver.WebServer; import de.bluecolored.bluemap.core.world.World; @@ -116,9 +119,10 @@ public void load() throws IOException, ParseResourceException { coreConfig = blueMap.getCoreConfig(); renderConfig = blueMap.getRenderConfig(); webServerConfig = blueMap.getWebServerConfig(); + blueMap.getMapStorages(); //load plugin config - pluginConfig = new PluginConfig(blueMap.getConfigManager().loadOrCreate( + pluginConfig = new PluginConfig(blueMap.getConfigManagerOld().loadOrCreate( new File(serverInterface.getConfigFolder(), "plugin.conf"), Plugin.class.getResource("/de/bluecolored/bluemap/plugin.conf"), Plugin.class.getResource("/de/bluecolored/bluemap/plugin-defaults.conf"), @@ -137,26 +141,6 @@ public void load() throws IOException, ParseResourceException { pluginState = new PluginState(); } - //create and start webserver - if (webServerConfig.isWebserverEnabled()) { - FileUtils.mkDirs(webServerConfig.getWebRoot()); - HttpRequestHandler requestHandler = new FileRequestHandler(webServerConfig.getWebRoot().toPath(), "BlueMap v" + BlueMap.VERSION); - - //inject live api if enabled - if (pluginConfig.isLiveUpdatesEnabled()) { - requestHandler = new LiveAPIRequestHandler(serverInterface, pluginConfig, requestHandler); - } - - webServer = new WebServer( - webServerConfig.getWebserverBindAddress(), - webServerConfig.getWebserverPort(), - webServerConfig.getWebserverMaxConnections(), - requestHandler, - false - ); - webServer.start(); - } - //try load resources try { blueMap.getResourcePack(); @@ -182,6 +166,31 @@ public void load() throws IOException, ParseResourceException { return; } + //create and start webserver + if (webServerConfig.isWebserverEnabled()) { + FileUtils.mkDirs(webServerConfig.getWebRoot()); + HttpRequestHandler requestHandler = new FileRequestHandler(webServerConfig.getWebRoot().toPath(), "BlueMap v" + BlueMap.VERSION); + + //use map-storage to provide map-tiles + requestHandler = new MapStorageRequestHandler( + MappableFunction.of(maps::get).mapNullable(BmMap::getStorage), + requestHandler); + + //inject live api if enabled + if (pluginConfig.isLiveUpdatesEnabled()) { + requestHandler = new LiveAPIRequestHandler(serverInterface, pluginConfig, requestHandler); + } + + webServer = new WebServer( + webServerConfig.getWebserverBindAddress(), + webServerConfig.getWebserverPort(), + webServerConfig.getWebserverMaxConnections(), + requestHandler, + false + ); + webServer.start(); + } + //initialize render manager renderManager = new RenderManager(); @@ -275,6 +284,9 @@ public void run() { //done loaded = true; } + } catch (ConfigurationException ex) { + Logger.global.logWarning(ex.getFormattedExplanation()); + throw new IOException(ex); } catch (InterruptedException e) { Thread.currentThread().interrupt(); Logger.global.logWarning("Loading has been interrupted!"); @@ -310,12 +322,30 @@ public void unload() { regionFileWatchServices = null; //stop services - if (renderManager != null) renderManager.stop(); + if (renderManager != null){ + renderManager.stop(); + try { + renderManager.awaitShutdown(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } renderManager = null; if (webServer != null) webServer.close(); webServer = null; + //close storages + if (maps != null) { + maps.values().forEach(map -> { + try { + map.getStorage().close(); + } catch (IOException ex) { + Logger.global.logWarning("Failed to close map-storage for map '" + map.getId() + "': " + ex); + } + }); + } + //clear resources and configs blueMap = null; worlds = null; diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java index 90a55ef2..493a164f 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java @@ -431,7 +431,7 @@ public int reloadCommand(CommandContext context) { source.sendMessage(Text.of(TextColor.RED, "Could not load BlueMap! See the console for details!")); } - } catch (IOException | ParseResourceException | RuntimeException ex) { + } catch (Exception ex) { Logger.global.logError("Failed to reload BlueMap!", ex); source.sendMessage(Text.of(TextColor.RED, "There was an error reloading BlueMap! See the console for details!")); diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapPurgeTask.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapPurgeTask.java index d2e81f74..66c48ad8 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapPurgeTask.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapPurgeTask.java @@ -26,7 +26,7 @@ import de.bluecolored.bluemap.core.debug.DebugDump; import de.bluecolored.bluemap.core.map.BmMap; -import de.bluecolored.bluemap.core.storage.FileStorage; +import de.bluecolored.bluemap.core.storage.file.FileStorage; import de.bluecolored.bluemap.core.storage.Storage; import de.bluecolored.bluemap.core.util.FileUtils; diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/FileRequestHandler.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/FileRequestHandler.java index 6e699f7c..bb906494 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/FileRequestHandler.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/FileRequestHandler.java @@ -28,26 +28,18 @@ import de.bluecolored.bluemap.core.webserver.HttpRequestHandler; import de.bluecolored.bluemap.core.webserver.HttpResponse; import de.bluecolored.bluemap.core.webserver.HttpStatusCode; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.time.DateFormatUtils; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.nio.file.InvalidPathException; import java.nio.file.Path; -import java.util.GregorianCalendar; -import java.util.Locale; -import java.util.Set; -import java.util.TimeZone; +import java.util.*; import java.util.concurrent.TimeUnit; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; public class FileRequestHandler implements HttpRequestHandler { - private static final long DEFLATE_MIN_SIZE = 10L * 1024L; - private static final long DEFLATE_MAX_SIZE = 10L * 1024L * 1024L; - private static final long INFLATE_MAX_SIZE = 10L * 1024L * 1024L; - private final Path webRoot; private final String serverName; @@ -86,17 +78,13 @@ private HttpResponse generateResponse(HttpRequest request) { if (path.startsWith("/")) path = path.substring(1); if (path.endsWith("/")) path = path.substring(0, path.length() - 1); - Path filePath = webRoot; + Path filePath; try { filePath = webRoot.resolve(path); } catch (InvalidPathException e){ return new HttpResponse(HttpStatusCode.NOT_FOUND); } - // can we use deflation? - boolean isDeflationPossible = request.getLowercaseHeader("Accept-Encoding").contains("gzip"); - boolean isDeflated = false; - // check if file is in web-root if (!filePath.normalize().startsWith(webRoot)){ return new HttpResponse(HttpStatusCode.FORBIDDEN); @@ -111,38 +99,20 @@ private HttpResponse generateResponse(HttpRequest request) { return response; } - if (!file.exists() || file.isDirectory()){ - file = new File(filePath.toString() + ".gz"); - isDeflated = true; - } - + // default to index.html if (!file.exists() || file.isDirectory()){ file = new File(filePath.toString() + "/index.html"); - isDeflated = false; - } - - if (!file.exists() || file.isDirectory()){ - file = new File(filePath.toString() + "/index.html.gz"); - isDeflated = true; } + // send empty tile-file if tile not exists if (!file.exists() && file.toPath().startsWith(webRoot.resolve("data"))){ file = emptyTileFile; - isDeflated = false; } if (!file.exists() || file.isDirectory()) { return new HttpResponse(HttpStatusCode.NOT_FOUND); } - if (isDeflationPossible && (!file.getName().endsWith(".gz"))){ - File deflatedFile = new File(file.getAbsolutePath() + ".gz"); - if (deflatedFile.exists()){ - file = deflatedFile; - isDeflated = true; - } - } - // check if file is still in web-root and is not a directory if (!file.toPath().normalize().startsWith(webRoot) || file.isDirectory()){ return new HttpResponse(HttpStatusCode.FORBIDDEN); @@ -178,76 +148,17 @@ private HttpResponse generateResponse(HttpRequest request) { //add content type header String filetype = file.getName(); - if (filetype.endsWith(".gz")) filetype = filetype.substring(0, filetype.length() - 3); int pointIndex = filetype.lastIndexOf('.'); if (pointIndex >= 0) filetype = filetype.substring(pointIndex + 1); - - String contentType = "text/plain"; - switch (filetype) { - case "json" : - contentType = "application/json"; - break; - case "png" : - contentType = "image/png"; - break; - case "jpg" : - case "jpeg" : - case "jpe" : - contentType = "image/jpeg"; - break; - case "svg" : - contentType = "image/svg+xml"; - break; - case "css" : - contentType = "text/css"; - break; - case "js" : - contentType = "text/javascript"; - break; - case "html" : - case "htm" : - case "shtml" : - contentType = "text/html"; - break; - case "xml" : - contentType = "text/xml"; - break; - } + String contentType = toContentType(filetype); response.addHeader("Content-Type", contentType); - + //send response try { - if (isDeflated){ - if (isDeflationPossible || file.length() > INFLATE_MAX_SIZE){ - response.addHeader("Content-Encoding", "gzip"); - response.setData(new FileInputStream(file)); - return response; - } else { - response.setData(new GZIPInputStream(new FileInputStream(file))); - return response; - } - } else { - if (isDeflationPossible && file.length() > DEFLATE_MIN_SIZE && file.length() < DEFLATE_MAX_SIZE){ - FileInputStream fis = new FileInputStream(file); - ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); - GZIPOutputStream zos = new GZIPOutputStream(byteOut); - IOUtils.copyLarge(fis, zos); - zos.close(); - fis.close(); - byte[] compressedData = byteOut.toByteArray(); - response.setData(new ByteArrayInputStream(compressedData)); - response.addHeader("Content-Encoding", "gzip"); - return response; - } else { - response.setData(new FileInputStream(file)); - return response; - } - } - + response.setData(new FileInputStream(file)); + return response; } catch (FileNotFoundException e) { return new HttpResponse(HttpStatusCode.NOT_FOUND); - } catch (IOException e) { - return new HttpResponse(HttpStatusCode.INTERNAL_SERVER_ERROR); } } @@ -258,20 +169,21 @@ private static String timestampToString(long time){ private static long stringToTimestamp(String timeString) throws IllegalArgumentException { try { int day = Integer.parseInt(timeString.substring(5, 7)); - int month = 1; + + int month = Calendar.JANUARY; switch (timeString.substring(8, 11)){ - case "Jan" : month = 0; break; - case "Feb" : month = 1; break; - case "Mar" : month = 2; break; - case "Apr" : month = 3; break; - case "May" : month = 4; break; - case "Jun" : month = 5; break; - case "Jul" : month = 6; break; - case "Aug" : month = 7; break; - case "Sep" : month = 8; break; - case "Oct" : month = 9; break; - case "Nov" : month = 10; break; - case "Dec" : month = 11; break; + case "Jan" : month = Calendar.JANUARY; break; + case "Feb" : month = Calendar.FEBRUARY; break; + case "Mar" : month = Calendar.MARCH; break; + case "Apr" : month = Calendar.APRIL; break; + case "May" : month = Calendar.MAY; break; + case "Jun" : month = Calendar.JUNE; break; + case "Jul" : month = Calendar.JULY; break; + case "Aug" : month = Calendar.AUGUST; break; + case "Sep" : month = Calendar.SEPTEMBER; break; + case "Oct" : month = Calendar.OCTOBER; break; + case "Nov" : month = Calendar.NOVEMBER; break; + case "Dec" : month = Calendar.DECEMBER; break; } int year = Integer.parseInt(timeString.substring(12, 16)); int hour = Integer.parseInt(timeString.substring(17, 19)); @@ -285,4 +197,39 @@ private static long stringToTimestamp(String timeString) throws IllegalArgumentE } } + private static String toContentType(String fileEnding) { + String contentType = "text/plain"; + switch (fileEnding) { + case "json" : + contentType = "application/json"; + break; + case "png" : + contentType = "image/png"; + break; + case "jpg" : + case "jpeg" : + case "jpe" : + contentType = "image/jpeg"; + break; + case "svg" : + contentType = "image/svg+xml"; + break; + case "css" : + contentType = "text/css"; + break; + case "js" : + contentType = "text/javascript"; + break; + case "html" : + case "htm" : + case "shtml" : + contentType = "text/html"; + break; + case "xml" : + contentType = "text/xml"; + break; + } + return contentType; + } + } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/MapStorageRequestHandler.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/MapStorageRequestHandler.java new file mode 100644 index 00000000..37c82ce5 --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/MapStorageRequestHandler.java @@ -0,0 +1,219 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.common.web; + +import com.flowpowered.math.vector.Vector2i; +import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.storage.*; +import de.bluecolored.bluemap.core.webserver.HttpRequest; +import de.bluecolored.bluemap.core.webserver.HttpRequestHandler; +import de.bluecolored.bluemap.core.webserver.HttpResponse; +import de.bluecolored.bluemap.core.webserver.HttpStatusCode; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.time.DateFormatUtils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.*; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class MapStorageRequestHandler implements HttpRequestHandler { + + private static final Pattern TILE_PATTERN = Pattern.compile("data/([^/]+)/([^/]+)/x(-?[\\d/]+)z(-?[\\d/]+).*"); + private static final Pattern META_PATTERN = Pattern.compile("data/([^/]+)/(.*)"); + + private final Function mapStorageProvider; + private final HttpRequestHandler notFoundHandler; + + public MapStorageRequestHandler(Function mapStorageProvider, HttpRequestHandler notFoundHandler) { + this.mapStorageProvider = mapStorageProvider; + this.notFoundHandler = notFoundHandler; + } + + @Override + public HttpResponse handle(HttpRequest request) { + String path = request.getPath(); + + //normalize path + if (path.startsWith("/")) path = path.substring(1); + if (path.endsWith("/")) path = path.substring(0, path.length() - 1); + + try { + + // provide map-tiles + Matcher tileMatcher = TILE_PATTERN.matcher(path); + if (tileMatcher.matches()) { + String mapId = tileMatcher.group(1); + String tileTypeId = tileMatcher.group(2); + Storage storage = mapStorageProvider.apply(mapId); + if (storage != null) { + TileType tileType = TileType.forTypeId(tileTypeId); + int x = Integer.parseInt(tileMatcher.group(3).replace("/", "")); + int z = Integer.parseInt(tileMatcher.group(4).replace("/", "")); + Optional optTileData = storage.readMapTileData(mapId, tileType, new Vector2i(x, z)); + + if (optTileData.isPresent()) { + TileData tileData = optTileData.get(); + + // check e-tag + String eTag = calculateETag(path, tileData); + Set etagStringSet = request.getHeader("If-None-Match"); + if (!etagStringSet.isEmpty()){ + if(etagStringSet.iterator().next().equals(eTag)) { + return new HttpResponse(HttpStatusCode.NOT_MODIFIED); + } + } + + // check modified-since + long lastModified = tileData.getLastModified(); + Set modStringSet = request.getHeader("If-Modified-Since"); + if (!modStringSet.isEmpty()){ + try { + long since = stringToTimestamp(modStringSet.iterator().next()); + if (since + 1000 >= lastModified){ + return new HttpResponse(HttpStatusCode.NOT_MODIFIED); + } + } catch (IllegalArgumentException ignored){} + } + + CompressedInputStream compressedIn = tileData.readMapTile(); + HttpResponse response = new HttpResponse(HttpStatusCode.OK); + response.addHeader("ETag", eTag); + if (lastModified > 0) + response.addHeader("Last-Modified", timestampToString(lastModified)); + response.addHeader("Content-Type", "application/json"); + writeToResponse(compressedIn, response, request); + return response; + } + } + } + + // provide meta-data + Matcher metaMatcher = META_PATTERN.matcher(path); + if (metaMatcher.matches()) { + String mapId = tileMatcher.group(1); + String metaFilePath = tileMatcher.group(2); + + Storage storage = mapStorageProvider.apply(mapId); + if (storage != null) { + + MetaType metaType = null; + for (MetaType mt : MetaType.values()) { + if (mt.getFilePath().equals(metaFilePath)) { + metaType = mt; + break; + } + } + + if (metaType != null) { + Optional optIn = storage.readMeta(mapId, metaType); + if (optIn.isPresent()) { + CompressedInputStream compressedIn = optIn.get(); + HttpResponse response = new HttpResponse(HttpStatusCode.OK); + response.addHeader("Content-Type", metaType.getContentType()); + writeToResponse(compressedIn, response, request); + return response; + } + } + } + } + + } catch (NumberFormatException | NoSuchElementException ignore){ + } catch (IOException ex) { + Logger.global.logError("Failed to read map-tile for web-request.", ex); + return new HttpResponse(HttpStatusCode.INTERNAL_SERVER_ERROR); + } + + return this.notFoundHandler.handle(request); + } + + private String calculateETag(String path, TileData tileData) { + return Long.toHexString(tileData.getSize()) + Integer.toHexString(path.hashCode()) + Long.toHexString(tileData.getLastModified()); + } + + private void writeToResponse(CompressedInputStream data, HttpResponse response, HttpRequest request) throws IOException { + Compression compression = data.getCompression(); + if ( + compression != Compression.NONE && + request.getLowercaseHeader("Accept-Encoding").contains(compression.getTypeId()) + ) { + response.addHeader("Content-Encoding", compression.getTypeId()); + response.setData(data); + } else if ( + compression != Compression.GZIP && + request.getLowercaseHeader("Accept-Encoding").contains(Compression.GZIP.getTypeId()) + ) { + response.addHeader("Content-Encoding", Compression.GZIP.getTypeId()); + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + try (OutputStream os = Compression.GZIP.compress(byteOut)) { + IOUtils.copyLarge(data.decompress(), os); + } + byte[] compressedData = byteOut.toByteArray(); + response.setData(new ByteArrayInputStream(compressedData)); + } else { + response.setData(data.decompress()); + } + } + + private static String timestampToString(long time){ + return DateFormatUtils.format(time, "EEE, dd MMM yyy HH:mm:ss 'GMT'", TimeZone.getTimeZone("GMT"), Locale.ENGLISH); + } + + private static long stringToTimestamp(String timeString) throws IllegalArgumentException { + try { + int day = Integer.parseInt(timeString.substring(5, 7)); + + int month = Calendar.JANUARY; + switch (timeString.substring(8, 11)){ + case "Jan" : month = Calendar.JANUARY; break; + case "Feb" : month = Calendar.FEBRUARY; break; + case "Mar" : month = Calendar.MARCH; break; + case "Apr" : month = Calendar.APRIL; break; + case "May" : month = Calendar.MAY; break; + case "Jun" : month = Calendar.JUNE; break; + case "Jul" : month = Calendar.JULY; break; + case "Aug" : month = Calendar.AUGUST; break; + case "Sep" : month = Calendar.SEPTEMBER; break; + case "Oct" : month = Calendar.OCTOBER; break; + case "Nov" : month = Calendar.NOVEMBER; break; + case "Dec" : month = Calendar.DECEMBER; break; + } + int year = Integer.parseInt(timeString.substring(12, 16)); + int hour = Integer.parseInt(timeString.substring(17, 19)); + int min = Integer.parseInt(timeString.substring(20, 22)); + int sec = Integer.parseInt(timeString.substring(23, 25)); + GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); + cal.set(year, month, day, hour, min, sec); + return cal.getTimeInMillis(); + } catch (NumberFormatException | IndexOutOfBoundsException e){ + throw new IllegalArgumentException(e); + } + } + +} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/WebSettings.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/WebSettings.java index 42672c8e..694132a6 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/WebSettings.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/WebSettings.java @@ -26,7 +26,7 @@ import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3f; -import de.bluecolored.bluemap.core.config.MapConfig; +import de.bluecolored.bluemap.core.config.old.MapConfig; import de.bluecolored.bluemap.core.map.BmMap; import de.bluecolored.bluemap.core.util.FileUtils; import de.bluecolored.bluemap.core.util.MathUtils; diff --git a/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/storages/file.conf b/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/storages/file.conf new file mode 100644 index 00000000..a5a47bc5 --- /dev/null +++ b/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/storages/file.conf @@ -0,0 +1,20 @@ +## ## +## BlueMap ## +## Storage-Config ## +## ## + +# The storage-type of this storage. +# Depending on this setting, different config-entries are allowed/expected in this config file. +# Don't change this value! (If you want a different storage-type, check out the other example-configs) +storage-type: FILE + +# The path to the folder on your file-system where bluemap will save the rendered map +# The default is: "bluemap/web/data" +root: "bluemap/web/data" + +# The compression-type that bluemap will use to compress generated map-data. +# Available compression-types are: +# - GZIP +# - NONE +# The default is: GZIP +compression: GZIP \ No newline at end of file diff --git a/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/storages/sql.conf b/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/storages/sql.conf new file mode 100644 index 00000000..245e9856 --- /dev/null +++ b/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/storages/sql.conf @@ -0,0 +1,39 @@ +## ## +## BlueMap ## +## Storage-Config ## +## ## + +# The storage-type of this storage. +# Depending on this setting, different config-entries are allowed/expected in this config file. +# Don't change this value! (If you want a different storage-type, check out the other example-configs) +storage-type: SQL + +# The JDBC-Connection URL that is used to connect to the database. +# The format for this url is: jdbc:[driver]://[host]:[port]/[database] +dbUrl: "jdbc:mysql://localhost:3306/bluemap" + +# The user that is used to connect to the database +user: "root" + +# The password that is used to connect to the database +# (If this is empty, no password is used to connect to the database) +password: "" + +# This can be used to load a custom jdbc-driver from a .jar file. +# E.g. if your runtime-environment is not already providing the sql-driver you need, +# you could download the MariaDB JDBC-Connector from https://mariadb.com/downloads/connectors/connectors-data-access/java8-connector/ +# place it in the './bluemap' folder and use is like this: +#driverJar: "bluemap/mariadb-java-client-2.7.4.jar" + +# This is the driver-class that bluemap will try to load and use. +# Check the documentation of the driver you are using if you don't know this. +# Leaving this commented means that bluemap automatically tries to find a suitable driver in your classpath. +# (If you added a custom driverJar above, you HAVE TO set the correct class name here) +#driverClass: "org.mariadb.jdbc.Driver" + +# The compression-type that bluemap will use to compress generated map-data. +# Available compression-types are: +# - GZIP +# - NONE +# The default is: GZIP +compression: GZIP \ No newline at end of file diff --git a/BlueMapCore/build.gradle b/BlueMapCore/build.gradle index ebecdb25..f1f1b937 100644 --- a/BlueMapCore/build.gradle +++ b/BlueMapCore/build.gradle @@ -7,6 +7,7 @@ dependencies { api 'org.spongepowered:configurate-hocon:4.1.1' api 'org.spongepowered:configurate-gson:4.1.1' api 'com.github.Querz:NBT:4.0' + api 'org.apache.commons:commons-dbcp2:2.9.0' testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2' } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/ConfigManager.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/ConfigManager.java index c9190efd..9bce850b 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/ConfigManager.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/ConfigManager.java @@ -24,110 +24,76 @@ */ package de.bluecolored.bluemap.core.config; - -import de.bluecolored.bluemap.core.BlueMap; -import de.bluecolored.bluemap.core.util.FileUtils; +import org.spongepowered.configurate.ConfigurateException; import org.spongepowered.configurate.ConfigurationNode; import org.spongepowered.configurate.gson.GsonConfigurationLoader; import org.spongepowered.configurate.hocon.HoconConfigurationLoader; import org.spongepowered.configurate.loader.ConfigurationLoader; -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URL; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.time.LocalDateTime; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; +import java.nio.file.Path; public class ConfigManager { - private static final Set CONFIG_PLACEHOLDERS = new HashSet<>(); + private static final String[] CONFIG_FILE_ENDINGS = new String[] { + ".conf", + ".json" + }; - static { - CONFIG_PLACEHOLDERS.add(new Placeholder("version", BlueMap.VERSION)); - CONFIG_PLACEHOLDERS.add(new Placeholder("datetime-iso", () -> LocalDateTime.now().withNano(0).toString())); + private final Path configRoot; + + public ConfigManager(Path configRoot) { + this.configRoot = configRoot; } - /** - * Loads or creates a config file for BlueMap. - * - * @param configFile The config file to load - * @param defaultConfig The default config that is used as a template if the config file does not exist (can be null) - * @param defaultValues The default values used if a key is not present in the config (can be null) - * @param usePlaceholders Whether to replace placeholders from the defaultConfig if it is newly generated - * @param generateEmptyConfig Whether to generate an empty config file if no default config is provided - * @return The loaded configuration node - * @throws IOException if an IOException occurs while loading - */ - public ConfigurationNode loadOrCreate(File configFile, URL defaultConfig, URL defaultValues, boolean usePlaceholders, boolean generateEmptyConfig) throws IOException { + public ConfigurationNode loadConfig(Path rawPath) throws ConfigurationException { + Path path = findConfigPath(configRoot.resolve(rawPath)); - ConfigurationNode configNode; - if (!configFile.exists()) { - FileUtils.mkDirsParent(configFile); - - if (defaultConfig != null) { - //load content of default config - String content; - try (BufferedReader reader = new BufferedReader(new InputStreamReader(defaultConfig.openStream(), StandardCharsets.UTF_8))){ - content = reader.lines().collect(Collectors.joining("\n")); - } - - //replace placeholders if enabled - if (usePlaceholders) { - for (Placeholder placeholder : CONFIG_PLACEHOLDERS) { - content = placeholder.apply(content); - } - } - - //create the config file - Files.write(configFile.toPath(), content.getBytes(StandardCharsets.UTF_8)); - - //load - configNode = getLoader(configFile).load(); - } else { - //create empty config - ConfigurationLoader loader = getLoader(configFile); - configNode = loader.createNode(); - - //save to create file - if (generateEmptyConfig) loader.save(configNode); - } - } else { - //load config - configNode = getLoader(configFile).load(); + if (!Files.exists(path)) { + throw new ConfigurationException( + "BlueMap tried to find this file, but it does not exist:\n" + + path); } - //populate missing values with default values - if (defaultValues != null) { - ConfigurationNode defaultValuesNode = getLoader(defaultValues).load(); - configNode.mergeFrom(defaultValuesNode); + if (!Files.isReadable(path)) { + throw new ConfigurationException( + "BlueMap tried to read this file, but can not access it:\n" + + path + "\n" + + "Check if BlueMap has the permission to read this file."); } - return configNode; + try { + return getLoader(path).load(); + } catch (ConfigurateException ex) { + throw new ConfigurationException( + "BlueMap failed to parse this file:\n" + + path + "\n" + + "Check if the file is correctly formatted.\n" + + "(for example there might be a } or ] or , missing somewhere)", + ex); + } } - private ConfigurationLoader getLoader(URL url){ - if (url.getFile().endsWith(".json")) return GsonConfigurationLoader.builder().url(url).build(); - else return HoconConfigurationLoader.builder().url(url).build(); + public Path getConfigRoot() { + return configRoot; } - private ConfigurationLoader getLoader(File file){ - if (file.getName().endsWith(".json")) return GsonConfigurationLoader.builder().file(file).build(); - else return HoconConfigurationLoader.builder().file(file).build(); + private Path findConfigPath(Path rawPath) { + for (String fileEnding : CONFIG_FILE_ENDINGS) { + if (rawPath.getFileName().endsWith(fileEnding)) return rawPath; + } + + for (String fileEnding : CONFIG_FILE_ENDINGS) { + Path path = rawPath.getParent().resolve(rawPath.getFileName() + fileEnding); + if (Files.exists(path)) return path; + } + + return rawPath.getParent().resolve(rawPath.getFileName() + CONFIG_FILE_ENDINGS[0]); } - public static File toFolder(String pathString) throws IOException { - Objects.requireNonNull(pathString); - - File file = new File(pathString); - if (file.exists() && !file.isDirectory()) throw new IOException("Invalid configuration: Path '" + file.getAbsolutePath() + "' is a file (should be a directory)"); - return file; + private ConfigurationLoader getLoader(Path path){ + if (path.getFileName().endsWith(".json")) return GsonConfigurationLoader.builder().path(path).build(); + else return HoconConfigurationLoader.builder().path(path).build(); } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/ConfigurationException.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/ConfigurationException.java new file mode 100644 index 00000000..caa277be --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/ConfigurationException.java @@ -0,0 +1,83 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.core.config; + +public class ConfigurationException extends Exception { + + private static final String FORMATTING_BAR = "################################"; + + private final String explanation; + + public ConfigurationException(String explanation) { + super(); + this.explanation = explanation; + } + + public ConfigurationException(String message, String explanation) { + super(message); + this.explanation = explanation; + } + + public ConfigurationException(String explanation, Throwable cause) { + super(cause); + this.explanation = explanation; + } + + public ConfigurationException(String message, String explanation, Throwable cause) { + super(message, cause); + this.explanation = explanation; + } + + public Throwable getRootCause() { + Throwable cause = getCause(); + if (cause instanceof ConfigurationException) { + return ((ConfigurationException) cause).getRootCause(); + } else { + return cause; + } + } + + public String getExplanation() { + return explanation; + } + + public String getFullExplanation() { + Throwable cause = getCause(); + if (cause instanceof ConfigurationException) { + return getExplanation() + "\n\n" + ((ConfigurationException) cause).getFullExplanation(); + } else { + return getExplanation(); + } + } + + public String getFormattedExplanation() { + String indentedExplanation = " " + getFullExplanation().replace("\n", "\n "); + return "\n" + FORMATTING_BAR + + "\n There is a problem with your BlueMap setup!\n" + + indentedExplanation + + "\n" + FORMATTING_BAR; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/old/ConfigManager.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/old/ConfigManager.java new file mode 100644 index 00000000..abf4bbd2 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/old/ConfigManager.java @@ -0,0 +1,162 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.core.config.old; + + +import de.bluecolored.bluemap.core.BlueMap; +import de.bluecolored.bluemap.core.config.ConfigurationException; +import de.bluecolored.bluemap.core.util.FileUtils; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.gson.GsonConfigurationLoader; +import org.spongepowered.configurate.hocon.HoconConfigurationLoader; +import org.spongepowered.configurate.loader.ConfigurationLoader; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.time.LocalDateTime; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +@Deprecated +public class ConfigManager { + + private static final Set CONFIG_PLACEHOLDERS = new HashSet<>(); + + static { + CONFIG_PLACEHOLDERS.add(new Placeholder("version", BlueMap.VERSION)); + CONFIG_PLACEHOLDERS.add(new Placeholder("datetime-iso", () -> LocalDateTime.now().withNano(0).toString())); + } + + /** + * Loads or creates a config file for BlueMap. + * + * @param configFile The config file to load + * @param defaultConfig The default config that is used as a template if the config file does not exist (can be null) + * @param defaultValues The default values used if a key is not present in the config (can be null) + * @param usePlaceholders Whether to replace placeholders from the defaultConfig if it is newly generated + * @param generateEmptyConfig Whether to generate an empty config file if no default config is provided + * @return The loaded configuration node + * @throws ConfigurationException if an IOException occurs while loading + */ + public ConfigurationNode loadOrCreate(File configFile, URL defaultConfig, URL defaultValues, boolean usePlaceholders, boolean generateEmptyConfig) throws ConfigurationException { + + ConfigurationNode configNode; + if (!configFile.exists()) { + try { + FileUtils.mkDirsParent(configFile); + + if (defaultConfig != null) { + //load content of default config + String content; + try (BufferedReader reader = new BufferedReader(new InputStreamReader(defaultConfig.openStream(), StandardCharsets.UTF_8))) { + content = reader.lines().collect(Collectors.joining("\n")); + } + + //replace placeholders if enabled + if (usePlaceholders) { + for (Placeholder placeholder : CONFIG_PLACEHOLDERS) { + content = placeholder.apply(content); + } + } + + //create the config file + Files.write(configFile.toPath(), content.getBytes(StandardCharsets.UTF_8)); + + //load + configNode = getLoader(configFile).load(); + } else { + //create empty config + ConfigurationLoader loader = getLoader(configFile); + configNode = loader.createNode(); + + //save to create file + if (generateEmptyConfig) loader.save(configNode); + } + } catch (IOException ex) { + throw new ConfigurationException( + "BlueMap tried to create this file:\n" + + configFile + + "but something went wrong!\n" + + "Does BlueMap has sufficient write permissions?", + ex); + } + } else { + try { + //load config + configNode = getLoader(configFile).load(); + } catch (IOException ex) { + throw new ConfigurationException( + "BlueMap tried to load this file:\n" + + configFile + + "but something went wrong!\n" + + "Is the config-file formatted correctly?\n" + + "Maybe there is a } or ] or , missing?" + + "Does BlueMap has sufficient read permissions to this file?", + ex); + } + } + + //populate missing values with default values + if (defaultValues != null) { + try { + ConfigurationNode defaultValuesNode = getLoader(defaultValues).load(); + configNode.mergeFrom(defaultValuesNode); + } catch (IOException ex) { + throw new ConfigurationException( + "Something went wrong trying to load this config:\n" + + configFile, + ex); + } + } + + return configNode; + } + + private ConfigurationLoader getLoader(URL url){ + if (url.getFile().endsWith(".json")) return GsonConfigurationLoader.builder().url(url).build(); + else return HoconConfigurationLoader.builder().url(url).build(); + } + + private ConfigurationLoader getLoader(File file){ + if (file.getName().endsWith(".json")) return GsonConfigurationLoader.builder().file(file).build(); + else return HoconConfigurationLoader.builder().file(file).build(); + } + + public static File toFolder(String pathString) throws ConfigurationException { + Objects.requireNonNull(pathString); + + File file = new File(pathString); + if (file.exists() && !file.isDirectory()) throw new ConfigurationException("Invalid configuration: Path '" + file.getAbsolutePath() + "' is a file (should be a directory)"); + return file; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/CoreConfig.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/old/CoreConfig.java similarity index 92% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/CoreConfig.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/old/CoreConfig.java index a420bf09..0b56e5ff 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/CoreConfig.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/old/CoreConfig.java @@ -22,8 +22,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.config; +package de.bluecolored.bluemap.core.config.old; +import de.bluecolored.bluemap.core.config.ConfigurationException; import de.bluecolored.bluemap.core.debug.DebugDump; import org.spongepowered.configurate.ConfigurationNode; @@ -38,7 +39,7 @@ public class CoreConfig { private boolean metricsEnabled = false; private File dataFolder = new File("data"); - public CoreConfig(ConfigurationNode node) throws IOException { + public CoreConfig(ConfigurationNode node) throws ConfigurationException { //accept-download downloadAccepted = node.node("accept-download").getBoolean(false); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/MapConfig.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/old/MapConfig.java similarity index 85% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/MapConfig.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/old/MapConfig.java index 2f05908a..10d3385d 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/MapConfig.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/old/MapConfig.java @@ -22,17 +22,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.config; +package de.bluecolored.bluemap.core.config.old; import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3i; +import de.bluecolored.bluemap.core.config.ConfigurationException; import de.bluecolored.bluemap.core.debug.DebugDump; import de.bluecolored.bluemap.core.map.MapSettings; -import de.bluecolored.bluemap.core.storage.Compression; import de.bluecolored.bluemap.core.util.ConfigUtils; import org.spongepowered.configurate.ConfigurationNode; -import java.io.IOException; import java.util.regex.Pattern; @DebugDump @@ -54,7 +53,7 @@ public class MapConfig implements MapSettings { private Vector3i min, max; private boolean renderEdges; - private Compression compression; + private String storage; private boolean ignoreMissingLightData; private int hiresTileSize; @@ -62,19 +61,19 @@ public class MapConfig implements MapSettings { private int lowresPointsPerHiresTile; private int lowresPointsPerLowresTile; - public MapConfig(ConfigurationNode node) throws IOException { + public MapConfig(ConfigurationNode node) throws ConfigurationException { //id this.id = node.node("id").getString(""); - if (id.isEmpty()) throw new IOException("Invalid configuration: Node maps[?].id is not defined"); - if (!VALID_ID_PATTERN.matcher(id).matches()) throw new IOException("Invalid configuration: Node maps[?].id '" + id + "' has invalid characters in it"); + if (id.isEmpty()) throw new ConfigurationException("Invalid configuration: Node maps[?].id is not defined"); + if (!VALID_ID_PATTERN.matcher(id).matches()) throw new ConfigurationException("Invalid configuration: Node maps[?].id '" + id + "' has invalid characters in it"); //name this.name = node.node("name").getString(id); //world this.world = node.node("world").getString(""); - if (world.isEmpty()) throw new IOException("Invalid configuration: Node maps[?].world is not defined"); + if (world.isEmpty()) throw new ConfigurationException("Invalid configuration: Node maps[?].world is not defined"); //startPos if (!node.node("startPos").virtual()) this.startPos = ConfigUtils.readVector2i(node.node("startPos")); @@ -106,8 +105,8 @@ public MapConfig(ConfigurationNode node) throws IOException { //renderEdges this.renderEdges = node.node("renderEdges").getBoolean(true); - //useCompression - this.compression = node.node("useCompression").getBoolean(true) ? Compression.GZIP : Compression.NONE; + //storage + this.storage = node.node("storage").getString("file"); //ignoreMissingLightData this.ignoreMissingLightData = node.node("ignoreMissingLightData").getBoolean(false); @@ -119,7 +118,7 @@ public MapConfig(ConfigurationNode node) throws IOException { //check valid tile configuration values double blocksPerPoint = (double) this.hiresTileSize / (double) this.lowresPointsPerHiresTile; - if (blocksPerPoint != Math.floor(blocksPerPoint)) throw new IOException("Invalid configuration: Invalid map resolution settings of map " + id + ": hires.tileSize / lowres.pointsPerTile has to be an integer result"); + if (blocksPerPoint != Math.floor(blocksPerPoint)) throw new ConfigurationException("Invalid configuration: Invalid map resolution settings of map " + id + ": hires.tileSize / lowres.pointsPerTile has to be an integer result"); } @@ -143,8 +142,8 @@ public int getSkyColor() { return skyColor; } - public Compression getCompression() { - return compression; + public String getStorage() { + return storage; } @Override diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/Placeholder.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/old/Placeholder.java similarity index 97% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/Placeholder.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/old/Placeholder.java index 2e4f0839..1b77f63b 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/Placeholder.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/old/Placeholder.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.config; +package de.bluecolored.bluemap.core.config.old; import java.util.function.Supplier; import java.util.regex.Pattern; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/RenderConfig.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/old/RenderConfig.java similarity index 88% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/RenderConfig.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/old/RenderConfig.java index 62bd4798..cedff645 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/RenderConfig.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/old/RenderConfig.java @@ -22,8 +22,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.config; +package de.bluecolored.bluemap.core.config.old; +import de.bluecolored.bluemap.core.config.ConfigurationException; import de.bluecolored.bluemap.core.debug.DebugDump; import org.spongepowered.configurate.ConfigurationNode; @@ -40,11 +41,11 @@ public class RenderConfig { private boolean enableFreeFlight; private List mapConfigs; - public RenderConfig(ConfigurationNode node) throws IOException { + public RenderConfig(ConfigurationNode node) throws ConfigurationException { //webroot String webRootString = node.node("webroot").getString(); - if (webRootString == null) throw new IOException("Invalid configuration: Node webroot is not defined"); + if (webRootString == null) throw new ConfigurationException("Invalid configuration: Node webroot is not defined"); webRoot = ConfigManager.toFolder(webRootString); //cookies diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/WebServerConfig.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/old/WebServerConfig.java similarity index 73% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/WebServerConfig.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/old/WebServerConfig.java index 625a26d8..7c412faa 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/WebServerConfig.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/old/WebServerConfig.java @@ -22,8 +22,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.config; +package de.bluecolored.bluemap.core.config.old; +import de.bluecolored.bluemap.core.config.ConfigurationException; import de.bluecolored.bluemap.core.debug.DebugDump; import org.spongepowered.configurate.ConfigurationNode; @@ -42,7 +43,7 @@ public class WebServerConfig { private int port = 8100; private int maxConnections = 100; - public WebServerConfig(ConfigurationNode node) throws IOException { + public WebServerConfig(ConfigurationNode node) throws ConfigurationException { //enabled enabled = node.node("enabled").getBoolean(false); @@ -50,17 +51,22 @@ public WebServerConfig(ConfigurationNode node) throws IOException { if (enabled) { //webroot String webRootString = node.node("webroot").getString(); - if (webRootString == null) throw new IOException("Invalid configuration: Node webroot is not defined"); + if (webRootString == null) throw new ConfigurationException("Invalid configuration: Node webroot is not defined"); webRoot = ConfigManager.toFolder(webRootString); //ip String bindAddressString = node.node("ip").getString(""); - if (bindAddressString.isEmpty() || bindAddressString.equals("0.0.0.0") || bindAddressString.equals("::0")) { - bindAddress = new InetSocketAddress(0).getAddress(); // 0.0.0.0 - } else if (bindAddressString.equals("#getLocalHost")) { - bindAddress = InetAddress.getLocalHost(); - } else { - bindAddress = InetAddress.getByName(bindAddressString); + try { + if (bindAddressString.isEmpty() || bindAddressString.equals("0.0.0.0") || + bindAddressString.equals("::0")) { + bindAddress = new InetSocketAddress(0).getAddress(); // 0.0.0.0 + } else if (bindAddressString.equals("#getLocalHost")) { + bindAddress = InetAddress.getLocalHost(); + } else { + bindAddress = InetAddress.getByName(bindAddressString); + } + } catch (IOException ex) { + throw new ConfigurationException("Failed to parse ip: '" + bindAddressString + "'", ex); } //port diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/storage/FileConfig.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/storage/FileConfig.java new file mode 100644 index 00000000..7fab65e7 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/storage/FileConfig.java @@ -0,0 +1,51 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.core.config.storage; + +import de.bluecolored.bluemap.core.debug.DebugDump; +import de.bluecolored.bluemap.core.storage.Compression; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +import java.nio.file.Path; +import java.nio.file.Paths; + +@SuppressWarnings("FieldMayBeFinal") +@DebugDump +@ConfigSerializable +public class FileConfig extends StorageConfig { + + private Path root = Paths.get("bluemap", "web", "data"); + + private Compression compression = Compression.GZIP; + + public Path getRoot() { + return root; + } + + public Compression getCompression() { + return compression; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/storage/SQLConfig.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/storage/SQLConfig.java new file mode 100644 index 00000000..e5c5ebec --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/storage/SQLConfig.java @@ -0,0 +1,81 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.core.config.storage; + +import de.bluecolored.bluemap.core.debug.DebugDump; +import de.bluecolored.bluemap.core.storage.Compression; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Paths; +import java.util.Optional; + +@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"}) +@DebugDump +@ConfigSerializable +public class SQLConfig extends StorageConfig { + + private String driverJar = null; + private String driverClass = null; + private String dbUrl = "jdbc:mysql://localhost:3306/bluemap"; + private String user = "root"; + private String password = ""; + + private Compression compression = Compression.GZIP; + + private transient URL driverJarURL = null; + + public Optional getDriverJar() throws MalformedURLException { + if (driverJar == null) return Optional.empty(); + + if (driverJarURL == null) { + driverJarURL = Paths.get(driverJar).toUri().toURL(); + } + + return Optional.of(driverJarURL); + } + + public Optional getDriverClass() { + return Optional.ofNullable(driverClass); + } + + public String getDbUrl() { + return dbUrl; + } + + public String getUser() { + return user; + } + + public String getPassword() { + return password; + } + + public Compression getCompression() { + return compression; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ThrowingRunnable.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/storage/StorageConfig.java similarity index 73% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ThrowingRunnable.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/storage/StorageConfig.java index c09a882e..ad618c35 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ThrowingRunnable.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/storage/StorageConfig.java @@ -22,11 +22,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.util; +package de.bluecolored.bluemap.core.config.storage; -@FunctionalInterface -public interface ThrowingRunnable { +import de.bluecolored.bluemap.core.debug.DebugDump; +import de.bluecolored.bluemap.core.storage.StorageType; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; - void run() throws E; +@SuppressWarnings("FieldMayBeFinal") +@DebugDump +@ConfigSerializable +public class StorageConfig { + + private StorageType storageType = StorageType.FILE; + + public StorageType getStorageType() { + return storageType; + } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java index e591d6ee..2d6a3140 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java @@ -31,14 +31,16 @@ import de.bluecolored.bluemap.core.map.hires.HiresTileMeta; import de.bluecolored.bluemap.core.map.lowres.LowresModelManager; import de.bluecolored.bluemap.core.resourcepack.ResourcePack; -import de.bluecolored.bluemap.core.storage.*; +import de.bluecolored.bluemap.core.storage.CompressedInputStream; +import de.bluecolored.bluemap.core.storage.MetaType; +import de.bluecolored.bluemap.core.storage.Storage; +import de.bluecolored.bluemap.core.storage.TileType; import de.bluecolored.bluemap.core.world.Grid; import de.bluecolored.bluemap.core.world.World; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.nio.file.Path; import java.util.Objects; import java.util.Optional; import java.util.function.Predicate; @@ -72,9 +74,9 @@ public BmMap(String id, String name, World world, Storage storage, ResourcePack this.renderState = new MapRenderState(); - Optional rstateData = storage.readMeta(id, MetaType.RENDER_STATE); + Optional rstateData = storage.readMeta(id, MetaType.RENDER_STATE); if (rstateData.isPresent()) { - try (InputStream in = rstateData.get()){ + try (InputStream in = rstateData.get().decompress()){ this.renderState.load(in); } catch (IOException ex) { Logger.global.logWarning("Failed to load render-state for map '" + getId() + "': " + ex); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModelManager.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModelManager.java index a0cf35f4..508c3746 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModelManager.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModelManager.java @@ -28,6 +28,7 @@ import com.flowpowered.math.vector.Vector3f; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.map.hires.HiresTileMeta; +import de.bluecolored.bluemap.core.storage.CompressedInputStream; import de.bluecolored.bluemap.core.storage.Storage; import de.bluecolored.bluemap.core.threejs.BufferGeometry; import de.bluecolored.bluemap.core.util.math.Color; @@ -163,9 +164,9 @@ private LowresModel getModel(Vector2i tile) { if (model == null){ try { - Optional optIs = storage.read(tile); + Optional optIs = storage.read(tile); if (optIs.isPresent()){ - try (InputStream is = optIs.get()) { + try (InputStream is = optIs.get().decompress()) { String json = IOUtils.toString(is, StandardCharsets.UTF_8); model = new CachedModel(BufferGeometry.fromJson(json)); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/CompressedInputStream.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/CompressedInputStream.java new file mode 100644 index 00000000..fa2df5cf --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/CompressedInputStream.java @@ -0,0 +1,73 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.core.storage; + +import java.io.IOException; +import java.io.InputStream; + +public class CompressedInputStream extends InputStream { + + private final InputStream in; + private final Compression compression; + + public CompressedInputStream(InputStream in, Compression compression) { + this.in = in; + this.compression = compression; + } + + public InputStream decompress() throws IOException { + return compression.decompress(in); + } + + public Compression getCompression() { + return compression; + } + + @Override + public int read() throws IOException { + return in.read(); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return in.read(b, off, len); + } + + @Override + public void close() throws IOException { + in.close(); + } + + @Override + public int available() throws IOException { + return in.available(); + } + + @Override + public void reset() throws IOException { + in.reset(); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Compression.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Compression.java index 2b4027ed..680a5553 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Compression.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Compression.java @@ -27,49 +27,56 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.NoSuchElementException; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; public enum Compression { - NONE("") { - @Override - public OutputStream compress(OutputStream out) { - return out; - } - - @Override - public InputStream decompress(InputStream in) { - return in; - } - }, - - GZIP(".gz"){ - - @Override - public OutputStream compress(OutputStream out) throws IOException { - return new GZIPOutputStream(out); - } - - @Override - public InputStream decompress(InputStream in) throws IOException { - return new GZIPInputStream(in); - } - - }; + NONE("none", "", out -> out, in -> in), + GZIP("gzip", ".gz", GZIPOutputStream::new, GZIPInputStream::new); + private final String typeId; private final String fileSuffix; + private final StreamTransformer compressor; + private final StreamTransformer decompressor; - Compression(String fileSuffix) { + Compression(String typeId, String fileSuffix, + StreamTransformer compressor, + StreamTransformer decompressor) { this.fileSuffix = fileSuffix; + this.typeId = typeId; + this.compressor = compressor; + this.decompressor = decompressor; + } + + public String getTypeId() { + return typeId; } public String getFileSuffix() { return fileSuffix; } - public abstract OutputStream compress(OutputStream out) throws IOException; + public OutputStream compress(OutputStream out) throws IOException { + return compressor.apply(out); + } - public abstract InputStream decompress(InputStream in) throws IOException; + public InputStream decompress(InputStream in) throws IOException { + return decompressor.apply(in); + } + + public static Compression forTypeId(String id) { + for (Compression compression : values()) { + if (compression.typeId.equals(id)) return compression; + } + + throw new NoSuchElementException("There is no Compression with type-id: " + id); + } + + @FunctionalInterface + private interface StreamTransformer { + T apply(T original) throws IOException; + } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/MetaType.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/MetaType.java index daf4eaa2..71ffcc4d 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/MetaType.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/MetaType.java @@ -26,9 +26,30 @@ public enum MetaType { - TEXTURES, - SETTINGS, - MARKERS, - RENDER_STATE + //TEXTURES ("textures", "textures.json", "application/json"), + //SETTINGS ("settings", "settings.json", "application/json"), + //MARKERS ("markers", "markers.json", "application/json"), + RENDER_STATE ("render_state", ".rstate", "application/octet-stream"); + private final String typeId; + private final String filePath; + private final String contentType; + + MetaType(String typeId, String filePath, String contentType) { + this.typeId = typeId; + this.filePath = filePath; + this.contentType = contentType; + } + + public String getTypeId() { + return typeId; + } + + public String getFilePath() { + return filePath; + } + + public String getContentType() { + return contentType; + } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Storage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Storage.java index d633a455..a2e2d3db 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Storage.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Storage.java @@ -26,22 +26,28 @@ import com.flowpowered.math.vector.Vector2i; +import java.io.Closeable; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.util.Optional; -public abstract class Storage { +public abstract class Storage implements Closeable { + + public abstract void initialize() throws IOException; public abstract OutputStream writeMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException; - public abstract Optional readMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException; + public abstract Optional readMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException; + + public abstract Optional readMapTileData(String mapId, TileType tileType, Vector2i tile) throws IOException; public abstract void deleteMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException; public abstract OutputStream writeMeta(String mapId, MetaType metaType) throws IOException; - public abstract Optional readMeta(String mapId, MetaType metaType) throws IOException; + public abstract Optional readMeta(String mapId, MetaType metaType) throws IOException; + + public abstract void deleteMeta(String mapId, MetaType metaType) throws IOException; public abstract void purgeMap(String mapId) throws IOException; @@ -60,11 +66,11 @@ private TileStorage(String mapId, TileType tileType) { } public OutputStream write(Vector2i tile) throws IOException { - return Storage.this.writeMapTile(mapId, tileType, tile); + return writeMapTile(mapId, tileType, tile); } - public Optional read(Vector2i tile) throws IOException { - return Storage.this.readMapTile(mapId, tileType, tile); + public Optional read(Vector2i tile) throws IOException { + return readMapTile(mapId, tileType, tile); } public void delete(Vector2i tile) throws IOException { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/StorageType.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/StorageType.java new file mode 100644 index 00000000..95b8e784 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/StorageType.java @@ -0,0 +1,55 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.core.storage; + +import de.bluecolored.bluemap.core.config.storage.FileConfig; +import de.bluecolored.bluemap.core.config.storage.SQLConfig; +import de.bluecolored.bluemap.core.storage.file.FileStorage; +import de.bluecolored.bluemap.core.storage.sql.SQLStorage; +import org.spongepowered.configurate.ConfigurationNode; + +import java.util.Objects; + +public enum StorageType { + + FILE (node -> new FileStorage(Objects.requireNonNull(node.get(FileConfig.class)))), + SQL (node -> new SQLStorage(Objects.requireNonNull(node.get(SQLConfig.class)))); + + private final StorageProvider storageProvider; + + StorageType(StorageProvider storageProvider) { + this.storageProvider = storageProvider; + } + + public Storage create(ConfigurationNode node) throws Exception { + return storageProvider.provide(node); + } + + @FunctionalInterface + private interface StorageProvider { + Storage provide(ConfigurationNode node) throws Exception; + } + +} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/ThrowingFunction.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/TileData.java similarity index 83% rename from BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/ThrowingFunction.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/TileData.java index 9ff77904..d028e317 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/ThrowingFunction.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/TileData.java @@ -22,10 +22,18 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.common; +package de.bluecolored.bluemap.core.storage; -public interface ThrowingFunction { +import java.io.IOException; - R apply(T t) throws E; +public interface TileData { + + CompressedInputStream readMapTile() throws IOException; + + Compression getCompression(); + + long getSize(); + + long getLastModified(); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/TileType.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/TileType.java index 1981e44d..0b24140c 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/TileType.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/TileType.java @@ -24,6 +24,8 @@ */ package de.bluecolored.bluemap.core.storage; +import java.util.NoSuchElementException; + public enum TileType { HIRES ("hires"), @@ -38,4 +40,13 @@ public enum TileType { public String getTypeId() { return typeId; } + + public static TileType forTypeId(String id) { + for (TileType type : values()) { + if (type.typeId.equals(id)) return type; + } + + throw new NoSuchElementException("There is no TileType with id: " + id); + } + } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/FileStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/FileStorage.java similarity index 65% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/FileStorage.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/FileStorage.java index 181a0256..abae25b6 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/FileStorage.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/FileStorage.java @@ -22,10 +22,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.storage; +package de.bluecolored.bluemap.core.storage.file; import com.flowpowered.math.vector.Vector2i; +import de.bluecolored.bluemap.core.config.storage.FileConfig; import de.bluecolored.bluemap.core.debug.DebugDump; +import de.bluecolored.bluemap.core.storage.*; import de.bluecolored.bluemap.core.util.AtomicFileHelper; import de.bluecolored.bluemap.core.util.FileUtils; @@ -38,22 +40,25 @@ @DebugDump public class FileStorage extends Storage { - private static final EnumMap metaTypeFileNames = new EnumMap<>(MetaType.class); - static { - metaTypeFileNames.put(MetaType.TEXTURES, "../textures.json"); - metaTypeFileNames.put(MetaType.SETTINGS, "../settings.json"); - metaTypeFileNames.put(MetaType.MARKERS, "../markers.json"); - metaTypeFileNames.put(MetaType.RENDER_STATE, ".rstate"); - } - private final Path root; private final Compression compression; + public FileStorage(FileConfig config) { + this.root = config.getRoot(); + this.compression = config.getCompression(); + } + public FileStorage(Path root, Compression compression) { this.root = root; this.compression = compression; } + @Override + public void initialize() {} + + @Override + public void close() throws IOException {} + @Override public OutputStream writeMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException { Path file = getFilePath(mapId, tileType, tile); @@ -66,16 +71,48 @@ public OutputStream writeMapTile(String mapId, TileType tileType, Vector2i tile) } @Override - public Optional readMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException { + public Optional readMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException { Path file = getFilePath(mapId, tileType, tile); if (!Files.exists(file)) return Optional.empty(); InputStream is = Files.newInputStream(file, StandardOpenOption.READ); is = new BufferedInputStream(is); - is = compression.decompress(is); - return Optional.of(is); + return Optional.of(new CompressedInputStream(is, compression)); + } + + @Override + public Optional readMapTileData(String mapId, TileType tileType, Vector2i tile) throws IOException { + Path file = getFilePath(mapId, tileType, tile); + + if (!Files.exists(file)) return Optional.empty(); + + final long size = Files.size(file); + final long lastModified = Files.getLastModifiedTime(file).toMillis(); + + return Optional.of(new TileData() { + @Override + public CompressedInputStream readMapTile() throws IOException { + return FileStorage.this.readMapTile(mapId, tileType, tile) + .orElseThrow(() -> new IOException("Tile no longer present!")); + } + + @Override + public Compression getCompression() { + return compression; + } + + @Override + public long getSize() { + return size; + } + + @Override + public long getLastModified() { + return lastModified; + } + }); } @Override @@ -86,7 +123,7 @@ public void deleteMapTile(String mapId, TileType tileType, Vector2i tile) throws @Override public OutputStream writeMeta(String mapId, MetaType metaType) throws IOException { - Path file = getFilePath(mapId).resolve(getFilename(metaType)); + Path file = getFilePath(mapId).resolve(metaType.getFilePath()); OutputStream os = AtomicFileHelper.createFilepartOutputStream(file); os = new BufferedOutputStream(os); @@ -95,15 +132,21 @@ public OutputStream writeMeta(String mapId, MetaType metaType) throws IOExceptio } @Override - public Optional readMeta(String mapId, MetaType metaType) throws IOException { - Path file = getFilePath(mapId).resolve(getFilename(metaType)); + public Optional readMeta(String mapId, MetaType metaType) throws IOException { + Path file = getFilePath(mapId).resolve(metaType.getFilePath()); if (!Files.exists(file)) return Optional.empty(); InputStream is = Files.newInputStream(file, StandardOpenOption.READ); is = new BufferedInputStream(is); - return Optional.of(is); + return Optional.of(new CompressedInputStream(is, Compression.NONE)); + } + + @Override + public void deleteMeta(String mapId, MetaType metaType) throws IOException { + Path file = getFilePath(mapId).resolve(metaType.getFilePath()); + FileUtils.delete(file.toFile()); } @Override @@ -137,8 +180,4 @@ public Path getFilePath(String mapId) { return root.resolve(mapId); } - private static String getFilename(MetaType metaType) { - return metaTypeFileNames.getOrDefault(metaType, metaType.name().toLowerCase(Locale.ROOT)); - } - } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLStorage.java new file mode 100644 index 00000000..e29d9045 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLStorage.java @@ -0,0 +1,718 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.core.storage.sql; + +import com.flowpowered.math.vector.Vector2i; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import de.bluecolored.bluemap.core.config.ConfigurationException; +import de.bluecolored.bluemap.core.config.storage.SQLConfig; +import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.storage.*; +import de.bluecolored.bluemap.core.util.WrappedOutputStream; +import org.apache.commons.dbcp2.*; +import org.apache.commons.pool2.ObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; + +import javax.sql.DataSource; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.sql.*; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; +import java.util.Properties; +import java.util.concurrent.CompletionException; + +public class SQLStorage extends Storage { + + private final DataSource dataSource; + private final Compression compression; + + private final LoadingCache mapFKs = Caffeine.newBuilder() + .build(this::loadMapFK); + private final LoadingCache mapTileTypeFKs = Caffeine.newBuilder() + .build(this::loadMapTileTypeFK); + private final LoadingCache mapTileCompressionFKs = Caffeine.newBuilder() + .build(this::loadMapTileCompressionFK); + + public SQLStorage(SQLConfig config) throws ConfigurationException { + try { + if (config.getDriverClass().isPresent()) { + if (config.getDriverJar().isPresent()) { + ClassLoader classLoader = new URLClassLoader(new URL[]{config.getDriverJar().get()}); + Class driverClass = Class.forName(config.getDriverClass().get(), true, classLoader); + + Driver driver; + try { + driver = (Driver) driverClass.getDeclaredConstructor().newInstance(); + } catch (Exception ex) { + throw new ConfigurationException( + "BlueMap is not able to create an instance of the configured Driver-Class.\n" + + "This means that BlueMap can not load this Driver at runtime.\n" + + "Instead you'll need to add your driver-jar to the classpath when starting your server," + + "e.g. using the '-classpath' command-line argument", ex); + } + this.dataSource = createDataSource(config.getDbUrl(), config.getUser(), config.getPassword(), driver); + } else { + Class.forName(config.getDriverClass().get()); + this.dataSource = createDataSource(config.getDbUrl(), config.getUser(), config.getPassword()); + } + } else { + this.dataSource = createDataSource(config.getDbUrl(), config.getUser(), config.getPassword()); + } + } catch (MalformedURLException ex) { + throw new ConfigurationException( + "The path to your driver-jar is invalid. Check your sql-storage-config!", ex); + } catch (ClassNotFoundException ex) { + throw new ConfigurationException( + "The driver-class does not exist. Check your sql-storage-config!", ex); + } + + this.compression = config.getCompression(); + } + + public SQLStorage(String dbUrl, String user, String password, Compression compression) { + this.dataSource = createDataSource(dbUrl, user, password); + this.compression = compression; + } + + public SQLStorage(DataSource dataSource, Compression compression) { + this.dataSource = dataSource; + this.compression = compression; + } + + @Override + public OutputStream writeMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException { + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + return new WrappedOutputStream(compression.compress(byteOut), () -> { + int mapFK = getMapFK(mapId); + int tileTypeFK = getMapTileTypeFK(tileType); + int tileCompressionFK = getMapTileCompressionFK(compression); + + recoveringConnection(connection -> { + Blob dataBlob = connection.createBlob(); + try { + try (OutputStream blobOut = dataBlob.setBinaryStream(1)) { + byteOut.writeTo(blobOut); + } + + executeUpdate(connection, + //language=SQL + "REPLACE INTO `bluemap_map_tile` (`map`, `type`, `x`, `z`, `compression`, `data`) " + + "VALUES (?, ?, ?, ?, ?, ?)", + mapFK, + tileTypeFK, + tile.getX(), + tile.getY(), + tileCompressionFK, + dataBlob + ); + } finally { + dataBlob.free(); + } + }, 2); + }); + } + + @Override + public Optional readMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException { + try { + byte[] data = recoveringConnection(connection -> { + ResultSet result = executeQuery(connection, + //language=SQL + "SELECT t.`data` " + + "FROM `bluemap_map_tile` t " + + " INNER JOIN `bluemap_map` m " + + " ON t.`map` = m.`id` " + + " INNER JOIN `bluemap_map_tile_type` u " + + " ON t.`type` = u.`id` " + + " INNER JOIN `bluemap_map_tile_compression` c " + + " ON t.`compression` = c.`id` " + + "WHERE m.`map_id` = ? " + + "AND u.`type` = ? " + + "AND t.`x` = ? " + + "AND t.`z` = ? " + + "AND c.`compression` = ?", + mapId, + tileType.getTypeId(), + tile.getX(), + tile.getY(), + compression.getTypeId() + ); + + if (result.next()) { + Blob dataBlob = result.getBlob("data"); + return dataBlob.getBytes(1, (int) dataBlob.length()); + } else { + return null; + } + }, 2); + + if (data == null) return Optional.empty(); + return Optional.of(new CompressedInputStream(new ByteArrayInputStream(data), compression)); + } catch (SQLException ex) { + throw new IOException(ex); + } + } + + @Override + public Optional readMapTileData(final String mapId, final TileType tileType, final Vector2i tile) throws IOException { + try { + TileData tileData = recoveringConnection(connection -> { + ResultSet result = executeQuery(connection, + //language=SQL + "SELECT c.`compression`, t.`changed`, LENGTH(t.`data`) as 'size' " + + "FROM `bluemap_map_tile` t " + + " INNER JOIN `bluemap_map` m " + + " ON t.`map` = m.`id` " + + " INNER JOIN `bluemap_map_tile_type` u " + + " ON t.`type` = u.`id` " + + " INNER JOIN `bluemap_map_tile_compression` c " + + " ON t.`compression` = c.`id` " + + "WHERE m.`map_id` = ? " + + "AND u.`type` = ? " + + "AND t.`x` = ? " + + "AND t.`z` = ? " + + "AND c.`compression` = ?", + mapId, + tileType.getTypeId(), + tile.getX(), + tile.getY(), + compression.getTypeId() + ); + + if (result.next()) { + final Compression compression = Compression.forTypeId(result.getString("compression")); + final long lastModified = result.getTimestamp("changed").getTime(); + final long size = result.getLong("size"); + + return new TileData() { + @Override + public CompressedInputStream readMapTile() throws IOException { + return SQLStorage.this.readMapTile(mapId, tileType, tile) + .orElseThrow(() -> new IOException("Tile no longer present!")); + } + + @Override + public Compression getCompression() { + return compression; + } + + @Override + public long getSize() { + return size; + } + + @Override + public long getLastModified() { + return lastModified; + } + }; + } else { + return null; + } + }, 2); + + return Optional.ofNullable(tileData); + } catch (SQLException | NoSuchElementException ex) { + throw new IOException(ex); + } + } + + @Override + public void deleteMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException { + try { + recoveringConnection(connection -> + executeUpdate(connection, + //language=SQL + "DELETE t " + + "FROM `bluemap_map_tile` t " + + " INNER JOIN `bluemap_map` m " + + " ON t.`map` = m.`id` " + + " INNER JOIN `bluemap_map_tile_type` u " + + " ON t.`type` = u.`id` " + + "WHERE m.`map_id` = ? " + + "AND u.`type` = ? " + + "AND t.`x` = ? " + + "AND t.`z` = ?", + mapId, + tileType.getTypeId(), + tile.getX(), + tile.getY() + ), 2); + } catch (SQLException ex) { + throw new IOException(ex); + } + } + + @Override + public OutputStream writeMeta(String mapId, MetaType metaType) { + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + return new WrappedOutputStream(byteOut, () -> { + int mapFK = getMapFK(mapId); + + recoveringConnection(connection -> { + Blob dataBlob = connection.createBlob(); + try { + try (OutputStream blobOut = dataBlob.setBinaryStream(1)) { + byteOut.writeTo(blobOut); + } + + executeUpdate(connection, + //language=SQL + "REPLACE INTO `bluemap_map_meta` (`map`, `key`, `value`) " + + "VALUES (?, ?, ?)", + mapFK, + metaType.getTypeId(), + dataBlob + ); + } finally { + dataBlob.free(); + } + }, 2); + }); + } + + @Override + public Optional readMeta(String mapId, MetaType metaType) throws IOException { + try { + byte[] data = recoveringConnection(connection -> { + ResultSet result = executeQuery(connection, + //language=SQL + "SELECT t.`value` " + + "FROM `bluemap_map_meta` t " + + " INNER JOIN `bluemap_map` m " + + " ON t.`map` = m.`id` " + + "WHERE m.`map_id` = ? " + + "AND t.`key` = ?", + mapId, + metaType.getTypeId() + ); + + if (result.next()) { + Blob dataBlob = result.getBlob("value"); + return dataBlob.getBytes(1, (int) dataBlob.length()); + } else { + return null; + } + }, 2); + + if (data == null) return Optional.empty(); + return Optional.of(new CompressedInputStream(new ByteArrayInputStream(data), Compression.NONE)); + } catch (SQLException ex) { + throw new IOException(ex); + } + } + + @Override + public void deleteMeta(String mapId, MetaType metaType) throws IOException { + try { + recoveringConnection(connection -> + executeUpdate(connection, + //language=SQL + "DELETE t " + + "FROM `bluemap_map_meta` t " + + " INNER JOIN `bluemap_map` m " + + " ON t.`map` = m.`id` " + + "WHERE m.`map_id` = ? " + + "AND t.`key` = ?", + mapId, + metaType.getTypeId() + ), 2); + } catch (SQLException ex) { + throw new IOException(ex); + } + } + + @Override + public void purgeMap(String mapId) throws IOException { + try { + recoveringConnection(connection -> { + executeUpdate(connection, + //language=SQL + "DELETE t " + + "FROM `bluemap_map_tile` t " + + " INNER JOIN `bluemap_map` m " + + " ON t.`map` = m.`id` " + + "WHERE m.`map_id` = ?", + mapId + ); + + executeUpdate(connection, + //language=SQL + "DELETE t " + + "FROM `bluemap_map_meta` t " + + " INNER JOIN `bluemap_map` m " + + " ON t.`map` = m.`id` " + + "WHERE m.`map_id` = ?", + mapId + ); + }, 2); + } catch (SQLException ex) { + throw new IOException(ex); + } + } + + public void initialize() throws IOException { + try { + + // initialize and get schema-version + String schemaVersionString = recoveringConnection(connection -> { + connection.createStatement().executeUpdate( + "CREATE TABLE IF NOT EXISTS `bluemap_storage_meta` (" + + "`key` varchar(255) NOT NULL, " + + "`value` varchar(255) DEFAULT NULL, " + + "PRIMARY KEY (`key`)" + + ")"); + + ResultSet result = executeQuery(connection, + //language=SQL + "SELECT `value` FROM `bluemap_storage_meta` " + + "WHERE `key` = ?", + "schema_version" + ); + + if (result.next()) { + return result.getString("value"); + } else { + executeUpdate(connection, + //language=SQL + "INSERT INTO `bluemap_storage_meta` (`key`, `value`) " + + "VALUES (?, ?)", + "schema_version", "0" + ); + return "0"; + } + }, 2); + + int schemaVersion; + try { + schemaVersion = Integer.parseInt(schemaVersionString); + } catch (NumberFormatException ex) { + throw new IOException("Invalid schema-version number: " + schemaVersionString, ex); + } + + // validate schema version + if (schemaVersion < 0 || schemaVersion > 1) + throw new IOException("Unknown schema-version: " + schemaVersion); + + // update schema to current version + if (schemaVersion == 0) { + Logger.global.logInfo("Initializing database-schema..."); + + recoveringConnection(connection -> { + + connection.createStatement().executeUpdate( + "CREATE TABLE `bluemap_map` (" + + "`id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT," + + "`map_id` VARCHAR(255) NOT NULL," + + "PRIMARY KEY (`id`)," + + "UNIQUE INDEX `map_id` (`map_id`)" + + ");" + ); + + connection.createStatement().executeUpdate( + "CREATE TABLE `bluemap_map_tile_type` (" + + "`id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT," + + "`type` VARCHAR(255) NOT NULL," + + "PRIMARY KEY (`id`)," + + "UNIQUE INDEX `type` (`type`)" + + ");" + ); + + connection.createStatement().executeUpdate( + "CREATE TABLE `bluemap_map_tile_compression` (" + + "`id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT," + + "`compression` VARCHAR(255) NOT NULL," + + "PRIMARY KEY (`id`)," + + "UNIQUE INDEX `compression` (`compression`)" + + ");" + ); + + connection.createStatement().executeUpdate( + "CREATE TABLE `bluemap_map_meta` (" + + "`map` SMALLINT UNSIGNED NOT NULL," + + "`key` varchar(255) NOT NULL," + + "`value` LONGBLOB NOT NULL," + + "PRIMARY KEY (`map`, `key`)," + + "CONSTRAINT `fk_bluemap_map_meta_map` FOREIGN KEY (`map`) REFERENCES `bluemap_map` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT" + + ")"); + + connection.createStatement().executeUpdate( + "CREATE TABLE `bluemap_map_tile` (" + + "`map` SMALLINT UNSIGNED NOT NULL," + + "`type` SMALLINT UNSIGNED NOT NULL," + + "`x` INT NOT NULL," + + "`z` INT NOT NULL," + + "`compression` SMALLINT UNSIGNED NOT NULL," + + "`changed` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," + + "`data` LONGBLOB NOT NULL," + + "PRIMARY KEY (`map`, `type`, `x`, `z`)," + + "CONSTRAINT `fk_bluemap_map_tile_map` FOREIGN KEY (`map`) REFERENCES `bluemap_map` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT," + + "CONSTRAINT `fk_bluemap_map_tile_type` FOREIGN KEY (`type`) REFERENCES `bluemap_map_tile_type` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT," + + "CONSTRAINT `fk_bluemap_map_tile_compression` FOREIGN KEY (`compression`) REFERENCES `bluemap_map_tile_compression` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT" + + ");" + ); + + executeUpdate(connection, + //language=SQL + "UPDATE `bluemap_storage_meta` " + + "SET `value` = ? " + + "WHERE `key` = ?", + "1", "schema_version" + ); + }, 2); + + //schemaVersion = 1; + } + } catch (SQLException ex) { + throw new IOException(ex); + } + } + + @Override + public void close() throws IOException { + if (dataSource instanceof AutoCloseable) { + try { + ((AutoCloseable) dataSource).close(); + } catch (Exception ex) { + throw new IOException("Failed to close datasource!", ex); + } + } + } + + private ResultSet executeQuery(Connection connection, String sql, Object... parameters) throws SQLException { + // we only use this prepared statement once, but the DB-Driver caches those and reuses them + PreparedStatement statement = connection.prepareStatement(sql); + for (int i = 0; i < parameters.length; i++) { + statement.setObject(i+1, parameters[i]); + } + return statement.executeQuery(); + } + + @SuppressWarnings("UnusedReturnValue") + private int executeUpdate(Connection connection, String sql, Object... parameters) throws SQLException { + // we only use this prepared statement once, but the DB-Driver caches those and reuses them + PreparedStatement statement = connection.prepareStatement(sql); + for (int i = 0; i < parameters.length; i++) { + statement.setObject(i+1, parameters[i]); + } + return statement.executeUpdate(); + } + + @SuppressWarnings("SameParameterValue") + private void recoveringConnection(ConnectionConsumer action, int tries) throws SQLException, IOException { + recoveringConnection((ConnectionFunction) action, tries); + } + + @SuppressWarnings("SameParameterValue") + private R recoveringConnection(ConnectionFunction action, int tries) throws SQLException, IOException { + SQLException sqlException = null; + for (int i = 0; i < tries; i++) { + try (Connection connection = dataSource.getConnection()) { + connection.setAutoCommit(false); + + R result; + try { + result = action.apply(connection); + connection.commit(); + } catch (SQLRecoverableException ex) { + connection.rollback(); + if (sqlException == null) { + sqlException = ex; + } else { + sqlException.addSuppressed(ex); + } + continue; + } catch (SQLException | RuntimeException ex) { + connection.rollback(); + throw ex; + } + + connection.setAutoCommit(true); + return result; + } catch (SQLException | IOException | RuntimeException ex) { + if (sqlException != null) + ex.addSuppressed(sqlException); + throw ex; + } + } + + assert sqlException != null; // should never be null if we end up here + throw sqlException; + } + + private int getMapFK(String mapId) throws SQLException { + try { + return Objects.requireNonNull(mapFKs.get(mapId)); + } catch (CompletionException ex) { + Throwable cause = ex.getCause(); + + if (cause instanceof SQLException) + throw (SQLException) cause; + + throw ex; + } + } + + private int getMapTileTypeFK(TileType tileType) throws SQLException { + try { + return Objects.requireNonNull(mapTileTypeFKs.get(tileType)); + } catch (CompletionException ex) { + Throwable cause = ex.getCause(); + + if (cause instanceof SQLException) + throw (SQLException) cause; + + throw ex; + } + } + + private int getMapTileCompressionFK(Compression compression) throws SQLException { + try { + return Objects.requireNonNull(mapTileCompressionFKs.get(compression)); + } catch (CompletionException ex) { + Throwable cause = ex.getCause(); + + if (cause instanceof SQLException) + throw (SQLException) cause; + + throw ex; + } + } + + private int loadMapFK(String mapId) throws SQLException, IOException { + return lookupFK("bluemap_map", "id", "map_id", mapId); + } + + private int loadMapTileTypeFK(TileType mapTileType) throws SQLException, IOException { + return lookupFK("bluemap_map_tile_type", "id", "type", mapTileType.getTypeId()); + } + + private int loadMapTileCompressionFK(Compression compression) throws SQLException, IOException { + return lookupFK("bluemap_map_tile_compression", "id", "compression", compression.getTypeId()); + } + + @SuppressWarnings("SameParameterValue") + private int lookupFK(String table, String idField, String valueField, String value) throws SQLException, IOException { + return recoveringConnection(connection -> { + int key; + ResultSet result = executeQuery(connection, + //language=SQL + "SELECT `" + idField + "` FROM `" + table + "` " + + "WHERE `" + valueField + "` = ?", + value + ); + + if (result.next()) { + key = result.getInt("id"); + } else { + PreparedStatement statement = connection.prepareStatement( + "INSERT INTO `" + table + "` (`" + valueField + "`) " + + "VALUES (?)", + Statement.RETURN_GENERATED_KEYS + ); + statement.setString(1, value); + statement.executeUpdate(); + + ResultSet keys = statement.getGeneratedKeys(); + if (!keys.next()) throw new IllegalStateException("No generated key returned!"); + key = keys.getInt(1); + } + + return key; + }, 2); + } + + private DataSource createDataSource(String dbUrl, String user, String password) { + ConnectionFactory connectionFactory = new DriverManagerConnectionFactory( + dbUrl, + user, + password + ); + + return createDataSource(connectionFactory); + } + + private DataSource createDataSource(String dbUrl, String user, String password, Driver driver) { + Properties properties = new Properties(); + properties.put("user", user); + properties.put("password", password); + + ConnectionFactory connectionFactory = new DriverConnectionFactory( + driver, + dbUrl, + properties + ); + + return createDataSource(connectionFactory); + } + + private DataSource createDataSource(ConnectionFactory connectionFactory) { + PoolableConnectionFactory poolableConnectionFactory = + new PoolableConnectionFactory(connectionFactory, null); + poolableConnectionFactory.setPoolStatements(true); + poolableConnectionFactory.setMaxOpenPreparedStatements(20); + + GenericObjectPoolConfig objectPoolConfig = new GenericObjectPoolConfig<>(); + objectPoolConfig.setMinIdle(1); + objectPoolConfig.setMaxIdle(Runtime.getRuntime().availableProcessors() * 2); + objectPoolConfig.setMaxTotal(-1); + + ObjectPool connectionPool = + new GenericObjectPool<>(poolableConnectionFactory, objectPoolConfig); + poolableConnectionFactory.setPool(connectionPool); + + return new PoolingDataSource<>(connectionPool); + } + + @FunctionalInterface + public interface ConnectionConsumer extends ConnectionFunction { + + void accept(Connection connection) throws SQLException, IOException; + + @Override + default Void apply(Connection connection) throws SQLException, IOException { + accept(connection); + return null; + } + + } + + @FunctionalInterface + public interface ConnectionFunction { + + R apply(Connection connection) throws SQLException, IOException; + + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/AtomicFileHelper.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/AtomicFileHelper.java index 2b2ebd78..28dbbe9c 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/AtomicFileHelper.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/AtomicFileHelper.java @@ -62,42 +62,4 @@ private static Path getPartFile(Path file) { return file.normalize().getParent().resolve(file.getFileName() + ".filepart"); } - private static class WrappedOutputStream extends OutputStream { - - private final OutputStream out; - private final ThrowingRunnable onClose; - - private WrappedOutputStream(OutputStream out, ThrowingRunnable onClose) { - this.out = out; - this.onClose = onClose; - } - - @Override - public void write(int b) throws IOException { - out.write(b); - } - - @Override - public void write(byte[] b) throws IOException { - out.write(b); - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - out.write(b, off, len); - } - - @Override - public void flush() throws IOException { - out.flush(); - } - - @Override - public void close() throws IOException { - out.close(); - onClose.run(); - } - - } - } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MappableFunction.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MappableFunction.java new file mode 100644 index 00000000..25ff8185 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MappableFunction.java @@ -0,0 +1,48 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.core.util; + +import java.util.function.Function; + +@FunctionalInterface +public interface MappableFunction extends Function { + + default MappableFunction map(Function mapping) { + return t -> mapping.apply(this.apply(t)); + } + + default MappableFunction mapNullable(Function mapping) { + return t -> { + R r = this.apply(t); + if (r == null) return null; + return mapping.apply(r); + }; + } + + static MappableFunction of(Function function) { + return function::apply; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WrappedInputStream.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WrappedInputStream.java new file mode 100644 index 00000000..46b772e0 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WrappedInputStream.java @@ -0,0 +1,82 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.core.util; + +import java.io.IOException; +import java.io.InputStream; + +public class WrappedInputStream extends InputStream { + + private final InputStream in; + private final AutoCloseable onClose; + + public WrappedInputStream(InputStream in, AutoCloseable onClose) { + this.in = in; + this.onClose = onClose; + } + + @Override + public int read() throws IOException { + return in.read(); + } + + @Override + public int read(byte[] b) throws IOException { + return in.read(b); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return in.read(b, off, len); + } + + @Override + public void close() throws IOException { + IOException ioExcetion = null; + + try { + in.close(); + } catch (IOException ex) { + ioExcetion = ex; + } + + try { + onClose.close(); + } catch (Exception ex) { + if (ioExcetion == null) { + if (ex instanceof IOException) { + ioExcetion = (IOException) ex; + } else { + ioExcetion = new IOException(ex); + } + } else { + ioExcetion.addSuppressed(ex); + } + } + + if (ioExcetion != null) throw ioExcetion; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WrappedOutputStream.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WrappedOutputStream.java new file mode 100644 index 00000000..80d9f946 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WrappedOutputStream.java @@ -0,0 +1,87 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.core.util; + +import java.io.IOException; +import java.io.OutputStream; + +public class WrappedOutputStream extends OutputStream { + + private final OutputStream out; + private final AutoCloseable onClose; + + public WrappedOutputStream(OutputStream out, AutoCloseable onClose) { + this.out = out; + this.onClose = onClose; + } + + @Override + public void write(int b) throws IOException { + out.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + out.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + out.write(b, off, len); + } + + @Override + public void flush() throws IOException { + out.flush(); + } + + @Override + public void close() throws IOException { + IOException ioExcetion = null; + + try { + out.close(); + } catch (IOException ex) { + ioExcetion = ex; + } + + try { + onClose.close(); + } catch (Exception ex) { + if (ioExcetion == null) { + if (ex instanceof IOException) { + ioExcetion = (IOException) ex; + } else { + ioExcetion = new IOException(ex); + } + } else { + ioExcetion.addSuppressed(ex); + } + } + + if (ioExcetion != null) throw ioExcetion; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpConnection.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpConnection.java index 541afc20..6aa0d6f2 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpConnection.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpConnection.java @@ -79,10 +79,11 @@ public void run() { //just slow down processing if limit is reached hasPermit = processingSemaphore.tryAcquire(1, TimeUnit.SECONDS); - HttpResponse response = handler.handle(request); - sendResponse(response); + try (HttpResponse response = handler.handle(request)) { + sendResponse(response); - if (verbose) log(request, response); + if (verbose) log(request, response); + } } finally { if (hasPermit) processingSemaphore.release(); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpResponse.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpResponse.java index ad4c40ef..71cbab8e 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpResponse.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpResponse.java @@ -91,13 +91,12 @@ public void write(OutputStream out) throws IOException { if(data != null){ chunkedPipe(data, out); out.flush(); - data.close(); } } @Override public void close() throws IOException { - data.close(); + if (data != null) data.close(); } private void writeLine(OutputStreamWriter writer, String line) throws IOException { diff --git a/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java b/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java index d3c59146..1f722e7c 100644 --- a/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java +++ b/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java @@ -31,13 +31,16 @@ import de.bluecolored.bluemap.common.rendermanager.RenderManager; import de.bluecolored.bluemap.common.rendermanager.RenderTask; import de.bluecolored.bluemap.common.web.FileRequestHandler; +import de.bluecolored.bluemap.common.web.MapStorageRequestHandler; import de.bluecolored.bluemap.core.BlueMap; import de.bluecolored.bluemap.core.MinecraftVersion; -import de.bluecolored.bluemap.core.config.WebServerConfig; +import de.bluecolored.bluemap.core.config.ConfigurationException; +import de.bluecolored.bluemap.core.config.old.WebServerConfig; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.logger.LoggerLogger; import de.bluecolored.bluemap.core.map.BmMap; import de.bluecolored.bluemap.core.metrics.Metrics; +import de.bluecolored.bluemap.core.storage.Storage; import de.bluecolored.bluemap.core.util.FileUtils; import de.bluecolored.bluemap.core.webserver.HttpRequestHandler; import de.bluecolored.bluemap.core.webserver.WebServer; @@ -51,7 +54,7 @@ public class BlueMapCLI { - public void renderMaps(BlueMapService blueMap, boolean watch, boolean forceRender, boolean forceGenerateWebapp) throws IOException, InterruptedException { + public void renderMaps(BlueMapService blueMap, boolean watch, boolean forceRender, boolean forceGenerateWebapp) throws ConfigurationException, IOException, InterruptedException { //metrics report if (blueMap.getCoreConfig().isMetricsEnabled()) Metrics.sendReportAsync("cli"); @@ -165,13 +168,24 @@ public void run() { } } - public void startWebserver(BlueMapService blueMap, boolean verbose) throws IOException { + public void startWebserver(BlueMapService blueMap, boolean verbose) throws IOException, ConfigurationException, InterruptedException { Logger.global.logInfo("Starting webserver ..."); WebServerConfig config = blueMap.getWebServerConfig(); FileUtils.mkDirs(config.getWebRoot()); HttpRequestHandler requestHandler = new FileRequestHandler(config.getWebRoot().toPath(), "BlueMap v" + BlueMap.VERSION); + try { + //use map-storage to provide map-tiles + Map mapStorages = blueMap.getMapStorages(); + requestHandler = new MapStorageRequestHandler(mapStorages::get, requestHandler); + } catch (ConfigurationException ex) { + Logger.global.logWarning(ex.getFormattedExplanation()); + Logger.global.logError("Here is the full error:", ex); + + Logger.global.logWarning("The webserver will still be started, but it will not be able to serve the map-tiles correctly!"); + } + WebServer webServer = new WebServer( config.getWebserverBindAddress(), config.getWebserverPort(), @@ -265,6 +279,7 @@ public static void main(String[] args) { blueMap.getCoreConfig(); blueMap.getRenderConfig(); blueMap.getWebServerConfig(); + blueMap.getMapStorages(); //create resourcepacks folder FileUtils.mkDirs(new File(configFolder, "resourcepacks")); @@ -283,6 +298,12 @@ public static void main(String[] args) { Logger.global.logError("Failed to parse provided arguments!", e); BlueMapCLI.printHelp(); System.exit(1); + } catch (ConfigurationException e) { + Logger.global.logWarning(e.getFormattedExplanation()); + Throwable cause = e.getRootCause(); + if (cause != null) { + Logger.global.logError("Detailed error:", e); + } } catch (IOException e) { Logger.global.logError("An IO-error occurred!", e); System.exit(1); diff --git a/implementations/cli/src/main/resources/de/bluecolored/bluemap/render.conf b/implementations/cli/src/main/resources/de/bluecolored/bluemap/render.conf index 2d4770aa..c4edf54a 100644 --- a/implementations/cli/src/main/resources/de/bluecolored/bluemap/render.conf +++ b/implementations/cli/src/main/resources/de/bluecolored/bluemap/render.conf @@ -89,13 +89,10 @@ maps: [ # Default is true renderEdges: true - # With this set to true, the generated files for this world are compressed using gzip to save A LOT of space. - # Files will be only 5% as big with compression! - # Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly. - # This is much better than disabling the compression. - # Changing this value requires a re-render of the map. - # Default is true - useCompression: true + # This defines the storage-config that will be used to save this map. + # You can find your storage configs next to this config file in the 'storages'-folder. + # Default is "file" + storage: "file" # Normally BlueMap detects if a chunk has not yet generated it's light-data and omits rendering those chunks. # If this is set to true BlueMap will render Chunks even if there is no light-data! diff --git a/implementations/fabric-1.16.2/src/main/resources/de/bluecolored/bluemap/render.conf b/implementations/fabric-1.16.2/src/main/resources/de/bluecolored/bluemap/render.conf index 0e7d8279..93109526 100644 --- a/implementations/fabric-1.16.2/src/main/resources/de/bluecolored/bluemap/render.conf +++ b/implementations/fabric-1.16.2/src/main/resources/de/bluecolored/bluemap/render.conf @@ -89,13 +89,10 @@ maps: [ # Default is true renderEdges: true - # With this set to true, the generated files for this world are compressed using gzip to save A LOT of space. - # Files will be only 5% as big with compression! - # Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly. - # This is much better than disabling the compression. - # Changing this value requires a re-render of the map. - # Default is true - useCompression: true + # This defines the storage-config that will be used to save this map. + # You can find your storage configs next to this config file in the 'storages'-folder. + # Default is "file" + storage: "file" # Normally BlueMap detects if a chunk has not yet generated it's light-data and omits rendering those chunks. # If this is set to true BlueMap will render Chunks even if there is no light-data! diff --git a/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/render.conf b/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/render.conf index 0e7d8279..93109526 100644 --- a/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/render.conf +++ b/implementations/fabric-1.17/src/main/resources/de/bluecolored/bluemap/render.conf @@ -89,13 +89,10 @@ maps: [ # Default is true renderEdges: true - # With this set to true, the generated files for this world are compressed using gzip to save A LOT of space. - # Files will be only 5% as big with compression! - # Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly. - # This is much better than disabling the compression. - # Changing this value requires a re-render of the map. - # Default is true - useCompression: true + # This defines the storage-config that will be used to save this map. + # You can find your storage configs next to this config file in the 'storages'-folder. + # Default is "file" + storage: "file" # Normally BlueMap detects if a chunk has not yet generated it's light-data and omits rendering those chunks. # If this is set to true BlueMap will render Chunks even if there is no light-data! diff --git a/implementations/forge-1.14.4/src/main/resources/de/bluecolored/bluemap/render.conf b/implementations/forge-1.14.4/src/main/resources/de/bluecolored/bluemap/render.conf index 0e7d8279..93109526 100644 --- a/implementations/forge-1.14.4/src/main/resources/de/bluecolored/bluemap/render.conf +++ b/implementations/forge-1.14.4/src/main/resources/de/bluecolored/bluemap/render.conf @@ -89,13 +89,10 @@ maps: [ # Default is true renderEdges: true - # With this set to true, the generated files for this world are compressed using gzip to save A LOT of space. - # Files will be only 5% as big with compression! - # Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly. - # This is much better than disabling the compression. - # Changing this value requires a re-render of the map. - # Default is true - useCompression: true + # This defines the storage-config that will be used to save this map. + # You can find your storage configs next to this config file in the 'storages'-folder. + # Default is "file" + storage: "file" # Normally BlueMap detects if a chunk has not yet generated it's light-data and omits rendering those chunks. # If this is set to true BlueMap will render Chunks even if there is no light-data! diff --git a/implementations/forge-1.15.2/src/main/resources/de/bluecolored/bluemap/render.conf b/implementations/forge-1.15.2/src/main/resources/de/bluecolored/bluemap/render.conf index 0e7d8279..93109526 100644 --- a/implementations/forge-1.15.2/src/main/resources/de/bluecolored/bluemap/render.conf +++ b/implementations/forge-1.15.2/src/main/resources/de/bluecolored/bluemap/render.conf @@ -89,13 +89,10 @@ maps: [ # Default is true renderEdges: true - # With this set to true, the generated files for this world are compressed using gzip to save A LOT of space. - # Files will be only 5% as big with compression! - # Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly. - # This is much better than disabling the compression. - # Changing this value requires a re-render of the map. - # Default is true - useCompression: true + # This defines the storage-config that will be used to save this map. + # You can find your storage configs next to this config file in the 'storages'-folder. + # Default is "file" + storage: "file" # Normally BlueMap detects if a chunk has not yet generated it's light-data and omits rendering those chunks. # If this is set to true BlueMap will render Chunks even if there is no light-data! diff --git a/implementations/forge-1.16.2/src/main/resources/de/bluecolored/bluemap/render.conf b/implementations/forge-1.16.2/src/main/resources/de/bluecolored/bluemap/render.conf index 0e7d8279..93109526 100644 --- a/implementations/forge-1.16.2/src/main/resources/de/bluecolored/bluemap/render.conf +++ b/implementations/forge-1.16.2/src/main/resources/de/bluecolored/bluemap/render.conf @@ -89,13 +89,10 @@ maps: [ # Default is true renderEdges: true - # With this set to true, the generated files for this world are compressed using gzip to save A LOT of space. - # Files will be only 5% as big with compression! - # Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly. - # This is much better than disabling the compression. - # Changing this value requires a re-render of the map. - # Default is true - useCompression: true + # This defines the storage-config that will be used to save this map. + # You can find your storage configs next to this config file in the 'storages'-folder. + # Default is "file" + storage: "file" # Normally BlueMap detects if a chunk has not yet generated it's light-data and omits rendering those chunks. # If this is set to true BlueMap will render Chunks even if there is no light-data! diff --git a/implementations/spigot/src/main/resources/de/bluecolored/bluemap/render.conf b/implementations/spigot/src/main/resources/de/bluecolored/bluemap/render.conf index 1c1fc973..5374a569 100644 --- a/implementations/spigot/src/main/resources/de/bluecolored/bluemap/render.conf +++ b/implementations/spigot/src/main/resources/de/bluecolored/bluemap/render.conf @@ -89,13 +89,10 @@ maps: [ # Default is true renderEdges: true - # With this set to true, the generated files for this world are compressed using gzip to save A LOT of space. - # Files will be only 5% as big with compression! - # Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly. - # This is much better than disabling the compression. - # Changing this value requires a re-render of the map. - # Default is true - useCompression: true + # This defines the storage-config that will be used to save this map. + # You can find your storage configs next to this config file in the 'storages'-folder. + # Default is "file" + storage: "file" # Normally BlueMap detects if a chunk has not yet generated it's light-data and omits rendering those chunks. # If this is set to true BlueMap will render Chunks even if there is no light-data! diff --git a/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/render.conf b/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/render.conf index 0e7d8279..93109526 100644 --- a/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/render.conf +++ b/implementations/sponge-8.0.0/src/main/resources/de/bluecolored/bluemap/render.conf @@ -89,13 +89,10 @@ maps: [ # Default is true renderEdges: true - # With this set to true, the generated files for this world are compressed using gzip to save A LOT of space. - # Files will be only 5% as big with compression! - # Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly. - # This is much better than disabling the compression. - # Changing this value requires a re-render of the map. - # Default is true - useCompression: true + # This defines the storage-config that will be used to save this map. + # You can find your storage configs next to this config file in the 'storages'-folder. + # Default is "file" + storage: "file" # Normally BlueMap detects if a chunk has not yet generated it's light-data and omits rendering those chunks. # If this is set to true BlueMap will render Chunks even if there is no light-data!