mirror of
https://github.com/BlueMap-Minecraft/BlueMap.git
synced 2024-11-22 02:26:00 +01:00
Rework storages to make them extensible with addons
This commit is contained in:
parent
7e7b1e4f53
commit
fdf242acdf
@ -244,7 +244,7 @@ private synchronized void loadMap(String id, MapConfig mapConfig) throws Configu
|
||||
id,
|
||||
name,
|
||||
world,
|
||||
storage,
|
||||
storage.map(id),
|
||||
getOrLoadResourcePack(),
|
||||
mapConfig
|
||||
);
|
||||
|
@ -25,7 +25,8 @@
|
||||
package de.bluecolored.bluemap.common.api;
|
||||
|
||||
import de.bluecolored.bluemap.api.AssetStorage;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.storage.MapStorage;
|
||||
import de.bluecolored.bluemap.core.storage.compression.CompressedInputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@ -34,39 +35,39 @@
|
||||
|
||||
public class AssetStorageImpl implements AssetStorage {
|
||||
|
||||
private static final String ASSET_PATH = "assets/";
|
||||
|
||||
private final Storage storage;
|
||||
private final MapStorage storage;
|
||||
private final String mapId;
|
||||
|
||||
public AssetStorageImpl(Storage storage, String mapId) {
|
||||
public AssetStorageImpl(MapStorage storage, String mapId) {
|
||||
this.storage = storage;
|
||||
this.mapId = mapId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream writeAsset(String name) throws IOException {
|
||||
return storage.writeMeta(mapId, ASSET_PATH + name);
|
||||
return storage.asset(name).write();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<InputStream> readAsset(String name) throws IOException {
|
||||
return storage.readMeta(mapId, ASSET_PATH + name);
|
||||
CompressedInputStream in = storage.asset(name).read();
|
||||
if (in == null) return Optional.empty();
|
||||
return Optional.of(in.decompress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean assetExists(String name) throws IOException {
|
||||
return storage.readMetaInfo(mapId, ASSET_PATH + name).isPresent();
|
||||
return storage.asset(name).exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAssetUrl(String name) {
|
||||
return "maps/" + mapId + "/" + Storage.escapeMetaName(ASSET_PATH + name);
|
||||
return "maps/" + mapId + "/assets/" + MapStorage.escapeAssetName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAsset(String name) throws IOException {
|
||||
storage.deleteMeta(mapId, ASSET_PATH + name);
|
||||
storage.asset(name).delete();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -81,6 +81,7 @@ public void registerStyle(String url) {
|
||||
|
||||
@Override
|
||||
@Deprecated(forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public String createImage(BufferedImage image, String path) throws IOException {
|
||||
path = path.replaceAll("[^a-zA-Z0-9_.\\-/]", "_");
|
||||
|
||||
@ -102,6 +103,7 @@ public String createImage(BufferedImage image, String path) throws IOException {
|
||||
|
||||
@Override
|
||||
@Deprecated(forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public Map<String, String> availableImages() throws IOException {
|
||||
Path webRoot = getWebRoot().toAbsolutePath();
|
||||
String separator = webRoot.getFileSystem().getSeparator();
|
||||
|
@ -269,18 +269,11 @@ private Map<String, MapConfig> loadMapConfigs(Collection<ServerWorld> autoConfig
|
||||
String name = worldFolder.getFileName() + " (" + dimensionName + ")";
|
||||
if (i > 1) name = name + " (" + i + ")";
|
||||
|
||||
ConfigTemplate template;
|
||||
switch (world.getDimension().getFormatted()) {
|
||||
case "minecraft:the_nether":
|
||||
template = createNetherMapTemplate(name, worldFolder, dimension, i - 1);
|
||||
break;
|
||||
case "minecraft:the_end":
|
||||
template = createEndMapTemplate(name, worldFolder, dimension, i - 1);
|
||||
break;
|
||||
default:
|
||||
template = createOverworldMapTemplate(name, worldFolder, dimension, i - 1);
|
||||
break;
|
||||
}
|
||||
ConfigTemplate template = switch (world.getDimension().getFormatted()) {
|
||||
case "minecraft:the_nether" -> createNetherMapTemplate(name, worldFolder, dimension, i - 1);
|
||||
case "minecraft:the_end" -> createEndMapTemplate(name, worldFolder, dimension, i - 1);
|
||||
default -> createOverworldMapTemplate(name, worldFolder, dimension, i - 1);
|
||||
};
|
||||
|
||||
Files.writeString(
|
||||
configFile,
|
||||
@ -358,7 +351,7 @@ private Map<String, StorageConfig> loadStorageConfigs(Path defaultWebroot) throw
|
||||
Path rawConfig = configManager.getRaw(configFile);
|
||||
String id = rawConfig.getFileName().toString();
|
||||
|
||||
StorageConfig storageConfig = configManager.loadConfig(rawConfig, StorageConfig.class); // load superclass
|
||||
StorageConfig storageConfig = configManager.loadConfig(rawConfig, StorageConfig.Base.class); // load superclass
|
||||
storageConfig = configManager.loadConfig(rawConfig, storageConfig.getStorageType().getConfigType()); // load actual config type
|
||||
|
||||
storageConfigs.put(id, storageConfig);
|
||||
|
@ -0,0 +1,47 @@
|
||||
package de.bluecolored.bluemap.common.config.storage;
|
||||
|
||||
import de.bluecolored.bluemap.core.storage.sql.Database;
|
||||
import de.bluecolored.bluemap.core.storage.sql.commandset.CommandSet;
|
||||
import de.bluecolored.bluemap.core.storage.sql.commandset.MySQLCommandSet;
|
||||
import de.bluecolored.bluemap.core.storage.sql.commandset.PostgreSQLCommandSet;
|
||||
import de.bluecolored.bluemap.core.storage.sql.commandset.SqliteCommandSet;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import de.bluecolored.bluemap.core.util.Keyed;
|
||||
import de.bluecolored.bluemap.core.util.Registry;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface Dialect extends Keyed {
|
||||
|
||||
Dialect MYSQL = new Impl(Key.bluemap("mysql"), MySQLCommandSet::new);
|
||||
Dialect MARIADB = new Impl(Key.bluemap("mariadb"), MySQLCommandSet::new);
|
||||
Dialect POSTGRESQL = new Impl(Key.bluemap("postgresql"), PostgreSQLCommandSet::new);
|
||||
Dialect SQLITE = new Impl(Key.bluemap("sqlite"), SqliteCommandSet::new);
|
||||
|
||||
Registry<Dialect> REGISTRY = new Registry<>(
|
||||
MYSQL,
|
||||
MARIADB,
|
||||
POSTGRESQL,
|
||||
SQLITE
|
||||
);
|
||||
|
||||
CommandSet createCommandSet(Database database);
|
||||
|
||||
@RequiredArgsConstructor
|
||||
class Impl implements Dialect {
|
||||
|
||||
@Getter
|
||||
private final Key key;
|
||||
|
||||
private final Function<Database, CommandSet> commandSetProvider;
|
||||
|
||||
@Override
|
||||
public CommandSet createCommandSet(Database database) {
|
||||
return commandSetProvider.apply(database);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -25,8 +25,10 @@
|
||||
package de.bluecolored.bluemap.common.config.storage;
|
||||
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.storage.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.file.FileStorageSettings;
|
||||
import de.bluecolored.bluemap.common.config.ConfigurationException;
|
||||
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.file.FileStorage;
|
||||
import lombok.Getter;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
@ -34,20 +36,19 @@
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
@DebugDump
|
||||
@ConfigSerializable
|
||||
public class FileConfig extends StorageConfig implements FileStorageSettings {
|
||||
@Getter
|
||||
public class FileConfig extends StorageConfig {
|
||||
|
||||
private Path root = Path.of("bluemap", "web", "maps");
|
||||
private String compression = Compression.GZIP.getKey().getFormatted();
|
||||
|
||||
private Compression compression = Compression.GZIP;
|
||||
|
||||
@Override
|
||||
public Path getRoot() {
|
||||
return root;
|
||||
public Compression getCompression() throws ConfigurationException {
|
||||
return parseKey(Compression.REGISTRY, compression, "compression");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Compression getCompression() {
|
||||
return compression;
|
||||
public FileStorage createStorage() throws ConfigurationException {
|
||||
return new FileStorage(root, getCompression());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,67 +25,146 @@
|
||||
package de.bluecolored.bluemap.common.config.storage;
|
||||
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.storage.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.sql.SQLStorageSettings;
|
||||
import de.bluecolored.bluemap.common.config.ConfigurationException;
|
||||
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.sql.Database;
|
||||
import de.bluecolored.bluemap.core.storage.sql.SQLStorage;
|
||||
import de.bluecolored.bluemap.core.storage.sql.commandset.CommandSet;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.Driver;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
|
||||
@ConfigSerializable
|
||||
public class SQLConfig extends StorageConfig implements SQLStorageSettings {
|
||||
@Getter
|
||||
public class SQLConfig extends StorageConfig {
|
||||
|
||||
private static final Pattern URL_DIALECT_PATTERN = Pattern.compile("jdbc:([^:]*)://.*");
|
||||
|
||||
private String connectionUrl = "jdbc:mysql://localhost/bluemap?permitMysqlScheme";
|
||||
private Map<String, String> connectionProperties = new HashMap<>();
|
||||
@DebugDump private String dialect = null;
|
||||
|
||||
@DebugDump private String driverJar = null;
|
||||
@DebugDump private String driverClass = null;
|
||||
private String connectionUrl = "jdbc:mysql://localhost/bluemap?permitMysqlScheme";
|
||||
|
||||
private Map<String, String> connectionProperties = new HashMap<>();
|
||||
|
||||
@DebugDump private Compression compression = Compression.GZIP;
|
||||
|
||||
@DebugDump private transient URL driverJarURL = null;
|
||||
|
||||
@DebugDump private int maxConnections = -1;
|
||||
|
||||
@Override
|
||||
public Optional<URL> getDriverJar() throws MalformedURLException {
|
||||
if (driverJar == null) return Optional.empty();
|
||||
@DebugDump private String compression = Compression.GZIP.getKey().getFormatted();
|
||||
|
||||
if (driverJarURL == null) {
|
||||
driverJarURL = Paths.get(driverJar).toUri().toURL();
|
||||
@DebugDump
|
||||
@Getter(AccessLevel.NONE)
|
||||
private transient URL driverJarURL = null;
|
||||
|
||||
public Optional<URL> getDriverJar() throws ConfigurationException {
|
||||
try {
|
||||
if (driverJar == null) return Optional.empty();
|
||||
|
||||
if (driverJarURL == null) {
|
||||
driverJarURL = Paths.get(driverJar).toUri().toURL();
|
||||
}
|
||||
|
||||
return Optional.of(driverJarURL);
|
||||
} catch (MalformedURLException ex) {
|
||||
throw new ConfigurationException("""
|
||||
The configured driver-jar path is not formatted correctly!
|
||||
Please check your 'driver-jar' setting in your configuration and make sure you have the correct path configured.
|
||||
""".strip(), ex);
|
||||
}
|
||||
|
||||
return Optional.of(driverJarURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unused")
|
||||
public Optional<String> getDriverClass() {
|
||||
return Optional.ofNullable(driverClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConnectionUrl() {
|
||||
return connectionUrl;
|
||||
public Compression getCompression() throws ConfigurationException {
|
||||
return parseKey(Compression.REGISTRY, compression, "compression");
|
||||
}
|
||||
|
||||
public Dialect getDialect() throws ConfigurationException {
|
||||
String key = dialect;
|
||||
|
||||
// default from connection-url
|
||||
if (key == null) {
|
||||
Matcher matcher = URL_DIALECT_PATTERN.matcher(connectionUrl);
|
||||
if (!matcher.find()) return Dialect.MYSQL;
|
||||
key = Key.bluemap(matcher.group(1)).getFormatted();
|
||||
}
|
||||
|
||||
return parseKey(Dialect.REGISTRY, key, "dialect");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getConnectionProperties() {
|
||||
return connectionProperties;
|
||||
public SQLStorage createStorage() throws ConfigurationException {
|
||||
Driver driver = createDriver();
|
||||
Database database;
|
||||
if (driver != null) {
|
||||
database = new Database(getConnectionUrl(), getConnectionProperties(), getMaxConnections(), driver);
|
||||
} else {
|
||||
database = new Database(getConnectionUrl(), getConnectionProperties(), getMaxConnections());
|
||||
}
|
||||
CommandSet commandSet = getDialect().createCommandSet(database);
|
||||
return new SQLStorage(commandSet, getCompression());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxConnections() {
|
||||
return maxConnections;
|
||||
}
|
||||
private @Nullable Driver createDriver() throws ConfigurationException {
|
||||
if (driverClass == null) return null;
|
||||
|
||||
@Override
|
||||
public Compression getCompression() {
|
||||
return compression;
|
||||
try {
|
||||
// load driver class
|
||||
Class<?> driverClazz;
|
||||
URL driverJarUrl = getDriverJar().orElse(null);
|
||||
if (driverJarUrl != null) {
|
||||
|
||||
// sanity-check if file exists
|
||||
if (!Files.exists(Path.of(driverJarUrl.toURI()))) {
|
||||
throw new ConfigurationException("""
|
||||
The configured driver-jar was not found!
|
||||
Please check your 'driver-jar' setting in your configuration and make sure you have the correct path configured.
|
||||
""".strip());
|
||||
}
|
||||
|
||||
ClassLoader classLoader = new URLClassLoader(new URL[]{driverJarUrl});
|
||||
driverClazz = Class.forName(driverClass, true, classLoader);
|
||||
} else {
|
||||
driverClazz = Class.forName(driverClass);
|
||||
}
|
||||
|
||||
// create driver
|
||||
return (Driver) driverClazz.getDeclaredConstructor().newInstance();
|
||||
} catch (ClassCastException ex) {
|
||||
throw new ConfigurationException("""
|
||||
The configured driver-class was found but is not of the correct class-type!
|
||||
Please check your 'driver-class' setting in your configuration and make sure you have the correct class configured.
|
||||
""".strip(), ex);
|
||||
} catch (ClassNotFoundException ex) {
|
||||
throw new ConfigurationException("""
|
||||
The configured driver-class was not found!
|
||||
Please check your 'driver-class' setting in your configuration and make sure you have the correct class configured.
|
||||
""".strip(), ex);
|
||||
} catch (ConfigurationException ex) {
|
||||
throw ex;
|
||||
} catch (Exception ex) {
|
||||
throw new ConfigurationException("""
|
||||
BlueMap failed to load the configured SQL-Driver!
|
||||
Please check your 'driver-jar' and 'driver-class' settings in your configuration.
|
||||
""".strip(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -28,6 +28,8 @@
|
||||
import de.bluecolored.bluemap.common.config.ConfigurationException;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import de.bluecolored.bluemap.core.util.Keyed;
|
||||
import de.bluecolored.bluemap.core.util.Registry;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
||||
import java.util.Locale;
|
||||
@ -35,34 +37,39 @@
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
@DebugDump
|
||||
@ConfigSerializable
|
||||
public class StorageConfig {
|
||||
public abstract class StorageConfig {
|
||||
|
||||
private Key storageType = StorageType.FILE.getKey();
|
||||
|
||||
public Key getStorageTypeKey() {
|
||||
return storageType;
|
||||
}
|
||||
private String storageType = StorageType.FILE.getKey().getFormatted();
|
||||
|
||||
public StorageType getStorageType() throws ConfigurationException {
|
||||
StorageType type = StorageType.REGISTRY.get(storageType);
|
||||
return parseKey(StorageType.REGISTRY, storageType, "storage-type");
|
||||
}
|
||||
|
||||
public abstract Storage createStorage() throws ConfigurationException;
|
||||
|
||||
static <T extends Keyed> T parseKey(Registry<T> registry, String key, String typeName) throws ConfigurationException {
|
||||
T type = registry.get(Key.parse(key, Key.BLUEMAP_NAMESPACE));
|
||||
|
||||
if (type == null) {
|
||||
// try legacy config format
|
||||
Key legacyFormatKey = Key.bluemap(storageType.getValue().toLowerCase(Locale.ROOT));
|
||||
type = StorageType.REGISTRY.get(legacyFormatKey);
|
||||
Key legacyFormatKey = Key.bluemap(key.toLowerCase(Locale.ROOT));
|
||||
type = registry.get(legacyFormatKey);
|
||||
}
|
||||
|
||||
if (type == null)
|
||||
throw new ConfigurationException("No storage-type found for key: " + storageType + "!");
|
||||
throw new ConfigurationException("No " + typeName + " found for key: " + key + "!");
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
public Storage createStorage() throws Exception {
|
||||
if (this.getClass().equals(StorageConfig.class))
|
||||
throw new UnsupportedOperationException("Can not create a Storage from the StorageConfig superclass.");
|
||||
@ConfigSerializable
|
||||
public static class Base extends StorageConfig {
|
||||
|
||||
@Override
|
||||
public Storage createStorage() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
return getStorageType().getStorageFactory(this.getClass()).provide(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,56 +24,31 @@
|
||||
*/
|
||||
package de.bluecolored.bluemap.common.config.storage;
|
||||
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.storage.file.FileStorage;
|
||||
import de.bluecolored.bluemap.core.storage.sql.SQLStorage;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import de.bluecolored.bluemap.core.util.Keyed;
|
||||
import de.bluecolored.bluemap.core.util.Registry;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
public class StorageType implements Keyed {
|
||||
public interface StorageType extends Keyed {
|
||||
|
||||
public static final StorageType FILE = new StorageType( Key.bluemap("file"), FileConfig.class, FileStorage::new );
|
||||
public static final StorageType SQL = new StorageType( Key.bluemap("sql"), SQLConfig.class, SQLStorage::create );
|
||||
StorageType FILE = new Impl(Key.bluemap("file"), FileConfig.class);
|
||||
StorageType SQL = new Impl(Key.bluemap("sql"), SQLConfig.class);
|
||||
|
||||
public static final Registry<StorageType> REGISTRY = new Registry<>(
|
||||
Registry<StorageType> REGISTRY = new Registry<>(
|
||||
FILE,
|
||||
SQL
|
||||
);
|
||||
|
||||
private final Key key;
|
||||
private final Class<? extends StorageConfig> configType;
|
||||
private final StorageFactory<? extends StorageConfig> storageFactory;
|
||||
Class<? extends StorageConfig> getConfigType();
|
||||
|
||||
public <C extends StorageConfig> StorageType(Key key, Class<C> configType, StorageFactory<C> storageFactory) {
|
||||
this.key = key;
|
||||
this.configType = configType;
|
||||
this.storageFactory = storageFactory;
|
||||
}
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
class Impl implements StorageType {
|
||||
|
||||
@Override
|
||||
public Key getKey() {
|
||||
return key;
|
||||
}
|
||||
private final Key key;
|
||||
private final Class<? extends StorageConfig> configType;
|
||||
|
||||
public Class<? extends StorageConfig> getConfigType() {
|
||||
return configType;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <C extends StorageConfig> StorageFactory<C> getStorageFactory(Class<C> configType) {
|
||||
if (!configType.isAssignableFrom(this.configType)) throw new ClassCastException(this.configType + " can not be cast to " + configType);
|
||||
return (StorageFactory<C>) storageFactory;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface StorageFactory<C extends StorageConfig> {
|
||||
Storage provideRaw(C config) throws Exception;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default Storage provide(StorageConfig config) throws Exception {
|
||||
return provideRaw((C) config);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -35,8 +35,8 @@
|
||||
import de.bluecolored.bluemap.common.plugin.skins.PlayerSkinUpdater;
|
||||
import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask;
|
||||
import de.bluecolored.bluemap.common.rendermanager.RenderManager;
|
||||
import de.bluecolored.bluemap.common.serverinterface.ServerEventListener;
|
||||
import de.bluecolored.bluemap.common.serverinterface.Server;
|
||||
import de.bluecolored.bluemap.common.serverinterface.ServerEventListener;
|
||||
import de.bluecolored.bluemap.common.serverinterface.ServerWorld;
|
||||
import de.bluecolored.bluemap.common.web.*;
|
||||
import de.bluecolored.bluemap.common.web.http.HttpServer;
|
||||
@ -201,7 +201,7 @@ private void load(@Nullable ResourcePack preloadedResourcePack) throws IOExcepti
|
||||
mapRequestHandler = new MapRequestHandler(map, serverInterface, pluginConfig, Predicate.not(pluginState::isPlayerHidden));
|
||||
} else {
|
||||
Storage storage = blueMap.getOrLoadStorage(mapConfig.getStorage());
|
||||
mapRequestHandler = new MapRequestHandler(id, storage);
|
||||
mapRequestHandler = new MapRequestHandler(storage.map(id));
|
||||
}
|
||||
|
||||
routingRequestHandler.register(
|
||||
@ -240,9 +240,11 @@ private void load(@Nullable ResourcePack preloadedResourcePack) throws IOExcepti
|
||||
throw new ConfigurationException("BlueMap failed to bind to the configured address.\n" +
|
||||
"This usually happens when the configured port (" + webserverConfig.getPort() + ") is already in use by some other program.", ex);
|
||||
} catch (IOException ex) {
|
||||
throw new ConfigurationException("BlueMap failed to initialize the webserver.\n" +
|
||||
"Check your webserver-config if everything is configured correctly.\n" +
|
||||
"(Make sure you DON'T use the same port for bluemap that you also use for your minecraft server)", ex);
|
||||
throw new ConfigurationException("""
|
||||
BlueMap failed to initialize the webserver.
|
||||
Check your webserver-config if everything is configured correctly.
|
||||
(Make sure you DON'T use the same port for bluemap that you also use for your minecraft server)
|
||||
""".strip(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -342,8 +344,8 @@ public void run() {
|
||||
TimerTask metricsTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (Plugin.this.serverInterface.isMetricsEnabled().getOr(coreConfig::isMetrics))
|
||||
Metrics.sendReport(Plugin.this.implementationType);
|
||||
if (serverInterface.isMetricsEnabled().getOr(coreConfig::isMetrics))
|
||||
Metrics.sendReport(implementationType, configManager.getMinecraftVersion().getVersionString());
|
||||
}
|
||||
};
|
||||
daemonTimer.scheduleAtFixedRate(metricsTask, TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(30));
|
||||
@ -539,7 +541,7 @@ public void savePlayerStates() {
|
||||
Predicate.not(pluginState::isPlayerHidden)
|
||||
);
|
||||
try (
|
||||
OutputStream out = map.getStorage().writeMeta(map.getId(), BmMap.META_FILE_PLAYERS);
|
||||
OutputStream out = map.getStorage().players().write();
|
||||
Writer writer = new OutputStreamWriter(out)
|
||||
) {
|
||||
writer.write(dataSupplier.get());
|
||||
|
@ -52,6 +52,7 @@
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.map.MapRenderState;
|
||||
import de.bluecolored.bluemap.core.storage.MapStorage;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.world.Chunk;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
@ -67,8 +68,6 @@
|
||||
|
||||
public class Commands<S> {
|
||||
|
||||
public static final String DEFAULT_MARKER_SET_ID = "markers";
|
||||
|
||||
private final Plugin plugin;
|
||||
private final CommandDispatcher<S> dispatcher;
|
||||
private final Function<S, CommandSource> commandSourceInterface;
|
||||
@ -892,8 +891,13 @@ public int storagesCommand(CommandContext<S> context) {
|
||||
|
||||
source.sendMessage(Text.of(TextColor.BLUE, "Storages loaded by BlueMap:"));
|
||||
for (var entry : plugin.getBlueMap().getConfig().getStorageConfigs().entrySet()) {
|
||||
String storageTypeKey = "?";
|
||||
try {
|
||||
storageTypeKey = entry.getValue().getStorageType().getKey().getFormatted();
|
||||
} catch (ConfigurationException ignore) {} // should never happen
|
||||
|
||||
source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, entry.getKey())
|
||||
.setHoverText(Text.of(entry.getValue().getStorageTypeKey().getFormatted()))
|
||||
.setHoverText(Text.of(storageTypeKey))
|
||||
.setClickAction(Text.ClickAction.RUN_COMMAND, "/bluemap storages " + entry.getKey())
|
||||
);
|
||||
}
|
||||
@ -916,7 +920,7 @@ public int storagesInfoCommand(CommandContext<S> context) {
|
||||
|
||||
Collection<String> mapIds;
|
||||
try {
|
||||
mapIds = storage.collectMapIds();
|
||||
mapIds = storage.mapIds().toList();
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logError("Unexpected exception trying to load mapIds from storage '" + storageId + "'!", ex);
|
||||
source.sendMessage(Text.of(TextColor.RED, "There was an unexpected exception trying to access this storage. Please check the console for more details..."));
|
||||
@ -929,7 +933,7 @@ public int storagesInfoCommand(CommandContext<S> context) {
|
||||
} else {
|
||||
for (String mapId : mapIds) {
|
||||
BmMap map = plugin.getBlueMap().getMaps().get(mapId);
|
||||
boolean isLoaded = map != null && map.getStorage().equals(storage);
|
||||
boolean isLoaded = map != null && map.getStorage().equals(storage.map(mapId));
|
||||
|
||||
if (isLoaded) {
|
||||
source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, mapId, TextColor.GREEN, TextFormat.ITALIC, " (loaded)"));
|
||||
@ -947,9 +951,9 @@ public int storagesDeleteMapCommand(CommandContext<S> context) {
|
||||
String storageId = context.getArgument("storage", String.class);
|
||||
String mapId = context.getArgument("map", String.class);
|
||||
|
||||
Storage storage;
|
||||
MapStorage storage;
|
||||
try {
|
||||
storage = plugin.getBlueMap().getOrLoadStorage(storageId);
|
||||
storage = plugin.getBlueMap().getOrLoadStorage(storageId).map(mapId);
|
||||
} catch (ConfigurationException | InterruptedException ex) {
|
||||
Logger.global.logError("Unexpected exception trying to load storage '" + storageId + "'!", ex);
|
||||
source.sendMessage(Text.of(TextColor.RED, "There was an unexpected exception trying to load this storage. Please check the console for more details..."));
|
||||
|
@ -92,7 +92,7 @@ public CompletableFuture<Void> updateSkin(final UUID playerUuid) {
|
||||
BufferedImage playerHead = playerMarkerIconFactory.apply(playerUuid, skin.get());
|
||||
|
||||
for (BmMap map : maps.values()) {
|
||||
try (OutputStream out = map.getStorage().writeMeta(map.getId(), "assets/playerheads/" + playerUuid + ".png")) {
|
||||
try (OutputStream out = map.getStorage().asset("playerheads/" + playerUuid + ".png").write()) {
|
||||
ImageIO.write(playerHead, "png", out);
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logError("Failed to write player skin to storage: " + playerUuid, ex);
|
||||
|
@ -57,8 +57,8 @@ public void doWork() throws Exception {
|
||||
|
||||
try {
|
||||
// purge the map
|
||||
map.getStorage().purgeMap(map.getId(), progressInfo -> {
|
||||
this.progress = progressInfo.getProgress();
|
||||
map.getStorage().delete(progress -> {
|
||||
this.progress = progress;
|
||||
return !this.cancelled;
|
||||
});
|
||||
|
||||
|
@ -25,20 +25,20 @@
|
||||
package de.bluecolored.bluemap.common.rendermanager;
|
||||
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.storage.MapStorage;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class StorageDeleteTask implements RenderTask {
|
||||
|
||||
private final Storage storage;
|
||||
private final MapStorage storage;
|
||||
private final String mapId;
|
||||
|
||||
private volatile double progress;
|
||||
private volatile boolean hasMoreWork;
|
||||
private volatile boolean cancelled;
|
||||
|
||||
public StorageDeleteTask(Storage storage, String mapId) {
|
||||
public StorageDeleteTask(MapStorage storage, String mapId) {
|
||||
this.storage = Objects.requireNonNull(storage);
|
||||
this.mapId = Objects.requireNonNull(mapId);
|
||||
this.progress = 0d;
|
||||
@ -55,8 +55,8 @@ public void doWork() throws Exception {
|
||||
if (this.cancelled) return;
|
||||
|
||||
// purge the map
|
||||
storage.purgeMap(mapId, progressInfo -> {
|
||||
this.progress = progressInfo.getProgress();
|
||||
storage.delete(progress -> {
|
||||
this.progress = progress;
|
||||
return !this.cancelled;
|
||||
});
|
||||
}
|
||||
|
@ -30,6 +30,7 @@
|
||||
import de.bluecolored.bluemap.common.serverinterface.Server;
|
||||
import de.bluecolored.bluemap.common.serverinterface.ServerWorld;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.storage.MapStorage;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -40,20 +41,20 @@
|
||||
public class MapRequestHandler extends RoutingRequestHandler {
|
||||
|
||||
public MapRequestHandler(BmMap map, Server serverInterface, PluginConfig pluginConfig, Predicate<UUID> playerFilter) {
|
||||
this(map.getId(), map.getStorage(),
|
||||
this(map.getStorage(),
|
||||
createPlayersDataSupplier(map, serverInterface, pluginConfig, playerFilter),
|
||||
new LiveMarkersDataSupplier(map.getMarkerSets()));
|
||||
}
|
||||
|
||||
public MapRequestHandler(String mapId, Storage mapStorage) {
|
||||
this(mapId, mapStorage, null, null);
|
||||
public MapRequestHandler(MapStorage mapStorage) {
|
||||
this(mapStorage, null, null);
|
||||
}
|
||||
|
||||
public MapRequestHandler(String mapId, Storage mapStorage,
|
||||
public MapRequestHandler(MapStorage mapStorage,
|
||||
@Nullable Supplier<String> livePlayersDataSupplier,
|
||||
@Nullable Supplier<String> liveMarkerDataSupplier) {
|
||||
|
||||
register(".*", new MapStorageRequestHandler(mapId, mapStorage));
|
||||
register(".*", new MapStorageRequestHandler(mapStorage));
|
||||
|
||||
if (livePlayersDataSupplier != null) {
|
||||
register("live/players\\.json", "", new JsonDataRequestHandler(
|
||||
|
@ -24,44 +24,38 @@
|
||||
*/
|
||||
package de.bluecolored.bluemap.common.web;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import de.bluecolored.bluemap.api.ContentTypeRegistry;
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.common.web.http.*;
|
||||
import de.bluecolored.bluemap.common.web.http.HttpRequest;
|
||||
import de.bluecolored.bluemap.common.web.http.HttpRequestHandler;
|
||||
import de.bluecolored.bluemap.common.web.http.HttpResponse;
|
||||
import de.bluecolored.bluemap.common.web.http.HttpStatusCode;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.storage.CompressedInputStream;
|
||||
import de.bluecolored.bluemap.core.storage.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.storage.TileInfo;
|
||||
import de.bluecolored.bluemap.core.storage.GridStorage;
|
||||
import de.bluecolored.bluemap.core.storage.MapStorage;
|
||||
import de.bluecolored.bluemap.core.storage.compression.CompressedInputStream;
|
||||
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@DebugDump
|
||||
@RequiredArgsConstructor
|
||||
public class MapStorageRequestHandler implements HttpRequestHandler {
|
||||
|
||||
private static final Pattern TILE_PATTERN = Pattern.compile("tiles/([\\d/]+)/x(-?[\\d/]+)z(-?[\\d/]+).*");
|
||||
|
||||
private final String mapId;
|
||||
private final Storage mapStorage;
|
||||
|
||||
|
||||
public MapStorageRequestHandler(BmMap map) {
|
||||
this.mapId = map.getId();
|
||||
this.mapStorage = map.getStorage();
|
||||
}
|
||||
|
||||
public MapStorageRequestHandler(String mapId, Storage mapStorage) {
|
||||
this.mapId = mapId;
|
||||
this.mapStorage = mapStorage;
|
||||
}
|
||||
private final MapStorage mapStorage;
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
@Override
|
||||
public HttpResponse handle(HttpRequest request) {
|
||||
String path = request.getPath();
|
||||
@ -78,58 +72,36 @@ public HttpResponse handle(HttpRequest request) {
|
||||
int lod = Integer.parseInt(tileMatcher.group(1));
|
||||
int x = Integer.parseInt(tileMatcher.group(2).replace("/", ""));
|
||||
int z = Integer.parseInt(tileMatcher.group(3).replace("/", ""));
|
||||
Optional<TileInfo> optTileInfo = mapStorage.readMapTileInfo(mapId, lod, new Vector2i(x, z));
|
||||
|
||||
if (optTileInfo.isPresent()) {
|
||||
TileInfo tileInfo = optTileInfo.get();
|
||||
GridStorage gridStorage = lod == 0 ? mapStorage.hiresTiles() : mapStorage.lowresTiles(lod);
|
||||
CompressedInputStream in = gridStorage.read(x, z);
|
||||
if (in == null) return new HttpResponse(HttpStatusCode.NO_CONTENT);
|
||||
|
||||
// check e-tag
|
||||
String eTag = calculateETag(path, tileInfo);
|
||||
HttpHeader etagHeader = request.getHeader("If-None-Match");
|
||||
if (etagHeader != null){
|
||||
if(etagHeader.getValue().equals(eTag)) {
|
||||
return new HttpResponse(HttpStatusCode.NOT_MODIFIED);
|
||||
}
|
||||
}
|
||||
HttpResponse response = new HttpResponse(HttpStatusCode.OK);
|
||||
response.addHeader("Cache-Control", "public");
|
||||
response.addHeader("Cache-Control", "max-age=" + TimeUnit.DAYS.toSeconds(1));
|
||||
|
||||
// check modified-since
|
||||
long lastModified = tileInfo.getLastModified();
|
||||
HttpHeader modHeader = request.getHeader("If-Modified-Since");
|
||||
if (modHeader != null){
|
||||
try {
|
||||
long since = stringToTimestamp(modHeader.getValue());
|
||||
if (since + 1000 >= lastModified){
|
||||
return new HttpResponse(HttpStatusCode.NOT_MODIFIED);
|
||||
}
|
||||
} catch (IllegalArgumentException ignored){}
|
||||
}
|
||||
if (lod == 0) response.addHeader("Content-Type", "application/octet-stream");
|
||||
else response.addHeader("Content-Type", "image/png");
|
||||
|
||||
CompressedInputStream compressedIn = tileInfo.readMapTile();
|
||||
HttpResponse response = new HttpResponse(HttpStatusCode.OK);
|
||||
response.addHeader("ETag", eTag);
|
||||
if (lastModified > 0)
|
||||
response.addHeader("Last-Modified", timestampToString(lastModified));
|
||||
|
||||
response.addHeader("Cache-Control", "public");
|
||||
response.addHeader("Cache-Control", "max-age=" + TimeUnit.DAYS.toSeconds(1));
|
||||
|
||||
if (lod == 0) response.addHeader("Content-Type", "application/octet-stream");
|
||||
else response.addHeader("Content-Type", "image/png");
|
||||
|
||||
writeToResponse(compressedIn, response, request);
|
||||
return response;
|
||||
}
|
||||
writeToResponse(in, response, request);
|
||||
return response;
|
||||
}
|
||||
|
||||
// provide meta-data
|
||||
Optional<InputStream> optIn = mapStorage.readMeta(mapId, path);
|
||||
if (optIn.isPresent()) {
|
||||
CompressedInputStream compressedIn = new CompressedInputStream(optIn.get(), Compression.NONE);
|
||||
CompressedInputStream in = switch (path) {
|
||||
case "settings.json" -> mapStorage.settings().read();
|
||||
case "textures.json" -> mapStorage.textures().read();
|
||||
case "live/markers.json" -> mapStorage.markers().read();
|
||||
case "live/players.json" -> mapStorage.players().read();
|
||||
default -> null;
|
||||
};
|
||||
if (in != null){
|
||||
HttpResponse response = new HttpResponse(HttpStatusCode.OK);
|
||||
response.addHeader("Cache-Control", "public");
|
||||
response.addHeader("Cache-Control", "max-age=" + TimeUnit.DAYS.toSeconds(1));
|
||||
response.addHeader("Content-Type", ContentTypeRegistry.fromFileName(path));
|
||||
writeToResponse(compressedIn, response, request);
|
||||
writeToResponse(in, response, request);
|
||||
return response;
|
||||
}
|
||||
|
||||
@ -139,27 +111,23 @@ public HttpResponse handle(HttpRequest request) {
|
||||
return new HttpResponse(HttpStatusCode.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
return new HttpResponse(HttpStatusCode.NO_CONTENT);
|
||||
}
|
||||
|
||||
private String calculateETag(String path, TileInfo tileInfo) {
|
||||
return Long.toHexString(tileInfo.getSize()) + Integer.toHexString(path.hashCode()) + Long.toHexString(tileInfo.getLastModified());
|
||||
return new HttpResponse(HttpStatusCode.NOT_FOUND);
|
||||
}
|
||||
|
||||
private void writeToResponse(CompressedInputStream data, HttpResponse response, HttpRequest request) throws IOException {
|
||||
Compression compression = data.getCompression();
|
||||
if (
|
||||
compression != Compression.NONE &&
|
||||
request.hasHeaderValue("Accept-Encoding", compression.getTypeId())
|
||||
request.hasHeaderValue("Accept-Encoding", compression.getId())
|
||||
) {
|
||||
response.addHeader("Content-Encoding", compression.getTypeId());
|
||||
response.addHeader("Content-Encoding", compression.getId());
|
||||
response.setData(data);
|
||||
} else if (
|
||||
compression != Compression.GZIP &&
|
||||
!response.hasHeaderValue("Content-Type", "image/png") &&
|
||||
request.hasHeaderValue("Accept-Encoding", Compression.GZIP.getTypeId())
|
||||
request.hasHeaderValue("Accept-Encoding", Compression.GZIP.getId())
|
||||
) {
|
||||
response.addHeader("Content-Encoding", Compression.GZIP.getTypeId());
|
||||
response.addHeader("Content-Encoding", Compression.GZIP.getId());
|
||||
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
||||
try (OutputStream os = Compression.GZIP.compress(byteOut)) {
|
||||
IOUtils.copyLarge(data.decompress(), os);
|
||||
@ -167,41 +135,7 @@ private void writeToResponse(CompressedInputStream data, HttpResponse response,
|
||||
byte[] compressedData = byteOut.toByteArray();
|
||||
response.setData(new ByteArrayInputStream(compressedData));
|
||||
} else {
|
||||
response.setData(new BufferedInputStream(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 "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);
|
||||
response.setData(data.decompress());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
# 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: "bluemap:file"
|
||||
storage-type: file
|
||||
|
||||
# The path to the folder on your file-system where bluemap will save the rendered map
|
||||
# The default is: "bluemap/web/maps"
|
||||
@ -17,4 +17,4 @@ root: "${root}"
|
||||
# - GZIP
|
||||
# - NONE
|
||||
# The default is: GZIP
|
||||
compression: GZIP
|
||||
compression: gzip
|
||||
|
@ -6,7 +6,7 @@
|
||||
# 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: "bluemap:sql"
|
||||
storage-type: sql
|
||||
|
||||
# The JDBC-Connection URL that is used to connect to the database.
|
||||
# The format for this url is usually something like: jdbc:[driver]://[host]:[port]/[database]
|
||||
@ -39,7 +39,7 @@ max-connections: -1
|
||||
|
||||
# The compression-type that bluemap will use to compress generated map-data.
|
||||
# Available compression-types are:
|
||||
# - GZIP
|
||||
# - NONE
|
||||
# The default is: GZIP
|
||||
compression: GZIP
|
||||
# - gzip
|
||||
# - none
|
||||
# The default is: gzip
|
||||
compression: gzip
|
||||
|
@ -36,7 +36,8 @@
|
||||
import de.bluecolored.bluemap.core.map.lowres.LowresTileManager;
|
||||
import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.storage.MapStorage;
|
||||
import de.bluecolored.bluemap.core.storage.compression.CompressedInputStream;
|
||||
import de.bluecolored.bluemap.core.util.Grid;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
|
||||
@ -44,19 +45,12 @@
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@DebugDump
|
||||
public class BmMap {
|
||||
|
||||
public static final String META_FILE_SETTINGS = "settings.json";
|
||||
public static final String META_FILE_TEXTURES = "textures.json";
|
||||
public static final String META_FILE_RENDER_STATE = ".rstate";
|
||||
public static final String META_FILE_MARKERS = "live/markers.json";
|
||||
public static final String META_FILE_PLAYERS = "live/players.json";
|
||||
|
||||
private static final Gson GSON = ResourcesGson.addAdapter(new GsonBuilder())
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
|
||||
.registerTypeAdapter(BmMap.class, new MapSettingsSerializer())
|
||||
@ -65,7 +59,7 @@ public class BmMap {
|
||||
private final String id;
|
||||
private final String name;
|
||||
private final World world;
|
||||
private final Storage storage;
|
||||
private final MapStorage storage;
|
||||
private final MapSettings mapSettings;
|
||||
|
||||
private final ResourcePack resourcePack;
|
||||
@ -82,7 +76,7 @@ public class BmMap {
|
||||
private long renderTimeSumNanos;
|
||||
private long tilesRendered;
|
||||
|
||||
public BmMap(String id, String name, World world, Storage storage, ResourcePack resourcePack, MapSettings settings) throws IOException {
|
||||
public BmMap(String id, String name, World world, MapStorage storage, ResourcePack resourcePack, MapSettings settings) throws IOException {
|
||||
this.id = Objects.requireNonNull(id);
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.world = Objects.requireNonNull(world);
|
||||
@ -98,7 +92,7 @@ public BmMap(String id, String name, World world, Storage storage, ResourcePack
|
||||
saveTextureGallery();
|
||||
|
||||
this.hiresModelManager = new HiresModelManager(
|
||||
storage.tileStorage(id, 0),
|
||||
storage.hiresTiles(),
|
||||
this.resourcePack,
|
||||
this.textureGallery,
|
||||
settings,
|
||||
@ -106,7 +100,7 @@ public BmMap(String id, String name, World world, Storage storage, ResourcePack
|
||||
);
|
||||
|
||||
this.lowresTileManager = new LowresTileManager(
|
||||
storage.mapStorage(id),
|
||||
storage,
|
||||
new Grid(settings.getLowresTileSize()),
|
||||
settings.getLodCount(),
|
||||
settings.getLodFactor()
|
||||
@ -145,7 +139,7 @@ public synchronized void save() {
|
||||
|
||||
// only save texture gallery if not present in storage
|
||||
try {
|
||||
if (storage.readMetaInfo(id, META_FILE_TEXTURES).isEmpty())
|
||||
if (!storage.textures().exists())
|
||||
saveTextureGallery();
|
||||
} catch (IOException e) {
|
||||
Logger.global.logError("Failed to read texture gallery", e);
|
||||
@ -153,18 +147,16 @@ public synchronized void save() {
|
||||
}
|
||||
|
||||
private void loadRenderState() throws IOException {
|
||||
Optional<InputStream> rstateData = storage.readMeta(id, META_FILE_RENDER_STATE);
|
||||
if (rstateData.isPresent()) {
|
||||
try (InputStream in = rstateData.get()){
|
||||
this.renderState.load(in);
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logWarning("Failed to load render-state for map '" + getId() + "': " + ex);
|
||||
}
|
||||
try (CompressedInputStream in = storage.renderState().read()){
|
||||
if (in != null)
|
||||
this.renderState.load(in.decompress());
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logWarning("Failed to load render-state for map '" + getId() + "': " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void saveRenderState() {
|
||||
try (OutputStream out = storage.writeMeta(id, META_FILE_RENDER_STATE)) {
|
||||
try (OutputStream out = storage.renderState().write()) {
|
||||
this.renderState.save(out);
|
||||
} catch (IOException ex){
|
||||
Logger.global.logError("Failed to save render-state for map: '" + this.id + "'!", ex);
|
||||
@ -172,20 +164,18 @@ public synchronized void saveRenderState() {
|
||||
}
|
||||
|
||||
private TextureGallery loadTextureGallery() throws IOException {
|
||||
TextureGallery gallery = null;
|
||||
Optional<InputStream> texturesData = storage.readMeta(id, META_FILE_TEXTURES);
|
||||
if (texturesData.isPresent()) {
|
||||
try (InputStream in = texturesData.get()){
|
||||
gallery = TextureGallery.readTexturesFile(in);
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logError("Failed to load textures for map '" + getId() + "'!", ex);
|
||||
}
|
||||
try (CompressedInputStream in = storage.textures().read()){
|
||||
if (in != null)
|
||||
return TextureGallery.readTexturesFile(in.decompress());
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logError("Failed to load textures for map '" + getId() + "'!", ex);
|
||||
}
|
||||
return gallery != null ? gallery : new TextureGallery();
|
||||
|
||||
return new TextureGallery();
|
||||
}
|
||||
|
||||
private void saveTextureGallery() {
|
||||
try (OutputStream out = storage.writeMeta(id, META_FILE_TEXTURES)) {
|
||||
try (OutputStream out = storage.textures().write()) {
|
||||
this.textureGallery.writeTexturesFile(out);
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logError("Failed to save textures for map '" + getId() + "'!", ex);
|
||||
@ -199,7 +189,7 @@ public synchronized void resetTextureGallery() {
|
||||
|
||||
private void saveMapSettings() {
|
||||
try (
|
||||
OutputStream out = storage.writeMeta(id, META_FILE_SETTINGS);
|
||||
OutputStream out = storage.settings().write();
|
||||
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)
|
||||
) {
|
||||
GSON.toJson(this, writer);
|
||||
@ -210,7 +200,7 @@ private void saveMapSettings() {
|
||||
|
||||
public synchronized void saveMarkerState() {
|
||||
try (
|
||||
OutputStream out = storage.writeMeta(id, META_FILE_MARKERS);
|
||||
OutputStream out = storage.markers().write();
|
||||
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)
|
||||
) {
|
||||
MarkerGson.INSTANCE.toJson(this.markerSets, writer);
|
||||
@ -220,9 +210,7 @@ public synchronized void saveMarkerState() {
|
||||
}
|
||||
|
||||
public synchronized void savePlayerState() {
|
||||
try (
|
||||
OutputStream out = storage.writeMeta(id, META_FILE_PLAYERS)
|
||||
) {
|
||||
try (OutputStream out = storage.players().write()) {
|
||||
out.write("{}".getBytes(StandardCharsets.UTF_8));
|
||||
} catch (Exception ex) {
|
||||
Logger.global.logError("Failed to save markers for map '" + getId() + "'!", ex);
|
||||
@ -241,7 +229,7 @@ public World getWorld() {
|
||||
return world;
|
||||
}
|
||||
|
||||
public Storage getStorage() {
|
||||
public MapStorage getStorage() {
|
||||
return storage;
|
||||
}
|
||||
|
||||
@ -284,12 +272,8 @@ public int hashCode() {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof BmMap) {
|
||||
BmMap that = (BmMap) obj;
|
||||
|
||||
if (obj instanceof BmMap that)
|
||||
return this.id.equals(that.id);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@
|
||||
import de.bluecolored.bluemap.core.map.TextureGallery;
|
||||
import de.bluecolored.bluemap.core.map.TileMetaConsumer;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.storage.GridStorage;
|
||||
import de.bluecolored.bluemap.core.util.Grid;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
import lombok.Getter;
|
||||
@ -40,17 +40,17 @@
|
||||
|
||||
public class HiresModelManager {
|
||||
|
||||
private final Storage.TileStorage storage;
|
||||
private final GridStorage storage;
|
||||
private final HiresModelRenderer renderer;
|
||||
|
||||
@Getter
|
||||
private final Grid tileGrid;
|
||||
|
||||
public HiresModelManager(Storage.TileStorage storage, ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings, Grid tileGrid) {
|
||||
public HiresModelManager(GridStorage storage, ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings, Grid tileGrid) {
|
||||
this(storage, new HiresModelRenderer(resourcePack, textureGallery, renderSettings), tileGrid);
|
||||
}
|
||||
|
||||
public HiresModelManager(Storage.TileStorage storage, HiresModelRenderer renderer, Grid tileGrid) {
|
||||
public HiresModelManager(GridStorage storage, HiresModelRenderer renderer, Grid tileGrid) {
|
||||
this.storage = storage;
|
||||
this.renderer = renderer;
|
||||
|
||||
@ -81,7 +81,7 @@ public void render(World world, Vector2i tile, TileMetaConsumer tileMetaConsumer
|
||||
|
||||
private void save(final TileModel model, Vector2i tile) {
|
||||
try (
|
||||
OutputStream out = storage.write(tile);
|
||||
OutputStream out = storage.write(tile.getX(), tile.getY());
|
||||
PRBMWriter modelWriter = new PRBMWriter(out)
|
||||
) {
|
||||
modelWriter.write(model);
|
||||
|
@ -28,10 +28,10 @@
|
||||
import com.github.benmanes.caffeine.cache.*;
|
||||
import de.bluecolored.bluemap.core.BlueMap;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.storage.GridStorage;
|
||||
import de.bluecolored.bluemap.core.util.Grid;
|
||||
import de.bluecolored.bluemap.core.util.Vector2iCache;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
import de.bluecolored.bluemap.core.util.Grid;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -44,7 +44,7 @@ public class LowresLayer {
|
||||
|
||||
private static final Vector2iCache VECTOR_2_I_CACHE = new Vector2iCache();
|
||||
|
||||
private final Storage.MapStorage mapStorage;
|
||||
private final GridStorage storage;
|
||||
|
||||
private final Grid tileGrid;
|
||||
private final int lodFactor;
|
||||
@ -54,10 +54,10 @@ public class LowresLayer {
|
||||
@Nullable private final LowresLayer nextLayer;
|
||||
|
||||
public LowresLayer(
|
||||
Storage.MapStorage mapStorage, Grid tileGrid, int lodCount, int lodFactor,
|
||||
GridStorage storage, Grid tileGrid, int lodFactor,
|
||||
int lod, @Nullable LowresLayer nextLayer
|
||||
) {
|
||||
this.mapStorage = mapStorage;
|
||||
this.storage = storage;
|
||||
|
||||
this.tileGrid = tileGrid;
|
||||
this.lodFactor = lodFactor;
|
||||
@ -83,7 +83,7 @@ public void write(@NonNull Vector2i key, @NonNull LowresTile value) {}
|
||||
|
||||
@Override
|
||||
public void delete(@NonNull Vector2i key, @Nullable LowresTile value, @NonNull RemovalCause cause) {
|
||||
saveTile(key, value, cause);
|
||||
saveTile(key, value);
|
||||
}
|
||||
})
|
||||
.build(tileWeakInstanceCache::get);
|
||||
@ -95,7 +95,7 @@ public void save() {
|
||||
}
|
||||
|
||||
private LowresTile createTile(Vector2i tilePos) {
|
||||
try (InputStream in = mapStorage.read(lod, tilePos).orElse(null)) {
|
||||
try (InputStream in = storage.read(tilePos.getX(), tilePos.getY())) {
|
||||
if (in != null) return new LowresTile(tileGrid.getGridSize(), in);
|
||||
} catch (IOException e) {
|
||||
Logger.global.logError("Failed to load tile " + tilePos + " (lod: " + lod + ")", e);
|
||||
@ -105,17 +105,17 @@ private LowresTile createTile(Vector2i tilePos) {
|
||||
return new LowresTile(tileGrid.getGridSize());
|
||||
}
|
||||
|
||||
private void saveTile(Vector2i tilePos, @Nullable LowresTile tile, RemovalCause removalCause) {
|
||||
private void saveTile(Vector2i tilePos, @Nullable LowresTile tile) {
|
||||
if (tile == null) return;
|
||||
|
||||
// check if storage is closed
|
||||
if (mapStorage.getStorage().isClosed()){
|
||||
if (storage.isClosed()){
|
||||
Logger.global.logDebug("Tried to save tile " + tilePos + " (lod: " + lod + ") but storage is already closed.");
|
||||
return;
|
||||
}
|
||||
|
||||
// save the tile
|
||||
try (OutputStream out = mapStorage.write(lod, tilePos)) {
|
||||
try (OutputStream out = storage.write(tilePos.getX(), tilePos.getY())) {
|
||||
tile.save(out);
|
||||
} catch (IOException e) {
|
||||
Logger.global.logError("Failed to save tile " + tilePos + " (lod: " + lod + ")", e);
|
||||
|
@ -25,9 +25,9 @@
|
||||
package de.bluecolored.bluemap.core.map.lowres;
|
||||
|
||||
import de.bluecolored.bluemap.core.map.TileMetaConsumer;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
import de.bluecolored.bluemap.core.storage.MapStorage;
|
||||
import de.bluecolored.bluemap.core.util.Grid;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
|
||||
public class LowresTileManager implements TileMetaConsumer {
|
||||
|
||||
@ -36,14 +36,14 @@ public class LowresTileManager implements TileMetaConsumer {
|
||||
|
||||
private final LowresLayer[] layers;
|
||||
|
||||
public LowresTileManager(Storage.MapStorage mapStorage, Grid tileGrid, int lodCount, int lodFactor) {
|
||||
public LowresTileManager(MapStorage storage, Grid tileGrid, int lodCount, int lodFactor) {
|
||||
this.tileGrid = tileGrid;
|
||||
this.lodFactor = lodFactor;
|
||||
this.lodCount = lodCount;
|
||||
|
||||
this.layers = new LowresLayer[lodCount];
|
||||
for (int i = lodCount - 1; i >= 0; i--) {
|
||||
this.layers[i] = new LowresLayer(mapStorage, tileGrid, lodCount, lodFactor, i + 1,
|
||||
this.layers[i] = new LowresLayer(storage.lowresTiles(i + 1), tileGrid, lodFactor, i + 1,
|
||||
(i == lodCount - 1) ? null : layers[i + 1]);
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,8 @@
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.metrics;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import de.bluecolored.bluemap.core.BlueMap;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
|
||||
@ -39,18 +40,22 @@
|
||||
public class Metrics {
|
||||
|
||||
private static final String METRICS_REPORT_URL = "https://metrics.bluecolored.de/bluemap/";
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.create();
|
||||
|
||||
public static void sendReportAsync(String implementation) {
|
||||
new Thread(() -> sendReport(implementation), "BlueMap-Plugin-SendMetricsReport").start();
|
||||
public static void sendReportAsync(String implementation, String mcVersion) {
|
||||
new Thread(() -> sendReport(implementation, mcVersion), "BlueMap-Plugin-SendMetricsReport").start();
|
||||
}
|
||||
|
||||
public static void sendReport(String implementation) {
|
||||
JsonObject data = new JsonObject();
|
||||
data.addProperty("implementation", implementation);
|
||||
data.addProperty("version", BlueMap.VERSION);
|
||||
public static void sendReport(String implementation, String mcVersion) {
|
||||
Report report = new Report(
|
||||
implementation,
|
||||
BlueMap.VERSION,
|
||||
mcVersion
|
||||
);
|
||||
|
||||
try {
|
||||
sendData(data.toString());
|
||||
sendData(GSON.toJson(report));
|
||||
} catch (IOException | RuntimeException ex) {
|
||||
Logger.global.logDebug("Failed to send Metrics-Report: " + ex);
|
||||
}
|
||||
@ -86,4 +91,10 @@ private static String sendData(String data) throws IOException {
|
||||
|
||||
}
|
||||
|
||||
record Report (
|
||||
String implementation,
|
||||
String version,
|
||||
String mcVersion
|
||||
) {}
|
||||
|
||||
}
|
||||
|
@ -1,92 +0,0 @@
|
||||
/*
|
||||
* 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 io.airlift.compress.zstd.ZstdInputStream;
|
||||
import io.airlift.compress.zstd.ZstdOutputStream;
|
||||
import net.jpountz.lz4.LZ4FrameInputStream;
|
||||
import net.jpountz.lz4.LZ4FrameOutputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
public enum Compression {
|
||||
|
||||
NONE("none", "", out -> out, in -> in),
|
||||
GZIP("gzip", ".gz", GZIPOutputStream::new, GZIPInputStream::new),
|
||||
DEFLATE("deflate", ".deflate", DeflaterOutputStream::new, InflaterInputStream::new),
|
||||
ZSTD("zstd", ".zst", ZstdOutputStream::new, ZstdInputStream::new),
|
||||
LZ4("lz4", ".lz4", LZ4FrameOutputStream::new, LZ4FrameInputStream::new);
|
||||
|
||||
private final String typeId;
|
||||
private final String fileSuffix;
|
||||
private final StreamTransformer<OutputStream> compressor;
|
||||
private final StreamTransformer<InputStream> decompressor;
|
||||
|
||||
Compression(String typeId, String fileSuffix,
|
||||
StreamTransformer<OutputStream> compressor,
|
||||
StreamTransformer<InputStream> decompressor) {
|
||||
this.fileSuffix = fileSuffix;
|
||||
this.typeId = typeId;
|
||||
this.compressor = compressor;
|
||||
this.decompressor = decompressor;
|
||||
}
|
||||
|
||||
public String getTypeId() {
|
||||
return typeId;
|
||||
}
|
||||
|
||||
public String getFileSuffix() {
|
||||
return fileSuffix;
|
||||
}
|
||||
|
||||
public OutputStream compress(OutputStream out) throws IOException {
|
||||
return compressor.apply(out);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package de.bluecolored.bluemap.core.storage;
|
||||
|
||||
import de.bluecolored.bluemap.core.storage.compression.CompressedInputStream;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A storage storing items on an infinite grid (x,z), each position on the grid can hold one item.
|
||||
*/
|
||||
public interface GridStorage {
|
||||
|
||||
/**
|
||||
* Returns an {@link OutputStream} that can be used to write an item into this storage at the given position
|
||||
* (overwriting any existing item).
|
||||
* The OutputStream is expected to be closed by the caller of this method.
|
||||
*/
|
||||
OutputStream write(int x, int z) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns a {@link CompressedInputStream} that can be used to read the item from this storage at the given position
|
||||
* or null if there is no item stored.
|
||||
* The CompressedInputStream is expected to be closed by the caller of this method.
|
||||
*/
|
||||
@Nullable CompressedInputStream read(int x, int z) throws IOException;
|
||||
|
||||
/**
|
||||
* Deletes the item from this storage at the given position
|
||||
*/
|
||||
void delete(int x, int z) throws IOException;
|
||||
|
||||
/**
|
||||
* Tests if there is an item stored on the given position in this storage
|
||||
*/
|
||||
boolean exists(int x, int z) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns a stream over all <b>existing</b> items in this storage
|
||||
*/
|
||||
Stream<Cell> stream() throws IOException;
|
||||
|
||||
/**
|
||||
* Checks if this storage is closed
|
||||
*/
|
||||
boolean isClosed();
|
||||
|
||||
interface Cell extends SingleItemStorage {
|
||||
|
||||
/**
|
||||
* Returns the x position of this item in the grid
|
||||
*/
|
||||
int getX();
|
||||
|
||||
/**
|
||||
* Returns the z position of this item in the grid
|
||||
*/
|
||||
int getZ();
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("ClassCanBeRecord")
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
class GridStorageCell implements Cell {
|
||||
|
||||
private final GridStorage storage;
|
||||
private final int x, z;
|
||||
|
||||
@Override
|
||||
public OutputStream write() throws IOException {
|
||||
return storage.write(x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompressedInputStream read() throws IOException {
|
||||
return storage.read(x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() throws IOException {
|
||||
storage.delete(x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() throws IOException {
|
||||
return storage.exists(x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return storage.isClosed();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package de.bluecolored.bluemap.core.storage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.DoublePredicate;
|
||||
|
||||
public interface MapStorage {
|
||||
|
||||
/**
|
||||
* Returns the {@link GridStorage} holding the maps hires-tiles
|
||||
*/
|
||||
GridStorage hiresTiles();
|
||||
|
||||
/**
|
||||
* Returns the {@link GridStorage} holding the maps lowres-tiles of the given lod level
|
||||
*/
|
||||
GridStorage lowresTiles(int lod);
|
||||
|
||||
/**
|
||||
* Returns a {@link SingleItemStorage} for a map asset with the given name
|
||||
*/
|
||||
SingleItemStorage asset(String name);
|
||||
|
||||
/**
|
||||
* Returns a {@link SingleItemStorage} for the render-state data of this map
|
||||
*/
|
||||
SingleItemStorage renderState();
|
||||
|
||||
/**
|
||||
* Returns a {@link SingleItemStorage} for the settings (settings.json) of this map
|
||||
*/
|
||||
SingleItemStorage settings();
|
||||
|
||||
/**
|
||||
* Returns a {@link SingleItemStorage} for the texture-data (textures.json) of this map
|
||||
*/
|
||||
SingleItemStorage textures();
|
||||
|
||||
/**
|
||||
* Returns a {@link SingleItemStorage} for the marker-data (live/markers.json) of this map
|
||||
*/
|
||||
SingleItemStorage markers();
|
||||
|
||||
/**
|
||||
* Returns a {@link SingleItemStorage} for the player-data (live/players.json) of this map
|
||||
*/
|
||||
SingleItemStorage players();
|
||||
|
||||
/**
|
||||
* Deletes the entire map from the storage
|
||||
*/
|
||||
default void delete() throws IOException {
|
||||
delete(info -> true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the entire map from the storage
|
||||
* @param onProgress a function that takes in a progress-percentage and returns true
|
||||
* if the deletion should continue or false if it should be aborted.
|
||||
* No guarantees are made on how often (if at all) this method is actually being called and if the
|
||||
* progress is actually aborted when false is returned.
|
||||
*/
|
||||
void delete(DoublePredicate onProgress) throws IOException;
|
||||
|
||||
/**
|
||||
* Tests whether this map currently exists on the storage or not
|
||||
*/
|
||||
boolean exists() throws IOException;
|
||||
|
||||
/**
|
||||
* Checks if this storage is closed
|
||||
*/
|
||||
boolean isClosed();
|
||||
|
||||
static String escapeAssetName(String name) {
|
||||
return name
|
||||
.replaceAll("[^\\w\\d.\\-_/]", "_")
|
||||
.replace("..", "_.");
|
||||
}
|
||||
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
/*
|
||||
* 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 interface MetaInfo {
|
||||
|
||||
InputStream readMeta() throws IOException;
|
||||
|
||||
long getSize();
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package de.bluecolored.bluemap.core.storage;
|
||||
|
||||
import de.bluecolored.bluemap.core.storage.compression.CompressedInputStream;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public interface SingleItemStorage {
|
||||
|
||||
/**
|
||||
* Returns an {@link OutputStream} that can be used to write the item-data of this storage
|
||||
* (overwriting any existing item).
|
||||
* The OutputStream is expected to be closed by the caller of this method.
|
||||
*/
|
||||
OutputStream write() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns a {@link CompressedInputStream} that can be used to read the item-data from this storage
|
||||
* or null if there is nothing stored.
|
||||
* The CompressedInputStream is expected to be closed by the caller of this method.
|
||||
*/
|
||||
@Nullable CompressedInputStream read() throws IOException;
|
||||
|
||||
/**
|
||||
* Deletes the item from this storage
|
||||
*/
|
||||
void delete() throws IOException;
|
||||
|
||||
/**
|
||||
* Tests if this item of this storage exists
|
||||
*/
|
||||
boolean exists() throws IOException;
|
||||
|
||||
/**
|
||||
* Checks if this storage is closed
|
||||
*/
|
||||
boolean isClosed();
|
||||
|
||||
}
|
@ -1,143 +1,30 @@
|
||||
/*
|
||||
* 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 com.flowpowered.math.vector.Vector2i;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class Storage implements Closeable {
|
||||
public interface Storage extends Closeable {
|
||||
|
||||
public abstract void initialize() throws IOException;
|
||||
/**
|
||||
* Does everything necessary to initialize this storage.
|
||||
* (E.g. create tables on a database if they don't exist or upgrade older data).
|
||||
*/
|
||||
void initialize() throws IOException;
|
||||
|
||||
public abstract OutputStream writeMapTile(String mapId, int lod, Vector2i tile) throws IOException;
|
||||
/**
|
||||
* Returns the {@link MapStorage} for the given mapId
|
||||
*/
|
||||
MapStorage map(String mapId);
|
||||
|
||||
public abstract Optional<CompressedInputStream> readMapTile(String mapId, int lod, Vector2i tile) throws IOException;
|
||||
/**
|
||||
* Fetches and returns a stream of all map-id's in this storage
|
||||
*/
|
||||
Stream<String> mapIds() throws IOException;
|
||||
|
||||
public abstract Optional<TileInfo> readMapTileInfo(String mapId, int lod, Vector2i tile) throws IOException;
|
||||
|
||||
public abstract void deleteMapTile(String mapId, int lod, Vector2i tile) throws IOException;
|
||||
|
||||
public abstract OutputStream writeMeta(String mapId, String name) throws IOException;
|
||||
|
||||
public abstract Optional<InputStream> readMeta(String mapId, String name) throws IOException;
|
||||
|
||||
public abstract Optional<MetaInfo> readMetaInfo(String mapId, String name) throws IOException;
|
||||
|
||||
public abstract void deleteMeta(String mapId, String name) throws IOException;
|
||||
|
||||
public abstract void purgeMap(String mapId, Function<ProgressInfo, Boolean> onProgress) throws IOException;
|
||||
|
||||
public abstract Collection<String> collectMapIds() throws IOException;
|
||||
|
||||
public MapStorage mapStorage(final String mapId) {
|
||||
return new MapStorage(mapId);
|
||||
}
|
||||
|
||||
public TileStorage tileStorage(final String mapId, final int lod) {
|
||||
return new TileStorage(mapId, lod);
|
||||
}
|
||||
|
||||
public abstract boolean isClosed();
|
||||
|
||||
public class MapStorage {
|
||||
|
||||
private final String mapId;
|
||||
|
||||
private MapStorage(String mapId) {
|
||||
this.mapId = mapId;
|
||||
}
|
||||
|
||||
public OutputStream write(int lod, Vector2i tile) throws IOException {
|
||||
return writeMapTile(mapId, lod, tile);
|
||||
}
|
||||
|
||||
public Optional<CompressedInputStream> read(int lod, Vector2i tile) throws IOException {
|
||||
return readMapTile(mapId, lod, tile);
|
||||
}
|
||||
|
||||
public void delete(int lod, Vector2i tile) throws IOException {
|
||||
deleteMapTile(mapId, lod, tile);
|
||||
}
|
||||
|
||||
public Storage getStorage() {
|
||||
return Storage.this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class TileStorage {
|
||||
|
||||
private final String mapId;
|
||||
private final int lod;
|
||||
|
||||
private TileStorage(String mapId, int lod) {
|
||||
this.mapId = mapId;
|
||||
this.lod = lod;
|
||||
}
|
||||
|
||||
public OutputStream write(Vector2i tile) throws IOException {
|
||||
return writeMapTile(mapId, lod, tile);
|
||||
}
|
||||
|
||||
public Optional<CompressedInputStream> read(Vector2i tile) throws IOException {
|
||||
return readMapTile(mapId, lod, tile);
|
||||
}
|
||||
|
||||
public void delete(Vector2i tile) throws IOException {
|
||||
deleteMapTile(mapId, lod, tile);
|
||||
}
|
||||
|
||||
public Storage getStorage() {
|
||||
return Storage.this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ProgressInfo {
|
||||
|
||||
private final double progress;
|
||||
|
||||
public ProgressInfo(double progress) {
|
||||
this.progress = progress;
|
||||
}
|
||||
|
||||
public double getProgress() {
|
||||
return progress;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static String escapeMetaName(String name) {
|
||||
return name.replaceAll("[^\\w\\d.\\-_/]", "_").replace("..", "_.");
|
||||
}
|
||||
/**
|
||||
* Checks if this storage is closed
|
||||
*/
|
||||
boolean isClosed();
|
||||
|
||||
}
|
||||
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public interface TileInfo {
|
||||
|
||||
CompressedInputStream readMapTile() throws IOException;
|
||||
|
||||
Compression getCompression();
|
||||
|
||||
long getSize();
|
||||
|
||||
long getLastModified();
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package de.bluecolored.bluemap.core.storage.compression;
|
||||
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class BufferedCompression implements Compression {
|
||||
|
||||
@Getter private final Key key;
|
||||
@Getter private final String id;
|
||||
@Getter private final String fileSuffix;
|
||||
private final StreamTransformer<OutputStream> compressor;
|
||||
private final StreamTransformer<InputStream> decompressor;
|
||||
|
||||
@Override
|
||||
public OutputStream compress(OutputStream out) throws IOException {
|
||||
return new BufferedOutputStream(compressor.apply(out));
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream decompress(InputStream in) throws IOException {
|
||||
return new BufferedInputStream(decompressor.apply(in));
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface StreamTransformer<T> {
|
||||
T apply(T original) throws IOException;
|
||||
}
|
||||
|
||||
}
|
@ -22,26 +22,40 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.storage;
|
||||
package de.bluecolored.bluemap.core.storage.compression;
|
||||
|
||||
import de.bluecolored.bluemap.core.util.stream.DelegateInputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* An InputStream that is aware of the {@link Compression} that it's data is compressed with.
|
||||
*/
|
||||
public class CompressedInputStream extends DelegateInputStream {
|
||||
|
||||
private final Compression compression;
|
||||
|
||||
/**
|
||||
* Creates a new CompressedInputStream from an <b>already compressed</b> {@link InputStream} and the {@link Compression}
|
||||
* it is compressed with.
|
||||
* This does <b>not</b> compress the provided InputStream.
|
||||
*/
|
||||
public CompressedInputStream(InputStream in, Compression compression) {
|
||||
super(in);
|
||||
this.compression = compression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the decompressed {@link InputStream}
|
||||
*/
|
||||
public InputStream decompress() throws IOException {
|
||||
return compression.decompress(in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Compression} this InputStream's data is compressed with
|
||||
*/
|
||||
public Compression getCompression() {
|
||||
return compression;
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.compression;
|
||||
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import de.bluecolored.bluemap.core.util.Keyed;
|
||||
import de.bluecolored.bluemap.core.util.Registry;
|
||||
import io.airlift.compress.zstd.ZstdInputStream;
|
||||
import io.airlift.compress.zstd.ZstdOutputStream;
|
||||
import net.jpountz.lz4.LZ4FrameInputStream;
|
||||
import net.jpountz.lz4.LZ4FrameOutputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
public interface Compression extends Keyed {
|
||||
|
||||
Compression NONE = new NoCompression(Key.bluemap("none"), "none", "");
|
||||
Compression GZIP = new BufferedCompression(Key.bluemap("gzip"), "gzip", ".gz", GZIPOutputStream::new, GZIPInputStream::new);
|
||||
Compression DEFLATE = new BufferedCompression(Key.bluemap("deflate"), "deflate", ".deflate", DeflaterOutputStream::new, InflaterInputStream::new);
|
||||
Compression ZSTD = new BufferedCompression(Key.bluemap("zstd"), "zstd", ".zst", ZstdOutputStream::new, ZstdInputStream::new);
|
||||
Compression LZ4 = new BufferedCompression(Key.bluemap("lz4"), "lz4", ".lz4", LZ4FrameOutputStream::new, LZ4FrameInputStream::new);
|
||||
|
||||
Registry<Compression> REGISTRY = new Registry<>(
|
||||
NONE,
|
||||
GZIP,
|
||||
DEFLATE,
|
||||
ZSTD,
|
||||
LZ4
|
||||
);
|
||||
|
||||
String getId();
|
||||
|
||||
String getFileSuffix();
|
||||
|
||||
OutputStream compress(OutputStream out) throws IOException;
|
||||
|
||||
InputStream decompress(InputStream in) throws IOException;
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package de.bluecolored.bluemap.core.storage.compression;
|
||||
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class NoCompression implements Compression {
|
||||
|
||||
@Getter private final Key key;
|
||||
@Getter private final String id;
|
||||
@Getter private final String fileSuffix;
|
||||
|
||||
@Override
|
||||
public OutputStream compress(OutputStream out) throws IOException {
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream decompress(InputStream in) throws IOException {
|
||||
return in;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
package de.bluecolored.bluemap.core.storage.file;
|
||||
|
||||
import de.bluecolored.bluemap.core.storage.compression.CompressedInputStream;
|
||||
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.SingleItemStorage;
|
||||
import de.bluecolored.bluemap.core.util.DeletingPathVisitor;
|
||||
import de.bluecolored.bluemap.core.util.FileHelper;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedList;
|
||||
import java.util.function.DoublePredicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Getter
|
||||
public class FileMapStorage extends PathBasedMapStorage {
|
||||
|
||||
private final Path root;
|
||||
|
||||
public FileMapStorage(Path root, Compression compression) {
|
||||
super(
|
||||
compression,
|
||||
".prbm",
|
||||
".png"
|
||||
);
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleItemStorage file(Path file, Compression compression) {
|
||||
return new FileItemStorage(root.resolve(file), compression);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("resource")
|
||||
public Stream<Path> files(Path path) throws IOException {
|
||||
return Files.walk(root.resolve(path))
|
||||
.filter(Files::isRegularFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(DoublePredicate onProgress) throws IOException {
|
||||
if (!Files.exists(root)) return;
|
||||
|
||||
final int subFilesCount;
|
||||
final LinkedList<Path> subFiles;
|
||||
|
||||
// collect sub-files to be able to provide progress-updates
|
||||
try (Stream<Path> pathStream = Files.walk(root, 3)) {
|
||||
subFiles = pathStream.collect(Collectors.toCollection(LinkedList::new));
|
||||
}
|
||||
subFilesCount = subFiles.size();
|
||||
|
||||
// delete subFiles first to be able to track the progress and cancel
|
||||
while (!subFiles.isEmpty()) {
|
||||
Path subFile = subFiles.getLast();
|
||||
Files.walkFileTree(subFile, DeletingPathVisitor.INSTANCE);
|
||||
subFiles.removeLast();
|
||||
|
||||
if (!onProgress.test(1d - (subFiles.size() / (double) subFilesCount)))
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure everything is deleted
|
||||
if (Files.exists(root))
|
||||
Files.walkFileTree(root, DeletingPathVisitor.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() throws IOException {
|
||||
return Files.exists(root);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class FileItemStorage implements SingleItemStorage {
|
||||
|
||||
private final Path file;
|
||||
private final Compression compression;
|
||||
|
||||
@Override
|
||||
public OutputStream write() throws IOException {
|
||||
return compression.compress(FileHelper.createFilepartOutputStream(file));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompressedInputStream read() throws IOException {
|
||||
if (!Files.exists(file)) return null;
|
||||
try {
|
||||
return new CompressedInputStream(Files.newInputStream(file), compression);
|
||||
} catch (FileNotFoundException | NoSuchFileException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() throws IOException {
|
||||
Files.delete(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return Files.exists(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,62 +1,43 @@
|
||||
/*
|
||||
* 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.file;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.storage.*;
|
||||
import de.bluecolored.bluemap.core.util.DeletingPathVisitor;
|
||||
import de.bluecolored.bluemap.core.util.FileHelper;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@DebugDump
|
||||
public class FileStorage extends Storage {
|
||||
public class FileStorage implements Storage {
|
||||
|
||||
private final Path root;
|
||||
private final Compression hiresCompression;
|
||||
|
||||
public FileStorage(FileStorageSettings config) {
|
||||
this.root = config.getRoot();
|
||||
this.hiresCompression = config.getCompression();
|
||||
}
|
||||
private final LoadingCache<String, FileMapStorage> mapStorages;
|
||||
|
||||
public FileStorage(Path root, Compression compression) {
|
||||
this.root = root;
|
||||
this.hiresCompression = compression;
|
||||
|
||||
mapStorages = Caffeine.newBuilder()
|
||||
.build(id -> new FileMapStorage(root.resolve(id), compression));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {}
|
||||
public void initialize() throws IOException {}
|
||||
|
||||
@Override
|
||||
public FileMapStorage map(String mapId) {
|
||||
return mapStorages.get(mapId);
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
@Override
|
||||
public Stream<String> mapIds() throws IOException {
|
||||
return Files.list(root)
|
||||
.filter(Files::isDirectory)
|
||||
.map(Path::getFileName)
|
||||
.map(Path::toString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
@ -66,186 +47,4 @@ public boolean isClosed() {
|
||||
@Override
|
||||
public void close() throws IOException {}
|
||||
|
||||
@Override
|
||||
public OutputStream writeMapTile(String mapId, int lod, Vector2i tile) throws IOException {
|
||||
Compression compression = lod == 0 ? this.hiresCompression : Compression.NONE;
|
||||
Path file = getFilePath(mapId, lod, tile);
|
||||
|
||||
OutputStream os = FileHelper.createFilepartOutputStream(file);
|
||||
return new BufferedOutputStream(compression.compress(os));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<CompressedInputStream> readMapTile(String mapId, int lod, Vector2i tile) throws IOException {
|
||||
Compression compression = lod == 0 ? this.hiresCompression : Compression.NONE;
|
||||
Path file = getFilePath(mapId, lod, tile);
|
||||
|
||||
if (!Files.exists(file)) return Optional.empty();
|
||||
|
||||
InputStream is = Files.newInputStream(file, StandardOpenOption.READ);
|
||||
return Optional.of(new CompressedInputStream(is, compression));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<TileInfo> readMapTileInfo(String mapId, int lod, Vector2i tile) throws IOException {
|
||||
Compression compression = lod == 0 ? this.hiresCompression : Compression.NONE;
|
||||
Path file = getFilePath(mapId, lod, 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 TileInfo() {
|
||||
@Override
|
||||
public CompressedInputStream readMapTile() throws IOException {
|
||||
return FileStorage.this.readMapTile(mapId, lod, 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
|
||||
public void deleteMapTile(String mapId, int lod, Vector2i tile) throws IOException {
|
||||
Path file = getFilePath(mapId, lod, tile);
|
||||
Files.deleteIfExists(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream writeMeta(String mapId, String name) throws IOException {
|
||||
Path file = getMetaFilePath(mapId, name);
|
||||
|
||||
OutputStream os = FileHelper.createFilepartOutputStream(file);
|
||||
return new BufferedOutputStream(os);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<InputStream> readMeta(String mapId, String name) throws IOException {
|
||||
Path file = getMetaFilePath(mapId, name);
|
||||
|
||||
if (!Files.exists(file)) return Optional.empty();
|
||||
|
||||
InputStream is = Files.newInputStream(file, StandardOpenOption.READ);
|
||||
return Optional.of(new BufferedInputStream(is));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MetaInfo> readMetaInfo(String mapId, String name) throws IOException {
|
||||
Path file = getMetaFilePath(mapId, name);
|
||||
|
||||
if (!Files.exists(file)) return Optional.empty();
|
||||
|
||||
final long size = Files.size(file);
|
||||
|
||||
return Optional.of(new MetaInfo() {
|
||||
@Override
|
||||
public InputStream readMeta() throws IOException {
|
||||
return FileStorage.this.readMeta(mapId, name)
|
||||
.orElseThrow(() -> new IOException("Meta no longer present!"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteMeta(String mapId, String name) throws IOException {
|
||||
Path file = getMetaFilePath(mapId, name);
|
||||
Files.deleteIfExists(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void purgeMap(String mapId, Function<ProgressInfo, Boolean> onProgress) throws IOException {
|
||||
final Path directory = getFilePath(mapId);
|
||||
if (!Files.exists(directory)) return;
|
||||
|
||||
final int subFilesCount;
|
||||
final LinkedList<Path> subFiles;
|
||||
|
||||
// collect sub-files to be able to provide progress-updates
|
||||
try (Stream<Path> pathStream = Files.walk(directory, 3)) {
|
||||
subFiles = pathStream.collect(Collectors.toCollection(LinkedList::new));
|
||||
}
|
||||
subFilesCount = subFiles.size();
|
||||
|
||||
// delete subFiles first to be able to track the progress and cancel
|
||||
while (!subFiles.isEmpty()) {
|
||||
Path subFile = subFiles.getLast();
|
||||
Files.walkFileTree(subFile, DeletingPathVisitor.INSTANCE);
|
||||
subFiles.removeLast();
|
||||
|
||||
if (!onProgress.apply(
|
||||
new ProgressInfo(1d - (subFiles.size() / (double) subFilesCount))
|
||||
)) return;
|
||||
}
|
||||
|
||||
// make sure everything is deleted
|
||||
if (Files.exists(directory))
|
||||
Files.walkFileTree(directory, DeletingPathVisitor.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> collectMapIds() throws IOException {
|
||||
try (Stream<Path> fileStream = Files.list(root)) {
|
||||
return fileStream
|
||||
.filter(Files::isDirectory)
|
||||
.map(path -> path.getFileName().toString())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
public Path getFilePath(String mapId, int lod, Vector2i tile){
|
||||
String path = "x" + tile.getX() + "z" + tile.getY();
|
||||
char[] cs = path.toCharArray();
|
||||
List<String> folders = new ArrayList<>();
|
||||
StringBuilder folder = new StringBuilder();
|
||||
for (char c : cs){
|
||||
folder.append(c);
|
||||
if (c >= '0' && c <= '9'){
|
||||
folders.add(folder.toString());
|
||||
folder.delete(0, folder.length());
|
||||
}
|
||||
}
|
||||
String fileName = folders.remove(folders.size() - 1);
|
||||
|
||||
Path p = getFilePath(mapId).resolve("tiles").resolve(Integer.toString(lod));
|
||||
for (String s : folders){
|
||||
p = p.resolve(s);
|
||||
}
|
||||
|
||||
if (lod == 0) {
|
||||
return p.resolve(fileName + ".prbm" + hiresCompression.getFileSuffix());
|
||||
} else {
|
||||
return p.resolve(fileName + ".png");
|
||||
}
|
||||
}
|
||||
|
||||
public Path getFilePath(String mapId) {
|
||||
return root.resolve(mapId);
|
||||
}
|
||||
|
||||
public Path getMetaFilePath(String mapId, String name) {
|
||||
return getFilePath(mapId).resolve(escapeMetaName(name)
|
||||
.replace("/", root.getFileSystem().getSeparator()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,37 +0,0 @@
|
||||
/*
|
||||
* 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.file;
|
||||
|
||||
import de.bluecolored.bluemap.core.storage.Compression;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface FileStorageSettings {
|
||||
|
||||
Path getRoot();
|
||||
|
||||
Compression getCompression();
|
||||
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
package de.bluecolored.bluemap.core.storage.file;
|
||||
|
||||
import de.bluecolored.bluemap.core.storage.compression.CompressedInputStream;
|
||||
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.GridStorage;
|
||||
import de.bluecolored.bluemap.core.storage.SingleItemStorage;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
class PathBasedGridStorage implements GridStorage {
|
||||
|
||||
private static final Pattern ITEM_PATH_PATTERN = Pattern.compile("x(-?\\d+)z(-?\\d+)");
|
||||
|
||||
private final PathBasedMapStorage storage;
|
||||
private final Path root;
|
||||
private final String suffix;
|
||||
private final Compression compression;
|
||||
|
||||
@Override
|
||||
public OutputStream write(int x, int z) throws IOException {
|
||||
return item(x, z).write();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompressedInputStream read(int x, int z) throws IOException {
|
||||
return item(x, z).read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(int x, int z) throws IOException {
|
||||
item(x, z).delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(int x, int z) throws IOException {
|
||||
return item(x, z).exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Cell> stream() throws IOException {
|
||||
return storage.files(root)
|
||||
.<Cell>map(itemPath -> {
|
||||
Path path = itemPath;
|
||||
if (!path.startsWith(root)) return null;
|
||||
path = root.relativize(path);
|
||||
|
||||
String name = path.toString();
|
||||
name = name.replace(root.getFileSystem().getSeparator(), "");
|
||||
if (!name.endsWith(suffix)) return null;
|
||||
name = name.substring(name.length() - suffix.length());
|
||||
|
||||
Matcher matcher = ITEM_PATH_PATTERN.matcher(name);
|
||||
if (!matcher.matches()) return null;
|
||||
int x = Integer.parseInt(matcher.group(1));
|
||||
int z = Integer.parseInt(matcher.group(2));
|
||||
|
||||
return new PathCell(x, z, itemPath);
|
||||
})
|
||||
.filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return storage.isClosed();
|
||||
}
|
||||
|
||||
public SingleItemStorage item(int x, int z) {
|
||||
return storage.file(root.resolve(getGridPath(x, z)), compression);
|
||||
}
|
||||
|
||||
public Path getGridPath(int x, int z) {
|
||||
StringBuilder sb = new StringBuilder()
|
||||
.append('x')
|
||||
.append(x)
|
||||
.append('z')
|
||||
.append(z);
|
||||
|
||||
LinkedList<String> folders = new LinkedList<>();
|
||||
StringBuilder folder = new StringBuilder();
|
||||
sb.chars().forEach(i -> {
|
||||
char c = (char) i;
|
||||
folder.append(c);
|
||||
if (c >= '0' && c <= '9') {
|
||||
folders.add(folder.toString());
|
||||
folder.delete(0, folder.length());
|
||||
}
|
||||
});
|
||||
|
||||
String fileName = folders.removeLast();
|
||||
folders.add(fileName + suffix);
|
||||
|
||||
return Path.of(folders.removeFirst(), folders.toArray(String[]::new));
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private class PathCell implements Cell {
|
||||
|
||||
@Getter
|
||||
private final int x, z;
|
||||
|
||||
private final Path path;
|
||||
private SingleItemStorage storage;
|
||||
|
||||
@Override
|
||||
public OutputStream write() throws IOException {
|
||||
return storage().write();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompressedInputStream read() throws IOException {
|
||||
return storage().read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() throws IOException {
|
||||
storage().delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() throws IOException {
|
||||
return storage().exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return PathBasedGridStorage.this.isClosed();
|
||||
}
|
||||
|
||||
private SingleItemStorage storage() {
|
||||
if (storage == null)
|
||||
storage = PathBasedGridStorage.this.storage.file(path, compression);
|
||||
return storage;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package de.bluecolored.bluemap.core.storage.file;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.GridStorage;
|
||||
import de.bluecolored.bluemap.core.storage.MapStorage;
|
||||
import de.bluecolored.bluemap.core.storage.SingleItemStorage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileVisitOption;
|
||||
import java.nio.file.Path;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class PathBasedMapStorage implements MapStorage {
|
||||
|
||||
public static final Path SETTINGS_PATH = Path.of("settings.json");
|
||||
public static final Path TEXTURES_PATH = Path.of("textures.json");
|
||||
public static final Path RENDER_STATE_PATH = Path.of(".rstate");
|
||||
public static final Path MARKERS_PATH = Path.of("live", "markers.json");
|
||||
public static final Path PLAYERS_PATH = Path.of("live", "players.json");
|
||||
|
||||
private final GridStorage hiresGridStorage;
|
||||
private final LoadingCache<Integer, GridStorage> lowresGridStorages;
|
||||
|
||||
public PathBasedMapStorage(Compression compression, String hiresSuffix, String lowresSuffix) {
|
||||
this.hiresGridStorage = new PathBasedGridStorage(
|
||||
this,
|
||||
Path.of("tiles", "0"),
|
||||
hiresSuffix + compression.getFileSuffix(),
|
||||
compression
|
||||
);
|
||||
|
||||
this.lowresGridStorages = Caffeine.newBuilder().build(lod -> new PathBasedGridStorage(
|
||||
this,
|
||||
Path.of("tiles", String.valueOf(lod)),
|
||||
lowresSuffix,
|
||||
Compression.NONE
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public GridStorage hiresTiles() {
|
||||
return hiresGridStorage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GridStorage lowresTiles(int lod) {
|
||||
return lowresGridStorages.get(lod);
|
||||
}
|
||||
|
||||
public Path getAssetPath(String name) {
|
||||
String[] parts = MapStorage.escapeAssetName(name)
|
||||
.split("/");
|
||||
return Path.of("assets", parts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleItemStorage asset(String name) {
|
||||
return file(getAssetPath(name), Compression.NONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleItemStorage renderState() {
|
||||
return file(RENDER_STATE_PATH, Compression.NONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleItemStorage settings() {
|
||||
return file(SETTINGS_PATH, Compression.NONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleItemStorage textures() {
|
||||
return file(TEXTURES_PATH, Compression.NONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleItemStorage markers() {
|
||||
return file(MARKERS_PATH, Compression.NONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleItemStorage players() {
|
||||
return file(PLAYERS_PATH, Compression.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link SingleItemStorage} for a file with the given path and compression.
|
||||
* The file does not have to actually exist.
|
||||
*/
|
||||
public abstract SingleItemStorage file(Path file, Compression compression);
|
||||
|
||||
/**
|
||||
* Returns a stream with all file-paths of existing files at or below the given path.
|
||||
* (Including files in potential sub-folders)<br>
|
||||
* Basically, this method should mimic the functionality of
|
||||
* {@link java.nio.file.Files#walk(Path, FileVisitOption...)}
|
||||
*/
|
||||
public abstract Stream<Path> files(Path path) throws IOException;
|
||||
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
package de.bluecolored.bluemap.core.storage.sql;
|
||||
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.Driver;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.SQLRecoverableException;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class Database implements Closeable {
|
||||
|
||||
private final DataSource dataSource;
|
||||
private boolean isClosed = false;
|
||||
|
||||
public Database(String url, Map<String, String> properties, int maxPoolSize) {
|
||||
Properties props = new Properties();
|
||||
props.putAll(properties);
|
||||
|
||||
this.dataSource = createDataSource(new DriverManagerConnectionFactory(url, props), maxPoolSize);
|
||||
}
|
||||
|
||||
public Database(String url, Map<String, String> properties, int maxPoolSize, Driver driver) {
|
||||
Properties props = new Properties();
|
||||
props.putAll(properties);
|
||||
|
||||
ConnectionFactory connectionFactory = new DriverConnectionFactory(
|
||||
driver,
|
||||
url,
|
||||
props
|
||||
);
|
||||
|
||||
this.dataSource = createDataSource(connectionFactory, maxPoolSize);
|
||||
}
|
||||
|
||||
public void run(ConnectionConsumer action) throws IOException {
|
||||
run((ConnectionFunction<Void>) action);
|
||||
}
|
||||
|
||||
public <R> R run(ConnectionFunction<R> action) throws IOException {
|
||||
SQLException sqlException = null;
|
||||
|
||||
try {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
try (Connection connection = dataSource.getConnection()) {
|
||||
try {
|
||||
R result = action.apply(connection);
|
||||
connection.commit();
|
||||
return result;
|
||||
} catch (SQLRecoverableException ex) {
|
||||
if (sqlException == null) {
|
||||
sqlException = ex;
|
||||
} else {
|
||||
sqlException.addSuppressed(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
if (sqlException != null)
|
||||
ex.addSuppressed(sqlException);
|
||||
throw new IOException(ex);
|
||||
} catch (IOException | RuntimeException ex) {
|
||||
if (sqlException != null)
|
||||
ex.addSuppressed(sqlException);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
throw new IOException(sqlException);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
isClosed = true;
|
||||
if (dataSource instanceof AutoCloseable) {
|
||||
try {
|
||||
((AutoCloseable) dataSource).close();
|
||||
} catch (IOException ex) {
|
||||
throw ex;
|
||||
} catch (Exception ex) {
|
||||
throw new IOException("Failed to close datasource!", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DataSource createDataSource(ConnectionFactory connectionFactory, int maxPoolSize) {
|
||||
PoolableConnectionFactory poolableConnectionFactory =
|
||||
new PoolableConnectionFactory(() -> {
|
||||
Logger.global.logDebug("Creating new SQL-Connection...");
|
||||
return connectionFactory.createConnection();
|
||||
}, null);
|
||||
poolableConnectionFactory.setPoolStatements(true);
|
||||
poolableConnectionFactory.setMaxOpenPreparedStatements(20);
|
||||
poolableConnectionFactory.setDefaultAutoCommit(false);
|
||||
poolableConnectionFactory.setAutoCommitOnReturn(false);
|
||||
poolableConnectionFactory.setRollbackOnReturn(true);
|
||||
poolableConnectionFactory.setFastFailValidation(true);
|
||||
|
||||
GenericObjectPoolConfig<PoolableConnection> objectPoolConfig = new GenericObjectPoolConfig<>();
|
||||
objectPoolConfig.setTestWhileIdle(true);
|
||||
objectPoolConfig.setTimeBetweenEvictionRuns(Duration.ofSeconds(10));
|
||||
objectPoolConfig.setNumTestsPerEvictionRun(3);
|
||||
objectPoolConfig.setBlockWhenExhausted(true);
|
||||
objectPoolConfig.setMinIdle(1);
|
||||
objectPoolConfig.setMaxIdle(Runtime.getRuntime().availableProcessors());
|
||||
objectPoolConfig.setMaxTotal(maxPoolSize);
|
||||
objectPoolConfig.setMaxWaitMillis(Duration.ofSeconds(30).toMillis());
|
||||
|
||||
ObjectPool<PoolableConnection> connectionPool =
|
||||
new GenericObjectPool<>(poolableConnectionFactory, objectPoolConfig);
|
||||
poolableConnectionFactory.setPool(connectionPool);
|
||||
|
||||
return new PoolingDataSource<>(connectionPool);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ConnectionConsumer extends ConnectionFunction<Void> {
|
||||
|
||||
void accept(java.sql.Connection connection) throws SQLException, IOException;
|
||||
|
||||
@Override
|
||||
default Void apply(java.sql.Connection connection) throws SQLException, IOException {
|
||||
accept(connection);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ConnectionFunction<R> {
|
||||
|
||||
R apply(java.sql.Connection connection) throws SQLException, IOException;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
/*
|
||||
* 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 de.bluecolored.bluemap.core.storage.sql.dialect.MySQLDialect;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
public class MySQLStorage extends SQLStorage {
|
||||
|
||||
public MySQLStorage(SQLStorageSettings config) throws MalformedURLException, SQLDriverException {
|
||||
super(MySQLDialect.INSTANCE, config);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package de.bluecolored.bluemap.core.storage.sql;
|
||||
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
public class PageSpliterator<T> implements Spliterator<T> {
|
||||
|
||||
private final IntFunction<T[]> pageSupplier;
|
||||
private T[] lastBatch;
|
||||
private int pos;
|
||||
private int page;
|
||||
|
||||
public PageSpliterator(IntFunction<T[]> pageSupplier) {
|
||||
this.pageSupplier = pageSupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean tryAdvance(Consumer<? super T> action) {
|
||||
if (!refill()) return false;
|
||||
action.accept(lastBatch[pos++]);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Spliterator<T> trySplit() {
|
||||
if (!refill()) return null;
|
||||
int from = pos;
|
||||
pos = lastBatch.length;
|
||||
return Spliterators.spliterator(
|
||||
lastBatch, from, pos,
|
||||
characteristics()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long estimateSize() {
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int characteristics() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private synchronized boolean refill() {
|
||||
if (lastBatch != null && pos < lastBatch.length) return true;
|
||||
pos = 0;
|
||||
lastBatch = pageSupplier.apply(page++);
|
||||
return lastBatch != null && lastBatch.length > 0;
|
||||
}
|
||||
|
||||
}
|
@ -1,144 +0,0 @@
|
||||
/*
|
||||
* 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 de.bluecolored.bluemap.core.storage.CompressedInputStream;
|
||||
import de.bluecolored.bluemap.core.storage.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.sql.dialect.Dialect;
|
||||
import de.bluecolored.bluemap.core.storage.sql.dialect.PostgresDialect;
|
||||
import de.bluecolored.bluemap.core.util.stream.OnCloseOutputStream;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
|
||||
public class PostgreSQLStorage extends SQLStorage {
|
||||
|
||||
public PostgreSQLStorage(SQLStorageSettings config) throws MalformedURLException, SQLDriverException {
|
||||
super(PostgresDialect.INSTANCE, config);
|
||||
}
|
||||
|
||||
public PostgreSQLStorage(Dialect dialect, SQLStorageSettings config) throws MalformedURLException, SQLDriverException {
|
||||
super(dialect, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream writeMapTile(String mapId, int lod, Vector2i tile) throws IOException {
|
||||
Compression compression = lod == 0 ? this.hiresCompression : Compression.NONE;
|
||||
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
||||
return new OnCloseOutputStream(new BufferedOutputStream(compression.compress(byteOut)), () -> {
|
||||
int mapFK = getMapFK(mapId);
|
||||
int tileCompressionFK = getMapTileCompressionFK(compression);
|
||||
|
||||
recoveringConnection(connection -> {
|
||||
executeUpdate(connection, this.dialect.writeMapTile(),
|
||||
mapFK,
|
||||
lod,
|
||||
tile.getX(),
|
||||
tile.getY(),
|
||||
tileCompressionFK,
|
||||
byteOut.toByteArray()
|
||||
);
|
||||
}, 2);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream writeMeta(String mapId, String name) {
|
||||
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
||||
return new OnCloseOutputStream(byteOut, () -> {
|
||||
int mapFK = getMapFK(mapId);
|
||||
recoveringConnection(connection -> {
|
||||
executeUpdate(connection, this.dialect.writeMeta(),
|
||||
mapFK,
|
||||
name,
|
||||
byteOut.toByteArray()
|
||||
);
|
||||
}, 2);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<CompressedInputStream> readMapTile(String mapId, int lod, Vector2i tile) throws IOException {
|
||||
Compression compression = lod == 0 ? this.hiresCompression : Compression.NONE;
|
||||
|
||||
try {
|
||||
byte[] data = recoveringConnection(connection -> {
|
||||
ResultSet result = executeQuery(connection, this.dialect.readMapTile(),
|
||||
mapId,
|
||||
lod,
|
||||
tile.getX(),
|
||||
tile.getY(),
|
||||
compression.getTypeId()
|
||||
);
|
||||
|
||||
if (result.next()) {
|
||||
return result.getBytes(1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, 2);
|
||||
|
||||
if (data == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
InputStream inputStream = new ByteArrayInputStream(data);
|
||||
return Optional.of(new CompressedInputStream(inputStream, compression));
|
||||
} catch (SQLException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<InputStream> readMeta(String mapId, String name) throws IOException {
|
||||
try {
|
||||
byte[] data = recoveringConnection(connection -> {
|
||||
ResultSet result = executeQuery(connection, this.dialect.readMeta(),
|
||||
mapId,
|
||||
escapeMetaName(name)
|
||||
);
|
||||
if (result.next()) {
|
||||
return result.getBytes(1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, 2);
|
||||
|
||||
if (data == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
InputStream inputStream = new ByteArrayInputStream(data);
|
||||
return Optional.of(inputStream);
|
||||
} catch (SQLException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public class SQLDriverException extends Exception {
|
||||
|
||||
public SQLDriverException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SQLDriverException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public SQLDriverException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public SQLDriverException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
package de.bluecolored.bluemap.core.storage.sql;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import de.bluecolored.bluemap.core.storage.GridStorage;
|
||||
import de.bluecolored.bluemap.core.storage.MapStorage;
|
||||
import de.bluecolored.bluemap.core.storage.SingleItemStorage;
|
||||
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.sql.commandset.CommandSet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.DoublePredicate;
|
||||
|
||||
public class SQLMapStorage implements MapStorage {
|
||||
|
||||
public static final String SETTINGS_META_NAME = "settings.json";
|
||||
public static final String TEXTURES_META_NAME = "textures.json";
|
||||
public static final String RENDER_STATE_META_NAME = ".rstate";
|
||||
public static final String MARKERS_META_NAME = "live/markers.json";
|
||||
public static final String PLAYERS_META_NAME = "live/players.json";
|
||||
|
||||
private final String mapId;
|
||||
private final CommandSet sql;
|
||||
|
||||
private final SQLTileStorage hiresTileStorage;
|
||||
private final LoadingCache<Integer, GridStorage> lowresGridStorages;
|
||||
|
||||
private final SingleItemStorage renderStateStorage;
|
||||
private final SingleItemStorage settingsStorage;
|
||||
private final SingleItemStorage texturesStorage;
|
||||
private final SingleItemStorage markersStorage;
|
||||
private final SingleItemStorage playersStorage;
|
||||
|
||||
public SQLMapStorage(String mapId, CommandSet sql, Compression compression) {
|
||||
this.mapId = mapId;
|
||||
this.sql = sql;
|
||||
|
||||
this.hiresTileStorage = new SQLTileStorage(
|
||||
sql,
|
||||
mapId,
|
||||
0,
|
||||
compression
|
||||
);
|
||||
|
||||
this.lowresGridStorages = Caffeine.newBuilder().build(lod -> new SQLTileStorage(
|
||||
sql,
|
||||
mapId,
|
||||
lod,
|
||||
Compression.NONE
|
||||
));
|
||||
|
||||
renderStateStorage = meta(RENDER_STATE_META_NAME, Compression.NONE);
|
||||
settingsStorage = meta(SETTINGS_META_NAME, Compression.NONE);
|
||||
texturesStorage = meta(TEXTURES_META_NAME, Compression.NONE);
|
||||
markersStorage = meta(MARKERS_META_NAME, Compression.NONE);
|
||||
playersStorage = meta(PLAYERS_META_NAME, Compression.NONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GridStorage hiresTiles() {
|
||||
return hiresTileStorage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GridStorage lowresTiles(int lod) {
|
||||
return lowresGridStorages.get(lod);
|
||||
}
|
||||
|
||||
public String getAssetMetaName(String assetName) {
|
||||
return "assets/" + MapStorage.escapeAssetName(assetName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleItemStorage asset(String name) {
|
||||
return meta(getAssetMetaName(name), Compression.NONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleItemStorage renderState() {
|
||||
return renderStateStorage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleItemStorage settings() {
|
||||
return settingsStorage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleItemStorage textures() {
|
||||
return texturesStorage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleItemStorage markers() {
|
||||
return markersStorage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleItemStorage players() {
|
||||
return playersStorage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(DoublePredicate onProgress) throws IOException {
|
||||
|
||||
// delete tiles in 1000er steps to track progress
|
||||
int tileCount = sql.countAllMapTiles(mapId);
|
||||
if (tileCount > 0) {
|
||||
int totalDeleted = 0;
|
||||
int deleted = 0;
|
||||
do {
|
||||
deleted = sql.purgeMapTiles(mapId, 1000);
|
||||
totalDeleted += deleted;
|
||||
|
||||
if (onProgress.test((double) totalDeleted / tileCount))
|
||||
return;
|
||||
|
||||
} while (deleted > 0 && totalDeleted < tileCount);
|
||||
}
|
||||
|
||||
// finally purge the map
|
||||
sql.purgeMap(mapId);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() throws IOException {
|
||||
return sql.hasMap(mapId);
|
||||
}
|
||||
|
||||
private SingleItemStorage meta(String name, Compression compression) {
|
||||
return new SQLMetaItemStorage(sql, mapId, name, compression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return sql.isClosed();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package de.bluecolored.bluemap.core.storage.sql;
|
||||
|
||||
import de.bluecolored.bluemap.core.storage.compression.CompressedInputStream;
|
||||
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.SingleItemStorage;
|
||||
import de.bluecolored.bluemap.core.storage.sql.commandset.CommandSet;
|
||||
import de.bluecolored.bluemap.core.util.stream.OnCloseOutputStream;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class SQLMetaItemStorage implements SingleItemStorage {
|
||||
|
||||
private final CommandSet sql;
|
||||
private final String mapId;
|
||||
private final String itemName;
|
||||
private final Compression compression;
|
||||
|
||||
@Override
|
||||
public OutputStream write() throws IOException {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
return new OnCloseOutputStream(compression.compress(bytes),
|
||||
() -> sql.writeMapMeta(mapId, itemName, bytes.toByteArray())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable CompressedInputStream read() throws IOException {
|
||||
byte[] data = sql.readMapMeta(mapId, itemName);
|
||||
if (data == null) return null;
|
||||
return new CompressedInputStream(new ByteArrayInputStream(data), compression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() throws IOException {
|
||||
sql.deleteMapMeta(mapId, itemName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() throws IOException {
|
||||
return sql.hasMapMeta(mapId, itemName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return sql.isClosed();
|
||||
}
|
||||
|
||||
}
|
@ -1,703 +1,59 @@
|
||||
/*
|
||||
* 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.BlueMap;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.storage.*;
|
||||
import de.bluecolored.bluemap.core.storage.sql.dialect.DialectType;
|
||||
import de.bluecolored.bluemap.core.storage.sql.dialect.Dialect;
|
||||
import de.bluecolored.bluemap.core.util.stream.OnCloseOutputStream;
|
||||
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 org.intellij.lang.annotations.Language;
|
||||
import de.bluecolored.bluemap.core.storage.MapStorage;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.sql.commandset.CommandSet;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.sql.*;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.function.Function;
|
||||
import java.io.IOException;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
public abstract class SQLStorage extends Storage {
|
||||
@RequiredArgsConstructor
|
||||
public class SQLStorage implements Storage {
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
protected final Dialect dialect;
|
||||
protected final Compression hiresCompression;
|
||||
|
||||
private final LoadingCache<String, Integer> mapFKs = Caffeine.newBuilder()
|
||||
.executor(BlueMap.THREAD_POOL)
|
||||
.build(this::loadMapFK);
|
||||
private final LoadingCache<Compression, Integer> mapTileCompressionFKs = Caffeine.newBuilder()
|
||||
.executor(BlueMap.THREAD_POOL)
|
||||
.build(this::loadMapTileCompressionFK);
|
||||
|
||||
private volatile boolean closed;
|
||||
|
||||
public SQLStorage(Dialect dialect, SQLStorageSettings config) throws MalformedURLException, SQLDriverException {
|
||||
this.dialect = dialect;
|
||||
this.closed = false;
|
||||
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 SQLDriverException("Failed to create an instance of the driver-class", 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.getConnectionUrl(), config.getConnectionProperties(), config.getMaxConnections(), driver);
|
||||
} else {
|
||||
Class.forName(config.getDriverClass().get());
|
||||
this.dataSource = createDataSource(config.getConnectionUrl(), config.getConnectionProperties(), config.getMaxConnections());
|
||||
}
|
||||
} else {
|
||||
this.dataSource = createDataSource(config.getConnectionUrl(), config.getConnectionProperties(), config.getMaxConnections());
|
||||
}
|
||||
} catch (ClassNotFoundException ex) {
|
||||
throw new SQLDriverException("The driver-class does not exist.", ex);
|
||||
//throw new ConfigurationException("The path to your driver-jar is invalid. Check your sql-storage-config!", ex);
|
||||
//throw new ConfigurationException("The driver-class does not exist. Check your sql-storage-config!", ex);
|
||||
}
|
||||
|
||||
this.hiresCompression = config.getCompression();
|
||||
}
|
||||
private final CommandSet sql;
|
||||
private final Compression compression;
|
||||
private final LoadingCache<String, SQLMapStorage> mapStorages = Caffeine.newBuilder()
|
||||
.build(this::create);
|
||||
|
||||
@Override
|
||||
public OutputStream writeMapTile(String mapId, int lod, Vector2i tile) throws IOException {
|
||||
Compression compression = lod == 0 ? this.hiresCompression : Compression.NONE;
|
||||
|
||||
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
||||
return new OnCloseOutputStream(new BufferedOutputStream(compression.compress(byteOut)), () -> {
|
||||
int mapFK = getMapFK(mapId);
|
||||
int tileCompressionFK = getMapTileCompressionFK(compression);
|
||||
|
||||
recoveringConnection(connection -> {
|
||||
Blob dataBlob = connection.createBlob();
|
||||
try {
|
||||
try (OutputStream blobOut = dataBlob.setBinaryStream(1)) {
|
||||
byteOut.writeTo(blobOut);
|
||||
}
|
||||
|
||||
executeUpdate(connection, this.dialect.writeMapTile(),
|
||||
mapFK,
|
||||
lod,
|
||||
tile.getX(),
|
||||
tile.getY(),
|
||||
tileCompressionFK,
|
||||
dataBlob
|
||||
);
|
||||
} finally {
|
||||
dataBlob.free();
|
||||
}
|
||||
}, 2);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<CompressedInputStream> readMapTile(String mapId, int lod, Vector2i tile) throws IOException {
|
||||
Compression compression = lod == 0 ? this.hiresCompression : Compression.NONE;
|
||||
|
||||
try {
|
||||
byte[] data = recoveringConnection(connection -> {
|
||||
ResultSet result = executeQuery(connection,
|
||||
this.dialect.readMapTile(),
|
||||
mapId,
|
||||
lod,
|
||||
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<TileInfo> readMapTileInfo(final String mapId, int lod, final Vector2i tile) throws IOException {
|
||||
Compression compression = lod == 0 ? this.hiresCompression : Compression.NONE;
|
||||
|
||||
try {
|
||||
TileInfo tileInfo = recoveringConnection(connection -> {
|
||||
ResultSet result = executeQuery(connection,
|
||||
this.dialect.readMapTileInfo(),
|
||||
mapId,
|
||||
lod,
|
||||
tile.getX(),
|
||||
tile.getY(),
|
||||
compression.getTypeId()
|
||||
);
|
||||
|
||||
if (result.next()) {
|
||||
final long lastModified = result.getTimestamp("changed").getTime();
|
||||
final long size = result.getLong("size");
|
||||
|
||||
return new TileInfo() {
|
||||
@Override
|
||||
public CompressedInputStream readMapTile() throws IOException {
|
||||
return SQLStorage.this.readMapTile(mapId, lod, 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(tileInfo);
|
||||
} catch (SQLException | NoSuchElementException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteMapTile(String mapId, int lod, Vector2i tile) throws IOException {
|
||||
try {
|
||||
recoveringConnection(connection ->
|
||||
executeUpdate(connection,this.dialect.deleteMapTile(),
|
||||
mapId,
|
||||
lod,
|
||||
tile.getX(),
|
||||
tile.getY()
|
||||
), 2);
|
||||
} catch (SQLException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream writeMeta(String mapId, String name) {
|
||||
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
||||
return new OnCloseOutputStream(byteOut, () -> {
|
||||
int mapFK = getMapFK(mapId);
|
||||
|
||||
recoveringConnection(connection -> {
|
||||
Blob dataBlob = connection.createBlob();
|
||||
try {
|
||||
try (OutputStream blobOut = dataBlob.setBinaryStream(1)) {
|
||||
byteOut.writeTo(blobOut);
|
||||
}
|
||||
|
||||
executeUpdate(connection,
|
||||
this.dialect.writeMeta(),
|
||||
mapFK,
|
||||
escapeMetaName(name),
|
||||
dataBlob
|
||||
);
|
||||
} finally {
|
||||
dataBlob.free();
|
||||
}
|
||||
}, 2);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<InputStream> readMeta(String mapId, String name) throws IOException {
|
||||
try {
|
||||
byte[] data = recoveringConnection(connection -> {
|
||||
ResultSet result = executeQuery(connection,
|
||||
this.dialect.readMeta(),
|
||||
mapId,
|
||||
escapeMetaName(name)
|
||||
);
|
||||
|
||||
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 Optional<MetaInfo> readMetaInfo(String mapId, String name) throws IOException {
|
||||
try {
|
||||
MetaInfo tileInfo = recoveringConnection(connection -> {
|
||||
ResultSet result = executeQuery(connection,
|
||||
this.dialect.readMetaSize(),
|
||||
mapId,
|
||||
escapeMetaName(name)
|
||||
);
|
||||
|
||||
if (result.next()) {
|
||||
final long size = result.getLong("size");
|
||||
|
||||
return new MetaInfo() {
|
||||
@Override
|
||||
public InputStream readMeta() throws IOException {
|
||||
return SQLStorage.this.readMeta(mapId, name)
|
||||
.orElseThrow(() -> new IOException("Tile no longer present!"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, 2);
|
||||
|
||||
return Optional.ofNullable(tileInfo);
|
||||
} catch (SQLException | NoSuchElementException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteMeta(String mapId, String name) throws IOException {
|
||||
try {
|
||||
recoveringConnection(connection ->
|
||||
executeUpdate(connection,
|
||||
this.dialect.deleteMeta(),
|
||||
mapId,
|
||||
escapeMetaName(name)
|
||||
), 2);
|
||||
} catch (SQLException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void purgeMap(String mapId, Function<ProgressInfo, Boolean> onProgress) throws IOException {
|
||||
synchronized (mapFKs) {
|
||||
try {
|
||||
recoveringConnection(connection -> {
|
||||
executeUpdate(connection,
|
||||
this.dialect.purgeMapTile(),
|
||||
mapId
|
||||
);
|
||||
|
||||
executeUpdate(connection,
|
||||
this.dialect.purgeMapMeta(),
|
||||
mapId
|
||||
);
|
||||
|
||||
|
||||
executeUpdate(connection,
|
||||
this.dialect.purgeMap(),
|
||||
mapId
|
||||
);
|
||||
}, 2);
|
||||
|
||||
mapFKs.invalidate(mapId);
|
||||
|
||||
} catch (SQLException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> collectMapIds() throws IOException {
|
||||
try {
|
||||
return recoveringConnection(connection -> {
|
||||
ResultSet result = executeQuery(connection,
|
||||
this.dialect.selectMapIds()
|
||||
);
|
||||
Collection<String> mapIds = new ArrayList<>();
|
||||
while (result.next()) {
|
||||
mapIds.add(result.getString("map_id"));
|
||||
}
|
||||
return mapIds;
|
||||
}, 2);
|
||||
} catch (SQLException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedAssignment")
|
||||
public void initialize() throws IOException {
|
||||
try {
|
||||
sql.initializeTables();
|
||||
}
|
||||
|
||||
// initialize and get schema-version
|
||||
String schemaVersionString = recoveringConnection(connection -> {
|
||||
connection.createStatement().executeUpdate(
|
||||
this.dialect.initializeStorageMeta());
|
||||
private SQLMapStorage create(String mapId) {
|
||||
return new SQLMapStorage(mapId, sql, compression);
|
||||
}
|
||||
|
||||
ResultSet result = executeQuery(connection,
|
||||
this.dialect.selectStorageMeta(),
|
||||
"schema_version"
|
||||
);
|
||||
@Override
|
||||
public MapStorage map(String mapId) {
|
||||
return mapStorages.get(mapId);
|
||||
}
|
||||
|
||||
if (result.next()) {
|
||||
return result.getString("value");
|
||||
} else {
|
||||
executeUpdate(connection,
|
||||
this.dialect.insertStorageMeta(),
|
||||
"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 > 3)
|
||||
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(
|
||||
this.dialect.initializeMap()
|
||||
);
|
||||
|
||||
connection.createStatement().executeUpdate(
|
||||
this.dialect.initializeMapTileCompression()
|
||||
);
|
||||
|
||||
connection.createStatement().executeUpdate(
|
||||
this.dialect.initializeMapMeta());
|
||||
|
||||
connection.createStatement().executeUpdate(
|
||||
this.dialect.initializeMapTile()
|
||||
);
|
||||
|
||||
executeUpdate(connection,
|
||||
this.dialect.updateStorageMeta(),
|
||||
"3", "schema_version"
|
||||
);
|
||||
}, 2);
|
||||
|
||||
schemaVersion = 3;
|
||||
}
|
||||
|
||||
if (schemaVersion == 1)
|
||||
throw new IOException("Outdated database schema: " + schemaVersion +
|
||||
" (Cannot automatically update, reset your database and reload bluemap to fix this)");
|
||||
|
||||
if (schemaVersion == 2) {
|
||||
Logger.global.logInfo("Updating database schema: Renaming bluemap_map_meta keys to new format...");
|
||||
recoveringConnection(connection -> {
|
||||
|
||||
// delete potential files that are already in the new format to avoid constraint-issues
|
||||
executeUpdate(connection,
|
||||
this.dialect.deleteMapMeta(),
|
||||
"settings.json", "textures.json", ".rstate"
|
||||
);
|
||||
|
||||
// rename files
|
||||
executeUpdate(connection,
|
||||
this.dialect.updateMapMeta(),
|
||||
"settings.json", "settings"
|
||||
);
|
||||
executeUpdate(connection,
|
||||
this.dialect.updateMapMeta(),
|
||||
"textures.json", "textures"
|
||||
);
|
||||
executeUpdate(connection,
|
||||
this.dialect.updateMapMeta(),
|
||||
".rstate", "render_state"
|
||||
);
|
||||
|
||||
// update schemaVersion
|
||||
executeUpdate(connection,
|
||||
this.dialect.updateStorageMeta(),
|
||||
"3", "schema_version"
|
||||
);
|
||||
}, 2);
|
||||
|
||||
schemaVersion = 3;
|
||||
}
|
||||
|
||||
} catch (SQLException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
@Override
|
||||
public Stream<String> mapIds() {
|
||||
return StreamSupport.stream(
|
||||
new PageSpliterator<>(page -> {
|
||||
try {
|
||||
return sql.listMapIds(page * 1000, 1000);
|
||||
} catch (IOException ex) { throw new RuntimeException(ex); }
|
||||
}),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return closed;
|
||||
return sql.isClosed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
this.closed = true;
|
||||
if (dataSource instanceof AutoCloseable) {
|
||||
try {
|
||||
((AutoCloseable) dataSource).close();
|
||||
} catch (Exception ex) {
|
||||
throw new IOException("Failed to close datasource!", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected ResultSet executeQuery(Connection connection, @Language("sql") String sql, Object... parameters) throws SQLException {
|
||||
return prepareStatement(connection, sql, parameters).executeQuery();
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
protected int executeUpdate(Connection connection, @Language("sql") String sql, Object... parameters) throws SQLException {
|
||||
return prepareStatement(connection, sql, parameters).executeUpdate();
|
||||
}
|
||||
|
||||
private PreparedStatement prepareStatement(Connection connection, @Language("sql") 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;
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
protected void recoveringConnection(ConnectionConsumer action, int tries) throws SQLException, IOException {
|
||||
recoveringConnection((ConnectionFunction<Void>) action, tries);
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
protected <R> R recoveringConnection(ConnectionFunction<R> action, int tries) throws SQLException, IOException {
|
||||
SQLException sqlException = null;
|
||||
|
||||
try {
|
||||
for (int i = 0; i < tries; i++) {
|
||||
try (Connection connection = dataSource.getConnection()) {
|
||||
try {
|
||||
R result = action.apply(connection);
|
||||
connection.commit();
|
||||
return result;
|
||||
} catch (SQLRecoverableException ex) {
|
||||
if (sqlException == null) {
|
||||
sqlException = ex;
|
||||
} else {
|
||||
sqlException.addSuppressed(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
protected 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;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
synchronized (mapFKs) {
|
||||
return lookupFK("bluemap_map", "id", "map_id", mapId);
|
||||
}
|
||||
}
|
||||
|
||||
private int loadMapTileCompressionFK(Compression compression) throws SQLException, IOException {
|
||||
return lookupFK("bluemap_map_tile_compression", "id", "compression", compression.getTypeId());
|
||||
}
|
||||
|
||||
@SuppressWarnings({"SameParameterValue", "SqlResolve"})
|
||||
private int lookupFK(String table, String idField, String valueField, String value) throws SQLException, IOException {
|
||||
return recoveringConnection(connection -> {
|
||||
int key;
|
||||
ResultSet result = executeQuery(connection,
|
||||
this.dialect.lookupFK(table,idField,valueField),
|
||||
value
|
||||
);
|
||||
|
||||
if (result.next()) {
|
||||
key = result.getInt("id");
|
||||
} else {
|
||||
PreparedStatement statement = connection.prepareStatement(
|
||||
this.dialect.insertFK(table,valueField),
|
||||
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, Map<String, String> properties, int maxPoolSize) {
|
||||
Properties props = new Properties();
|
||||
props.putAll(properties);
|
||||
|
||||
return createDataSource(new DriverManagerConnectionFactory(dbUrl, props), maxPoolSize);
|
||||
}
|
||||
|
||||
private DataSource createDataSource(String dbUrl, Map<String, String> properties, int maxPoolSize, Driver driver) {
|
||||
Properties props = new Properties();
|
||||
props.putAll(properties);
|
||||
|
||||
ConnectionFactory connectionFactory = new DriverConnectionFactory(
|
||||
driver,
|
||||
dbUrl,
|
||||
props
|
||||
);
|
||||
|
||||
return createDataSource(connectionFactory, maxPoolSize);
|
||||
}
|
||||
|
||||
private DataSource createDataSource(ConnectionFactory connectionFactory, int maxPoolSize) {
|
||||
PoolableConnectionFactory poolableConnectionFactory =
|
||||
new PoolableConnectionFactory(() -> {
|
||||
Logger.global.logDebug("Creating new SQL-Connection...");
|
||||
return connectionFactory.createConnection();
|
||||
}, null);
|
||||
poolableConnectionFactory.setPoolStatements(true);
|
||||
poolableConnectionFactory.setMaxOpenPreparedStatements(20);
|
||||
poolableConnectionFactory.setDefaultAutoCommit(false);
|
||||
poolableConnectionFactory.setAutoCommitOnReturn(false);
|
||||
poolableConnectionFactory.setRollbackOnReturn(true);
|
||||
poolableConnectionFactory.setFastFailValidation(true);
|
||||
|
||||
GenericObjectPoolConfig<PoolableConnection> objectPoolConfig = new GenericObjectPoolConfig<>();
|
||||
objectPoolConfig.setTestWhileIdle(true);
|
||||
objectPoolConfig.setTimeBetweenEvictionRuns(Duration.ofSeconds(10));
|
||||
objectPoolConfig.setNumTestsPerEvictionRun(3);
|
||||
objectPoolConfig.setBlockWhenExhausted(true);
|
||||
objectPoolConfig.setMinIdle(1);
|
||||
objectPoolConfig.setMaxIdle(Runtime.getRuntime().availableProcessors());
|
||||
objectPoolConfig.setMaxTotal(maxPoolSize);
|
||||
objectPoolConfig.setMaxWaitMillis(Duration.ofSeconds(30).toMillis());
|
||||
|
||||
ObjectPool<PoolableConnection> connectionPool =
|
||||
new GenericObjectPool<>(poolableConnectionFactory, objectPoolConfig);
|
||||
poolableConnectionFactory.setPool(connectionPool);
|
||||
|
||||
return new PoolingDataSource<>(connectionPool);
|
||||
}
|
||||
|
||||
public static SQLStorage create(SQLStorageSettings settings) throws Exception {
|
||||
String dbUrl = settings.getConnectionUrl();
|
||||
String provider = dbUrl.strip().split(":", 3)[1];
|
||||
return DialectType.getStorage(provider,settings);
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
sql.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* 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 de.bluecolored.bluemap.core.storage.Compression;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface SQLStorageSettings {
|
||||
|
||||
Optional<URL> getDriverJar() throws MalformedURLException;
|
||||
|
||||
Optional<String> getDriverClass();
|
||||
|
||||
String getConnectionUrl();
|
||||
|
||||
Map<String, String> getConnectionProperties();
|
||||
|
||||
int getMaxConnections();
|
||||
|
||||
Compression getCompression();
|
||||
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package de.bluecolored.bluemap.core.storage.sql;
|
||||
|
||||
import de.bluecolored.bluemap.core.storage.compression.CompressedInputStream;
|
||||
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.GridStorage;
|
||||
import de.bluecolored.bluemap.core.storage.sql.commandset.CommandSet;
|
||||
import de.bluecolored.bluemap.core.util.stream.OnCloseOutputStream;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class SQLTileStorage implements GridStorage {
|
||||
|
||||
private final CommandSet sql;
|
||||
private final String mapId;
|
||||
private final int lod;
|
||||
private final Compression compression;
|
||||
|
||||
@Override
|
||||
public OutputStream write(int x, int z) throws IOException {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
return new OnCloseOutputStream(compression.compress(bytes),
|
||||
() -> sql.writeMapTile(mapId, lod, x, z, compression, bytes.toByteArray())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable CompressedInputStream read(int x, int z) throws IOException {
|
||||
byte[] data = sql.readMapTile(mapId, lod, x, z, compression);
|
||||
if (data == null) return null;
|
||||
return new CompressedInputStream(new ByteArrayInputStream(data), compression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(int x, int z) throws IOException {
|
||||
sql.deleteMapTile(mapId, lod, x, z, compression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(int x, int z) throws IOException {
|
||||
return sql.hasMapTile(mapId, lod, x, z, compression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Cell> stream() throws IOException {
|
||||
return StreamSupport.stream(
|
||||
new PageSpliterator<>(page -> {
|
||||
try {
|
||||
return sql.listMapTiles(mapId, lod, compression, page * 1000, 1000);
|
||||
} catch (IOException ex) { throw new RuntimeException(ex); }
|
||||
}),
|
||||
false
|
||||
).map(tilePosition -> new GridStorageCell(this, tilePosition.x(), tilePosition.z()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return sql.isClosed();
|
||||
}
|
||||
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
/*
|
||||
* 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 de.bluecolored.bluemap.core.storage.sql.dialect.SqliteDialect;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
public class SQLiteStorage extends PostgreSQLStorage {
|
||||
|
||||
public SQLiteStorage(SQLStorageSettings config) throws MalformedURLException, SQLDriverException {
|
||||
super(SqliteDialect.INSTANCE, config);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,383 @@
|
||||
package de.bluecolored.bluemap.core.storage.sql.commandset;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.sql.Database;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@SuppressWarnings("SqlSourceToSinkFlow")
|
||||
@RequiredArgsConstructor
|
||||
public abstract class AbstractCommandSet implements CommandSet {
|
||||
|
||||
private final Database db;
|
||||
|
||||
final LoadingCache<String, Integer> mapKeys = Caffeine.newBuilder()
|
||||
.build(this::mapKey);
|
||||
final LoadingCache<Compression, Integer> compressionKeys = Caffeine.newBuilder()
|
||||
.build(this::compressionKey);
|
||||
|
||||
@Language("sql")
|
||||
public abstract String createMapTableStatement();
|
||||
|
||||
@Language("sql")
|
||||
public abstract String createCompressionTableStatement();
|
||||
|
||||
@Language("sql")
|
||||
public abstract String createMapMetaTableStatement();
|
||||
|
||||
@Language("sql")
|
||||
public abstract String createMapTileTableStatement();
|
||||
|
||||
@Language("sql")
|
||||
public abstract String fixLegacyCompressionIdsStatement();
|
||||
|
||||
public void initializeTables() throws IOException {
|
||||
db.run(connection -> {
|
||||
executeUpdate(connection, createMapTableStatement());
|
||||
executeUpdate(connection, createCompressionTableStatement());
|
||||
executeUpdate(connection, createMapMetaTableStatement());
|
||||
executeUpdate(connection, createMapTileTableStatement());
|
||||
});
|
||||
|
||||
db.run(connection -> executeUpdate(connection, fixLegacyCompressionIdsStatement()));
|
||||
}
|
||||
|
||||
@Language("sql")
|
||||
public abstract String writeMapTileStatement();
|
||||
|
||||
@Override
|
||||
public int writeMapTile(
|
||||
String mapId, int lod, int x, int z, Compression compression,
|
||||
byte[] bytes
|
||||
) throws IOException {
|
||||
int mapKey = Objects.requireNonNull(mapKeys.get(mapId));
|
||||
int compressionKey = Objects.requireNonNull(compressionKeys.get(compression));
|
||||
return db.run(connection -> {
|
||||
return executeUpdate(connection,
|
||||
writeMapTileStatement(),
|
||||
mapKey, lod, x, z, compressionKey,
|
||||
bytes
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Language("sql")
|
||||
public abstract String readMapTileStatement();
|
||||
|
||||
@Override
|
||||
public byte @Nullable [] readMapTile(String mapId, int lod, int x, int z, Compression compression) throws IOException {
|
||||
return db.run(connection -> {
|
||||
ResultSet result = executeQuery(connection,
|
||||
readMapTileStatement(),
|
||||
mapId, lod, x, z, compression.getKey().getFormatted()
|
||||
);
|
||||
if (!result.next()) return null;
|
||||
return result.getBytes(1);
|
||||
});
|
||||
}
|
||||
|
||||
@Language("sql")
|
||||
public abstract String deleteMapTileStatement();
|
||||
|
||||
@Override
|
||||
public int deleteMapTile(String mapId, int lod, int x, int z, Compression compression) throws IOException {
|
||||
return db.run(connection -> {
|
||||
return executeUpdate(connection,
|
||||
deleteMapTileStatement(),
|
||||
mapId, lod, x, z, compression.getKey().getFormatted()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Language("sql")
|
||||
public abstract String hasMapTileStatement();
|
||||
|
||||
@Override
|
||||
public boolean hasMapTile(String mapId, int lod, int x, int z, Compression compression) throws IOException {
|
||||
return db.run(connection -> {
|
||||
ResultSet result = executeQuery(connection,
|
||||
hasMapTileStatement(),
|
||||
mapId, lod, x, z, compression.getKey().getFormatted()
|
||||
);
|
||||
if (!result.next()) throw new IllegalStateException("Counting query returned empty result!");
|
||||
return result.getBoolean(1);
|
||||
});
|
||||
}
|
||||
|
||||
@Language("sql")
|
||||
public abstract String countAllMapTilesStatement();
|
||||
|
||||
@Override
|
||||
public int countAllMapTiles(String mapId) throws IOException {
|
||||
return db.run(connection -> {
|
||||
ResultSet result = executeQuery(connection,
|
||||
countAllMapTilesStatement(),
|
||||
mapId
|
||||
);
|
||||
if (!result.next()) throw new IllegalStateException("Counting query returned empty result!");
|
||||
return result.getInt(1);
|
||||
});
|
||||
}
|
||||
|
||||
@Language("sql")
|
||||
public abstract String purgeMapTilesStatement();
|
||||
|
||||
@Override
|
||||
public int purgeMapTiles(String mapId, int limit) throws IOException {
|
||||
return db.run(connection -> {
|
||||
return executeUpdate(connection,
|
||||
purgeMapTilesStatement(),
|
||||
mapId, limit
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Language("sql")
|
||||
public abstract String listMapTilesStatement();
|
||||
|
||||
@Override
|
||||
public TilePosition[] listMapTiles(String mapId, int lod, Compression compression, int start, int count) throws IOException {
|
||||
return db.run(connection -> {
|
||||
ResultSet result = executeQuery(connection,
|
||||
listMapTilesStatement(),
|
||||
mapId, lod, compression.getKey().getFormatted(),
|
||||
count, start
|
||||
);
|
||||
|
||||
TilePosition[] tiles = new TilePosition[count];
|
||||
int i = 0;
|
||||
while (result.next()) {
|
||||
tiles[i++] = new TilePosition(
|
||||
result.getInt(1),
|
||||
result.getInt(2)
|
||||
);
|
||||
}
|
||||
|
||||
if (i < count) {
|
||||
TilePosition[] trimmed = new TilePosition[i];
|
||||
System.arraycopy(tiles, 0, trimmed, 0, i);
|
||||
tiles = trimmed;
|
||||
}
|
||||
|
||||
return tiles;
|
||||
});
|
||||
}
|
||||
|
||||
@Language("sql")
|
||||
public abstract String writeMapMetaStatement();
|
||||
|
||||
@Override
|
||||
public int writeMapMeta(String mapId, String itemName, byte[] bytes) throws IOException {
|
||||
int mapKey = Objects.requireNonNull(mapKeys.get(mapId));
|
||||
return db.run(connection -> {
|
||||
return executeUpdate(connection,
|
||||
writeMapMetaStatement(),
|
||||
mapKey, itemName,
|
||||
bytes
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Language("sql")
|
||||
public abstract String readMapMetaStatement();
|
||||
|
||||
@Override
|
||||
public byte @Nullable [] readMapMeta(String mapId, String itemName) throws IOException {
|
||||
return db.run(connection -> {
|
||||
ResultSet result = executeQuery(connection,
|
||||
readMapMetaStatement(),
|
||||
mapId, itemName
|
||||
);
|
||||
if (!result.next()) return null;
|
||||
return result.getBytes(1);
|
||||
});
|
||||
}
|
||||
|
||||
@Language("sql")
|
||||
public abstract String deleteMapMetaStatement();
|
||||
|
||||
@Override
|
||||
public int deleteMapMeta(String mapId, String itemName) throws IOException {
|
||||
return db.run(connection -> {
|
||||
return executeUpdate(connection,
|
||||
deleteMapMetaStatement(),
|
||||
mapId, itemName
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Language("sql")
|
||||
public abstract String hasMapMetaStatement();
|
||||
|
||||
@Override
|
||||
public boolean hasMapMeta(String mapId, String itemName) throws IOException {
|
||||
return db.run(connection -> {
|
||||
ResultSet result = executeQuery(connection,
|
||||
hasMapMetaStatement(),
|
||||
mapId, itemName
|
||||
);
|
||||
if (!result.next()) throw new IllegalStateException("Counting query returned empty result!");
|
||||
return result.getBoolean(1);
|
||||
});
|
||||
}
|
||||
|
||||
@Language("sql")
|
||||
public abstract String purgeMapTileTableStatement();
|
||||
|
||||
@Language("sql")
|
||||
public abstract String purgeMapMetaTableStatement();
|
||||
|
||||
@Language("sql")
|
||||
public abstract String deleteMapStatement();
|
||||
|
||||
@Override
|
||||
public void purgeMap(String mapId) throws IOException {
|
||||
db.run(connection -> {
|
||||
|
||||
executeUpdate(connection,
|
||||
purgeMapTileTableStatement(),
|
||||
mapId
|
||||
);
|
||||
|
||||
executeUpdate(connection,
|
||||
purgeMapMetaTableStatement(),
|
||||
mapId
|
||||
);
|
||||
|
||||
executeUpdate(connection,
|
||||
deleteMapStatement(),
|
||||
mapId
|
||||
);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Language("sql")
|
||||
public abstract String hasMapStatement();
|
||||
|
||||
@Override
|
||||
public boolean hasMap(String mapId) throws IOException {
|
||||
return db.run(connection -> {
|
||||
ResultSet result = executeQuery(connection,
|
||||
hasMapStatement(),
|
||||
mapId
|
||||
);
|
||||
if (!result.next()) throw new IllegalStateException("Counting query returned empty result!");
|
||||
return result.getBoolean(1);
|
||||
});
|
||||
}
|
||||
|
||||
@Language("sql")
|
||||
public abstract String listMapIdsStatement();
|
||||
|
||||
@Override
|
||||
public String[] listMapIds(int start, int count) throws IOException {
|
||||
return db.run(connection -> {
|
||||
ResultSet result = executeQuery(connection,
|
||||
listMapIdsStatement(),
|
||||
count, start
|
||||
);
|
||||
List<String> mapIds = new ArrayList<>();
|
||||
while (result.next()) {
|
||||
mapIds.add(result.getString(1));
|
||||
}
|
||||
return mapIds.toArray(String[]::new);
|
||||
});
|
||||
}
|
||||
|
||||
@Language("sql")
|
||||
public abstract String findMapKeyStatement();
|
||||
|
||||
@Language("sql")
|
||||
public abstract String createMapKeyStatement();
|
||||
|
||||
public int mapKey(String mapId) throws IOException {
|
||||
return db.run(connection -> {
|
||||
ResultSet result = executeQuery(connection,
|
||||
findMapKeyStatement(),
|
||||
mapId
|
||||
);
|
||||
|
||||
if (result.next())
|
||||
return result.getInt(1);
|
||||
|
||||
PreparedStatement statement = connection.prepareStatement(
|
||||
createMapKeyStatement(),
|
||||
Statement.RETURN_GENERATED_KEYS
|
||||
);
|
||||
statement.setString(1, mapId);
|
||||
statement.executeUpdate();
|
||||
|
||||
ResultSet keys = statement.getGeneratedKeys();
|
||||
if (!keys.next()) throw new IllegalStateException("No generated key returned!");
|
||||
return keys.getInt(1);
|
||||
});
|
||||
}
|
||||
|
||||
@Language("sql")
|
||||
public abstract String findCompressionKeyStatement();
|
||||
|
||||
@Language("sql")
|
||||
public abstract String createCompressionKeyStatement();
|
||||
|
||||
public int compressionKey(Compression compression) throws IOException {
|
||||
return db.run(connection -> {
|
||||
ResultSet result = executeQuery(connection,
|
||||
findCompressionKeyStatement(),
|
||||
compression.getKey().getFormatted()
|
||||
);
|
||||
|
||||
if (result.next())
|
||||
return result.getInt(1);
|
||||
|
||||
PreparedStatement statement = connection.prepareStatement(
|
||||
createCompressionKeyStatement(),
|
||||
Statement.RETURN_GENERATED_KEYS
|
||||
);
|
||||
statement.setString(1, compression.getKey().getFormatted());
|
||||
statement.executeUpdate();
|
||||
|
||||
ResultSet keys = statement.getGeneratedKeys();
|
||||
if (!keys.next()) throw new IllegalStateException("No generated key returned!");
|
||||
return keys.getInt(1);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return db.isClosed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
db.close();
|
||||
}
|
||||
|
||||
protected static ResultSet executeQuery(Connection connection, @Language("sql") String sql, Object... parameters) throws SQLException {
|
||||
return prepareStatement(connection, sql, parameters).executeQuery();
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
protected static int executeUpdate(Connection connection, @Language("sql") String sql, Object... parameters) throws SQLException {
|
||||
return prepareStatement(connection, sql, parameters).executeUpdate();
|
||||
}
|
||||
|
||||
private static PreparedStatement prepareStatement(Connection connection, @Language("sql") 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package de.bluecolored.bluemap.core.storage.sql.commandset;
|
||||
|
||||
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
public interface CommandSet extends Closeable {
|
||||
|
||||
void initializeTables() throws IOException;
|
||||
|
||||
int writeMapTile(
|
||||
String mapId, int lod, int x, int z, Compression compression,
|
||||
byte[] bytes
|
||||
) throws IOException;
|
||||
|
||||
byte @Nullable [] readMapTile(
|
||||
String mapId, int lod, int x, int z, Compression compression
|
||||
) throws IOException;
|
||||
|
||||
int deleteMapTile(
|
||||
String mapId, int lod, int x, int z, Compression compression
|
||||
) throws IOException;
|
||||
|
||||
boolean hasMapTile(
|
||||
String mapId, int lod, int x, int z, Compression compression
|
||||
) throws IOException;
|
||||
|
||||
TilePosition[] listMapTiles(
|
||||
String mapId, int lod, Compression compression,
|
||||
int start, int count
|
||||
) throws IOException;
|
||||
|
||||
int countAllMapTiles(String mapId) throws IOException;
|
||||
|
||||
int purgeMapTiles(String mapId, int limit) throws IOException;
|
||||
|
||||
int writeMapMeta(String mapId, String itemName, byte[] bytes) throws IOException;
|
||||
|
||||
byte @Nullable [] readMapMeta(String mapId, String itemName) throws IOException;
|
||||
|
||||
int deleteMapMeta(String mapId, String itemName) throws IOException;
|
||||
|
||||
boolean hasMapMeta(String mapId, String itemName) throws IOException;
|
||||
|
||||
void purgeMap(String mapId) throws IOException;
|
||||
|
||||
boolean hasMap(String mapId) throws IOException;
|
||||
|
||||
String[] listMapIds(int start, int count) throws IOException;
|
||||
|
||||
boolean isClosed();
|
||||
|
||||
record TilePosition (int x, int z) {}
|
||||
|
||||
}
|
@ -0,0 +1,353 @@
|
||||
package de.bluecolored.bluemap.core.storage.sql.commandset;
|
||||
|
||||
import de.bluecolored.bluemap.core.storage.sql.Database;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
|
||||
public class MySQLCommandSet extends AbstractCommandSet {
|
||||
|
||||
public MySQLCommandSet(Database db) {
|
||||
super(db);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String createMapTableStatement() {
|
||||
return """
|
||||
CREATE TABLE IF NOT EXISTS `bluemap_map` (
|
||||
`id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`map_id` VARCHAR(190) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE INDEX `map_id` (`map_id`)
|
||||
) COLLATE 'utf8mb4_bin'
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String createCompressionTableStatement() {
|
||||
return """
|
||||
CREATE TABLE IF NOT EXISTS `bluemap_map_tile_compression` (
|
||||
`id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`compression` VARCHAR(190) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE INDEX `compression` (`compression`)
|
||||
) COLLATE 'utf8mb4_bin'
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String createMapMetaTableStatement() {
|
||||
return """
|
||||
CREATE TABLE IF NOT EXISTS `bluemap_map_meta` (
|
||||
`map` SMALLINT UNSIGNED NOT NULL,
|
||||
`key` varchar(190) 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 CASCADE
|
||||
) COLLATE 'utf8mb4_bin'
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String createMapTileTableStatement() {
|
||||
return """
|
||||
CREATE TABLE IF NOT EXISTS `bluemap_map_tile` (
|
||||
`map` SMALLINT UNSIGNED NOT NULL,
|
||||
`lod` SMALLINT UNSIGNED NOT NULL,
|
||||
`x` INT NOT NULL,
|
||||
`z` INT NOT NULL,
|
||||
`compression` SMALLINT UNSIGNED NOT NULL,
|
||||
`data` LONGBLOB NOT NULL,
|
||||
PRIMARY KEY (`map`, `lod`, `x`, `z`),
|
||||
CONSTRAINT `fk_bluemap_map_tile_map`
|
||||
FOREIGN KEY (`map`)
|
||||
REFERENCES `bluemap_map` (`id`)
|
||||
ON UPDATE RESTRICT
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_bluemap_map_tile_compression`
|
||||
FOREIGN KEY (`compression`)
|
||||
REFERENCES `bluemap_map_tile_compression` (`id`)
|
||||
ON UPDATE RESTRICT
|
||||
ON DELETE CASCADE
|
||||
) COLLATE 'utf8mb4_bin'
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String fixLegacyCompressionIdsStatement() {
|
||||
return """
|
||||
UPDATE IGNORE `bluemap_map_tile_compression`
|
||||
SET `compression` = CONCAT('bluemap:', `compression`)
|
||||
WHERE NOT `compression` LIKE '%:%'
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String writeMapTileStatement() {
|
||||
return """
|
||||
REPLACE
|
||||
INTO `bluemap_map_tile` (`map`, `lod`, `x`, `z`, `compression`, `data`)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String readMapTileStatement() {
|
||||
return """
|
||||
SELECT t.`data`
|
||||
FROM `bluemap_map_tile` t
|
||||
INNER JOIN `bluemap_map` m
|
||||
ON t.`map` = m.`id`
|
||||
INNER JOIN `bluemap_map_tile_compression` c
|
||||
ON t.`compression` = c.`id`
|
||||
WHERE m.`map_id` = ?
|
||||
AND t.`lod` = ?
|
||||
AND t.`x` = ?
|
||||
AND t.`z` = ?
|
||||
AND c.`compression` = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String deleteMapTileStatement() {
|
||||
return """
|
||||
DELETE
|
||||
FROM `bluemap_map_tile`
|
||||
WHERE `map` IN (
|
||||
SELECT m.`id`
|
||||
FROM `bluemap_map` m
|
||||
WHERE m.`map_id` = ?
|
||||
)
|
||||
AND `lod` = ?
|
||||
AND `x` = ?
|
||||
AND `z` = ?
|
||||
AND `compression` IN (
|
||||
SELECT c.`id`
|
||||
FROM `bluemap_map_tile_compression` c
|
||||
WHERE c.`compression` = ?
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String hasMapTileStatement() {
|
||||
return """
|
||||
SELECT COUNT(*) > 0
|
||||
FROM `bluemap_map_tile` t
|
||||
INNER JOIN `bluemap_map` m
|
||||
ON t.`map` = m.`id`
|
||||
INNER JOIN `bluemap_map_tile_compression` c
|
||||
ON t.`compression` = c.`id`
|
||||
WHERE m.`map_id` = ?
|
||||
AND t.`lod` = ?
|
||||
AND t.`x` = ?
|
||||
AND t.`z` = ?
|
||||
AND c.`compression` = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String countAllMapTilesStatement() {
|
||||
return """
|
||||
SELECT COUNT(*)
|
||||
FROM `bluemap_map_tile` t
|
||||
INNER JOIN `bluemap_map` m
|
||||
ON t.`map` = m.`id`
|
||||
WHERE m.`map_id` = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String purgeMapTilesStatement() {
|
||||
return """
|
||||
DELETE
|
||||
FROM `bluemap_map_tile`
|
||||
WHERE `map` IN (
|
||||
SELECT `id`
|
||||
FROM `bluemap_map`
|
||||
WHERE `map_id` = ?
|
||||
)
|
||||
LIMIT ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String listMapTilesStatement() {
|
||||
return """
|
||||
SELECT t.`x`, t.`z`
|
||||
FROM `bluemap_map_tile` t
|
||||
INNER JOIN `bluemap_map` m
|
||||
ON t.`map` = m.`id`
|
||||
INNER JOIN `bluemap_map_tile_compression` c
|
||||
ON t.`compression` = c.`id`
|
||||
WHERE m.`map_id` = ?
|
||||
AND t.`lod` = ?
|
||||
AND c.`compression` = ?
|
||||
LIMIT ? OFFSET ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String writeMapMetaStatement() {
|
||||
return """
|
||||
REPLACE
|
||||
INTO `bluemap_map_meta` (`map`, `key`, `value`)
|
||||
VALUES (?, ?, ?)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String readMapMetaStatement() {
|
||||
return """
|
||||
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` = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String deleteMapMetaStatement() {
|
||||
return """
|
||||
DELETE
|
||||
FROM `bluemap_map_meta`
|
||||
WHERE `map` IN (
|
||||
SELECT `id`
|
||||
FROM `bluemap_map`
|
||||
WHERE `map_id` = ?
|
||||
)
|
||||
AND `key` = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String hasMapMetaStatement() {
|
||||
return """
|
||||
SELECT COUNT(*) > 0
|
||||
FROM `bluemap_map_meta` t
|
||||
INNER JOIN `bluemap_map` m
|
||||
ON t.`map` = m.`id`
|
||||
WHERE m.`map_id` = ?
|
||||
AND t.`key` = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String purgeMapTileTableStatement() {
|
||||
return """
|
||||
DELETE
|
||||
FROM `bluemap_map_tile`
|
||||
WHERE `map` IN (
|
||||
SELECT m.`id`
|
||||
FROM `bluemap_map` m
|
||||
WHERE m.`map_id` = ?
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String purgeMapMetaTableStatement() {
|
||||
return """
|
||||
DELETE
|
||||
FROM `bluemap_map_meta`
|
||||
WHERE `map` IN (
|
||||
SELECT m.`id`
|
||||
FROM `bluemap_map` m
|
||||
WHERE m.`map_id` = ?
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String deleteMapStatement() {
|
||||
return """
|
||||
DELETE
|
||||
FROM `bluemap_map`
|
||||
WHERE `map_id` = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String hasMapStatement() {
|
||||
return """
|
||||
SELECT COUNT(*) > 0
|
||||
FROM `bluemap_map` m
|
||||
WHERE m.`map_id` = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String listMapIdsStatement() {
|
||||
return """
|
||||
SELECT `map_id`
|
||||
FROM `bluemap_map` m
|
||||
LIMIT ? OFFSET ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String findMapKeyStatement() {
|
||||
return """
|
||||
SELECT `id`
|
||||
FROM `bluemap_map`
|
||||
WHERE map_id = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String createMapKeyStatement() {
|
||||
return """
|
||||
INSERT
|
||||
INTO `bluemap_map` (`map_id`)
|
||||
VALUES (?)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String findCompressionKeyStatement() {
|
||||
return """
|
||||
SELECT `id`
|
||||
FROM `bluemap_map_tile_compression`
|
||||
WHERE `compression` = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("mysql")
|
||||
public String createCompressionKeyStatement() {
|
||||
return """
|
||||
INSERT
|
||||
INTO `bluemap_map_tile_compression` (`compression`)
|
||||
VALUES (?)
|
||||
""";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,352 @@
|
||||
package de.bluecolored.bluemap.core.storage.sql.commandset;
|
||||
|
||||
import de.bluecolored.bluemap.core.storage.sql.Database;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
|
||||
public class PostgreSQLCommandSet extends AbstractCommandSet {
|
||||
|
||||
public PostgreSQLCommandSet(Database db) {
|
||||
super(db);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String createMapTableStatement() {
|
||||
return """
|
||||
CREATE TABLE IF NOT EXISTS bluemap_map (
|
||||
id SMALLSERIAL PRIMARY KEY,
|
||||
map_id VARCHAR(190) UNIQUE NOT NULL
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String createCompressionTableStatement() {
|
||||
return """
|
||||
CREATE TABLE IF NOT EXISTS bluemap_map_tile_compression (
|
||||
id SMALLSERIAL PRIMARY KEY,
|
||||
compression VARCHAR(190) UNIQUE NOT NULL
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String createMapMetaTableStatement() {
|
||||
return """
|
||||
CREATE TABLE IF NOT EXISTS bluemap_map_meta (
|
||||
map SMALLINT NOT NULL
|
||||
REFERENCES bluemap_map(id)
|
||||
ON UPDATE RESTRICT
|
||||
ON DELETE CASCADE,
|
||||
key VARCHAR(190) NOT NULL,
|
||||
value BYTEA NOT NULL,
|
||||
PRIMARY KEY (map, key)
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String createMapTileTableStatement() {
|
||||
return """
|
||||
CREATE TABLE IF NOT EXISTS bluemap_map_tile (
|
||||
map SMALLINT NOT NULL
|
||||
REFERENCES bluemap_map (id)
|
||||
ON UPDATE RESTRICT
|
||||
ON DELETE CASCADE,
|
||||
lod SMALLINT NOT NULL,
|
||||
x INT NOT NULL,
|
||||
z INT NOT NULL,
|
||||
compression SMALLINT NOT NULL
|
||||
REFERENCES bluemap_map_tile_compression (id)
|
||||
ON UPDATE RESTRICT
|
||||
ON DELETE CASCADE,
|
||||
data BYTEA NOT NULL,
|
||||
PRIMARY KEY (map, lod, x, z)
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String fixLegacyCompressionIdsStatement() {
|
||||
return """
|
||||
UPDATE bluemap_map_tile_compression
|
||||
SET compression = CONCAT('bluemap:', compression)
|
||||
WHERE NOT compression LIKE '%:%'
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String writeMapTileStatement() {
|
||||
return """
|
||||
INSERT
|
||||
INTO bluemap_map_tile (map, lod, x, z, compression, data)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (map, lod, x, z)
|
||||
DO UPDATE SET
|
||||
compression = excluded.compression,
|
||||
data = excluded.data
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String readMapTileStatement() {
|
||||
return """
|
||||
SELECT t.data
|
||||
FROM bluemap_map_tile t
|
||||
INNER JOIN bluemap_map m
|
||||
ON t.map = m.id
|
||||
INNER JOIN bluemap_map_tile_compression c
|
||||
ON t.compression = c.id
|
||||
WHERE m.map_id = ?
|
||||
AND t.lod = ?
|
||||
AND t.x = ?
|
||||
AND t.z = ?
|
||||
AND c.compression = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String deleteMapTileStatement() {
|
||||
return """
|
||||
DELETE
|
||||
FROM bluemap_map_tile
|
||||
WHERE map IN (
|
||||
SELECT m.id
|
||||
FROM bluemap_map m
|
||||
WHERE m.map_id = ?
|
||||
)
|
||||
AND lod = ?
|
||||
AND x = ?
|
||||
AND z = ?
|
||||
AND compression IN (
|
||||
SELECT c.id
|
||||
FROM bluemap_map_tile_compression c
|
||||
WHERE c.compression = ?
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String hasMapTileStatement() {
|
||||
return """
|
||||
SELECT COUNT(*) > 0
|
||||
FROM bluemap_map_tile t
|
||||
INNER JOIN bluemap_map m
|
||||
ON t.map = m.id
|
||||
INNER JOIN bluemap_map_tile_compression c
|
||||
ON t.compression = c.id
|
||||
WHERE m.map_id = ?
|
||||
AND t.lod = ?
|
||||
AND t.x = ?
|
||||
AND t.z = ?
|
||||
AND c.compression = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String countAllMapTilesStatement() {
|
||||
return """
|
||||
SELECT COUNT(*)
|
||||
FROM bluemap_map_tile t
|
||||
INNER JOIN bluemap_map m
|
||||
ON t.map = m.id
|
||||
WHERE m.map_id = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String purgeMapTilesStatement() {
|
||||
return """
|
||||
DELETE
|
||||
FROM bluemap_map_tile
|
||||
WHERE CTID IN (
|
||||
SELECT CTID
|
||||
FROM bluemap_map_tile t
|
||||
INNER JOIN bluemap_map m
|
||||
ON t.map = m.id
|
||||
WHERE m.map_id = ?
|
||||
LIMIT ?
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String listMapTilesStatement() {
|
||||
return """
|
||||
SELECT t.x, t.z
|
||||
FROM bluemap_map_tile t
|
||||
INNER JOIN bluemap_map m
|
||||
ON t.map = m.id
|
||||
INNER JOIN bluemap_map_tile_compression c
|
||||
ON t.compression = c.id
|
||||
WHERE m.map_id = ?
|
||||
AND t.lod = ?
|
||||
AND c.compression = ?
|
||||
LIMIT ? OFFSET ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String writeMapMetaStatement() {
|
||||
return """
|
||||
INSERT
|
||||
INTO bluemap_map_meta (map, key, value)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT (map, key)
|
||||
DO UPDATE SET
|
||||
value = excluded.value
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String readMapMetaStatement() {
|
||||
return """
|
||||
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 = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String deleteMapMetaStatement() {
|
||||
return """
|
||||
DELETE
|
||||
FROM bluemap_map_meta
|
||||
WHERE map IN (
|
||||
SELECT id
|
||||
FROM bluemap_map
|
||||
WHERE map_id = ?
|
||||
)
|
||||
AND key = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String hasMapMetaStatement() {
|
||||
return """
|
||||
SELECT COUNT(*) > 0
|
||||
FROM bluemap_map_meta t
|
||||
INNER JOIN bluemap_map m
|
||||
ON t.map = m.id
|
||||
WHERE m.map_id = ?
|
||||
AND t.key = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String purgeMapTileTableStatement() {
|
||||
return """
|
||||
DELETE
|
||||
FROM bluemap_map_tile
|
||||
WHERE map IN (
|
||||
SELECT m.id
|
||||
FROM bluemap_map m
|
||||
WHERE m.map_id = ?
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String purgeMapMetaTableStatement() {
|
||||
return """
|
||||
DELETE
|
||||
FROM bluemap_map_meta
|
||||
WHERE map IN (
|
||||
SELECT m.id
|
||||
FROM bluemap_map m
|
||||
WHERE m.map_id = ?
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String deleteMapStatement() {
|
||||
return """
|
||||
DELETE
|
||||
FROM bluemap_map
|
||||
WHERE map_id = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String hasMapStatement() {
|
||||
return """
|
||||
SELECT COUNT(*) > 0
|
||||
FROM bluemap_map m
|
||||
WHERE m.map_id = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String listMapIdsStatement() {
|
||||
return """
|
||||
SELECT map_id
|
||||
FROM bluemap_map m
|
||||
LIMIT ? OFFSET ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String findMapKeyStatement() {
|
||||
return """
|
||||
SELECT id
|
||||
FROM bluemap_map
|
||||
WHERE map_id = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String createMapKeyStatement() {
|
||||
return """
|
||||
INSERT
|
||||
INTO bluemap_map (map_id)
|
||||
VALUES (?)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String findCompressionKeyStatement() {
|
||||
return """
|
||||
SELECT id
|
||||
FROM bluemap_map_tile_compression
|
||||
WHERE compression = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("postgresql")
|
||||
public String createCompressionKeyStatement() {
|
||||
return """
|
||||
INSERT
|
||||
INTO bluemap_map_tile_compression (compression)
|
||||
VALUES (?)
|
||||
""";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,351 @@
|
||||
package de.bluecolored.bluemap.core.storage.sql.commandset;
|
||||
|
||||
import de.bluecolored.bluemap.core.storage.sql.Database;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
|
||||
public class SqliteCommandSet extends AbstractCommandSet {
|
||||
|
||||
public SqliteCommandSet(Database db) {
|
||||
super(db);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String createMapTableStatement() {
|
||||
return """
|
||||
CREATE TABLE IF NOT EXISTS `bluemap_map` (
|
||||
`id` INTEGER UNSIGNED PRIMARY KEY AUTOINCREMENT,
|
||||
`map_id` TEXT UNIQUE NOT NULL
|
||||
) STRICT
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String createCompressionTableStatement() {
|
||||
return """
|
||||
CREATE TABLE IF NOT EXISTS `bluemap_map_tile_compression` (
|
||||
`id` INTEGER UNSIGNED PRIMARY KEY AUTOINCREMENT,
|
||||
`compression` TEXT UNIQUE NOT NULL
|
||||
) STRICT
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String createMapMetaTableStatement() {
|
||||
return """
|
||||
CREATE TABLE IF NOT EXISTS `bluemap_map_meta` (
|
||||
`map` INTEGER UNSIGNED NOT NULL,
|
||||
`key` TEXT NOT NULL,
|
||||
`value` BLOB NOT NULL,
|
||||
PRIMARY KEY (`map`, `key`),
|
||||
CONSTRAINT `fk_bluemap_map_meta_map`
|
||||
FOREIGN KEY (`map`)
|
||||
REFERENCES `bluemap_map` (`id`)
|
||||
ON UPDATE RESTRICT
|
||||
ON DELETE CASCADE
|
||||
) STRICT
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String createMapTileTableStatement() {
|
||||
return """
|
||||
CREATE TABLE IF NOT EXISTS `bluemap_map_tile` (
|
||||
`map` INTEGER UNSIGNED NOT NULL,
|
||||
`lod` INTEGER UNSIGNED NOT NULL,
|
||||
`x` INTEGER NOT NULL,
|
||||
`z` INTEGER NOT NULL,
|
||||
`compression` INTEGER UNSIGNED NOT NULL,
|
||||
`data` BLOB NOT NULL,
|
||||
PRIMARY KEY (`map`, `lod`, `x`, `z`),
|
||||
CONSTRAINT `fk_bluemap_map_tile_map`
|
||||
FOREIGN KEY (`map`)
|
||||
REFERENCES `bluemap_map` (`id`)
|
||||
ON UPDATE RESTRICT
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_bluemap_map_tile_compression`
|
||||
FOREIGN KEY (`compression`)
|
||||
REFERENCES `bluemap_map_tile_compression` (`id`)
|
||||
ON UPDATE RESTRICT
|
||||
ON DELETE CASCADE
|
||||
) STRICT
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String fixLegacyCompressionIdsStatement() {
|
||||
return """
|
||||
UPDATE `bluemap_map_tile_compression`
|
||||
SET `compression` = CONCAT('bluemap:', `compression`)
|
||||
WHERE NOT `compression` LIKE '%:%'
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String writeMapTileStatement() {
|
||||
return """
|
||||
REPLACE
|
||||
INTO `bluemap_map_tile` (`map`, `lod`, `x`, `z`, `compression`, `data`)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String readMapTileStatement() {
|
||||
return """
|
||||
SELECT t.`data`
|
||||
FROM `bluemap_map_tile` t
|
||||
INNER JOIN `bluemap_map` m
|
||||
ON t.`map` = m.`id`
|
||||
INNER JOIN `bluemap_map_tile_compression` c
|
||||
ON t.`compression` = c.`id`
|
||||
WHERE m.`map_id` = ?
|
||||
AND t.`lod` = ?
|
||||
AND t.`x` = ?
|
||||
AND t.`z` = ?
|
||||
AND c.`compression` = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String deleteMapTileStatement() {
|
||||
return """
|
||||
DELETE
|
||||
FROM `bluemap_map_tile`
|
||||
WHERE `map` IN (
|
||||
SELECT m.`id`
|
||||
FROM `bluemap_map` m
|
||||
WHERE m.`map_id` = ?
|
||||
)
|
||||
AND `lod` = ?
|
||||
AND `x` = ?
|
||||
AND `z` = ?
|
||||
AND `compression` IN (
|
||||
SELECT c.`id`
|
||||
FROM `bluemap_map_tile_compression` c
|
||||
WHERE c.`compression` = ?
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String hasMapTileStatement() {
|
||||
return """
|
||||
SELECT COUNT(*) > 0
|
||||
FROM `bluemap_map_tile` t
|
||||
INNER JOIN `bluemap_map` m
|
||||
ON t.`map` = m.`id`
|
||||
INNER JOIN `bluemap_map_tile_compression` c
|
||||
ON t.`compression` = c.`id`
|
||||
WHERE m.`map_id` = ?
|
||||
AND t.`lod` = ?
|
||||
AND t.`x` = ?
|
||||
AND t.`z` = ?
|
||||
AND c.`compression` = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String countAllMapTilesStatement() {
|
||||
return """
|
||||
SELECT COUNT(*)
|
||||
FROM `bluemap_map_tile` t
|
||||
INNER JOIN `bluemap_map` m
|
||||
ON t.`map` = m.`id`
|
||||
WHERE m.`map_id` = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String purgeMapTilesStatement() {
|
||||
return """
|
||||
DELETE
|
||||
FROM bluemap_map_tile
|
||||
WHERE ROWID IN (
|
||||
SELECT ROWID
|
||||
FROM bluemap_map_tile t
|
||||
INNER JOIN bluemap_map m
|
||||
ON t.map = m.id
|
||||
WHERE m.map_id = ?
|
||||
LIMIT ?
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String listMapTilesStatement() {
|
||||
return """
|
||||
SELECT t.`x`, t.`z`
|
||||
FROM `bluemap_map_tile` t
|
||||
INNER JOIN `bluemap_map` m
|
||||
ON t.`map` = m.`id`
|
||||
INNER JOIN `bluemap_map_tile_compression` c
|
||||
ON t.`compression` = c.`id`
|
||||
WHERE m.`map_id` = ?
|
||||
AND t.`lod` = ?
|
||||
AND c.`compression` = ?
|
||||
LIMIT ? OFFSET ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String writeMapMetaStatement() {
|
||||
return """
|
||||
REPLACE
|
||||
INTO `bluemap_map_meta` (`map`, `key`, `value`)
|
||||
VALUES (?, ?, ?)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String readMapMetaStatement() {
|
||||
return """
|
||||
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` = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String deleteMapMetaStatement() {
|
||||
return """
|
||||
DELETE
|
||||
FROM `bluemap_map_meta`
|
||||
WHERE `map` IN (
|
||||
SELECT `id`
|
||||
FROM `bluemap_map`
|
||||
WHERE `map_id` = ?
|
||||
)
|
||||
AND `key` = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String hasMapMetaStatement() {
|
||||
return """
|
||||
SELECT COUNT(*) > 0
|
||||
FROM `bluemap_map_meta` t
|
||||
INNER JOIN `bluemap_map` m
|
||||
ON t.`map` = m.`id`
|
||||
WHERE m.`map_id` = ?
|
||||
AND t.`key` = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String purgeMapTileTableStatement() {
|
||||
return """
|
||||
DELETE
|
||||
FROM `bluemap_map_tile`
|
||||
WHERE `map` IN (
|
||||
SELECT m.`id`
|
||||
FROM `bluemap_map` m
|
||||
WHERE m.`map_id` = ?
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String purgeMapMetaTableStatement() {
|
||||
return """
|
||||
DELETE
|
||||
FROM `bluemap_map_meta`
|
||||
WHERE `map` IN (
|
||||
SELECT m.`id`
|
||||
FROM `bluemap_map` m
|
||||
WHERE m.`map_id` = ?
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String deleteMapStatement() {
|
||||
return """
|
||||
DELETE
|
||||
FROM `bluemap_map`
|
||||
WHERE `map_id` = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String hasMapStatement() {
|
||||
return """
|
||||
SELECT COUNT(*) > 0
|
||||
FROM `bluemap_map` m
|
||||
WHERE m.`map_id` = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String listMapIdsStatement() {
|
||||
return """
|
||||
SELECT `map_id`
|
||||
FROM `bluemap_map` m
|
||||
LIMIT ? OFFSET ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String findMapKeyStatement() {
|
||||
return """
|
||||
SELECT `id`
|
||||
FROM `bluemap_map`
|
||||
WHERE map_id = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String createMapKeyStatement() {
|
||||
return """
|
||||
INSERT
|
||||
INTO `bluemap_map` (`map_id`)
|
||||
VALUES (?)
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String findCompressionKeyStatement() {
|
||||
return """
|
||||
SELECT `id`
|
||||
FROM `bluemap_map_tile_compression`
|
||||
WHERE `compression` = ?
|
||||
""";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String createCompressionKeyStatement() {
|
||||
return """
|
||||
INSERT
|
||||
INTO `bluemap_map_tile_compression` (`compression`)
|
||||
VALUES (?)
|
||||
""";
|
||||
}
|
||||
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
/*
|
||||
* 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.dialect;
|
||||
|
||||
import org.intellij.lang.annotations.Language;
|
||||
|
||||
public interface Dialect {
|
||||
@Language("sql")
|
||||
String writeMapTile();
|
||||
|
||||
@Language("sql")
|
||||
String readMapTile();
|
||||
|
||||
@Language("sql")
|
||||
String readMapTileInfo();
|
||||
|
||||
@Language("sql")
|
||||
String deleteMapTile();
|
||||
|
||||
@Language("sql")
|
||||
String writeMeta();
|
||||
|
||||
@Language("sql")
|
||||
String readMeta();
|
||||
|
||||
@Language("sql")
|
||||
String readMetaSize();
|
||||
|
||||
@Language("sql")
|
||||
String deleteMeta();
|
||||
|
||||
@Language("sql")
|
||||
String purgeMapTile();
|
||||
|
||||
@Language("sql")
|
||||
String purgeMapMeta();
|
||||
|
||||
@Language("sql")
|
||||
String purgeMap();
|
||||
|
||||
@Language("sql")
|
||||
String selectMapIds();
|
||||
|
||||
@Language("sql")
|
||||
String initializeStorageMeta();
|
||||
|
||||
@Language("sql")
|
||||
String selectStorageMeta();
|
||||
|
||||
@Language("sql")
|
||||
String insertStorageMeta();
|
||||
|
||||
@Language("sql")
|
||||
String initializeMap();
|
||||
|
||||
@Language("sql")
|
||||
String initializeMapTileCompression();
|
||||
|
||||
@Language("sql")
|
||||
String initializeMapMeta();
|
||||
|
||||
@Language("sql")
|
||||
String initializeMapTile();
|
||||
|
||||
@Language("sql")
|
||||
String updateStorageMeta(); // can be use twice in init
|
||||
|
||||
@Language("sql")
|
||||
String deleteMapMeta();
|
||||
|
||||
@Language("sql")
|
||||
String updateMapMeta(); // can be used twice in init
|
||||
|
||||
@Language("sql")
|
||||
String lookupFK(String table, String idField, String valueField);
|
||||
|
||||
@Language("sql")
|
||||
String insertFK(String table, String valueField);
|
||||
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
/*
|
||||
* 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.dialect;
|
||||
|
||||
import de.bluecolored.bluemap.core.storage.sql.*;
|
||||
|
||||
public enum DialectType {
|
||||
|
||||
MYSQL (MySQLStorage::new, "mysql"),
|
||||
MARIADB (MySQLStorage::new, "mariadb"),
|
||||
POSTGRESQL (PostgreSQLStorage::new, "postgresql"),
|
||||
SQLITE (SQLiteStorage::new, "sqlite");
|
||||
|
||||
private static final DialectType FALLBACK = MYSQL;
|
||||
|
||||
private final SQLStorageFactory storageFactory;
|
||||
private final String dialectName;
|
||||
|
||||
DialectType(SQLStorageFactory storageFactory, String dialectName) {
|
||||
this.storageFactory = storageFactory;
|
||||
this.dialectName = dialectName;
|
||||
}
|
||||
public String getDialectName() {
|
||||
return dialectName;
|
||||
}
|
||||
|
||||
public static SQLStorage getStorage(String dialectName, SQLStorageSettings settings) throws Exception {
|
||||
for (DialectType dialect : values()) {
|
||||
if (dialect.getDialectName().equals(dialectName)) {
|
||||
return dialect.storageFactory.provide(settings);
|
||||
}
|
||||
}
|
||||
|
||||
// unknown dialect, use fallback
|
||||
return FALLBACK.storageFactory.provide(settings);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface SQLStorageFactory {
|
||||
SQLStorage provide(SQLStorageSettings config) throws Exception;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,274 +0,0 @@
|
||||
/*
|
||||
* 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.dialect;
|
||||
|
||||
import org.intellij.lang.annotations.Language;
|
||||
|
||||
public class MySQLDialect implements Dialect {
|
||||
|
||||
public static final MySQLDialect INSTANCE = new MySQLDialect();
|
||||
|
||||
private MySQLDialect() {};
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String writeMapTile() {
|
||||
return "REPLACE INTO `bluemap_map_tile` (`map`, `lod`, `x`, `z`, `compression`, `data`) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?)";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String readMapTile() {
|
||||
return "SELECT t.`data` " +
|
||||
"FROM `bluemap_map_tile` t " +
|
||||
" INNER JOIN `bluemap_map` m " +
|
||||
" ON t.`map` = m.`id` " +
|
||||
" INNER JOIN `bluemap_map_tile_compression` c " +
|
||||
" ON t.`compression` = c.`id` " +
|
||||
"WHERE m.`map_id` = ? " +
|
||||
"AND t.`lod` = ? " +
|
||||
"AND t.`x` = ? " +
|
||||
"AND t.`z` = ? " +
|
||||
"AND c.`compression` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String readMapTileInfo() {
|
||||
return "SELECT 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_compression` c " +
|
||||
" ON t.`compression` = c.`id` " +
|
||||
"WHERE m.`map_id` = ? " +
|
||||
"AND t.`lod` = ? " +
|
||||
"AND t.`x` = ? " +
|
||||
"AND t.`z` = ? " +
|
||||
"AND c.`compression` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String deleteMapTile() {
|
||||
return "DELETE t " +
|
||||
"FROM `bluemap_map_tile` t " +
|
||||
" INNER JOIN `bluemap_map` m " +
|
||||
" ON t.`map` = m.`id` " +
|
||||
"WHERE m.`map_id` = ? " +
|
||||
"AND t.`lod` = ? " +
|
||||
"AND t.`x` = ? " +
|
||||
"AND t.`z` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String writeMeta() {
|
||||
return "REPLACE INTO `bluemap_map_meta` (`map`, `key`, `value`) " +
|
||||
"VALUES (?, ?, ?)";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String readMeta() {
|
||||
return "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` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String readMetaSize() {
|
||||
return "SELECT LENGTH(t.`value`) as 'size' " +
|
||||
"FROM `bluemap_map_meta` t " +
|
||||
" INNER JOIN `bluemap_map` m " +
|
||||
" ON t.`map` = m.`id` " +
|
||||
"WHERE m.`map_id` = ? " +
|
||||
"AND t.`key` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String deleteMeta() {
|
||||
return "DELETE t " +
|
||||
"FROM `bluemap_map_meta` t " +
|
||||
" INNER JOIN `bluemap_map` m " +
|
||||
" ON t.`map` = m.`id` " +
|
||||
"WHERE m.`map_id` = ? " +
|
||||
"AND t.`key` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String purgeMapTile() {
|
||||
return "DELETE t " +
|
||||
"FROM `bluemap_map_tile` t " +
|
||||
" INNER JOIN `bluemap_map` m " +
|
||||
" ON t.`map` = m.`id` " +
|
||||
"WHERE m.`map_id` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String purgeMapMeta() {
|
||||
return "DELETE t " +
|
||||
"FROM `bluemap_map_meta` t " +
|
||||
" INNER JOIN `bluemap_map` m " +
|
||||
" ON t.`map` = m.`id` " +
|
||||
"WHERE m.`map_id` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String purgeMap() {
|
||||
return "DELETE " +
|
||||
"FROM `bluemap_map` " +
|
||||
"WHERE `map_id` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String selectMapIds() {
|
||||
return "SELECT `map_id` FROM `bluemap_map`";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String initializeStorageMeta() {
|
||||
return "CREATE TABLE IF NOT EXISTS `bluemap_storage_meta` (" +
|
||||
"`key` varchar(190) NOT NULL, " +
|
||||
"`value` varchar(255) DEFAULT NULL, " +
|
||||
"PRIMARY KEY (`key`)" +
|
||||
") COLLATE 'utf8mb4_bin'";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String selectStorageMeta() {
|
||||
return "SELECT `value` FROM `bluemap_storage_meta` " +
|
||||
"WHERE `key` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String insertStorageMeta() {
|
||||
return "INSERT INTO `bluemap_storage_meta` (`key`, `value`) " +
|
||||
"VALUES (?, ?)";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String initializeMap() {
|
||||
return "CREATE TABLE `bluemap_map` (" +
|
||||
"`id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT," +
|
||||
"`map_id` VARCHAR(190) NOT NULL," +
|
||||
"PRIMARY KEY (`id`)," +
|
||||
"UNIQUE INDEX `map_id` (`map_id`)" +
|
||||
") COLLATE 'utf8mb4_bin';";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String initializeMapTileCompression() {
|
||||
return "CREATE TABLE `bluemap_map_tile_compression` (" +
|
||||
"`id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT," +
|
||||
"`compression` VARCHAR(190) NOT NULL," +
|
||||
"PRIMARY KEY (`id`)," +
|
||||
"UNIQUE INDEX `compression` (`compression`)" +
|
||||
") COLLATE 'utf8mb4_bin';";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String initializeMapMeta() {
|
||||
return "CREATE TABLE `bluemap_map_meta` (" +
|
||||
"`map` SMALLINT UNSIGNED NOT NULL," +
|
||||
"`key` varchar(190) 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" +
|
||||
") COLLATE 'utf8mb4_bin'";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String initializeMapTile() {
|
||||
return "CREATE TABLE `bluemap_map_tile` (" +
|
||||
"`map` SMALLINT UNSIGNED NOT NULL," +
|
||||
"`lod` 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`, `lod`, `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_compression` FOREIGN KEY (`compression`) REFERENCES `bluemap_map_tile_compression` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT" +
|
||||
") COLLATE 'utf8mb4_bin';";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String updateStorageMeta() {
|
||||
return "UPDATE `bluemap_storage_meta` " +
|
||||
"SET `value` = ? " +
|
||||
"WHERE `key` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String deleteMapMeta() {
|
||||
return "DELETE FROM `bluemap_map_meta`" +
|
||||
"WHERE `key` IN (?, ?, ?)";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String updateMapMeta() {
|
||||
return "UPDATE `bluemap_map_meta` " +
|
||||
"SET `key` = ? " +
|
||||
"WHERE `key` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String lookupFK(String table, String idField, String valueField) {
|
||||
return "SELECT `" + idField + "` FROM `" + table + "` " +
|
||||
"WHERE `" + valueField + "` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("MySQL")
|
||||
public String insertFK(String table, String valueField) {
|
||||
return "INSERT INTO `" + table + "` (`" + valueField + "`) " +
|
||||
"VALUES (?)";
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,264 +0,0 @@
|
||||
/*
|
||||
* 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.dialect;
|
||||
|
||||
import org.intellij.lang.annotations.Language;
|
||||
|
||||
public class PostgresDialect implements Dialect {
|
||||
|
||||
public static final PostgresDialect INSTANCE = new PostgresDialect();
|
||||
|
||||
private PostgresDialect() {};
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String writeMapTile() {
|
||||
return "INSERT INTO bluemap_map_tile (map, lod, x, z, compression, data) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?) " +
|
||||
"ON CONFLICT (map, lod, x, z) DO UPDATE SET compression = EXCLUDED.compression, data = EXCLUDED.data";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String readMapTile() {
|
||||
return "SELECT t.data " +
|
||||
"FROM bluemap_map_tile t " +
|
||||
" INNER JOIN bluemap_map m " +
|
||||
" ON t.map = m.id " +
|
||||
" INNER JOIN bluemap_map_tile_compression c " +
|
||||
" ON t.compression = c.id " +
|
||||
"WHERE m.map_id = ? " +
|
||||
"AND t.lod = ? " +
|
||||
"AND t.x = ? " +
|
||||
"AND t.z = ? " +
|
||||
"AND c.compression = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String readMapTileInfo() {
|
||||
return "SELECT t.changed, OCTET_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_compression c " +
|
||||
" ON t.compression = c.id " +
|
||||
"WHERE m.map_id = ? " +
|
||||
"AND t.lod = ? " +
|
||||
"AND t.x = ? " +
|
||||
"AND t.z = ? " +
|
||||
"AND c.compression = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String deleteMapTile() {
|
||||
return "DELETE FROM bluemap_map_tile t " +
|
||||
"USING bluemap_map m " +
|
||||
"WHERE t.map = m.id " +
|
||||
"AND m.map_id = ? " +
|
||||
"AND t.lod = ? " +
|
||||
"AND t.x = ? " +
|
||||
"AND t.z = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String writeMeta() {
|
||||
return "INSERT INTO bluemap_map_meta (map, key, value) " +
|
||||
"VALUES (?, ?, ?) " +
|
||||
"ON CONFLICT (map, key) DO UPDATE SET value = EXCLUDED.value";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String readMeta() {
|
||||
return "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 = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String readMetaSize() {
|
||||
return "SELECT OCTET_LENGTH(t.value) as size " +
|
||||
"FROM bluemap_map_meta t " +
|
||||
" INNER JOIN bluemap_map m " +
|
||||
" ON t.map = m.id " +
|
||||
"WHERE m.map_id = ? " +
|
||||
"AND t.key = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String deleteMeta() {
|
||||
return "DELETE FROM bluemap_map_meta t " +
|
||||
"USING bluemap_map m " +
|
||||
"WHERE t.map = m.id " +
|
||||
"AND m.map_id = ? " +
|
||||
"AND t.key = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String purgeMapTile() {
|
||||
return "DELETE FROM bluemap_map_tile t " +
|
||||
"USING bluemap_map m " +
|
||||
"WHERE t.map = m.id " +
|
||||
"AND m.map_id = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String purgeMapMeta() {
|
||||
return "DELETE FROM bluemap_map_meta t " +
|
||||
"USING bluemap_map m " +
|
||||
"WHERE t.map = m.id " +
|
||||
"AND m.map_id = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String purgeMap() {
|
||||
return "DELETE FROM bluemap_map " +
|
||||
"WHERE map_id = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String selectMapIds() {
|
||||
return "SELECT map_id FROM bluemap_map";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String initializeStorageMeta() {
|
||||
return "CREATE TABLE IF NOT EXISTS bluemap_storage_meta (" +
|
||||
"key varchar(255) PRIMARY KEY, " +
|
||||
"value varchar(255)" +
|
||||
")";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String selectStorageMeta() {
|
||||
return "SELECT value FROM bluemap_storage_meta " +
|
||||
"WHERE key = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String insertStorageMeta() {
|
||||
return "INSERT INTO bluemap_storage_meta (key, value) " +
|
||||
"VALUES (?, ?) " +
|
||||
"ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String initializeMap() {
|
||||
return "CREATE TABLE IF NOT EXISTS bluemap_map (" +
|
||||
"id SERIAL PRIMARY KEY, " +
|
||||
"map_id VARCHAR(255) UNIQUE NOT NULL" +
|
||||
")";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String initializeMapTileCompression() {
|
||||
return "CREATE TABLE IF NOT EXISTS bluemap_map_tile_compression (" +
|
||||
"id SERIAL PRIMARY KEY, " +
|
||||
"compression VARCHAR(255) UNIQUE NOT NULL" +
|
||||
")";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String initializeMapMeta() {
|
||||
return "CREATE TABLE IF NOT EXISTS bluemap_map_meta (" +
|
||||
"map SMALLINT REFERENCES bluemap_map(id) ON UPDATE RESTRICT ON DELETE RESTRICT, " +
|
||||
"key varchar(255) NOT NULL, " +
|
||||
"value BYTEA NOT NULL, " +
|
||||
"PRIMARY KEY (map, key)" +
|
||||
")";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String initializeMapTile() {
|
||||
return "CREATE TABLE IF NOT EXISTS bluemap_map_tile (" +
|
||||
"map SMALLINT REFERENCES bluemap_map(id) ON UPDATE RESTRICT ON DELETE RESTRICT, " +
|
||||
"lod SMALLINT NOT NULL, " +
|
||||
"x INT NOT NULL, " +
|
||||
"z INT NOT NULL, " +
|
||||
"compression SMALLINT REFERENCES bluemap_map_tile_compression(id) ON UPDATE RESTRICT ON DELETE RESTRICT, " +
|
||||
"changed TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
|
||||
"data BYTEA NOT NULL, " +
|
||||
"PRIMARY KEY (map, lod, x, z)" +
|
||||
")";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String updateStorageMeta() {
|
||||
return "UPDATE bluemap_storage_meta " +
|
||||
"SET value = ? " +
|
||||
"WHERE key = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String deleteMapMeta() {
|
||||
return "DELETE FROM bluemap_map_meta " +
|
||||
"WHERE key IN (?, ?, ?)";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String updateMapMeta() {
|
||||
return "UPDATE bluemap_map_meta " +
|
||||
"SET key = ? " +
|
||||
"WHERE key = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String lookupFK(String table, String idField, String valueField) {
|
||||
return "SELECT " + idField + " FROM " + table +
|
||||
" WHERE " + valueField + " = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("PostgreSQL")
|
||||
public String insertFK(String table, String valueField) {
|
||||
return "INSERT INTO " + table + " (" + valueField + ") " +
|
||||
"VALUES (?)";
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,280 +0,0 @@
|
||||
/*
|
||||
* 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.dialect;
|
||||
|
||||
import org.intellij.lang.annotations.Language;
|
||||
|
||||
public class SqliteDialect implements Dialect {
|
||||
|
||||
public static final SqliteDialect INSTANCE = new SqliteDialect();
|
||||
|
||||
private SqliteDialect() {}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String writeMapTile() {
|
||||
return "REPLACE INTO `bluemap_map_tile` (`map`, `lod`, `x`, `z`, `compression`, `data`) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?)";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String readMapTile() {
|
||||
return "SELECT t.`data` " +
|
||||
"FROM `bluemap_map_tile` t " +
|
||||
" INNER JOIN `bluemap_map` m " +
|
||||
" ON t.`map` = m.`id` " +
|
||||
" INNER JOIN `bluemap_map_tile_compression` c " +
|
||||
" ON t.`compression` = c.`id` " +
|
||||
"WHERE m.`map_id` = ? " +
|
||||
"AND t.`lod` = ? " +
|
||||
"AND t.`x` = ? " +
|
||||
"AND t.`z` = ? " +
|
||||
"AND c.`compression` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String readMapTileInfo() {
|
||||
return "SELECT 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_compression` c " +
|
||||
" ON t.`compression` = c.`id` " +
|
||||
"WHERE m.`map_id` = ? " +
|
||||
"AND t.`lod` = ? " +
|
||||
"AND t.`x` = ? " +
|
||||
"AND t.`z` = ? " +
|
||||
"AND c.`compression` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String deleteMapTile() {
|
||||
return "DELETE FROM `bluemap_map_tile` " +
|
||||
"WHERE `map` IN( " +
|
||||
" SELECT `id` " +
|
||||
" FROM `bluemap_map` " +
|
||||
" WHERE `map_id` = ? " +
|
||||
" LIMIT 1 " +
|
||||
") " +
|
||||
"AND `lod` = ? " +
|
||||
"AND `x` = ? " +
|
||||
"AND `z` = ? ";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String writeMeta() {
|
||||
return "REPLACE INTO `bluemap_map_meta` (`map`, `key`, `value`) " +
|
||||
"VALUES (?, ?, ?)";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String readMeta() {
|
||||
return "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` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String readMetaSize() {
|
||||
return "SELECT LENGTH(t.`value`) as 'size' " +
|
||||
"FROM `bluemap_map_meta` t " +
|
||||
" INNER JOIN `bluemap_map` m " +
|
||||
" ON t.`map` = m.`id` " +
|
||||
"WHERE m.`map_id` = ? " +
|
||||
"AND t.`key` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String deleteMeta() {
|
||||
return "DELETE FROM `bluemap_map_meta` " +
|
||||
"WHERE `map` IN( " +
|
||||
" SELECT `id` " +
|
||||
" FROM `bluemap_map` " +
|
||||
" WHERE `map_id` = ? " +
|
||||
" LIMIT 1 " +
|
||||
") " +
|
||||
"AND `key` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String purgeMapTile() {
|
||||
return "DELETE FROM `bluemap_map_tile` " +
|
||||
"WHERE `map` IN( " +
|
||||
" SELECT `id` " +
|
||||
" FROM `bluemap_map` " +
|
||||
" WHERE `map_id` = ? " +
|
||||
" LIMIT 1 " +
|
||||
")";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String purgeMapMeta() {
|
||||
return "DELETE FROM `bluemap_map_meta` " +
|
||||
"WHERE `map` IN( " +
|
||||
" SELECT `id` " +
|
||||
" FROM `bluemap_map` " +
|
||||
" WHERE `map_id` = ? " +
|
||||
" LIMIT 1 " +
|
||||
")";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String purgeMap() {
|
||||
return "DELETE " +
|
||||
"FROM `bluemap_map` " +
|
||||
"WHERE `map_id` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String selectMapIds() {
|
||||
return "SELECT `map_id` FROM `bluemap_map`";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String initializeStorageMeta() {
|
||||
return "CREATE TABLE IF NOT EXISTS `bluemap_storage_meta` (" +
|
||||
"`key` varchar(255) NOT NULL, " +
|
||||
"`value` varchar(255) DEFAULT NULL, " +
|
||||
"PRIMARY KEY (`key`)" +
|
||||
")";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String selectStorageMeta() {
|
||||
return "SELECT `value` FROM `bluemap_storage_meta` " +
|
||||
"WHERE `key` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String insertStorageMeta() {
|
||||
return "INSERT INTO `bluemap_storage_meta` (`key`, `value`) " +
|
||||
"VALUES (?, ?)";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String initializeMap() {
|
||||
return "CREATE TABLE `bluemap_map` (" +
|
||||
"`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," +
|
||||
"`map_id` VARCHAR(255) NOT NULL," +
|
||||
"UNIQUE (`map_id`)" +
|
||||
");";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String initializeMapTileCompression() {
|
||||
return "CREATE TABLE `bluemap_map_tile_compression` (" +
|
||||
"`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," +
|
||||
"`compression` VARCHAR(255) NOT NULL," +
|
||||
"UNIQUE (`compression`)" +
|
||||
");";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String initializeMapMeta() {
|
||||
return "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" +
|
||||
")";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String initializeMapTile() {
|
||||
return "CREATE TABLE `bluemap_map_tile` (" +
|
||||
"`map` SMALLINT UNSIGNED NOT NULL," +
|
||||
"`lod` SMALLINT UNSIGNED NOT NULL," +
|
||||
"`x` INT NOT NULL," +
|
||||
"`z` INT NOT NULL," +
|
||||
"`compression` SMALLINT UNSIGNED NOT NULL," +
|
||||
"`changed` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP," +
|
||||
"`data` LONGBLOB NOT NULL," +
|
||||
"PRIMARY KEY (`map`, `lod`, `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_compression` FOREIGN KEY (`compression`) REFERENCES `bluemap_map_tile_compression` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT" +
|
||||
");";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String updateStorageMeta() {
|
||||
return "UPDATE `bluemap_storage_meta` " +
|
||||
"SET `value` = ? " +
|
||||
"WHERE `key` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String deleteMapMeta() {
|
||||
return "DELETE FROM `bluemap_map_meta`" +
|
||||
"WHERE `key` IN (?, ?, ?)";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String updateMapMeta() {
|
||||
return "UPDATE `bluemap_map_meta` " +
|
||||
"SET `key` = ? " +
|
||||
"WHERE `key` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String lookupFK(String table, String idField, String valueField) {
|
||||
return "SELECT `" + idField + "` FROM `" + table + "` " +
|
||||
"WHERE `" + valueField + "` = ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Language("sqlite")
|
||||
public String insertFK(String table, String valueField) {
|
||||
return "INSERT INTO `" + table + "` (`" + valueField + "`) " +
|
||||
"VALUES (?)";
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,144 +0,0 @@
|
||||
/*
|
||||
* 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.threejs;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import de.bluecolored.bluemap.core.util.Preconditions;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a ThreeJS BufferAttribute
|
||||
*/
|
||||
public class BufferAttribute {
|
||||
|
||||
private int itemSize;
|
||||
private boolean normalized;
|
||||
private float[] values;
|
||||
|
||||
/**
|
||||
* Creates a new {@link BufferAttribute} with the defined item-size
|
||||
*/
|
||||
public BufferAttribute(float[] values, int itemSize) {
|
||||
Preconditions.checkArgument(values.length % itemSize == 0, "The length of the values-array is not a multiple of the item-size!");
|
||||
|
||||
this.values = values;
|
||||
this.itemSize = itemSize;
|
||||
this.normalized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link BufferAttribute} with the defined item-size and the
|
||||
* defined threejs "normalized" attribute
|
||||
*/
|
||||
public BufferAttribute(float[] values, int itemSize, boolean normalized) {
|
||||
Preconditions.checkArgument(values.length % itemSize == 0, "The length of the values-array is not a multiple of the item-size!");
|
||||
|
||||
this.values = values;
|
||||
this.itemSize = itemSize;
|
||||
this.normalized = normalized;
|
||||
}
|
||||
|
||||
public void writeJson(JsonWriter json) throws IOException {
|
||||
json.beginObject();
|
||||
|
||||
json.name("type").value("Float32Array");
|
||||
json.name("itemSize").value(itemSize);
|
||||
json.name("normalized").value(normalized);
|
||||
|
||||
json.name("array").beginArray();
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
// rounding and remove ".0" to save string space
|
||||
double d = Math.round(values[i] * 10000d) / 10000d;
|
||||
if (d == (long) d) json.value((long) d);
|
||||
else json.value(d);
|
||||
}
|
||||
json.endArray();
|
||||
|
||||
json.endObject();
|
||||
}
|
||||
|
||||
public int getItemSize() {
|
||||
return this.itemSize;
|
||||
}
|
||||
|
||||
public boolean isNormalized() {
|
||||
return this.normalized;
|
||||
}
|
||||
|
||||
public int getValueCount() {
|
||||
return this.values.length;
|
||||
}
|
||||
|
||||
public int getItemCount() {
|
||||
return Math.floorDiv(getValueCount(), getItemSize());
|
||||
}
|
||||
|
||||
public float[] values() {
|
||||
return values;
|
||||
}
|
||||
|
||||
public static BufferAttribute readJson(JsonReader json) throws IOException {
|
||||
List<Float> list = new ArrayList<>(1000);
|
||||
int itemSize = 1;
|
||||
boolean normalized = false;
|
||||
|
||||
json.beginObject(); //root
|
||||
while (json.hasNext()){
|
||||
String name = json.nextName();
|
||||
|
||||
if(name.equals("array")){
|
||||
json.beginArray(); //array
|
||||
while (json.hasNext()){
|
||||
list.add((float) json.nextDouble());
|
||||
}
|
||||
json.endArray(); //array
|
||||
}
|
||||
|
||||
else if (name.equals("itemSize")) {
|
||||
itemSize = json.nextInt();
|
||||
}
|
||||
|
||||
else if (name.equals("normalized")) {
|
||||
normalized = json.nextBoolean();
|
||||
}
|
||||
|
||||
else json.skipValue();
|
||||
}
|
||||
json.endObject(); //root
|
||||
|
||||
//collect values in array
|
||||
float[] values = new float[list.size()];
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
values[i] = list.get(i);
|
||||
}
|
||||
|
||||
return new BufferAttribute(values, itemSize, normalized);
|
||||
}
|
||||
|
||||
}
|
@ -1,214 +0,0 @@
|
||||
/*
|
||||
* 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.threejs;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class BufferGeometry {
|
||||
|
||||
public Map<String, BufferAttribute> attributes;
|
||||
public MaterialGroup[] groups;
|
||||
|
||||
private BufferGeometry() {}
|
||||
|
||||
public BufferGeometry(float[] position, float[] normal, float[] color, float[] uv, MaterialGroup[] groups) {
|
||||
this.attributes = new HashMap<>();
|
||||
addAttribute("position", new BufferAttribute(position, 3));
|
||||
addAttribute("normal", new BufferAttribute(normal, 3));
|
||||
addAttribute("color", new BufferAttribute(color, 3));
|
||||
addAttribute("uv", new BufferAttribute(uv, 2));
|
||||
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
public void addAttribute(String name, BufferAttribute attribute) {
|
||||
this.attributes.put(name, attribute);
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
int faceCount = getFaceCount();
|
||||
|
||||
for (BufferAttribute attribute : attributes.values()) {
|
||||
if (attribute.getItemCount() != faceCount) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getFaceCount() {
|
||||
if (attributes.isEmpty()) return 0;
|
||||
BufferAttribute attribute = attributes.values().iterator().next();
|
||||
return attribute.getItemCount();
|
||||
}
|
||||
|
||||
public String toJson() {
|
||||
try {
|
||||
|
||||
StringWriter sw = new StringWriter();
|
||||
Gson gson = new GsonBuilder().create();
|
||||
JsonWriter json = gson.newJsonWriter(sw);
|
||||
|
||||
json.beginObject(); // main-object
|
||||
json.name("tileGeometry").beginObject(); // tile-geometry-object
|
||||
|
||||
// set special values
|
||||
json.name("type").value("BufferGeometry");
|
||||
json.name("uuid").value(UUID.randomUUID().toString().toUpperCase());
|
||||
|
||||
json.name("data").beginObject(); // data
|
||||
json.name("attributes").beginObject(); // attributes
|
||||
|
||||
for (Entry<String, BufferAttribute> entry : attributes.entrySet()) {
|
||||
json.name(entry.getKey());
|
||||
entry.getValue().writeJson(json);
|
||||
}
|
||||
|
||||
json.endObject(); // attributes
|
||||
|
||||
json.name("groups").beginArray(); // groups
|
||||
|
||||
// write groups into json
|
||||
for (MaterialGroup g : groups) {
|
||||
json.beginObject();
|
||||
|
||||
json.name("materialIndex").value(g.getMaterialIndex());
|
||||
json.name("start").value(g.getStart());
|
||||
json.name("count").value(g.getCount());
|
||||
|
||||
json.endObject();
|
||||
}
|
||||
|
||||
json.endArray(); // groups
|
||||
json.endObject(); // data
|
||||
json.endObject(); // tile-geometry-object
|
||||
json.endObject(); // main-object
|
||||
|
||||
// save and return
|
||||
json.flush();
|
||||
return sw.toString();
|
||||
|
||||
} catch (IOException e) {
|
||||
// since we are using a StringWriter there should never be an IO exception
|
||||
// thrown
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static BufferGeometry fromJson(String jsonString) throws IOException {
|
||||
|
||||
Gson gson = new GsonBuilder().create();
|
||||
JsonReader json = gson.newJsonReader(new StringReader(jsonString));
|
||||
|
||||
List<MaterialGroup> groups = new ArrayList<>(10);
|
||||
Map<String, BufferAttribute> attributes = new HashMap<>();
|
||||
|
||||
json.beginObject(); // root
|
||||
|
||||
while (!"tileGeometry".equals(json.nextName())) json.skipValue(); // find tileGeometry
|
||||
json.beginObject(); // tileGeometry
|
||||
|
||||
while (json.hasNext()) {
|
||||
String name1 = json.nextName();
|
||||
|
||||
if (name1.equals("data")) {
|
||||
json.beginObject(); // data
|
||||
while (json.hasNext()) {
|
||||
String name2 = json.nextName();
|
||||
|
||||
if (name2.equals("attributes")) {
|
||||
json.beginObject(); // attributes
|
||||
while (json.hasNext()) {
|
||||
String name3 = json.nextName();
|
||||
attributes.put(name3, BufferAttribute.readJson(json));
|
||||
}
|
||||
json.endObject(); // attributes
|
||||
}
|
||||
|
||||
else if (name2.equals("groups")) {
|
||||
json.beginArray(); // groups
|
||||
while (json.hasNext()) {
|
||||
MaterialGroup group = new MaterialGroup(0, 0, 0);
|
||||
json.beginObject(); // group
|
||||
while (json.hasNext()) {
|
||||
String name3 = json.nextName();
|
||||
|
||||
if (name3.equals("materialIndex")) {
|
||||
group.setMaterialIndex(json.nextInt());
|
||||
}
|
||||
|
||||
else if (name3.equals("start")) {
|
||||
group.setStart(json.nextInt());
|
||||
}
|
||||
|
||||
else if (name3.equals("count")) {
|
||||
group.setCount(json.nextInt());
|
||||
}
|
||||
|
||||
else json.skipValue();
|
||||
}
|
||||
json.endObject(); // group
|
||||
groups.add(group);
|
||||
}
|
||||
json.endArray(); // groups
|
||||
}
|
||||
|
||||
else json.skipValue();
|
||||
}
|
||||
json.endObject();// data
|
||||
}
|
||||
|
||||
else json.skipValue();
|
||||
}
|
||||
json.endObject(); // tileGeometry
|
||||
|
||||
while (json.hasNext()) json.skipValue(); // skip remaining values
|
||||
json.endObject(); // root
|
||||
|
||||
groups.sort((g1, g2) -> (int) Math.signum(g1.getStart() - g2.getStart()));
|
||||
int nextGroup = 0;
|
||||
for (MaterialGroup g : groups) {
|
||||
if (g.getStart() != nextGroup)
|
||||
throw new IllegalArgumentException("Group did not start at correct index! (Got " + g.getStart() + " but expected " + nextGroup + ")");
|
||||
if (g.getCount() < 0) throw new IllegalArgumentException("Group has a negative count! (" + g.getCount() + ")");
|
||||
nextGroup += g.getCount();
|
||||
}
|
||||
|
||||
BufferGeometry bufferGeometry = new BufferGeometry();
|
||||
bufferGeometry.attributes = attributes;
|
||||
bufferGeometry.groups = groups.toArray(new MaterialGroup[groups.size()]);
|
||||
|
||||
return bufferGeometry;
|
||||
}
|
||||
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
/*
|
||||
* 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.threejs;
|
||||
|
||||
public class MaterialGroup {
|
||||
private int materialIndex;
|
||||
private int start;
|
||||
private int count;
|
||||
|
||||
public MaterialGroup(int materialIndex, int start, int count) {
|
||||
this.materialIndex = materialIndex;
|
||||
this.start = start;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public int getMaterialIndex() {
|
||||
return materialIndex;
|
||||
}
|
||||
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setMaterialIndex(int materialIndex) {
|
||||
this.materialIndex = materialIndex;
|
||||
}
|
||||
|
||||
public void setStart(int start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
public void setCount(int count) {
|
||||
this.count = count;
|
||||
}
|
||||
}
|
@ -30,7 +30,12 @@
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class DelegateInputStream extends InputStream {
|
||||
/**
|
||||
* An {@link InputStream} implementation delegating all methods to another InputStream.
|
||||
* Can be used as a base-class for other InputStream implementations that wrap around an existing stream and only want
|
||||
* to modify certain methods.
|
||||
*/
|
||||
public abstract class DelegateInputStream extends InputStream {
|
||||
|
||||
protected final InputStream in;
|
||||
|
||||
|
@ -29,7 +29,12 @@
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class DelegateOutputStream extends OutputStream {
|
||||
/**
|
||||
* An {@link OutputStream} implementation delegating all methods to another OutputStream.
|
||||
* Can be used as a base-class for other OutputStream implementations that wrap around an existing stream and only want
|
||||
* to modify certain methods.
|
||||
*/
|
||||
public abstract class DelegateOutputStream extends OutputStream {
|
||||
|
||||
protected final OutputStream out;
|
||||
|
||||
|
@ -27,6 +27,9 @@
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* An {@link InputStream} implementation that performs an additional action right <b>after</b> the base stream got closed.
|
||||
*/
|
||||
public class OnCloseInputStream extends DelegateInputStream {
|
||||
|
||||
private final AutoCloseable onClose;
|
||||
|
@ -27,6 +27,9 @@
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* An {@link OutputStream} implementation that performs an additional action right <b>after</b> the base stream got closed.
|
||||
*/
|
||||
public class OnCloseOutputStream extends DelegateOutputStream {
|
||||
|
||||
private final AutoCloseable onClose;
|
||||
|
@ -32,6 +32,7 @@
|
||||
import de.bluecolored.bluemap.core.BlueMap;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.resources.datapack.DataPack;
|
||||
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||
import de.bluecolored.bluemap.core.util.Grid;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import de.bluecolored.bluemap.core.util.Vector2iCache;
|
||||
@ -45,7 +46,6 @@
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@ -54,7 +54,6 @@
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@ -260,7 +259,7 @@ private Chunk loadChunk(int x, int z) {
|
||||
public static MCAWorld load(Path worldFolder, Key dimension) throws IOException, InterruptedException {
|
||||
// load level.dat
|
||||
Path levelFile = worldFolder.resolve("level.dat");
|
||||
InputStream levelFileIn = new GZIPInputStream(new BufferedInputStream(Files.newInputStream(levelFile)));
|
||||
InputStream levelFileIn = Compression.GZIP.decompress(Files.newInputStream(levelFile));
|
||||
LevelData levelData = MCAUtil.BLUENBT.read(levelFileIn, LevelData.class);
|
||||
|
||||
// load datapacks
|
||||
|
@ -24,14 +24,13 @@
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.world.mca.chunk;
|
||||
|
||||
import de.bluecolored.bluemap.core.storage.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||
import de.bluecolored.bluemap.core.world.mca.MCAUtil;
|
||||
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@ -63,7 +62,7 @@ public MCAChunk load(byte[] data, int offset, int length, Compression compressio
|
||||
// try last used version
|
||||
ChunkVersionLoader<?> usedLoader = lastUsedLoader;
|
||||
MCAChunk chunk;
|
||||
try (InputStream decompressedIn = new BufferedInputStream(compression.decompress(in))) {
|
||||
try (InputStream decompressedIn = compression.decompress(in)) {
|
||||
chunk = usedLoader.load(world, decompressedIn);
|
||||
}
|
||||
|
||||
@ -71,7 +70,7 @@ public MCAChunk load(byte[] data, int offset, int length, Compression compressio
|
||||
ChunkVersionLoader<?> actualLoader = findBestLoaderForVersion(chunk.getDataVersion());
|
||||
if (actualLoader != null && usedLoader != actualLoader) {
|
||||
in.reset(); // reset read position
|
||||
try (InputStream decompressedIn = new BufferedInputStream(compression.decompress(in))) {
|
||||
try (InputStream decompressedIn = compression.decompress(in)) {
|
||||
chunk = actualLoader.load(world, decompressedIn);
|
||||
}
|
||||
lastUsedLoader = actualLoader;
|
||||
|
@ -25,12 +25,11 @@
|
||||
package de.bluecolored.bluemap.core.world.mca.region;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import de.bluecolored.bluemap.core.storage.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||
import de.bluecolored.bluemap.core.world.ChunkConsumer;
|
||||
import de.bluecolored.bluemap.core.world.Region;
|
||||
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
|
||||
import de.bluecolored.bluemap.core.world.mca.chunk.MCAChunk;
|
||||
import io.airlift.compress.zstd.ZstdInputStream;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
@ -147,9 +146,8 @@ public void iterateAllChunks(ChunkConsumer consumer) throws IOException {
|
||||
byte[] chunkDataBuffer = null;
|
||||
|
||||
try (
|
||||
InputStream in = new ZstdInputStream(new ByteArrayInputStream(compressedData));
|
||||
BufferedInputStream bIn = new BufferedInputStream(in);
|
||||
DataInputStream dIn = new DataInputStream(bIn)
|
||||
InputStream in = Compression.ZSTD.decompress(new ByteArrayInputStream(compressedData));
|
||||
DataInputStream dIn = new DataInputStream(in)
|
||||
) {
|
||||
int[] chunkDataLengths = new int[1024];
|
||||
int[] chunkTimestamps = new int[1024];
|
||||
|
@ -25,7 +25,7 @@
|
||||
package de.bluecolored.bluemap.core.world.mca.region;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import de.bluecolored.bluemap.core.storage.Compression;
|
||||
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||
import de.bluecolored.bluemap.core.world.Chunk;
|
||||
import de.bluecolored.bluemap.core.world.ChunkConsumer;
|
||||
import de.bluecolored.bluemap.core.world.Region;
|
||||
|
@ -43,7 +43,7 @@
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.metrics.Metrics;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.storage.MapStorage;
|
||||
import de.bluecolored.bluemap.core.util.FileHelper;
|
||||
import org.apache.commons.cli.*;
|
||||
import org.apache.commons.lang3.time.DurationFormatUtils;
|
||||
@ -72,7 +72,10 @@ public void renderMaps(BlueMapService blueMap, boolean watch, boolean forceRende
|
||||
@Nullable String mapsToRender) throws ConfigurationException, IOException, InterruptedException {
|
||||
|
||||
//metrics report
|
||||
if (blueMap.getConfig().getCoreConfig().isMetrics()) Metrics.sendReportAsync("cli");
|
||||
if (blueMap.getConfig().getCoreConfig().isMetrics()) Metrics.sendReportAsync(
|
||||
"cli",
|
||||
blueMap.getConfig().getMinecraftVersion().getVersionString()
|
||||
);
|
||||
|
||||
if (blueMap.getConfig().getWebappConfig().isEnabled())
|
||||
blueMap.createOrUpdateWebApp(forceGenerateWebapp);
|
||||
@ -201,12 +204,13 @@ public void startWebserver(BlueMapService blueMap, boolean verbose) throws IOExc
|
||||
|
||||
// map route
|
||||
for (var mapConfigEntry : blueMap.getConfig().getMapConfigs().entrySet()) {
|
||||
Storage storage = blueMap.getOrLoadStorage(mapConfigEntry.getValue().getStorage());
|
||||
MapStorage storage = blueMap.getOrLoadStorage(mapConfigEntry.getValue().getStorage())
|
||||
.map(mapConfigEntry.getKey());
|
||||
|
||||
routingRequestHandler.register(
|
||||
"maps/" + Pattern.quote(mapConfigEntry.getKey()) + "/(.*)",
|
||||
"$1",
|
||||
new MapRequestHandler(mapConfigEntry.getKey(), storage)
|
||||
new MapRequestHandler(storage)
|
||||
);
|
||||
}
|
||||
|
||||
@ -242,9 +246,10 @@ public void startWebserver(BlueMapService blueMap, boolean verbose) throws IOExc
|
||||
throw new ConfigurationException("BlueMap failed to bind to the configured address.\n" +
|
||||
"This usually happens when the configured port (" + config.getPort() + ") is already in use by some other program.", ex);
|
||||
} catch (IOException ex) {
|
||||
throw new ConfigurationException("BlueMap failed to initialize the webserver.\n" +
|
||||
"Check your webserver-config if everything is configured correctly.\n" +
|
||||
"(Make sure you DON'T use the same port for bluemap that you also use for your minecraft server)", ex);
|
||||
throw new ConfigurationException("""
|
||||
BlueMap failed to initialize the webserver.
|
||||
Check your webserver-config if everything is configured correctly.
|
||||
(Make sure you DON'T use the same port for bluemap that you also use for your minecraft server)""", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,6 @@
|
||||
import de.bluecolored.bluemap.common.serverinterface.ServerWorld;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
import net.md_5.bungee.chat.ComponentSerializer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.command.BlockCommandSender;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
Loading…
Reference in New Issue
Block a user