mirror of
https://github.com/BlueMap-Minecraft/BlueMap.git
synced 2024-11-22 02:26:00 +01:00
Add SQL-Storage and add storage configs
This commit is contained in:
parent
f52d463458
commit
915f9d9608
@ -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<UUID, String, IOException> 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<String, Storage> storages;
|
||||
|
||||
private ResourcePack resourcePack;
|
||||
|
||||
private Map<UUID, World> worlds;
|
||||
private Map<String, BmMap> maps;
|
||||
private Map<String, Storage> 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<UUID, World> getWorlds() throws IOException, InterruptedException {
|
||||
public synchronized Map<UUID, World> getWorlds() throws ConfigurationException, InterruptedException {
|
||||
if (worlds == null) loadWorldsAndMaps();
|
||||
return worlds;
|
||||
}
|
||||
|
||||
public synchronized Map<String, BmMap> getMaps() throws IOException, InterruptedException {
|
||||
public synchronized Map<String, BmMap> getMaps() throws ConfigurationException, InterruptedException {
|
||||
if (maps == null) loadWorldsAndMaps();
|
||||
return maps;
|
||||
}
|
||||
|
||||
private synchronized void loadWorldsAndMaps() throws IOException, InterruptedException {
|
||||
public synchronized Map<String, Storage> 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<T, R, E extends Throwable> {
|
||||
R apply(T t) throws E;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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!");
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -431,7 +431,7 @@ public int reloadCommand(CommandContext<S> 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!"));
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,219 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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<? super String, Storage> mapStorageProvider;
|
||||
private final HttpRequestHandler notFoundHandler;
|
||||
|
||||
public MapStorageRequestHandler(Function<? super String, Storage> 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<TileData> 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<String> 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<String> 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<CompressedInputStream> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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
|
@ -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
|
@ -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'
|
||||
}
|
||||
|
@ -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<Placeholder> 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<? extends ConfigurationNode> 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<? extends ConfigurationNode> 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<? extends ConfigurationNode> 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<? extends ConfigurationNode> getLoader(Path path){
|
||||
if (path.getFileName().endsWith(".json")) return GsonConfigurationLoader.builder().path(path).build();
|
||||
else return HoconConfigurationLoader.builder().path(path).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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<Placeholder> 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<? extends ConfigurationNode> 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<? extends ConfigurationNode> getLoader(URL url){
|
||||
if (url.getFile().endsWith(".json")) return GsonConfigurationLoader.builder().url(url).build();
|
||||
else return HoconConfigurationLoader.builder().url(url).build();
|
||||
}
|
||||
|
||||
private ConfigurationLoader<? extends ConfigurationNode> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
@ -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
|
@ -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;
|
@ -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<MapConfig> 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
|
@ -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
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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<URL> getDriverJar() throws MalformedURLException {
|
||||
if (driverJar == null) return Optional.empty();
|
||||
|
||||
if (driverJarURL == null) {
|
||||
driverJarURL = Paths.get(driverJar).toUri().toURL();
|
||||
}
|
||||
|
||||
return Optional.of(driverJarURL);
|
||||
}
|
||||
|
||||
public Optional<String> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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<E extends Throwable> {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -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<InputStream> rstateData = storage.readMeta(id, MetaType.RENDER_STATE);
|
||||
Optional<CompressedInputStream> 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);
|
||||
|
@ -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<InputStream> optIs = storage.read(tile);
|
||||
Optional<CompressedInputStream> 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));
|
||||
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
@ -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<OutputStream> compressor;
|
||||
private final StreamTransformer<InputStream> decompressor;
|
||||
|
||||
Compression(String fileSuffix) {
|
||||
Compression(String typeId, String fileSuffix,
|
||||
StreamTransformer<OutputStream> compressor,
|
||||
StreamTransformer<InputStream> 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> {
|
||||
T apply(T original) throws IOException;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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<InputStream> readMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException;
|
||||
public abstract Optional<CompressedInputStream> readMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException;
|
||||
|
||||
public abstract Optional<TileData> 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<InputStream> readMeta(String mapId, MetaType metaType) throws IOException;
|
||||
public abstract Optional<CompressedInputStream> 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<InputStream> read(Vector2i tile) throws IOException {
|
||||
return Storage.this.readMapTile(mapId, tileType, tile);
|
||||
public Optional<CompressedInputStream> read(Vector2i tile) throws IOException {
|
||||
return readMapTile(mapId, tileType, tile);
|
||||
}
|
||||
|
||||
public void delete(Vector2i tile) throws IOException {
|
||||
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
@ -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<T, R, E extends Throwable> {
|
||||
import java.io.IOException;
|
||||
|
||||
R apply(T t) throws E;
|
||||
public interface TileData {
|
||||
|
||||
CompressedInputStream readMapTile() throws IOException;
|
||||
|
||||
Compression getCompression();
|
||||
|
||||
long getSize();
|
||||
|
||||
long getLastModified();
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<MetaType, String> 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<InputStream> readMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException {
|
||||
public Optional<CompressedInputStream> 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<TileData> 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<InputStream> readMeta(String mapId, MetaType metaType) throws IOException {
|
||||
Path file = getFilePath(mapId).resolve(getFilename(metaType));
|
||||
public Optional<CompressedInputStream> 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));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,718 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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<String, Integer> mapFKs = Caffeine.newBuilder()
|
||||
.build(this::loadMapFK);
|
||||
private final LoadingCache<TileType, Integer> mapTileTypeFKs = Caffeine.newBuilder()
|
||||
.build(this::loadMapTileTypeFK);
|
||||
private final LoadingCache<Compression, Integer> 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<CompressedInputStream> 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<TileData> 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<CompressedInputStream> 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<Void>) action, tries);
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private <R> R recoveringConnection(ConnectionFunction<R> 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<PoolableConnection> objectPoolConfig = new GenericObjectPoolConfig<>();
|
||||
objectPoolConfig.setMinIdle(1);
|
||||
objectPoolConfig.setMaxIdle(Runtime.getRuntime().availableProcessors() * 2);
|
||||
objectPoolConfig.setMaxTotal(-1);
|
||||
|
||||
ObjectPool<PoolableConnection> connectionPool =
|
||||
new GenericObjectPool<>(poolableConnectionFactory, objectPoolConfig);
|
||||
poolableConnectionFactory.setPool(connectionPool);
|
||||
|
||||
return new PoolingDataSource<>(connectionPool);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ConnectionConsumer extends ConnectionFunction<Void> {
|
||||
|
||||
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> {
|
||||
|
||||
R apply(Connection connection) throws SQLException, IOException;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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<IOException> onClose;
|
||||
|
||||
private WrappedOutputStream(OutputStream out, ThrowingRunnable<IOException> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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<T, R> extends Function<T, R> {
|
||||
|
||||
default <S> MappableFunction<T, S> map(Function<R, S> mapping) {
|
||||
return t -> mapping.apply(this.apply(t));
|
||||
}
|
||||
|
||||
default <S> MappableFunction<T, S> mapNullable(Function<R, S> mapping) {
|
||||
return t -> {
|
||||
R r = this.apply(t);
|
||||
if (r == null) return null;
|
||||
return mapping.apply(r);
|
||||
};
|
||||
}
|
||||
|
||||
static <T, R> MappableFunction<T, R> of(Function<T, R> function) {
|
||||
return function::apply;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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<String, Storage> 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);
|
||||
|
@ -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!
|
||||
|
@ -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!
|
||||
|
@ -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!
|
||||
|
@ -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!
|
||||
|
@ -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!
|
||||
|
@ -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!
|
||||
|
@ -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!
|
||||
|
@ -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!
|
||||
|
Loading…
Reference in New Issue
Block a user