mirror of
https://github.com/BlueMap-Minecraft/BlueMap.git
synced 2024-11-22 18:45:21 +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.plugin.serverinterface.ServerInterface;
|
||||||
import de.bluecolored.bluemap.common.web.WebSettings;
|
import de.bluecolored.bluemap.common.web.WebSettings;
|
||||||
import de.bluecolored.bluemap.core.MinecraftVersion;
|
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.debug.DebugDump;
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
import de.bluecolored.bluemap.core.map.BmMap;
|
import de.bluecolored.bluemap.core.map.BmMap;
|
||||||
import de.bluecolored.bluemap.core.mca.MCAWorld;
|
import de.bluecolored.bluemap.core.mca.MCAWorld;
|
||||||
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
|
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
|
||||||
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
|
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.storage.Storage;
|
||||||
import de.bluecolored.bluemap.core.world.World;
|
import de.bluecolored.bluemap.core.world.World;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.spongepowered.configurate.ConfigurationNode;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,15 +65,19 @@ public class BlueMapService {
|
|||||||
private final ThrowingFunction<UUID, String, IOException> worldNameProvider;
|
private final ThrowingFunction<UUID, String, IOException> worldNameProvider;
|
||||||
|
|
||||||
private final ConfigManager configManager;
|
private final ConfigManager configManager;
|
||||||
|
private final de.bluecolored.bluemap.core.config.old.ConfigManager configManagerOld;
|
||||||
|
|
||||||
private CoreConfig coreConfig;
|
private CoreConfig coreConfig;
|
||||||
private RenderConfig renderConfig;
|
private RenderConfig renderConfig;
|
||||||
private WebServerConfig webServerConfig;
|
private WebServerConfig webServerConfig;
|
||||||
|
|
||||||
|
private final Map<String, Storage> storages;
|
||||||
|
|
||||||
private ResourcePack resourcePack;
|
private ResourcePack resourcePack;
|
||||||
|
|
||||||
private Map<UUID, World> worlds;
|
private Map<UUID, World> worlds;
|
||||||
private Map<String, BmMap> maps;
|
private Map<String, BmMap> maps;
|
||||||
|
private Map<String, Storage> mapStorages;
|
||||||
|
|
||||||
public BlueMapService(MinecraftVersion minecraftVersion, File configFolder) {
|
public BlueMapService(MinecraftVersion minecraftVersion, File configFolder) {
|
||||||
this.minecraftVersion = minecraftVersion;
|
this.minecraftVersion = minecraftVersion;
|
||||||
@ -82,7 +95,10 @@ public BlueMapService(MinecraftVersion minecraftVersion, File configFolder) {
|
|||||||
|
|
||||||
this.worldNameProvider = uuid -> null;
|
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) {
|
public BlueMapService(MinecraftVersion minecraftVersion, ServerInterface serverInterface) {
|
||||||
@ -91,47 +107,76 @@ public BlueMapService(MinecraftVersion minecraftVersion, ServerInterface serverI
|
|||||||
this.worldUUIDProvider = serverInterface::getUUIDForWorld;
|
this.worldUUIDProvider = serverInterface::getUUIDForWorld;
|
||||||
this.worldNameProvider = serverInterface::getWorldName;
|
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());
|
WebFilesManager webFilesManager = new WebFilesManager(getRenderConfig().getWebRoot());
|
||||||
if (force || webFilesManager.needsUpdate()) {
|
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 {
|
public synchronized WebSettings updateWebAppSettings() throws ConfigurationException, InterruptedException {
|
||||||
WebSettings webSettings = new WebSettings(new File(getRenderConfig().getWebRoot(), "data" + File.separator + "settings.json"));
|
try {
|
||||||
webSettings.set(getRenderConfig().isUseCookies(), "useCookies");
|
WebSettings webSettings = new WebSettings(new File(getRenderConfig().getWebRoot(),
|
||||||
webSettings.set(getRenderConfig().isEnableFreeFlight(), "freeFlightEnabled");
|
"data" + File.separator + "settings.json"));
|
||||||
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;
|
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();
|
if (worlds == null) loadWorldsAndMaps();
|
||||||
return worlds;
|
return worlds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Map<String, BmMap> getMaps() throws IOException, InterruptedException {
|
public synchronized Map<String, BmMap> getMaps() throws ConfigurationException, InterruptedException {
|
||||||
if (maps == null) loadWorldsAndMaps();
|
if (maps == null) loadWorldsAndMaps();
|
||||||
return maps;
|
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<>();
|
maps = new HashMap<>();
|
||||||
worlds = new HashMap<>();
|
worlds = new HashMap<>();
|
||||||
|
|
||||||
@ -141,7 +186,7 @@ private synchronized void loadWorldsAndMaps() throws IOException, InterruptedExc
|
|||||||
|
|
||||||
File worldFolder = new File(mapConfig.getWorldPath());
|
File worldFolder = new File(mapConfig.getWorldPath());
|
||||||
if (!worldFolder.exists() || !worldFolder.isDirectory()) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,36 +203,85 @@ private synchronized void loadWorldsAndMaps() throws IOException, InterruptedExc
|
|||||||
try {
|
try {
|
||||||
world = MCAWorld.load(worldFolder.toPath(), worldUUID, worldNameProvider.apply(worldUUID), mapConfig.getWorldSkyLight(), mapConfig.isIgnoreMissingLightData());
|
world = MCAWorld.load(worldFolder.toPath(), worldUUID, worldNameProvider.apply(worldUUID), mapConfig.getWorldSkyLight(), mapConfig.isIgnoreMissingLightData());
|
||||||
worlds.put(worldUUID, world);
|
worlds.put(worldUUID, world);
|
||||||
} catch (MissingResourcesException e) {
|
|
||||||
throw e; // rethrow this to stop loading and display resource-missing message
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Logger.global.logError("Failed to load map '" + id + "'!", e);
|
Logger.global.logError("Failed to load map '" + id + "'!", e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage storage = new FileStorage(
|
Storage storage = getStorage(mapConfig.getStorage());
|
||||||
getRenderConfig().getWebRoot().toPath().resolve("data"),
|
try {
|
||||||
mapConfig.getCompression()
|
BmMap map = new BmMap(
|
||||||
);
|
id,
|
||||||
|
name,
|
||||||
|
world,
|
||||||
|
storage,
|
||||||
|
getResourcePack(),
|
||||||
|
mapConfig
|
||||||
|
);
|
||||||
|
|
||||||
BmMap map = new BmMap(
|
maps.put(id, map);
|
||||||
id,
|
} catch (IOException ex) {
|
||||||
name,
|
Logger.global.logError("Failed to load map '" + id + "'!", ex);
|
||||||
world,
|
}
|
||||||
storage,
|
|
||||||
getResourcePack(),
|
|
||||||
mapConfig
|
|
||||||
);
|
|
||||||
|
|
||||||
maps.put(id, map);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
worlds = Collections.unmodifiableMap(worlds);
|
worlds = Collections.unmodifiableMap(worlds);
|
||||||
maps = Collections.unmodifiableMap(maps);
|
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) {
|
if (resourcePack == null) {
|
||||||
File defaultResourceFile = new File(getCoreConfig().getDataFolder(), "minecraft-client-" + minecraftVersion.getResource().getVersion().getVersionString() + ".jar");
|
File defaultResourceFile = new File(getCoreConfig().getDataFolder(), "minecraft-client-" + minecraftVersion.getResource().getVersion().getVersionString() + ".jar");
|
||||||
File resourceExtensionsFile = new File(getCoreConfig().getDataFolder(), "resourceExtensions.zip");
|
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 textureExportFile = new File(getRenderConfig().getWebRoot(), "data" + File.separator + "textures.json");
|
||||||
|
|
||||||
File resourcePackFolder = new File(configFolder, "resourcepacks");
|
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 (!defaultResourceFile.exists()) {
|
||||||
if (getCoreConfig().isDownloadAccepted()) {
|
if (getCoreConfig().isDownloadAccepted()) {
|
||||||
@ -205,8 +307,8 @@ public synchronized ResourcePack getResourcePack() throws IOException, Interrupt
|
|||||||
Logger.global.logInfo("Downloading " + minecraftVersion.getResource().getClientUrl() + " to " + defaultResourceFile + " ...");
|
Logger.global.logInfo("Downloading " + minecraftVersion.getResource().getClientUrl() + " to " + defaultResourceFile + " ...");
|
||||||
FileUtils.forceMkdirParent(defaultResourceFile);
|
FileUtils.forceMkdirParent(defaultResourceFile);
|
||||||
FileUtils.copyURLToFile(new URL(minecraftVersion.getResource().getClientUrl()), defaultResourceFile, 10000, 10000);
|
FileUtils.copyURLToFile(new URL(minecraftVersion.getResource().getClientUrl()), defaultResourceFile, 10000, 10000);
|
||||||
} catch (IOException e) {
|
} catch (IOException ex) {
|
||||||
throw new IOException("Failed to download resources!", e);
|
throw new ConfigurationException("Failed to download resources!", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -216,12 +318,21 @@ public synchronized ResourcePack getResourcePack() throws IOException, Interrupt
|
|||||||
|
|
||||||
Logger.global.logInfo("Loading resources...");
|
Logger.global.logInfo("Loading resources...");
|
||||||
|
|
||||||
if (resourceExtensionsFile.exists()) FileUtils.forceDelete(resourceExtensionsFile);
|
try {
|
||||||
FileUtils.forceMkdirParent(resourceExtensionsFile);
|
if (resourceExtensionsFile.exists()) FileUtils.forceDelete(resourceExtensionsFile);
|
||||||
URL resourceExtensionsUrl = Objects.requireNonNull(
|
FileUtils.forceMkdirParent(resourceExtensionsFile);
|
||||||
Plugin.class.getResource("/de/bluecolored/bluemap/" + minecraftVersion.getResource().getResourcePrefix() + "/resourceExtensions.zip")
|
URL resourceExtensionsUrl = Objects.requireNonNull(
|
||||||
);
|
Plugin.class.getResource(
|
||||||
FileUtils.copyURLToFile(resourceExtensionsUrl, resourceExtensionsFile, 10000, 10000);
|
"/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
|
//find more resource packs
|
||||||
File[] resourcePacks = resourcePackFolder.listFiles();
|
File[] resourcePacks = resourcePackFolder.listFiles();
|
||||||
@ -238,8 +349,8 @@ public synchronized ResourcePack getResourcePack() throws IOException, Interrupt
|
|||||||
if (textureExportFile.exists()) resourcePack.loadTextureFile(textureExportFile);
|
if (textureExportFile.exists()) resourcePack.loadTextureFile(textureExportFile);
|
||||||
resourcePack.load(resources);
|
resourcePack.load(resources);
|
||||||
resourcePack.saveTextureFile(textureExportFile);
|
resourcePack.saveTextureFile(textureExportFile);
|
||||||
} catch (ParseResourceException e) {
|
} catch (IOException | ParseResourceException e) {
|
||||||
throw new IOException("Failed to parse resources!", e);
|
throw new ConfigurationException("Failed to parse resources!", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -247,17 +358,18 @@ public synchronized ResourcePack getResourcePack() throws IOException, Interrupt
|
|||||||
return resourcePack;
|
return resourcePack;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized ConfigManager getConfigManager() {
|
@Deprecated
|
||||||
return configManager;
|
public synchronized de.bluecolored.bluemap.core.config.old.ConfigManager getConfigManagerOld() {
|
||||||
|
return configManagerOld;
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getCoreConfigFile() {
|
public File getCoreConfigFile() {
|
||||||
return new File(configFolder, "core.conf");
|
return new File(configFolder, "core.conf");
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized CoreConfig getCoreConfig() throws IOException {
|
public synchronized CoreConfig getCoreConfig() throws ConfigurationException {
|
||||||
if (coreConfig == null) {
|
if (coreConfig == null) {
|
||||||
coreConfig = new CoreConfig(configManager.loadOrCreate(
|
coreConfig = new CoreConfig(configManagerOld.loadOrCreate(
|
||||||
getCoreConfigFile(),
|
getCoreConfigFile(),
|
||||||
Plugin.class.getResource("/de/bluecolored/bluemap/core.conf"),
|
Plugin.class.getResource("/de/bluecolored/bluemap/core.conf"),
|
||||||
Plugin.class.getResource("/de/bluecolored/bluemap/core-defaults.conf"),
|
Plugin.class.getResource("/de/bluecolored/bluemap/core-defaults.conf"),
|
||||||
@ -273,9 +385,9 @@ public File getRenderConfigFile() {
|
|||||||
return new File(configFolder, "render.conf");
|
return new File(configFolder, "render.conf");
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized RenderConfig getRenderConfig() throws IOException {
|
public synchronized RenderConfig getRenderConfig() throws ConfigurationException {
|
||||||
if (renderConfig == null) {
|
if (renderConfig == null) {
|
||||||
renderConfig = new RenderConfig(configManager.loadOrCreate(
|
renderConfig = new RenderConfig(configManagerOld.loadOrCreate(
|
||||||
getRenderConfigFile(),
|
getRenderConfigFile(),
|
||||||
Plugin.class.getResource("/de/bluecolored/bluemap/render.conf"),
|
Plugin.class.getResource("/de/bluecolored/bluemap/render.conf"),
|
||||||
Plugin.class.getResource("/de/bluecolored/bluemap/render-defaults.conf"),
|
Plugin.class.getResource("/de/bluecolored/bluemap/render-defaults.conf"),
|
||||||
@ -291,9 +403,9 @@ public File getWebServerConfigFile() {
|
|||||||
return new File(configFolder, "webserver.conf");
|
return new File(configFolder, "webserver.conf");
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized WebServerConfig getWebServerConfig() throws IOException {
|
public synchronized WebServerConfig getWebServerConfig() throws ConfigurationException {
|
||||||
if (webServerConfig == null) {
|
if (webServerConfig == null) {
|
||||||
webServerConfig = new WebServerConfig(configManager.loadOrCreate(
|
webServerConfig = new WebServerConfig(configManagerOld.loadOrCreate(
|
||||||
getWebServerConfigFile(),
|
getWebServerConfigFile(),
|
||||||
Plugin.class.getResource("/de/bluecolored/bluemap/webserver.conf"),
|
Plugin.class.getResource("/de/bluecolored/bluemap/webserver.conf"),
|
||||||
Plugin.class.getResource("/de/bluecolored/bluemap/webserver-defaults.conf"),
|
Plugin.class.getResource("/de/bluecolored/bluemap/webserver-defaults.conf"),
|
||||||
@ -309,4 +421,8 @@ public File getConfigFolder() {
|
|||||||
return configFolder;
|
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;
|
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;
|
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.MapUpdateTask;
|
||||||
import de.bluecolored.bluemap.common.rendermanager.RenderManager;
|
import de.bluecolored.bluemap.common.rendermanager.RenderManager;
|
||||||
import de.bluecolored.bluemap.common.web.FileRequestHandler;
|
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.BlueMap;
|
||||||
import de.bluecolored.bluemap.core.MinecraftVersion;
|
import de.bluecolored.bluemap.core.MinecraftVersion;
|
||||||
import de.bluecolored.bluemap.core.config.CoreConfig;
|
import de.bluecolored.bluemap.core.config.ConfigurationException;
|
||||||
import de.bluecolored.bluemap.core.config.RenderConfig;
|
import de.bluecolored.bluemap.core.config.old.CoreConfig;
|
||||||
import de.bluecolored.bluemap.core.config.WebServerConfig;
|
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.DebugDump;
|
||||||
import de.bluecolored.bluemap.core.debug.StateDumper;
|
import de.bluecolored.bluemap.core.debug.StateDumper;
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
@ -47,6 +49,7 @@
|
|||||||
import de.bluecolored.bluemap.core.metrics.Metrics;
|
import de.bluecolored.bluemap.core.metrics.Metrics;
|
||||||
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
|
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
|
||||||
import de.bluecolored.bluemap.core.util.FileUtils;
|
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.HttpRequestHandler;
|
||||||
import de.bluecolored.bluemap.core.webserver.WebServer;
|
import de.bluecolored.bluemap.core.webserver.WebServer;
|
||||||
import de.bluecolored.bluemap.core.world.World;
|
import de.bluecolored.bluemap.core.world.World;
|
||||||
@ -116,9 +119,10 @@ public void load() throws IOException, ParseResourceException {
|
|||||||
coreConfig = blueMap.getCoreConfig();
|
coreConfig = blueMap.getCoreConfig();
|
||||||
renderConfig = blueMap.getRenderConfig();
|
renderConfig = blueMap.getRenderConfig();
|
||||||
webServerConfig = blueMap.getWebServerConfig();
|
webServerConfig = blueMap.getWebServerConfig();
|
||||||
|
blueMap.getMapStorages();
|
||||||
|
|
||||||
//load plugin config
|
//load plugin config
|
||||||
pluginConfig = new PluginConfig(blueMap.getConfigManager().loadOrCreate(
|
pluginConfig = new PluginConfig(blueMap.getConfigManagerOld().loadOrCreate(
|
||||||
new File(serverInterface.getConfigFolder(), "plugin.conf"),
|
new File(serverInterface.getConfigFolder(), "plugin.conf"),
|
||||||
Plugin.class.getResource("/de/bluecolored/bluemap/plugin.conf"),
|
Plugin.class.getResource("/de/bluecolored/bluemap/plugin.conf"),
|
||||||
Plugin.class.getResource("/de/bluecolored/bluemap/plugin-defaults.conf"),
|
Plugin.class.getResource("/de/bluecolored/bluemap/plugin-defaults.conf"),
|
||||||
@ -137,26 +141,6 @@ public void load() throws IOException, ParseResourceException {
|
|||||||
pluginState = new PluginState();
|
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 load resources
|
||||||
try {
|
try {
|
||||||
blueMap.getResourcePack();
|
blueMap.getResourcePack();
|
||||||
@ -182,6 +166,31 @@ public void load() throws IOException, ParseResourceException {
|
|||||||
return;
|
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
|
//initialize render manager
|
||||||
renderManager = new RenderManager();
|
renderManager = new RenderManager();
|
||||||
|
|
||||||
@ -275,6 +284,9 @@ public void run() {
|
|||||||
//done
|
//done
|
||||||
loaded = true;
|
loaded = true;
|
||||||
}
|
}
|
||||||
|
} catch (ConfigurationException ex) {
|
||||||
|
Logger.global.logWarning(ex.getFormattedExplanation());
|
||||||
|
throw new IOException(ex);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
Logger.global.logWarning("Loading has been interrupted!");
|
Logger.global.logWarning("Loading has been interrupted!");
|
||||||
@ -310,12 +322,30 @@ public void unload() {
|
|||||||
regionFileWatchServices = null;
|
regionFileWatchServices = null;
|
||||||
|
|
||||||
//stop services
|
//stop services
|
||||||
if (renderManager != null) renderManager.stop();
|
if (renderManager != null){
|
||||||
|
renderManager.stop();
|
||||||
|
try {
|
||||||
|
renderManager.awaitShutdown();
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
renderManager = null;
|
renderManager = null;
|
||||||
|
|
||||||
if (webServer != null) webServer.close();
|
if (webServer != null) webServer.close();
|
||||||
webServer = null;
|
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
|
//clear resources and configs
|
||||||
blueMap = null;
|
blueMap = null;
|
||||||
worlds = 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!"));
|
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);
|
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!"));
|
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.debug.DebugDump;
|
||||||
import de.bluecolored.bluemap.core.map.BmMap;
|
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.storage.Storage;
|
||||||
import de.bluecolored.bluemap.core.util.FileUtils;
|
import de.bluecolored.bluemap.core.util.FileUtils;
|
||||||
|
|
||||||
|
@ -28,26 +28,18 @@
|
|||||||
import de.bluecolored.bluemap.core.webserver.HttpRequestHandler;
|
import de.bluecolored.bluemap.core.webserver.HttpRequestHandler;
|
||||||
import de.bluecolored.bluemap.core.webserver.HttpResponse;
|
import de.bluecolored.bluemap.core.webserver.HttpResponse;
|
||||||
import de.bluecolored.bluemap.core.webserver.HttpStatusCode;
|
import de.bluecolored.bluemap.core.webserver.HttpStatusCode;
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
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.InvalidPathException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.*;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.TimeZone;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.zip.GZIPInputStream;
|
|
||||||
import java.util.zip.GZIPOutputStream;
|
|
||||||
|
|
||||||
public class FileRequestHandler implements HttpRequestHandler {
|
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 Path webRoot;
|
||||||
private final String serverName;
|
private final String serverName;
|
||||||
|
|
||||||
@ -86,17 +78,13 @@ private HttpResponse generateResponse(HttpRequest request) {
|
|||||||
if (path.startsWith("/")) path = path.substring(1);
|
if (path.startsWith("/")) path = path.substring(1);
|
||||||
if (path.endsWith("/")) path = path.substring(0, path.length() - 1);
|
if (path.endsWith("/")) path = path.substring(0, path.length() - 1);
|
||||||
|
|
||||||
Path filePath = webRoot;
|
Path filePath;
|
||||||
try {
|
try {
|
||||||
filePath = webRoot.resolve(path);
|
filePath = webRoot.resolve(path);
|
||||||
} catch (InvalidPathException e){
|
} catch (InvalidPathException e){
|
||||||
return new HttpResponse(HttpStatusCode.NOT_FOUND);
|
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
|
// check if file is in web-root
|
||||||
if (!filePath.normalize().startsWith(webRoot)){
|
if (!filePath.normalize().startsWith(webRoot)){
|
||||||
return new HttpResponse(HttpStatusCode.FORBIDDEN);
|
return new HttpResponse(HttpStatusCode.FORBIDDEN);
|
||||||
@ -111,38 +99,20 @@ private HttpResponse generateResponse(HttpRequest request) {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!file.exists() || file.isDirectory()){
|
// default to index.html
|
||||||
file = new File(filePath.toString() + ".gz");
|
|
||||||
isDeflated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!file.exists() || file.isDirectory()){
|
if (!file.exists() || file.isDirectory()){
|
||||||
file = new File(filePath.toString() + "/index.html");
|
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"))){
|
if (!file.exists() && file.toPath().startsWith(webRoot.resolve("data"))){
|
||||||
file = emptyTileFile;
|
file = emptyTileFile;
|
||||||
isDeflated = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!file.exists() || file.isDirectory()) {
|
if (!file.exists() || file.isDirectory()) {
|
||||||
return new HttpResponse(HttpStatusCode.NOT_FOUND);
|
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
|
// check if file is still in web-root and is not a directory
|
||||||
if (!file.toPath().normalize().startsWith(webRoot) || file.isDirectory()){
|
if (!file.toPath().normalize().startsWith(webRoot) || file.isDirectory()){
|
||||||
return new HttpResponse(HttpStatusCode.FORBIDDEN);
|
return new HttpResponse(HttpStatusCode.FORBIDDEN);
|
||||||
@ -178,76 +148,17 @@ private HttpResponse generateResponse(HttpRequest request) {
|
|||||||
|
|
||||||
//add content type header
|
//add content type header
|
||||||
String filetype = file.getName();
|
String filetype = file.getName();
|
||||||
if (filetype.endsWith(".gz")) filetype = filetype.substring(0, filetype.length() - 3);
|
|
||||||
int pointIndex = filetype.lastIndexOf('.');
|
int pointIndex = filetype.lastIndexOf('.');
|
||||||
if (pointIndex >= 0) filetype = filetype.substring(pointIndex + 1);
|
if (pointIndex >= 0) filetype = filetype.substring(pointIndex + 1);
|
||||||
|
String contentType = toContentType(filetype);
|
||||||
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;
|
|
||||||
}
|
|
||||||
response.addHeader("Content-Type", contentType);
|
response.addHeader("Content-Type", contentType);
|
||||||
|
|
||||||
|
//send response
|
||||||
try {
|
try {
|
||||||
if (isDeflated){
|
response.setData(new FileInputStream(file));
|
||||||
if (isDeflationPossible || file.length() > INFLATE_MAX_SIZE){
|
return response;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
return new HttpResponse(HttpStatusCode.NOT_FOUND);
|
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 {
|
private static long stringToTimestamp(String timeString) throws IllegalArgumentException {
|
||||||
try {
|
try {
|
||||||
int day = Integer.parseInt(timeString.substring(5, 7));
|
int day = Integer.parseInt(timeString.substring(5, 7));
|
||||||
int month = 1;
|
|
||||||
|
int month = Calendar.JANUARY;
|
||||||
switch (timeString.substring(8, 11)){
|
switch (timeString.substring(8, 11)){
|
||||||
case "Jan" : month = 0; break;
|
case "Jan" : month = Calendar.JANUARY; break;
|
||||||
case "Feb" : month = 1; break;
|
case "Feb" : month = Calendar.FEBRUARY; break;
|
||||||
case "Mar" : month = 2; break;
|
case "Mar" : month = Calendar.MARCH; break;
|
||||||
case "Apr" : month = 3; break;
|
case "Apr" : month = Calendar.APRIL; break;
|
||||||
case "May" : month = 4; break;
|
case "May" : month = Calendar.MAY; break;
|
||||||
case "Jun" : month = 5; break;
|
case "Jun" : month = Calendar.JUNE; break;
|
||||||
case "Jul" : month = 6; break;
|
case "Jul" : month = Calendar.JULY; break;
|
||||||
case "Aug" : month = 7; break;
|
case "Aug" : month = Calendar.AUGUST; break;
|
||||||
case "Sep" : month = 8; break;
|
case "Sep" : month = Calendar.SEPTEMBER; break;
|
||||||
case "Oct" : month = 9; break;
|
case "Oct" : month = Calendar.OCTOBER; break;
|
||||||
case "Nov" : month = 10; break;
|
case "Nov" : month = Calendar.NOVEMBER; break;
|
||||||
case "Dec" : month = 11; break;
|
case "Dec" : month = Calendar.DECEMBER; break;
|
||||||
}
|
}
|
||||||
int year = Integer.parseInt(timeString.substring(12, 16));
|
int year = Integer.parseInt(timeString.substring(12, 16));
|
||||||
int hour = Integer.parseInt(timeString.substring(17, 19));
|
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.Vector2i;
|
||||||
import com.flowpowered.math.vector.Vector3f;
|
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.map.BmMap;
|
||||||
import de.bluecolored.bluemap.core.util.FileUtils;
|
import de.bluecolored.bluemap.core.util.FileUtils;
|
||||||
import de.bluecolored.bluemap.core.util.MathUtils;
|
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-hocon:4.1.1'
|
||||||
api 'org.spongepowered:configurate-gson:4.1.1'
|
api 'org.spongepowered:configurate-gson:4.1.1'
|
||||||
api 'com.github.Querz:NBT:4.0'
|
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'
|
testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2'
|
||||||
}
|
}
|
||||||
|
@ -24,110 +24,76 @@
|
|||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.core.config;
|
package de.bluecolored.bluemap.core.config;
|
||||||
|
|
||||||
|
import org.spongepowered.configurate.ConfigurateException;
|
||||||
import de.bluecolored.bluemap.core.BlueMap;
|
|
||||||
import de.bluecolored.bluemap.core.util.FileUtils;
|
|
||||||
import org.spongepowered.configurate.ConfigurationNode;
|
import org.spongepowered.configurate.ConfigurationNode;
|
||||||
import org.spongepowered.configurate.gson.GsonConfigurationLoader;
|
import org.spongepowered.configurate.gson.GsonConfigurationLoader;
|
||||||
import org.spongepowered.configurate.hocon.HoconConfigurationLoader;
|
import org.spongepowered.configurate.hocon.HoconConfigurationLoader;
|
||||||
import org.spongepowered.configurate.loader.ConfigurationLoader;
|
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.nio.file.Files;
|
||||||
import java.time.LocalDateTime;
|
import java.nio.file.Path;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class ConfigManager {
|
public class ConfigManager {
|
||||||
|
|
||||||
private static final Set<Placeholder> CONFIG_PLACEHOLDERS = new HashSet<>();
|
private static final String[] CONFIG_FILE_ENDINGS = new String[] {
|
||||||
|
".conf",
|
||||||
|
".json"
|
||||||
|
};
|
||||||
|
|
||||||
static {
|
private final Path configRoot;
|
||||||
CONFIG_PLACEHOLDERS.add(new Placeholder("version", BlueMap.VERSION));
|
|
||||||
CONFIG_PLACEHOLDERS.add(new Placeholder("datetime-iso", () -> LocalDateTime.now().withNano(0).toString()));
|
public ConfigManager(Path configRoot) {
|
||||||
|
this.configRoot = configRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public ConfigurationNode loadConfig(Path rawPath) throws ConfigurationException {
|
||||||
* Loads or creates a config file for BlueMap.
|
Path path = findConfigPath(configRoot.resolve(rawPath));
|
||||||
*
|
|
||||||
* @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 {
|
|
||||||
|
|
||||||
ConfigurationNode configNode;
|
if (!Files.exists(path)) {
|
||||||
if (!configFile.exists()) {
|
throw new ConfigurationException(
|
||||||
FileUtils.mkDirsParent(configFile);
|
"BlueMap tried to find this file, but it does not exist:\n" +
|
||||||
|
path);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//populate missing values with default values
|
if (!Files.isReadable(path)) {
|
||||||
if (defaultValues != null) {
|
throw new ConfigurationException(
|
||||||
ConfigurationNode defaultValuesNode = getLoader(defaultValues).load();
|
"BlueMap tried to read this file, but can not access it:\n" +
|
||||||
configNode.mergeFrom(defaultValuesNode);
|
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){
|
public Path getConfigRoot() {
|
||||||
if (url.getFile().endsWith(".json")) return GsonConfigurationLoader.builder().url(url).build();
|
return configRoot;
|
||||||
else return HoconConfigurationLoader.builder().url(url).build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConfigurationLoader<? extends ConfigurationNode> getLoader(File file){
|
private Path findConfigPath(Path rawPath) {
|
||||||
if (file.getName().endsWith(".json")) return GsonConfigurationLoader.builder().file(file).build();
|
for (String fileEnding : CONFIG_FILE_ENDINGS) {
|
||||||
else return HoconConfigurationLoader.builder().file(file).build();
|
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 {
|
private ConfigurationLoader<? extends ConfigurationNode> getLoader(Path path){
|
||||||
Objects.requireNonNull(pathString);
|
if (path.getFileName().endsWith(".json")) return GsonConfigurationLoader.builder().path(path).build();
|
||||||
|
else return HoconConfigurationLoader.builder().path(path).build();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* 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 de.bluecolored.bluemap.core.debug.DebugDump;
|
||||||
import org.spongepowered.configurate.ConfigurationNode;
|
import org.spongepowered.configurate.ConfigurationNode;
|
||||||
|
|
||||||
@ -38,7 +39,7 @@ public class CoreConfig {
|
|||||||
private boolean metricsEnabled = false;
|
private boolean metricsEnabled = false;
|
||||||
private File dataFolder = new File("data");
|
private File dataFolder = new File("data");
|
||||||
|
|
||||||
public CoreConfig(ConfigurationNode node) throws IOException {
|
public CoreConfig(ConfigurationNode node) throws ConfigurationException {
|
||||||
|
|
||||||
//accept-download
|
//accept-download
|
||||||
downloadAccepted = node.node("accept-download").getBoolean(false);
|
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
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* 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.Vector2i;
|
||||||
import com.flowpowered.math.vector.Vector3i;
|
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.debug.DebugDump;
|
||||||
import de.bluecolored.bluemap.core.map.MapSettings;
|
import de.bluecolored.bluemap.core.map.MapSettings;
|
||||||
import de.bluecolored.bluemap.core.storage.Compression;
|
|
||||||
import de.bluecolored.bluemap.core.util.ConfigUtils;
|
import de.bluecolored.bluemap.core.util.ConfigUtils;
|
||||||
import org.spongepowered.configurate.ConfigurationNode;
|
import org.spongepowered.configurate.ConfigurationNode;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@DebugDump
|
@DebugDump
|
||||||
@ -54,7 +53,7 @@ public class MapConfig implements MapSettings {
|
|||||||
private Vector3i min, max;
|
private Vector3i min, max;
|
||||||
private boolean renderEdges;
|
private boolean renderEdges;
|
||||||
|
|
||||||
private Compression compression;
|
private String storage;
|
||||||
private boolean ignoreMissingLightData;
|
private boolean ignoreMissingLightData;
|
||||||
|
|
||||||
private int hiresTileSize;
|
private int hiresTileSize;
|
||||||
@ -62,19 +61,19 @@ public class MapConfig implements MapSettings {
|
|||||||
private int lowresPointsPerHiresTile;
|
private int lowresPointsPerHiresTile;
|
||||||
private int lowresPointsPerLowresTile;
|
private int lowresPointsPerLowresTile;
|
||||||
|
|
||||||
public MapConfig(ConfigurationNode node) throws IOException {
|
public MapConfig(ConfigurationNode node) throws ConfigurationException {
|
||||||
|
|
||||||
//id
|
//id
|
||||||
this.id = node.node("id").getString("");
|
this.id = node.node("id").getString("");
|
||||||
if (id.isEmpty()) throw new IOException("Invalid configuration: Node maps[?].id is not defined");
|
if (id.isEmpty()) throw new ConfigurationException("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 (!VALID_ID_PATTERN.matcher(id).matches()) throw new ConfigurationException("Invalid configuration: Node maps[?].id '" + id + "' has invalid characters in it");
|
||||||
|
|
||||||
//name
|
//name
|
||||||
this.name = node.node("name").getString(id);
|
this.name = node.node("name").getString(id);
|
||||||
|
|
||||||
//world
|
//world
|
||||||
this.world = node.node("world").getString("");
|
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
|
//startPos
|
||||||
if (!node.node("startPos").virtual()) this.startPos = ConfigUtils.readVector2i(node.node("startPos"));
|
if (!node.node("startPos").virtual()) this.startPos = ConfigUtils.readVector2i(node.node("startPos"));
|
||||||
@ -106,8 +105,8 @@ public MapConfig(ConfigurationNode node) throws IOException {
|
|||||||
//renderEdges
|
//renderEdges
|
||||||
this.renderEdges = node.node("renderEdges").getBoolean(true);
|
this.renderEdges = node.node("renderEdges").getBoolean(true);
|
||||||
|
|
||||||
//useCompression
|
//storage
|
||||||
this.compression = node.node("useCompression").getBoolean(true) ? Compression.GZIP : Compression.NONE;
|
this.storage = node.node("storage").getString("file");
|
||||||
|
|
||||||
//ignoreMissingLightData
|
//ignoreMissingLightData
|
||||||
this.ignoreMissingLightData = node.node("ignoreMissingLightData").getBoolean(false);
|
this.ignoreMissingLightData = node.node("ignoreMissingLightData").getBoolean(false);
|
||||||
@ -119,7 +118,7 @@ public MapConfig(ConfigurationNode node) throws IOException {
|
|||||||
|
|
||||||
//check valid tile configuration values
|
//check valid tile configuration values
|
||||||
double blocksPerPoint = (double) this.hiresTileSize / (double) this.lowresPointsPerHiresTile;
|
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;
|
return skyColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Compression getCompression() {
|
public String getStorage() {
|
||||||
return compression;
|
return storage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
@ -22,7 +22,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.core.config;
|
package de.bluecolored.bluemap.core.config.old;
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
@ -22,8 +22,9 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* 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 de.bluecolored.bluemap.core.debug.DebugDump;
|
||||||
import org.spongepowered.configurate.ConfigurationNode;
|
import org.spongepowered.configurate.ConfigurationNode;
|
||||||
|
|
||||||
@ -40,11 +41,11 @@ public class RenderConfig {
|
|||||||
private boolean enableFreeFlight;
|
private boolean enableFreeFlight;
|
||||||
private List<MapConfig> mapConfigs;
|
private List<MapConfig> mapConfigs;
|
||||||
|
|
||||||
public RenderConfig(ConfigurationNode node) throws IOException {
|
public RenderConfig(ConfigurationNode node) throws ConfigurationException {
|
||||||
|
|
||||||
//webroot
|
//webroot
|
||||||
String webRootString = node.node("webroot").getString();
|
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);
|
webRoot = ConfigManager.toFolder(webRootString);
|
||||||
|
|
||||||
//cookies
|
//cookies
|
@ -22,8 +22,9 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* 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 de.bluecolored.bluemap.core.debug.DebugDump;
|
||||||
import org.spongepowered.configurate.ConfigurationNode;
|
import org.spongepowered.configurate.ConfigurationNode;
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ public class WebServerConfig {
|
|||||||
private int port = 8100;
|
private int port = 8100;
|
||||||
private int maxConnections = 100;
|
private int maxConnections = 100;
|
||||||
|
|
||||||
public WebServerConfig(ConfigurationNode node) throws IOException {
|
public WebServerConfig(ConfigurationNode node) throws ConfigurationException {
|
||||||
|
|
||||||
//enabled
|
//enabled
|
||||||
enabled = node.node("enabled").getBoolean(false);
|
enabled = node.node("enabled").getBoolean(false);
|
||||||
@ -50,17 +51,22 @@ public WebServerConfig(ConfigurationNode node) throws IOException {
|
|||||||
if (enabled) {
|
if (enabled) {
|
||||||
//webroot
|
//webroot
|
||||||
String webRootString = node.node("webroot").getString();
|
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);
|
webRoot = ConfigManager.toFolder(webRootString);
|
||||||
|
|
||||||
//ip
|
//ip
|
||||||
String bindAddressString = node.node("ip").getString("");
|
String bindAddressString = node.node("ip").getString("");
|
||||||
if (bindAddressString.isEmpty() || bindAddressString.equals("0.0.0.0") || bindAddressString.equals("::0")) {
|
try {
|
||||||
bindAddress = new InetSocketAddress(0).getAddress(); // 0.0.0.0
|
if (bindAddressString.isEmpty() || bindAddressString.equals("0.0.0.0") ||
|
||||||
} else if (bindAddressString.equals("#getLocalHost")) {
|
bindAddressString.equals("::0")) {
|
||||||
bindAddress = InetAddress.getLocalHost();
|
bindAddress = new InetSocketAddress(0).getAddress(); // 0.0.0.0
|
||||||
} else {
|
} else if (bindAddressString.equals("#getLocalHost")) {
|
||||||
bindAddress = InetAddress.getByName(bindAddressString);
|
bindAddress = InetAddress.getLocalHost();
|
||||||
|
} else {
|
||||||
|
bindAddress = InetAddress.getByName(bindAddressString);
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new ConfigurationException("Failed to parse ip: '" + bindAddressString + "'", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
//port
|
//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
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.core.util;
|
package de.bluecolored.bluemap.core.config.storage;
|
||||||
|
|
||||||
@FunctionalInterface
|
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||||
public interface ThrowingRunnable<E extends Throwable> {
|
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.hires.HiresTileMeta;
|
||||||
import de.bluecolored.bluemap.core.map.lowres.LowresModelManager;
|
import de.bluecolored.bluemap.core.map.lowres.LowresModelManager;
|
||||||
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
|
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.Grid;
|
||||||
import de.bluecolored.bluemap.core.world.World;
|
import de.bluecolored.bluemap.core.world.World;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
@ -72,9 +74,9 @@ public BmMap(String id, String name, World world, Storage storage, ResourcePack
|
|||||||
|
|
||||||
this.renderState = new MapRenderState();
|
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()) {
|
if (rstateData.isPresent()) {
|
||||||
try (InputStream in = rstateData.get()){
|
try (InputStream in = rstateData.get().decompress()){
|
||||||
this.renderState.load(in);
|
this.renderState.load(in);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
Logger.global.logWarning("Failed to load render-state for map '" + getId() + "': " + ex);
|
Logger.global.logWarning("Failed to load render-state for map '" + getId() + "': " + ex);
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
import com.flowpowered.math.vector.Vector3f;
|
import com.flowpowered.math.vector.Vector3f;
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
import de.bluecolored.bluemap.core.map.hires.HiresTileMeta;
|
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.storage.Storage;
|
||||||
import de.bluecolored.bluemap.core.threejs.BufferGeometry;
|
import de.bluecolored.bluemap.core.threejs.BufferGeometry;
|
||||||
import de.bluecolored.bluemap.core.util.math.Color;
|
import de.bluecolored.bluemap.core.util.math.Color;
|
||||||
@ -163,9 +164,9 @@ private LowresModel getModel(Vector2i tile) {
|
|||||||
if (model == null){
|
if (model == null){
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Optional<InputStream> optIs = storage.read(tile);
|
Optional<CompressedInputStream> optIs = storage.read(tile);
|
||||||
if (optIs.isPresent()){
|
if (optIs.isPresent()){
|
||||||
try (InputStream is = optIs.get()) {
|
try (InputStream is = optIs.get().decompress()) {
|
||||||
String json = IOUtils.toString(is, StandardCharsets.UTF_8);
|
String json = IOUtils.toString(is, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
model = new CachedModel(BufferGeometry.fromJson(json));
|
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.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
import java.util.zip.GZIPOutputStream;
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
|
||||||
public enum Compression {
|
public enum Compression {
|
||||||
|
|
||||||
NONE("") {
|
NONE("none", "", out -> out, in -> in),
|
||||||
@Override
|
GZIP("gzip", ".gz", GZIPOutputStream::new, GZIPInputStream::new);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
private final String typeId;
|
||||||
private final String fileSuffix;
|
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.fileSuffix = fileSuffix;
|
||||||
|
this.typeId = typeId;
|
||||||
|
this.compressor = compressor;
|
||||||
|
this.decompressor = decompressor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTypeId() {
|
||||||
|
return typeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFileSuffix() {
|
public String getFileSuffix() {
|
||||||
return fileSuffix;
|
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 {
|
public enum MetaType {
|
||||||
|
|
||||||
TEXTURES,
|
//TEXTURES ("textures", "textures.json", "application/json"),
|
||||||
SETTINGS,
|
//SETTINGS ("settings", "settings.json", "application/json"),
|
||||||
MARKERS,
|
//MARKERS ("markers", "markers.json", "application/json"),
|
||||||
RENDER_STATE
|
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 com.flowpowered.math.vector.Vector2i;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.Optional;
|
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 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 void deleteMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException;
|
||||||
|
|
||||||
public abstract OutputStream writeMeta(String mapId, MetaType metaType) 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;
|
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 {
|
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 {
|
public Optional<CompressedInputStream> read(Vector2i tile) throws IOException {
|
||||||
return Storage.this.readMapTile(mapId, tileType, tile);
|
return readMapTile(mapId, tileType, tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void delete(Vector2i tile) throws IOException {
|
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
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* 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;
|
package de.bluecolored.bluemap.core.storage;
|
||||||
|
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
public enum TileType {
|
public enum TileType {
|
||||||
|
|
||||||
HIRES ("hires"),
|
HIRES ("hires"),
|
||||||
@ -38,4 +40,13 @@ public enum TileType {
|
|||||||
public String getTypeId() {
|
public String getTypeId() {
|
||||||
return typeId;
|
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
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.core.storage;
|
package de.bluecolored.bluemap.core.storage.file;
|
||||||
|
|
||||||
import com.flowpowered.math.vector.Vector2i;
|
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.debug.DebugDump;
|
||||||
|
import de.bluecolored.bluemap.core.storage.*;
|
||||||
import de.bluecolored.bluemap.core.util.AtomicFileHelper;
|
import de.bluecolored.bluemap.core.util.AtomicFileHelper;
|
||||||
import de.bluecolored.bluemap.core.util.FileUtils;
|
import de.bluecolored.bluemap.core.util.FileUtils;
|
||||||
|
|
||||||
@ -38,22 +40,25 @@
|
|||||||
@DebugDump
|
@DebugDump
|
||||||
public class FileStorage extends Storage {
|
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 Path root;
|
||||||
private final Compression compression;
|
private final Compression compression;
|
||||||
|
|
||||||
|
public FileStorage(FileConfig config) {
|
||||||
|
this.root = config.getRoot();
|
||||||
|
this.compression = config.getCompression();
|
||||||
|
}
|
||||||
|
|
||||||
public FileStorage(Path root, Compression compression) {
|
public FileStorage(Path root, Compression compression) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.compression = compression;
|
this.compression = compression;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OutputStream writeMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException {
|
public OutputStream writeMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException {
|
||||||
Path file = getFilePath(mapId, tileType, tile);
|
Path file = getFilePath(mapId, tileType, tile);
|
||||||
@ -66,16 +71,48 @@ public OutputStream writeMapTile(String mapId, TileType tileType, Vector2i tile)
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
Path file = getFilePath(mapId, tileType, tile);
|
||||||
|
|
||||||
if (!Files.exists(file)) return Optional.empty();
|
if (!Files.exists(file)) return Optional.empty();
|
||||||
|
|
||||||
InputStream is = Files.newInputStream(file, StandardOpenOption.READ);
|
InputStream is = Files.newInputStream(file, StandardOpenOption.READ);
|
||||||
is = new BufferedInputStream(is);
|
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
|
@Override
|
||||||
@ -86,7 +123,7 @@ public void deleteMapTile(String mapId, TileType tileType, Vector2i tile) throws
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OutputStream writeMeta(String mapId, MetaType metaType) throws IOException {
|
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);
|
OutputStream os = AtomicFileHelper.createFilepartOutputStream(file);
|
||||||
os = new BufferedOutputStream(os);
|
os = new BufferedOutputStream(os);
|
||||||
@ -95,15 +132,21 @@ public OutputStream writeMeta(String mapId, MetaType metaType) throws IOExceptio
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<InputStream> readMeta(String mapId, MetaType metaType) throws IOException {
|
public Optional<CompressedInputStream> readMeta(String mapId, MetaType metaType) throws IOException {
|
||||||
Path file = getFilePath(mapId).resolve(getFilename(metaType));
|
Path file = getFilePath(mapId).resolve(metaType.getFilePath());
|
||||||
|
|
||||||
if (!Files.exists(file)) return Optional.empty();
|
if (!Files.exists(file)) return Optional.empty();
|
||||||
|
|
||||||
InputStream is = Files.newInputStream(file, StandardOpenOption.READ);
|
InputStream is = Files.newInputStream(file, StandardOpenOption.READ);
|
||||||
is = new BufferedInputStream(is);
|
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
|
@Override
|
||||||
@ -137,8 +180,4 @@ public Path getFilePath(String mapId) {
|
|||||||
return root.resolve(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");
|
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
|
//just slow down processing if limit is reached
|
||||||
hasPermit = processingSemaphore.tryAcquire(1, TimeUnit.SECONDS);
|
hasPermit = processingSemaphore.tryAcquire(1, TimeUnit.SECONDS);
|
||||||
|
|
||||||
HttpResponse response = handler.handle(request);
|
try (HttpResponse response = handler.handle(request)) {
|
||||||
sendResponse(response);
|
sendResponse(response);
|
||||||
|
|
||||||
if (verbose) log(request, response);
|
if (verbose) log(request, response);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (hasPermit) processingSemaphore.release();
|
if (hasPermit) processingSemaphore.release();
|
||||||
}
|
}
|
||||||
|
@ -91,13 +91,12 @@ public void write(OutputStream out) throws IOException {
|
|||||||
if(data != null){
|
if(data != null){
|
||||||
chunkedPipe(data, out);
|
chunkedPipe(data, out);
|
||||||
out.flush();
|
out.flush();
|
||||||
data.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
data.close();
|
if (data != null) data.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeLine(OutputStreamWriter writer, String line) throws IOException {
|
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.RenderManager;
|
||||||
import de.bluecolored.bluemap.common.rendermanager.RenderTask;
|
import de.bluecolored.bluemap.common.rendermanager.RenderTask;
|
||||||
import de.bluecolored.bluemap.common.web.FileRequestHandler;
|
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.BlueMap;
|
||||||
import de.bluecolored.bluemap.core.MinecraftVersion;
|
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.Logger;
|
||||||
import de.bluecolored.bluemap.core.logger.LoggerLogger;
|
import de.bluecolored.bluemap.core.logger.LoggerLogger;
|
||||||
import de.bluecolored.bluemap.core.map.BmMap;
|
import de.bluecolored.bluemap.core.map.BmMap;
|
||||||
import de.bluecolored.bluemap.core.metrics.Metrics;
|
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.util.FileUtils;
|
||||||
import de.bluecolored.bluemap.core.webserver.HttpRequestHandler;
|
import de.bluecolored.bluemap.core.webserver.HttpRequestHandler;
|
||||||
import de.bluecolored.bluemap.core.webserver.WebServer;
|
import de.bluecolored.bluemap.core.webserver.WebServer;
|
||||||
@ -51,7 +54,7 @@
|
|||||||
|
|
||||||
public class BlueMapCLI {
|
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
|
//metrics report
|
||||||
if (blueMap.getCoreConfig().isMetricsEnabled()) Metrics.sendReportAsync("cli");
|
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 ...");
|
Logger.global.logInfo("Starting webserver ...");
|
||||||
|
|
||||||
WebServerConfig config = blueMap.getWebServerConfig();
|
WebServerConfig config = blueMap.getWebServerConfig();
|
||||||
FileUtils.mkDirs(config.getWebRoot());
|
FileUtils.mkDirs(config.getWebRoot());
|
||||||
HttpRequestHandler requestHandler = new FileRequestHandler(config.getWebRoot().toPath(), "BlueMap v" + BlueMap.VERSION);
|
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(
|
WebServer webServer = new WebServer(
|
||||||
config.getWebserverBindAddress(),
|
config.getWebserverBindAddress(),
|
||||||
config.getWebserverPort(),
|
config.getWebserverPort(),
|
||||||
@ -265,6 +279,7 @@ public static void main(String[] args) {
|
|||||||
blueMap.getCoreConfig();
|
blueMap.getCoreConfig();
|
||||||
blueMap.getRenderConfig();
|
blueMap.getRenderConfig();
|
||||||
blueMap.getWebServerConfig();
|
blueMap.getWebServerConfig();
|
||||||
|
blueMap.getMapStorages();
|
||||||
|
|
||||||
//create resourcepacks folder
|
//create resourcepacks folder
|
||||||
FileUtils.mkDirs(new File(configFolder, "resourcepacks"));
|
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);
|
Logger.global.logError("Failed to parse provided arguments!", e);
|
||||||
BlueMapCLI.printHelp();
|
BlueMapCLI.printHelp();
|
||||||
System.exit(1);
|
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) {
|
} catch (IOException e) {
|
||||||
Logger.global.logError("An IO-error occurred!", e);
|
Logger.global.logError("An IO-error occurred!", e);
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
|
@ -89,13 +89,10 @@ maps: [
|
|||||||
# Default is true
|
# Default is true
|
||||||
renderEdges: true
|
renderEdges: true
|
||||||
|
|
||||||
# With this set to true, the generated files for this world are compressed using gzip to save A LOT of space.
|
# This defines the storage-config that will be used to save this map.
|
||||||
# Files will be only 5% as big with compression!
|
# You can find your storage configs next to this config file in the 'storages'-folder.
|
||||||
# Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly.
|
# Default is "file"
|
||||||
# This is much better than disabling the compression.
|
storage: "file"
|
||||||
# Changing this value requires a re-render of the map.
|
|
||||||
# Default is true
|
|
||||||
useCompression: true
|
|
||||||
|
|
||||||
# Normally BlueMap detects if a chunk has not yet generated it's light-data and omits rendering those chunks.
|
# 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!
|
# 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
|
# Default is true
|
||||||
renderEdges: true
|
renderEdges: true
|
||||||
|
|
||||||
# With this set to true, the generated files for this world are compressed using gzip to save A LOT of space.
|
# This defines the storage-config that will be used to save this map.
|
||||||
# Files will be only 5% as big with compression!
|
# You can find your storage configs next to this config file in the 'storages'-folder.
|
||||||
# Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly.
|
# Default is "file"
|
||||||
# This is much better than disabling the compression.
|
storage: "file"
|
||||||
# Changing this value requires a re-render of the map.
|
|
||||||
# Default is true
|
|
||||||
useCompression: true
|
|
||||||
|
|
||||||
# Normally BlueMap detects if a chunk has not yet generated it's light-data and omits rendering those chunks.
|
# 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!
|
# 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
|
# Default is true
|
||||||
renderEdges: true
|
renderEdges: true
|
||||||
|
|
||||||
# With this set to true, the generated files for this world are compressed using gzip to save A LOT of space.
|
# This defines the storage-config that will be used to save this map.
|
||||||
# Files will be only 5% as big with compression!
|
# You can find your storage configs next to this config file in the 'storages'-folder.
|
||||||
# Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly.
|
# Default is "file"
|
||||||
# This is much better than disabling the compression.
|
storage: "file"
|
||||||
# Changing this value requires a re-render of the map.
|
|
||||||
# Default is true
|
|
||||||
useCompression: true
|
|
||||||
|
|
||||||
# Normally BlueMap detects if a chunk has not yet generated it's light-data and omits rendering those chunks.
|
# 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!
|
# 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
|
# Default is true
|
||||||
renderEdges: true
|
renderEdges: true
|
||||||
|
|
||||||
# With this set to true, the generated files for this world are compressed using gzip to save A LOT of space.
|
# This defines the storage-config that will be used to save this map.
|
||||||
# Files will be only 5% as big with compression!
|
# You can find your storage configs next to this config file in the 'storages'-folder.
|
||||||
# Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly.
|
# Default is "file"
|
||||||
# This is much better than disabling the compression.
|
storage: "file"
|
||||||
# Changing this value requires a re-render of the map.
|
|
||||||
# Default is true
|
|
||||||
useCompression: true
|
|
||||||
|
|
||||||
# Normally BlueMap detects if a chunk has not yet generated it's light-data and omits rendering those chunks.
|
# 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!
|
# 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
|
# Default is true
|
||||||
renderEdges: true
|
renderEdges: true
|
||||||
|
|
||||||
# With this set to true, the generated files for this world are compressed using gzip to save A LOT of space.
|
# This defines the storage-config that will be used to save this map.
|
||||||
# Files will be only 5% as big with compression!
|
# You can find your storage configs next to this config file in the 'storages'-folder.
|
||||||
# Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly.
|
# Default is "file"
|
||||||
# This is much better than disabling the compression.
|
storage: "file"
|
||||||
# Changing this value requires a re-render of the map.
|
|
||||||
# Default is true
|
|
||||||
useCompression: true
|
|
||||||
|
|
||||||
# Normally BlueMap detects if a chunk has not yet generated it's light-data and omits rendering those chunks.
|
# 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!
|
# 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
|
# Default is true
|
||||||
renderEdges: true
|
renderEdges: true
|
||||||
|
|
||||||
# With this set to true, the generated files for this world are compressed using gzip to save A LOT of space.
|
# This defines the storage-config that will be used to save this map.
|
||||||
# Files will be only 5% as big with compression!
|
# You can find your storage configs next to this config file in the 'storages'-folder.
|
||||||
# Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly.
|
# Default is "file"
|
||||||
# This is much better than disabling the compression.
|
storage: "file"
|
||||||
# Changing this value requires a re-render of the map.
|
|
||||||
# Default is true
|
|
||||||
useCompression: true
|
|
||||||
|
|
||||||
# Normally BlueMap detects if a chunk has not yet generated it's light-data and omits rendering those chunks.
|
# 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!
|
# 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
|
# Default is true
|
||||||
renderEdges: true
|
renderEdges: true
|
||||||
|
|
||||||
# With this set to true, the generated files for this world are compressed using gzip to save A LOT of space.
|
# This defines the storage-config that will be used to save this map.
|
||||||
# Files will be only 5% as big with compression!
|
# You can find your storage configs next to this config file in the 'storages'-folder.
|
||||||
# Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly.
|
# Default is "file"
|
||||||
# This is much better than disabling the compression.
|
storage: "file"
|
||||||
# Changing this value requires a re-render of the map.
|
|
||||||
# Default is true
|
|
||||||
useCompression: true
|
|
||||||
|
|
||||||
# Normally BlueMap detects if a chunk has not yet generated it's light-data and omits rendering those chunks.
|
# 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!
|
# 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
|
# Default is true
|
||||||
renderEdges: true
|
renderEdges: true
|
||||||
|
|
||||||
# With this set to true, the generated files for this world are compressed using gzip to save A LOT of space.
|
# This defines the storage-config that will be used to save this map.
|
||||||
# Files will be only 5% as big with compression!
|
# You can find your storage configs next to this config file in the 'storages'-folder.
|
||||||
# Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly.
|
# Default is "file"
|
||||||
# This is much better than disabling the compression.
|
storage: "file"
|
||||||
# Changing this value requires a re-render of the map.
|
|
||||||
# Default is true
|
|
||||||
useCompression: true
|
|
||||||
|
|
||||||
# Normally BlueMap detects if a chunk has not yet generated it's light-data and omits rendering those chunks.
|
# 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!
|
# 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