mirror of
https://github.com/BlueMap-Minecraft/BlueMap.git
synced 2025-02-16 20:41:57 +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,
|
id,
|
||||||
name,
|
name,
|
||||||
world,
|
world,
|
||||||
storage,
|
storage.map(id),
|
||||||
getOrLoadResourcePack(),
|
getOrLoadResourcePack(),
|
||||||
mapConfig
|
mapConfig
|
||||||
);
|
);
|
||||||
|
@ -25,7 +25,8 @@
|
|||||||
package de.bluecolored.bluemap.common.api;
|
package de.bluecolored.bluemap.common.api;
|
||||||
|
|
||||||
import de.bluecolored.bluemap.api.AssetStorage;
|
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.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -34,39 +35,39 @@
|
|||||||
|
|
||||||
public class AssetStorageImpl implements AssetStorage {
|
public class AssetStorageImpl implements AssetStorage {
|
||||||
|
|
||||||
private static final String ASSET_PATH = "assets/";
|
private final MapStorage storage;
|
||||||
|
|
||||||
private final Storage storage;
|
|
||||||
private final String mapId;
|
private final String mapId;
|
||||||
|
|
||||||
public AssetStorageImpl(Storage storage, String mapId) {
|
public AssetStorageImpl(MapStorage storage, String mapId) {
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
this.mapId = mapId;
|
this.mapId = mapId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OutputStream writeAsset(String name) throws IOException {
|
public OutputStream writeAsset(String name) throws IOException {
|
||||||
return storage.writeMeta(mapId, ASSET_PATH + name);
|
return storage.asset(name).write();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<InputStream> readAsset(String name) throws IOException {
|
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
|
@Override
|
||||||
public boolean assetExists(String name) throws IOException {
|
public boolean assetExists(String name) throws IOException {
|
||||||
return storage.readMetaInfo(mapId, ASSET_PATH + name).isPresent();
|
return storage.asset(name).exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getAssetUrl(String name) {
|
public String getAssetUrl(String name) {
|
||||||
return "maps/" + mapId + "/" + Storage.escapeMetaName(ASSET_PATH + name);
|
return "maps/" + mapId + "/assets/" + MapStorage.escapeAssetName(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteAsset(String name) throws IOException {
|
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
|
@Override
|
||||||
@Deprecated(forRemoval = true)
|
@Deprecated(forRemoval = true)
|
||||||
|
@SuppressWarnings("removal")
|
||||||
public String createImage(BufferedImage image, String path) throws IOException {
|
public String createImage(BufferedImage image, String path) throws IOException {
|
||||||
path = path.replaceAll("[^a-zA-Z0-9_.\\-/]", "_");
|
path = path.replaceAll("[^a-zA-Z0-9_.\\-/]", "_");
|
||||||
|
|
||||||
@ -102,6 +103,7 @@ public String createImage(BufferedImage image, String path) throws IOException {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Deprecated(forRemoval = true)
|
@Deprecated(forRemoval = true)
|
||||||
|
@SuppressWarnings("removal")
|
||||||
public Map<String, String> availableImages() throws IOException {
|
public Map<String, String> availableImages() throws IOException {
|
||||||
Path webRoot = getWebRoot().toAbsolutePath();
|
Path webRoot = getWebRoot().toAbsolutePath();
|
||||||
String separator = webRoot.getFileSystem().getSeparator();
|
String separator = webRoot.getFileSystem().getSeparator();
|
||||||
|
@ -269,18 +269,11 @@ private Map<String, MapConfig> loadMapConfigs(Collection<ServerWorld> autoConfig
|
|||||||
String name = worldFolder.getFileName() + " (" + dimensionName + ")";
|
String name = worldFolder.getFileName() + " (" + dimensionName + ")";
|
||||||
if (i > 1) name = name + " (" + i + ")";
|
if (i > 1) name = name + " (" + i + ")";
|
||||||
|
|
||||||
ConfigTemplate template;
|
ConfigTemplate template = switch (world.getDimension().getFormatted()) {
|
||||||
switch (world.getDimension().getFormatted()) {
|
case "minecraft:the_nether" -> createNetherMapTemplate(name, worldFolder, dimension, i - 1);
|
||||||
case "minecraft:the_nether":
|
case "minecraft:the_end" -> createEndMapTemplate(name, worldFolder, dimension, i - 1);
|
||||||
template = createNetherMapTemplate(name, worldFolder, dimension, i - 1);
|
default -> createOverworldMapTemplate(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
Files.writeString(
|
Files.writeString(
|
||||||
configFile,
|
configFile,
|
||||||
@ -358,7 +351,7 @@ private Map<String, StorageConfig> loadStorageConfigs(Path defaultWebroot) throw
|
|||||||
Path rawConfig = configManager.getRaw(configFile);
|
Path rawConfig = configManager.getRaw(configFile);
|
||||||
String id = rawConfig.getFileName().toString();
|
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
|
storageConfig = configManager.loadConfig(rawConfig, storageConfig.getStorageType().getConfigType()); // load actual config type
|
||||||
|
|
||||||
storageConfigs.put(id, storageConfig);
|
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;
|
package de.bluecolored.bluemap.common.config.storage;
|
||||||
|
|
||||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
import de.bluecolored.bluemap.core.storage.Compression;
|
import de.bluecolored.bluemap.common.config.ConfigurationException;
|
||||||
import de.bluecolored.bluemap.core.storage.file.FileStorageSettings;
|
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 org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@ -34,20 +36,19 @@
|
|||||||
@SuppressWarnings("FieldMayBeFinal")
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
@DebugDump
|
@DebugDump
|
||||||
@ConfigSerializable
|
@ConfigSerializable
|
||||||
public class FileConfig extends StorageConfig implements FileStorageSettings {
|
@Getter
|
||||||
|
public class FileConfig extends StorageConfig {
|
||||||
|
|
||||||
private Path root = Path.of("bluemap", "web", "maps");
|
private Path root = Path.of("bluemap", "web", "maps");
|
||||||
|
private String compression = Compression.GZIP.getKey().getFormatted();
|
||||||
|
|
||||||
private Compression compression = Compression.GZIP;
|
public Compression getCompression() throws ConfigurationException {
|
||||||
|
return parseKey(Compression.REGISTRY, compression, "compression");
|
||||||
@Override
|
|
||||||
public Path getRoot() {
|
|
||||||
return root;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Compression getCompression() {
|
public FileStorage createStorage() throws ConfigurationException {
|
||||||
return compression;
|
return new FileStorage(root, getCompression());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,67 +25,146 @@
|
|||||||
package de.bluecolored.bluemap.common.config.storage;
|
package de.bluecolored.bluemap.common.config.storage;
|
||||||
|
|
||||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
import de.bluecolored.bluemap.api.debug.DebugDump;
|
||||||
import de.bluecolored.bluemap.core.storage.Compression;
|
import de.bluecolored.bluemap.common.config.ConfigurationException;
|
||||||
import de.bluecolored.bluemap.core.storage.sql.SQLStorageSettings;
|
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 org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||||
|
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
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.nio.file.Paths;
|
||||||
|
import java.sql.Driver;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
|
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
|
||||||
@ConfigSerializable
|
@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 driverJar = null;
|
||||||
@DebugDump private String driverClass = 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;
|
@DebugDump private int maxConnections = -1;
|
||||||
|
|
||||||
@Override
|
@DebugDump private String compression = Compression.GZIP.getKey().getFormatted();
|
||||||
public Optional<URL> getDriverJar() throws MalformedURLException {
|
|
||||||
if (driverJar == null) return Optional.empty();
|
|
||||||
|
|
||||||
if (driverJarURL == null) {
|
@DebugDump
|
||||||
driverJarURL = Paths.get(driverJar).toUri().toURL();
|
@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() {
|
public Optional<String> getDriverClass() {
|
||||||
return Optional.ofNullable(driverClass);
|
return Optional.ofNullable(driverClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public Compression getCompression() throws ConfigurationException {
|
||||||
public String getConnectionUrl() {
|
return parseKey(Compression.REGISTRY, compression, "compression");
|
||||||
return connectionUrl;
|
}
|
||||||
|
|
||||||
|
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
|
@Override
|
||||||
public Map<String, String> getConnectionProperties() {
|
public SQLStorage createStorage() throws ConfigurationException {
|
||||||
return connectionProperties;
|
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
|
private @Nullable Driver createDriver() throws ConfigurationException {
|
||||||
public int getMaxConnections() {
|
if (driverClass == null) return null;
|
||||||
return maxConnections;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
try {
|
||||||
public Compression getCompression() {
|
// load driver class
|
||||||
return compression;
|
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.common.config.ConfigurationException;
|
||||||
import de.bluecolored.bluemap.core.storage.Storage;
|
import de.bluecolored.bluemap.core.storage.Storage;
|
||||||
import de.bluecolored.bluemap.core.util.Key;
|
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 org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@ -35,34 +37,39 @@
|
|||||||
@SuppressWarnings("FieldMayBeFinal")
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
@DebugDump
|
@DebugDump
|
||||||
@ConfigSerializable
|
@ConfigSerializable
|
||||||
public class StorageConfig {
|
public abstract class StorageConfig {
|
||||||
|
|
||||||
private Key storageType = StorageType.FILE.getKey();
|
private String storageType = StorageType.FILE.getKey().getFormatted();
|
||||||
|
|
||||||
public Key getStorageTypeKey() {
|
|
||||||
return storageType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public StorageType getStorageType() throws ConfigurationException {
|
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) {
|
if (type == null) {
|
||||||
// try legacy config format
|
// try legacy config format
|
||||||
Key legacyFormatKey = Key.bluemap(storageType.getValue().toLowerCase(Locale.ROOT));
|
Key legacyFormatKey = Key.bluemap(key.toLowerCase(Locale.ROOT));
|
||||||
type = StorageType.REGISTRY.get(legacyFormatKey);
|
type = registry.get(legacyFormatKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == null)
|
if (type == null)
|
||||||
throw new ConfigurationException("No storage-type found for key: " + storageType + "!");
|
throw new ConfigurationException("No " + typeName + " found for key: " + key + "!");
|
||||||
|
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Storage createStorage() throws Exception {
|
@ConfigSerializable
|
||||||
if (this.getClass().equals(StorageConfig.class))
|
public static class Base extends StorageConfig {
|
||||||
throw new UnsupportedOperationException("Can not create a Storage from the StorageConfig superclass.");
|
|
||||||
|
@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;
|
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.Key;
|
||||||
import de.bluecolored.bluemap.core.util.Keyed;
|
import de.bluecolored.bluemap.core.util.Keyed;
|
||||||
import de.bluecolored.bluemap.core.util.Registry;
|
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 );
|
StorageType FILE = new Impl(Key.bluemap("file"), FileConfig.class);
|
||||||
public static final StorageType SQL = new StorageType( Key.bluemap("sql"), SQLConfig.class, SQLStorage::create );
|
StorageType SQL = new Impl(Key.bluemap("sql"), SQLConfig.class);
|
||||||
|
|
||||||
public static final Registry<StorageType> REGISTRY = new Registry<>(
|
Registry<StorageType> REGISTRY = new Registry<>(
|
||||||
FILE,
|
FILE,
|
||||||
SQL
|
SQL
|
||||||
);
|
);
|
||||||
|
|
||||||
private final Key key;
|
Class<? extends StorageConfig> getConfigType();
|
||||||
private final Class<? extends StorageConfig> configType;
|
|
||||||
private final StorageFactory<? extends StorageConfig> storageFactory;
|
|
||||||
|
|
||||||
public <C extends StorageConfig> StorageType(Key key, Class<C> configType, StorageFactory<C> storageFactory) {
|
@RequiredArgsConstructor
|
||||||
this.key = key;
|
@Getter
|
||||||
this.configType = configType;
|
class Impl implements StorageType {
|
||||||
this.storageFactory = storageFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private final Key key;
|
||||||
public Key getKey() {
|
private final Class<? extends StorageConfig> configType;
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
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.plugin.skins.PlayerSkinUpdater;
|
||||||
import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask;
|
import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask;
|
||||||
import de.bluecolored.bluemap.common.rendermanager.RenderManager;
|
import de.bluecolored.bluemap.common.rendermanager.RenderManager;
|
||||||
import de.bluecolored.bluemap.common.serverinterface.ServerEventListener;
|
|
||||||
import de.bluecolored.bluemap.common.serverinterface.Server;
|
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.serverinterface.ServerWorld;
|
||||||
import de.bluecolored.bluemap.common.web.*;
|
import de.bluecolored.bluemap.common.web.*;
|
||||||
import de.bluecolored.bluemap.common.web.http.HttpServer;
|
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));
|
mapRequestHandler = new MapRequestHandler(map, serverInterface, pluginConfig, Predicate.not(pluginState::isPlayerHidden));
|
||||||
} else {
|
} else {
|
||||||
Storage storage = blueMap.getOrLoadStorage(mapConfig.getStorage());
|
Storage storage = blueMap.getOrLoadStorage(mapConfig.getStorage());
|
||||||
mapRequestHandler = new MapRequestHandler(id, storage);
|
mapRequestHandler = new MapRequestHandler(storage.map(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
routingRequestHandler.register(
|
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" +
|
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);
|
"This usually happens when the configured port (" + webserverConfig.getPort() + ") is already in use by some other program.", ex);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new ConfigurationException("BlueMap failed to initialize the webserver.\n" +
|
throw new ConfigurationException("""
|
||||||
"Check your webserver-config if everything is configured correctly.\n" +
|
BlueMap failed to initialize the webserver.
|
||||||
"(Make sure you DON'T use the same port for bluemap that you also use for your minecraft server)", ex);
|
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() {
|
TimerTask metricsTask = new TimerTask() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (Plugin.this.serverInterface.isMetricsEnabled().getOr(coreConfig::isMetrics))
|
if (serverInterface.isMetricsEnabled().getOr(coreConfig::isMetrics))
|
||||||
Metrics.sendReport(Plugin.this.implementationType);
|
Metrics.sendReport(implementationType, configManager.getMinecraftVersion().getVersionString());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
daemonTimer.scheduleAtFixedRate(metricsTask, TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(30));
|
daemonTimer.scheduleAtFixedRate(metricsTask, TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(30));
|
||||||
@ -539,7 +541,7 @@ public void savePlayerStates() {
|
|||||||
Predicate.not(pluginState::isPlayerHidden)
|
Predicate.not(pluginState::isPlayerHidden)
|
||||||
);
|
);
|
||||||
try (
|
try (
|
||||||
OutputStream out = map.getStorage().writeMeta(map.getId(), BmMap.META_FILE_PLAYERS);
|
OutputStream out = map.getStorage().players().write();
|
||||||
Writer writer = new OutputStreamWriter(out)
|
Writer writer = new OutputStreamWriter(out)
|
||||||
) {
|
) {
|
||||||
writer.write(dataSupplier.get());
|
writer.write(dataSupplier.get());
|
||||||
|
@ -52,6 +52,7 @@
|
|||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
import de.bluecolored.bluemap.core.map.BmMap;
|
import de.bluecolored.bluemap.core.map.BmMap;
|
||||||
import de.bluecolored.bluemap.core.map.MapRenderState;
|
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.storage.Storage;
|
||||||
import de.bluecolored.bluemap.core.world.Chunk;
|
import de.bluecolored.bluemap.core.world.Chunk;
|
||||||
import de.bluecolored.bluemap.core.world.World;
|
import de.bluecolored.bluemap.core.world.World;
|
||||||
@ -67,8 +68,6 @@
|
|||||||
|
|
||||||
public class Commands<S> {
|
public class Commands<S> {
|
||||||
|
|
||||||
public static final String DEFAULT_MARKER_SET_ID = "markers";
|
|
||||||
|
|
||||||
private final Plugin plugin;
|
private final Plugin plugin;
|
||||||
private final CommandDispatcher<S> dispatcher;
|
private final CommandDispatcher<S> dispatcher;
|
||||||
private final Function<S, CommandSource> commandSourceInterface;
|
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:"));
|
source.sendMessage(Text.of(TextColor.BLUE, "Storages loaded by BlueMap:"));
|
||||||
for (var entry : plugin.getBlueMap().getConfig().getStorageConfigs().entrySet()) {
|
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())
|
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())
|
.setClickAction(Text.ClickAction.RUN_COMMAND, "/bluemap storages " + entry.getKey())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -916,7 +920,7 @@ public int storagesInfoCommand(CommandContext<S> context) {
|
|||||||
|
|
||||||
Collection<String> mapIds;
|
Collection<String> mapIds;
|
||||||
try {
|
try {
|
||||||
mapIds = storage.collectMapIds();
|
mapIds = storage.mapIds().toList();
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
Logger.global.logError("Unexpected exception trying to load mapIds from storage '" + storageId + "'!", 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..."));
|
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 {
|
} else {
|
||||||
for (String mapId : mapIds) {
|
for (String mapId : mapIds) {
|
||||||
BmMap map = plugin.getBlueMap().getMaps().get(mapId);
|
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) {
|
if (isLoaded) {
|
||||||
source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, mapId, TextColor.GREEN, TextFormat.ITALIC, " (loaded)"));
|
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 storageId = context.getArgument("storage", String.class);
|
||||||
String mapId = context.getArgument("map", String.class);
|
String mapId = context.getArgument("map", String.class);
|
||||||
|
|
||||||
Storage storage;
|
MapStorage storage;
|
||||||
try {
|
try {
|
||||||
storage = plugin.getBlueMap().getOrLoadStorage(storageId);
|
storage = plugin.getBlueMap().getOrLoadStorage(storageId).map(mapId);
|
||||||
} catch (ConfigurationException | InterruptedException ex) {
|
} catch (ConfigurationException | InterruptedException ex) {
|
||||||
Logger.global.logError("Unexpected exception trying to load storage '" + storageId + "'!", 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..."));
|
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());
|
BufferedImage playerHead = playerMarkerIconFactory.apply(playerUuid, skin.get());
|
||||||
|
|
||||||
for (BmMap map : maps.values()) {
|
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);
|
ImageIO.write(playerHead, "png", out);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
Logger.global.logError("Failed to write player skin to storage: " + playerUuid, ex);
|
Logger.global.logError("Failed to write player skin to storage: " + playerUuid, ex);
|
||||||
|
@ -57,8 +57,8 @@ public void doWork() throws Exception {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// purge the map
|
// purge the map
|
||||||
map.getStorage().purgeMap(map.getId(), progressInfo -> {
|
map.getStorage().delete(progress -> {
|
||||||
this.progress = progressInfo.getProgress();
|
this.progress = progress;
|
||||||
return !this.cancelled;
|
return !this.cancelled;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -25,20 +25,20 @@
|
|||||||
package de.bluecolored.bluemap.common.rendermanager;
|
package de.bluecolored.bluemap.common.rendermanager;
|
||||||
|
|
||||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
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;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class StorageDeleteTask implements RenderTask {
|
public class StorageDeleteTask implements RenderTask {
|
||||||
|
|
||||||
private final Storage storage;
|
private final MapStorage storage;
|
||||||
private final String mapId;
|
private final String mapId;
|
||||||
|
|
||||||
private volatile double progress;
|
private volatile double progress;
|
||||||
private volatile boolean hasMoreWork;
|
private volatile boolean hasMoreWork;
|
||||||
private volatile boolean cancelled;
|
private volatile boolean cancelled;
|
||||||
|
|
||||||
public StorageDeleteTask(Storage storage, String mapId) {
|
public StorageDeleteTask(MapStorage storage, String mapId) {
|
||||||
this.storage = Objects.requireNonNull(storage);
|
this.storage = Objects.requireNonNull(storage);
|
||||||
this.mapId = Objects.requireNonNull(mapId);
|
this.mapId = Objects.requireNonNull(mapId);
|
||||||
this.progress = 0d;
|
this.progress = 0d;
|
||||||
@ -55,8 +55,8 @@ public void doWork() throws Exception {
|
|||||||
if (this.cancelled) return;
|
if (this.cancelled) return;
|
||||||
|
|
||||||
// purge the map
|
// purge the map
|
||||||
storage.purgeMap(mapId, progressInfo -> {
|
storage.delete(progress -> {
|
||||||
this.progress = progressInfo.getProgress();
|
this.progress = progress;
|
||||||
return !this.cancelled;
|
return !this.cancelled;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
import de.bluecolored.bluemap.common.serverinterface.Server;
|
import de.bluecolored.bluemap.common.serverinterface.Server;
|
||||||
import de.bluecolored.bluemap.common.serverinterface.ServerWorld;
|
import de.bluecolored.bluemap.common.serverinterface.ServerWorld;
|
||||||
import de.bluecolored.bluemap.core.map.BmMap;
|
import de.bluecolored.bluemap.core.map.BmMap;
|
||||||
|
import de.bluecolored.bluemap.core.storage.MapStorage;
|
||||||
import de.bluecolored.bluemap.core.storage.Storage;
|
import de.bluecolored.bluemap.core.storage.Storage;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@ -40,20 +41,20 @@
|
|||||||
public class MapRequestHandler extends RoutingRequestHandler {
|
public class MapRequestHandler extends RoutingRequestHandler {
|
||||||
|
|
||||||
public MapRequestHandler(BmMap map, Server serverInterface, PluginConfig pluginConfig, Predicate<UUID> playerFilter) {
|
public MapRequestHandler(BmMap map, Server serverInterface, PluginConfig pluginConfig, Predicate<UUID> playerFilter) {
|
||||||
this(map.getId(), map.getStorage(),
|
this(map.getStorage(),
|
||||||
createPlayersDataSupplier(map, serverInterface, pluginConfig, playerFilter),
|
createPlayersDataSupplier(map, serverInterface, pluginConfig, playerFilter),
|
||||||
new LiveMarkersDataSupplier(map.getMarkerSets()));
|
new LiveMarkersDataSupplier(map.getMarkerSets()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public MapRequestHandler(String mapId, Storage mapStorage) {
|
public MapRequestHandler(MapStorage mapStorage) {
|
||||||
this(mapId, mapStorage, null, null);
|
this(mapStorage, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MapRequestHandler(String mapId, Storage mapStorage,
|
public MapRequestHandler(MapStorage mapStorage,
|
||||||
@Nullable Supplier<String> livePlayersDataSupplier,
|
@Nullable Supplier<String> livePlayersDataSupplier,
|
||||||
@Nullable Supplier<String> liveMarkerDataSupplier) {
|
@Nullable Supplier<String> liveMarkerDataSupplier) {
|
||||||
|
|
||||||
register(".*", new MapStorageRequestHandler(mapId, mapStorage));
|
register(".*", new MapStorageRequestHandler(mapStorage));
|
||||||
|
|
||||||
if (livePlayersDataSupplier != null) {
|
if (livePlayersDataSupplier != null) {
|
||||||
register("live/players\\.json", "", new JsonDataRequestHandler(
|
register("live/players\\.json", "", new JsonDataRequestHandler(
|
||||||
|
@ -24,44 +24,38 @@
|
|||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.common.web;
|
package de.bluecolored.bluemap.common.web;
|
||||||
|
|
||||||
import com.flowpowered.math.vector.Vector2i;
|
|
||||||
import de.bluecolored.bluemap.api.ContentTypeRegistry;
|
import de.bluecolored.bluemap.api.ContentTypeRegistry;
|
||||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
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.logger.Logger;
|
||||||
import de.bluecolored.bluemap.core.map.BmMap;
|
import de.bluecolored.bluemap.core.storage.GridStorage;
|
||||||
import de.bluecolored.bluemap.core.storage.CompressedInputStream;
|
import de.bluecolored.bluemap.core.storage.MapStorage;
|
||||||
import de.bluecolored.bluemap.core.storage.Compression;
|
import de.bluecolored.bluemap.core.storage.compression.CompressedInputStream;
|
||||||
import de.bluecolored.bluemap.core.storage.Storage;
|
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||||
import de.bluecolored.bluemap.core.storage.TileInfo;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.util.*;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@DebugDump
|
@DebugDump
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class MapStorageRequestHandler implements HttpRequestHandler {
|
public class MapStorageRequestHandler implements HttpRequestHandler {
|
||||||
|
|
||||||
private static final Pattern TILE_PATTERN = Pattern.compile("tiles/([\\d/]+)/x(-?[\\d/]+)z(-?[\\d/]+).*");
|
private static final Pattern TILE_PATTERN = Pattern.compile("tiles/([\\d/]+)/x(-?[\\d/]+)z(-?[\\d/]+).*");
|
||||||
|
|
||||||
private final String mapId;
|
private final MapStorage mapStorage;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@SuppressWarnings("resource")
|
||||||
@Override
|
@Override
|
||||||
public HttpResponse handle(HttpRequest request) {
|
public HttpResponse handle(HttpRequest request) {
|
||||||
String path = request.getPath();
|
String path = request.getPath();
|
||||||
@ -78,58 +72,36 @@ public HttpResponse handle(HttpRequest request) {
|
|||||||
int lod = Integer.parseInt(tileMatcher.group(1));
|
int lod = Integer.parseInt(tileMatcher.group(1));
|
||||||
int x = Integer.parseInt(tileMatcher.group(2).replace("/", ""));
|
int x = Integer.parseInt(tileMatcher.group(2).replace("/", ""));
|
||||||
int z = Integer.parseInt(tileMatcher.group(3).replace("/", ""));
|
int z = Integer.parseInt(tileMatcher.group(3).replace("/", ""));
|
||||||
Optional<TileInfo> optTileInfo = mapStorage.readMapTileInfo(mapId, lod, new Vector2i(x, z));
|
|
||||||
|
|
||||||
if (optTileInfo.isPresent()) {
|
GridStorage gridStorage = lod == 0 ? mapStorage.hiresTiles() : mapStorage.lowresTiles(lod);
|
||||||
TileInfo tileInfo = optTileInfo.get();
|
CompressedInputStream in = gridStorage.read(x, z);
|
||||||
|
if (in == null) return new HttpResponse(HttpStatusCode.NO_CONTENT);
|
||||||
|
|
||||||
// check e-tag
|
HttpResponse response = new HttpResponse(HttpStatusCode.OK);
|
||||||
String eTag = calculateETag(path, tileInfo);
|
response.addHeader("Cache-Control", "public");
|
||||||
HttpHeader etagHeader = request.getHeader("If-None-Match");
|
response.addHeader("Cache-Control", "max-age=" + TimeUnit.DAYS.toSeconds(1));
|
||||||
if (etagHeader != null){
|
|
||||||
if(etagHeader.getValue().equals(eTag)) {
|
|
||||||
return new HttpResponse(HttpStatusCode.NOT_MODIFIED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check modified-since
|
if (lod == 0) response.addHeader("Content-Type", "application/octet-stream");
|
||||||
long lastModified = tileInfo.getLastModified();
|
else response.addHeader("Content-Type", "image/png");
|
||||||
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){}
|
|
||||||
}
|
|
||||||
|
|
||||||
CompressedInputStream compressedIn = tileInfo.readMapTile();
|
writeToResponse(in, response, request);
|
||||||
HttpResponse response = new HttpResponse(HttpStatusCode.OK);
|
return response;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// provide meta-data
|
// provide meta-data
|
||||||
Optional<InputStream> optIn = mapStorage.readMeta(mapId, path);
|
CompressedInputStream in = switch (path) {
|
||||||
if (optIn.isPresent()) {
|
case "settings.json" -> mapStorage.settings().read();
|
||||||
CompressedInputStream compressedIn = new CompressedInputStream(optIn.get(), Compression.NONE);
|
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);
|
HttpResponse response = new HttpResponse(HttpStatusCode.OK);
|
||||||
response.addHeader("Cache-Control", "public");
|
response.addHeader("Cache-Control", "public");
|
||||||
response.addHeader("Cache-Control", "max-age=" + TimeUnit.DAYS.toSeconds(1));
|
response.addHeader("Cache-Control", "max-age=" + TimeUnit.DAYS.toSeconds(1));
|
||||||
response.addHeader("Content-Type", ContentTypeRegistry.fromFileName(path));
|
response.addHeader("Content-Type", ContentTypeRegistry.fromFileName(path));
|
||||||
writeToResponse(compressedIn, response, request);
|
writeToResponse(in, response, request);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,27 +111,23 @@ public HttpResponse handle(HttpRequest request) {
|
|||||||
return new HttpResponse(HttpStatusCode.INTERNAL_SERVER_ERROR);
|
return new HttpResponse(HttpStatusCode.INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new HttpResponse(HttpStatusCode.NO_CONTENT);
|
return new HttpResponse(HttpStatusCode.NOT_FOUND);
|
||||||
}
|
|
||||||
|
|
||||||
private String calculateETag(String path, TileInfo tileInfo) {
|
|
||||||
return Long.toHexString(tileInfo.getSize()) + Integer.toHexString(path.hashCode()) + Long.toHexString(tileInfo.getLastModified());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeToResponse(CompressedInputStream data, HttpResponse response, HttpRequest request) throws IOException {
|
private void writeToResponse(CompressedInputStream data, HttpResponse response, HttpRequest request) throws IOException {
|
||||||
Compression compression = data.getCompression();
|
Compression compression = data.getCompression();
|
||||||
if (
|
if (
|
||||||
compression != Compression.NONE &&
|
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);
|
response.setData(data);
|
||||||
} else if (
|
} else if (
|
||||||
compression != Compression.GZIP &&
|
compression != Compression.GZIP &&
|
||||||
!response.hasHeaderValue("Content-Type", "image/png") &&
|
!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();
|
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
||||||
try (OutputStream os = Compression.GZIP.compress(byteOut)) {
|
try (OutputStream os = Compression.GZIP.compress(byteOut)) {
|
||||||
IOUtils.copyLarge(data.decompress(), os);
|
IOUtils.copyLarge(data.decompress(), os);
|
||||||
@ -167,41 +135,7 @@ private void writeToResponse(CompressedInputStream data, HttpResponse response,
|
|||||||
byte[] compressedData = byteOut.toByteArray();
|
byte[] compressedData = byteOut.toByteArray();
|
||||||
response.setData(new ByteArrayInputStream(compressedData));
|
response.setData(new ByteArrayInputStream(compressedData));
|
||||||
} else {
|
} else {
|
||||||
response.setData(new BufferedInputStream(data.decompress()));
|
response.setData(data.decompress());
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String timestampToString(long time){
|
|
||||||
return DateFormatUtils.format(time, "EEE, dd MMM yyy HH:mm:ss 'GMT'", TimeZone.getTimeZone("GMT"), Locale.ENGLISH);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long stringToTimestamp(String timeString) throws IllegalArgumentException {
|
|
||||||
try {
|
|
||||||
int day = Integer.parseInt(timeString.substring(5, 7));
|
|
||||||
|
|
||||||
int month = Calendar.JANUARY;
|
|
||||||
switch (timeString.substring(8, 11)){
|
|
||||||
case "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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
# The storage-type of this storage.
|
# The storage-type of this storage.
|
||||||
# Depending on this setting, different config-entries are allowed/expected in this config file.
|
# 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)
|
# 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 path to the folder on your file-system where bluemap will save the rendered map
|
||||||
# The default is: "bluemap/web/maps"
|
# The default is: "bluemap/web/maps"
|
||||||
@ -17,4 +17,4 @@ root: "${root}"
|
|||||||
# - GZIP
|
# - GZIP
|
||||||
# - NONE
|
# - NONE
|
||||||
# The default is: GZIP
|
# The default is: GZIP
|
||||||
compression: GZIP
|
compression: gzip
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
# The storage-type of this storage.
|
# The storage-type of this storage.
|
||||||
# Depending on this setting, different config-entries are allowed/expected in this config file.
|
# 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)
|
# 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 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]
|
# 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.
|
# The compression-type that bluemap will use to compress generated map-data.
|
||||||
# Available compression-types are:
|
# Available compression-types are:
|
||||||
# - GZIP
|
# - gzip
|
||||||
# - NONE
|
# - none
|
||||||
# The default is: GZIP
|
# The default is: gzip
|
||||||
compression: GZIP
|
compression: gzip
|
||||||
|
@ -36,7 +36,8 @@
|
|||||||
import de.bluecolored.bluemap.core.map.lowres.LowresTileManager;
|
import de.bluecolored.bluemap.core.map.lowres.LowresTileManager;
|
||||||
import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson;
|
import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson;
|
||||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
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.util.Grid;
|
||||||
import de.bluecolored.bluemap.core.world.World;
|
import de.bluecolored.bluemap.core.world.World;
|
||||||
|
|
||||||
@ -44,19 +45,12 @@
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
@DebugDump
|
@DebugDump
|
||||||
public class BmMap {
|
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())
|
private static final Gson GSON = ResourcesGson.addAdapter(new GsonBuilder())
|
||||||
.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
|
.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
|
||||||
.registerTypeAdapter(BmMap.class, new MapSettingsSerializer())
|
.registerTypeAdapter(BmMap.class, new MapSettingsSerializer())
|
||||||
@ -65,7 +59,7 @@ public class BmMap {
|
|||||||
private final String id;
|
private final String id;
|
||||||
private final String name;
|
private final String name;
|
||||||
private final World world;
|
private final World world;
|
||||||
private final Storage storage;
|
private final MapStorage storage;
|
||||||
private final MapSettings mapSettings;
|
private final MapSettings mapSettings;
|
||||||
|
|
||||||
private final ResourcePack resourcePack;
|
private final ResourcePack resourcePack;
|
||||||
@ -82,7 +76,7 @@ public class BmMap {
|
|||||||
private long renderTimeSumNanos;
|
private long renderTimeSumNanos;
|
||||||
private long tilesRendered;
|
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.id = Objects.requireNonNull(id);
|
||||||
this.name = Objects.requireNonNull(name);
|
this.name = Objects.requireNonNull(name);
|
||||||
this.world = Objects.requireNonNull(world);
|
this.world = Objects.requireNonNull(world);
|
||||||
@ -98,7 +92,7 @@ public BmMap(String id, String name, World world, Storage storage, ResourcePack
|
|||||||
saveTextureGallery();
|
saveTextureGallery();
|
||||||
|
|
||||||
this.hiresModelManager = new HiresModelManager(
|
this.hiresModelManager = new HiresModelManager(
|
||||||
storage.tileStorage(id, 0),
|
storage.hiresTiles(),
|
||||||
this.resourcePack,
|
this.resourcePack,
|
||||||
this.textureGallery,
|
this.textureGallery,
|
||||||
settings,
|
settings,
|
||||||
@ -106,7 +100,7 @@ public BmMap(String id, String name, World world, Storage storage, ResourcePack
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.lowresTileManager = new LowresTileManager(
|
this.lowresTileManager = new LowresTileManager(
|
||||||
storage.mapStorage(id),
|
storage,
|
||||||
new Grid(settings.getLowresTileSize()),
|
new Grid(settings.getLowresTileSize()),
|
||||||
settings.getLodCount(),
|
settings.getLodCount(),
|
||||||
settings.getLodFactor()
|
settings.getLodFactor()
|
||||||
@ -145,7 +139,7 @@ public synchronized void save() {
|
|||||||
|
|
||||||
// only save texture gallery if not present in storage
|
// only save texture gallery if not present in storage
|
||||||
try {
|
try {
|
||||||
if (storage.readMetaInfo(id, META_FILE_TEXTURES).isEmpty())
|
if (!storage.textures().exists())
|
||||||
saveTextureGallery();
|
saveTextureGallery();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Logger.global.logError("Failed to read texture gallery", e);
|
Logger.global.logError("Failed to read texture gallery", e);
|
||||||
@ -153,18 +147,16 @@ public synchronized void save() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadRenderState() throws IOException {
|
private void loadRenderState() throws IOException {
|
||||||
Optional<InputStream> rstateData = storage.readMeta(id, META_FILE_RENDER_STATE);
|
try (CompressedInputStream in = storage.renderState().read()){
|
||||||
if (rstateData.isPresent()) {
|
if (in != null)
|
||||||
try (InputStream in = rstateData.get()){
|
this.renderState.load(in.decompress());
|
||||||
this.renderState.load(in);
|
} catch (IOException ex) {
|
||||||
} catch (IOException ex) {
|
Logger.global.logWarning("Failed to load render-state for map '" + getId() + "': " + ex);
|
||||||
Logger.global.logWarning("Failed to load render-state for map '" + getId() + "': " + ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void saveRenderState() {
|
public synchronized void saveRenderState() {
|
||||||
try (OutputStream out = storage.writeMeta(id, META_FILE_RENDER_STATE)) {
|
try (OutputStream out = storage.renderState().write()) {
|
||||||
this.renderState.save(out);
|
this.renderState.save(out);
|
||||||
} catch (IOException ex){
|
} catch (IOException ex){
|
||||||
Logger.global.logError("Failed to save render-state for map: '" + this.id + "'!", 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 {
|
private TextureGallery loadTextureGallery() throws IOException {
|
||||||
TextureGallery gallery = null;
|
try (CompressedInputStream in = storage.textures().read()){
|
||||||
Optional<InputStream> texturesData = storage.readMeta(id, META_FILE_TEXTURES);
|
if (in != null)
|
||||||
if (texturesData.isPresent()) {
|
return TextureGallery.readTexturesFile(in.decompress());
|
||||||
try (InputStream in = texturesData.get()){
|
} catch (IOException ex) {
|
||||||
gallery = TextureGallery.readTexturesFile(in);
|
Logger.global.logError("Failed to load textures for map '" + getId() + "'!", ex);
|
||||||
} 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() {
|
private void saveTextureGallery() {
|
||||||
try (OutputStream out = storage.writeMeta(id, META_FILE_TEXTURES)) {
|
try (OutputStream out = storage.textures().write()) {
|
||||||
this.textureGallery.writeTexturesFile(out);
|
this.textureGallery.writeTexturesFile(out);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
Logger.global.logError("Failed to save textures for map '" + getId() + "'!", ex);
|
Logger.global.logError("Failed to save textures for map '" + getId() + "'!", ex);
|
||||||
@ -199,7 +189,7 @@ public synchronized void resetTextureGallery() {
|
|||||||
|
|
||||||
private void saveMapSettings() {
|
private void saveMapSettings() {
|
||||||
try (
|
try (
|
||||||
OutputStream out = storage.writeMeta(id, META_FILE_SETTINGS);
|
OutputStream out = storage.settings().write();
|
||||||
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)
|
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)
|
||||||
) {
|
) {
|
||||||
GSON.toJson(this, writer);
|
GSON.toJson(this, writer);
|
||||||
@ -210,7 +200,7 @@ private void saveMapSettings() {
|
|||||||
|
|
||||||
public synchronized void saveMarkerState() {
|
public synchronized void saveMarkerState() {
|
||||||
try (
|
try (
|
||||||
OutputStream out = storage.writeMeta(id, META_FILE_MARKERS);
|
OutputStream out = storage.markers().write();
|
||||||
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)
|
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)
|
||||||
) {
|
) {
|
||||||
MarkerGson.INSTANCE.toJson(this.markerSets, writer);
|
MarkerGson.INSTANCE.toJson(this.markerSets, writer);
|
||||||
@ -220,9 +210,7 @@ public synchronized void saveMarkerState() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void savePlayerState() {
|
public synchronized void savePlayerState() {
|
||||||
try (
|
try (OutputStream out = storage.players().write()) {
|
||||||
OutputStream out = storage.writeMeta(id, META_FILE_PLAYERS)
|
|
||||||
) {
|
|
||||||
out.write("{}".getBytes(StandardCharsets.UTF_8));
|
out.write("{}".getBytes(StandardCharsets.UTF_8));
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.global.logError("Failed to save markers for map '" + getId() + "'!", ex);
|
Logger.global.logError("Failed to save markers for map '" + getId() + "'!", ex);
|
||||||
@ -241,7 +229,7 @@ public World getWorld() {
|
|||||||
return world;
|
return world;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Storage getStorage() {
|
public MapStorage getStorage() {
|
||||||
return storage;
|
return storage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,12 +272,8 @@ public int hashCode() {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj instanceof BmMap) {
|
if (obj instanceof BmMap that)
|
||||||
BmMap that = (BmMap) obj;
|
|
||||||
|
|
||||||
return this.id.equals(that.id);
|
return this.id.equals(that.id);
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
import de.bluecolored.bluemap.core.map.TextureGallery;
|
import de.bluecolored.bluemap.core.map.TextureGallery;
|
||||||
import de.bluecolored.bluemap.core.map.TileMetaConsumer;
|
import de.bluecolored.bluemap.core.map.TileMetaConsumer;
|
||||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
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.util.Grid;
|
||||||
import de.bluecolored.bluemap.core.world.World;
|
import de.bluecolored.bluemap.core.world.World;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@ -40,17 +40,17 @@
|
|||||||
|
|
||||||
public class HiresModelManager {
|
public class HiresModelManager {
|
||||||
|
|
||||||
private final Storage.TileStorage storage;
|
private final GridStorage storage;
|
||||||
private final HiresModelRenderer renderer;
|
private final HiresModelRenderer renderer;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final Grid tileGrid;
|
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);
|
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.storage = storage;
|
||||||
this.renderer = renderer;
|
this.renderer = renderer;
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ public void render(World world, Vector2i tile, TileMetaConsumer tileMetaConsumer
|
|||||||
|
|
||||||
private void save(final TileModel model, Vector2i tile) {
|
private void save(final TileModel model, Vector2i tile) {
|
||||||
try (
|
try (
|
||||||
OutputStream out = storage.write(tile);
|
OutputStream out = storage.write(tile.getX(), tile.getY());
|
||||||
PRBMWriter modelWriter = new PRBMWriter(out)
|
PRBMWriter modelWriter = new PRBMWriter(out)
|
||||||
) {
|
) {
|
||||||
modelWriter.write(model);
|
modelWriter.write(model);
|
||||||
|
@ -28,10 +28,10 @@
|
|||||||
import com.github.benmanes.caffeine.cache.*;
|
import com.github.benmanes.caffeine.cache.*;
|
||||||
import de.bluecolored.bluemap.core.BlueMap;
|
import de.bluecolored.bluemap.core.BlueMap;
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
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.Vector2iCache;
|
||||||
import de.bluecolored.bluemap.core.util.math.Color;
|
import de.bluecolored.bluemap.core.util.math.Color;
|
||||||
import de.bluecolored.bluemap.core.util.Grid;
|
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ public class LowresLayer {
|
|||||||
|
|
||||||
private static final Vector2iCache VECTOR_2_I_CACHE = new Vector2iCache();
|
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 Grid tileGrid;
|
||||||
private final int lodFactor;
|
private final int lodFactor;
|
||||||
@ -54,10 +54,10 @@ public class LowresLayer {
|
|||||||
@Nullable private final LowresLayer nextLayer;
|
@Nullable private final LowresLayer nextLayer;
|
||||||
|
|
||||||
public LowresLayer(
|
public LowresLayer(
|
||||||
Storage.MapStorage mapStorage, Grid tileGrid, int lodCount, int lodFactor,
|
GridStorage storage, Grid tileGrid, int lodFactor,
|
||||||
int lod, @Nullable LowresLayer nextLayer
|
int lod, @Nullable LowresLayer nextLayer
|
||||||
) {
|
) {
|
||||||
this.mapStorage = mapStorage;
|
this.storage = storage;
|
||||||
|
|
||||||
this.tileGrid = tileGrid;
|
this.tileGrid = tileGrid;
|
||||||
this.lodFactor = lodFactor;
|
this.lodFactor = lodFactor;
|
||||||
@ -83,7 +83,7 @@ public void write(@NonNull Vector2i key, @NonNull LowresTile value) {}
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(@NonNull Vector2i key, @Nullable LowresTile value, @NonNull RemovalCause cause) {
|
public void delete(@NonNull Vector2i key, @Nullable LowresTile value, @NonNull RemovalCause cause) {
|
||||||
saveTile(key, value, cause);
|
saveTile(key, value);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.build(tileWeakInstanceCache::get);
|
.build(tileWeakInstanceCache::get);
|
||||||
@ -95,7 +95,7 @@ public void save() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private LowresTile createTile(Vector2i tilePos) {
|
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);
|
if (in != null) return new LowresTile(tileGrid.getGridSize(), in);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Logger.global.logError("Failed to load tile " + tilePos + " (lod: " + lod + ")", 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());
|
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;
|
if (tile == null) return;
|
||||||
|
|
||||||
// check if storage is closed
|
// 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.");
|
Logger.global.logDebug("Tried to save tile " + tilePos + " (lod: " + lod + ") but storage is already closed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// save the tile
|
// save the tile
|
||||||
try (OutputStream out = mapStorage.write(lod, tilePos)) {
|
try (OutputStream out = storage.write(tilePos.getX(), tilePos.getY())) {
|
||||||
tile.save(out);
|
tile.save(out);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Logger.global.logError("Failed to save tile " + tilePos + " (lod: " + lod + ")", e);
|
Logger.global.logError("Failed to save tile " + tilePos + " (lod: " + lod + ")", e);
|
||||||
|
@ -25,9 +25,9 @@
|
|||||||
package de.bluecolored.bluemap.core.map.lowres;
|
package de.bluecolored.bluemap.core.map.lowres;
|
||||||
|
|
||||||
import de.bluecolored.bluemap.core.map.TileMetaConsumer;
|
import de.bluecolored.bluemap.core.map.TileMetaConsumer;
|
||||||
import de.bluecolored.bluemap.core.storage.Storage;
|
import de.bluecolored.bluemap.core.storage.MapStorage;
|
||||||
import de.bluecolored.bluemap.core.util.math.Color;
|
|
||||||
import de.bluecolored.bluemap.core.util.Grid;
|
import de.bluecolored.bluemap.core.util.Grid;
|
||||||
|
import de.bluecolored.bluemap.core.util.math.Color;
|
||||||
|
|
||||||
public class LowresTileManager implements TileMetaConsumer {
|
public class LowresTileManager implements TileMetaConsumer {
|
||||||
|
|
||||||
@ -36,14 +36,14 @@ public class LowresTileManager implements TileMetaConsumer {
|
|||||||
|
|
||||||
private final LowresLayer[] layers;
|
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.tileGrid = tileGrid;
|
||||||
this.lodFactor = lodFactor;
|
this.lodFactor = lodFactor;
|
||||||
this.lodCount = lodCount;
|
this.lodCount = lodCount;
|
||||||
|
|
||||||
this.layers = new LowresLayer[lodCount];
|
this.layers = new LowresLayer[lodCount];
|
||||||
for (int i = lodCount - 1; i >= 0; i--) {
|
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]);
|
(i == lodCount - 1) ? null : layers[i + 1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,8 @@
|
|||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.core.metrics;
|
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.BlueMap;
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
|
|
||||||
@ -39,18 +40,22 @@
|
|||||||
public class Metrics {
|
public class Metrics {
|
||||||
|
|
||||||
private static final String METRICS_REPORT_URL = "https://metrics.bluecolored.de/bluemap/";
|
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) {
|
public static void sendReportAsync(String implementation, String mcVersion) {
|
||||||
new Thread(() -> sendReport(implementation), "BlueMap-Plugin-SendMetricsReport").start();
|
new Thread(() -> sendReport(implementation, mcVersion), "BlueMap-Plugin-SendMetricsReport").start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void sendReport(String implementation) {
|
public static void sendReport(String implementation, String mcVersion) {
|
||||||
JsonObject data = new JsonObject();
|
Report report = new Report(
|
||||||
data.addProperty("implementation", implementation);
|
implementation,
|
||||||
data.addProperty("version", BlueMap.VERSION);
|
BlueMap.VERSION,
|
||||||
|
mcVersion
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
sendData(data.toString());
|
sendData(GSON.toJson(report));
|
||||||
} catch (IOException | RuntimeException ex) {
|
} catch (IOException | RuntimeException ex) {
|
||||||
Logger.global.logDebug("Failed to send Metrics-Report: " + 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;
|
package de.bluecolored.bluemap.core.storage;
|
||||||
|
|
||||||
import com.flowpowered.math.vector.Vector2i;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.util.stream.Stream;
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
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;
|
/**
|
||||||
|
* Checks if this storage is closed
|
||||||
public abstract void deleteMapTile(String mapId, int lod, Vector2i tile) throws IOException;
|
*/
|
||||||
|
boolean isClosed();
|
||||||
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("..", "_.");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.core.storage;
|
package de.bluecolored.bluemap.core.storage.compression;
|
||||||
|
|
||||||
import de.bluecolored.bluemap.core.util.stream.DelegateInputStream;
|
import de.bluecolored.bluemap.core.util.stream.DelegateInputStream;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
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 {
|
public class CompressedInputStream extends DelegateInputStream {
|
||||||
|
|
||||||
private final Compression compression;
|
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) {
|
public CompressedInputStream(InputStream in, Compression compression) {
|
||||||
super(in);
|
super(in);
|
||||||
this.compression = compression;
|
this.compression = compression;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the decompressed {@link InputStream}
|
||||||
|
*/
|
||||||
public InputStream decompress() throws IOException {
|
public InputStream decompress() throws IOException {
|
||||||
return compression.decompress(in);
|
return compression.decompress(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link Compression} this InputStream's data is compressed with
|
||||||
|
*/
|
||||||
public Compression getCompression() {
|
public Compression getCompression() {
|
||||||
return compression;
|
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;
|
package de.bluecolored.bluemap.core.storage.file;
|
||||||
|
|
||||||
import com.flowpowered.math.vector.Vector2i;
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
import de.bluecolored.bluemap.api.debug.DebugDump;
|
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||||
import de.bluecolored.bluemap.core.storage.*;
|
import de.bluecolored.bluemap.core.storage.Storage;
|
||||||
import de.bluecolored.bluemap.core.util.DeletingPathVisitor;
|
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||||
import de.bluecolored.bluemap.core.util.FileHelper;
|
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
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;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@DebugDump
|
public class FileStorage implements Storage {
|
||||||
public class FileStorage extends Storage {
|
|
||||||
|
|
||||||
private final Path root;
|
private final Path root;
|
||||||
private final Compression hiresCompression;
|
private final LoadingCache<String, FileMapStorage> mapStorages;
|
||||||
|
|
||||||
public FileStorage(FileStorageSettings config) {
|
|
||||||
this.root = config.getRoot();
|
|
||||||
this.hiresCompression = config.getCompression();
|
|
||||||
}
|
|
||||||
|
|
||||||
public FileStorage(Path root, Compression compression) {
|
public FileStorage(Path root, Compression compression) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.hiresCompression = compression;
|
|
||||||
|
mapStorages = Caffeine.newBuilder()
|
||||||
|
.build(id -> new FileMapStorage(root.resolve(id), compression));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
@Override
|
||||||
public boolean isClosed() {
|
public boolean isClosed() {
|
||||||
@ -66,186 +47,4 @@ public boolean isClosed() {
|
|||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {}
|
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;
|
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.Caffeine;
|
||||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||||
import de.bluecolored.bluemap.core.BlueMap;
|
import de.bluecolored.bluemap.core.storage.MapStorage;
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
import de.bluecolored.bluemap.core.storage.Storage;
|
||||||
import de.bluecolored.bluemap.core.storage.*;
|
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||||||
import de.bluecolored.bluemap.core.storage.sql.dialect.DialectType;
|
import de.bluecolored.bluemap.core.storage.sql.commandset.CommandSet;
|
||||||
import de.bluecolored.bluemap.core.storage.sql.dialect.Dialect;
|
import lombok.RequiredArgsConstructor;
|
||||||
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 javax.sql.DataSource;
|
import java.io.IOException;
|
||||||
import java.io.*;
|
import java.util.stream.Stream;
|
||||||
import java.net.MalformedURLException;
|
import java.util.stream.StreamSupport;
|
||||||
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;
|
|
||||||
|
|
||||||
public abstract class SQLStorage extends Storage {
|
@RequiredArgsConstructor
|
||||||
|
public class SQLStorage implements Storage {
|
||||||
|
|
||||||
private final DataSource dataSource;
|
private final CommandSet sql;
|
||||||
|
private final Compression compression;
|
||||||
protected final Dialect dialect;
|
private final LoadingCache<String, SQLMapStorage> mapStorages = Caffeine.newBuilder()
|
||||||
protected final Compression hiresCompression;
|
.build(this::create);
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@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 {
|
public void initialize() throws IOException {
|
||||||
try {
|
sql.initializeTables();
|
||||||
|
}
|
||||||
|
|
||||||
// initialize and get schema-version
|
private SQLMapStorage create(String mapId) {
|
||||||
String schemaVersionString = recoveringConnection(connection -> {
|
return new SQLMapStorage(mapId, sql, compression);
|
||||||
connection.createStatement().executeUpdate(
|
}
|
||||||
this.dialect.initializeStorageMeta());
|
|
||||||
|
|
||||||
ResultSet result = executeQuery(connection,
|
@Override
|
||||||
this.dialect.selectStorageMeta(),
|
public MapStorage map(String mapId) {
|
||||||
"schema_version"
|
return mapStorages.get(mapId);
|
||||||
);
|
}
|
||||||
|
|
||||||
if (result.next()) {
|
@Override
|
||||||
return result.getString("value");
|
public Stream<String> mapIds() {
|
||||||
} else {
|
return StreamSupport.stream(
|
||||||
executeUpdate(connection,
|
new PageSpliterator<>(page -> {
|
||||||
this.dialect.insertStorageMeta(),
|
try {
|
||||||
"schema_version", "0"
|
return sql.listMapIds(page * 1000, 1000);
|
||||||
);
|
} catch (IOException ex) { throw new RuntimeException(ex); }
|
||||||
return "0";
|
}),
|
||||||
}
|
false
|
||||||
}, 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
|
@Override
|
||||||
public boolean isClosed() {
|
public boolean isClosed() {
|
||||||
return closed;
|
return sql.isClosed();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
this.closed = true;
|
sql.close();
|
||||||
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;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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.InputStream;
|
||||||
import java.io.OutputStream;
|
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;
|
protected final InputStream in;
|
||||||
|
|
||||||
|
@ -29,7 +29,12 @@
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
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;
|
protected final OutputStream out;
|
||||||
|
|
||||||
|
@ -27,6 +27,9 @@
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
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 {
|
public class OnCloseInputStream extends DelegateInputStream {
|
||||||
|
|
||||||
private final AutoCloseable onClose;
|
private final AutoCloseable onClose;
|
||||||
|
@ -27,6 +27,9 @@
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
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 {
|
public class OnCloseOutputStream extends DelegateOutputStream {
|
||||||
|
|
||||||
private final AutoCloseable onClose;
|
private final AutoCloseable onClose;
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
import de.bluecolored.bluemap.core.BlueMap;
|
import de.bluecolored.bluemap.core.BlueMap;
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
import de.bluecolored.bluemap.core.resources.datapack.DataPack;
|
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.Grid;
|
||||||
import de.bluecolored.bluemap.core.util.Key;
|
import de.bluecolored.bluemap.core.util.Key;
|
||||||
import de.bluecolored.bluemap.core.util.Vector2iCache;
|
import de.bluecolored.bluemap.core.util.Vector2iCache;
|
||||||
@ -45,7 +46,6 @@
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -54,7 +54,6 @@
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.zip.GZIPInputStream;
|
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@ToString
|
@ToString
|
||||||
@ -260,7 +259,7 @@ private Chunk loadChunk(int x, int z) {
|
|||||||
public static MCAWorld load(Path worldFolder, Key dimension) throws IOException, InterruptedException {
|
public static MCAWorld load(Path worldFolder, Key dimension) throws IOException, InterruptedException {
|
||||||
// load level.dat
|
// load level.dat
|
||||||
Path levelFile = worldFolder.resolve("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);
|
LevelData levelData = MCAUtil.BLUENBT.read(levelFileIn, LevelData.class);
|
||||||
|
|
||||||
// load datapacks
|
// load datapacks
|
||||||
|
@ -24,14 +24,13 @@
|
|||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.core.world.mca.chunk;
|
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.MCAUtil;
|
||||||
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
|
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -63,7 +62,7 @@ public MCAChunk load(byte[] data, int offset, int length, Compression compressio
|
|||||||
// try last used version
|
// try last used version
|
||||||
ChunkVersionLoader<?> usedLoader = lastUsedLoader;
|
ChunkVersionLoader<?> usedLoader = lastUsedLoader;
|
||||||
MCAChunk chunk;
|
MCAChunk chunk;
|
||||||
try (InputStream decompressedIn = new BufferedInputStream(compression.decompress(in))) {
|
try (InputStream decompressedIn = compression.decompress(in)) {
|
||||||
chunk = usedLoader.load(world, decompressedIn);
|
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());
|
ChunkVersionLoader<?> actualLoader = findBestLoaderForVersion(chunk.getDataVersion());
|
||||||
if (actualLoader != null && usedLoader != actualLoader) {
|
if (actualLoader != null && usedLoader != actualLoader) {
|
||||||
in.reset(); // reset read position
|
in.reset(); // reset read position
|
||||||
try (InputStream decompressedIn = new BufferedInputStream(compression.decompress(in))) {
|
try (InputStream decompressedIn = compression.decompress(in)) {
|
||||||
chunk = actualLoader.load(world, decompressedIn);
|
chunk = actualLoader.load(world, decompressedIn);
|
||||||
}
|
}
|
||||||
lastUsedLoader = actualLoader;
|
lastUsedLoader = actualLoader;
|
||||||
|
@ -25,12 +25,11 @@
|
|||||||
package de.bluecolored.bluemap.core.world.mca.region;
|
package de.bluecolored.bluemap.core.world.mca.region;
|
||||||
|
|
||||||
import com.flowpowered.math.vector.Vector2i;
|
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.ChunkConsumer;
|
||||||
import de.bluecolored.bluemap.core.world.Region;
|
import de.bluecolored.bluemap.core.world.Region;
|
||||||
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
|
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
|
||||||
import de.bluecolored.bluemap.core.world.mca.chunk.MCAChunk;
|
import de.bluecolored.bluemap.core.world.mca.chunk.MCAChunk;
|
||||||
import io.airlift.compress.zstd.ZstdInputStream;
|
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@ -147,9 +146,8 @@ public void iterateAllChunks(ChunkConsumer consumer) throws IOException {
|
|||||||
byte[] chunkDataBuffer = null;
|
byte[] chunkDataBuffer = null;
|
||||||
|
|
||||||
try (
|
try (
|
||||||
InputStream in = new ZstdInputStream(new ByteArrayInputStream(compressedData));
|
InputStream in = Compression.ZSTD.decompress(new ByteArrayInputStream(compressedData));
|
||||||
BufferedInputStream bIn = new BufferedInputStream(in);
|
DataInputStream dIn = new DataInputStream(in)
|
||||||
DataInputStream dIn = new DataInputStream(bIn)
|
|
||||||
) {
|
) {
|
||||||
int[] chunkDataLengths = new int[1024];
|
int[] chunkDataLengths = new int[1024];
|
||||||
int[] chunkTimestamps = new int[1024];
|
int[] chunkTimestamps = new int[1024];
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
package de.bluecolored.bluemap.core.world.mca.region;
|
package de.bluecolored.bluemap.core.world.mca.region;
|
||||||
|
|
||||||
import com.flowpowered.math.vector.Vector2i;
|
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.Chunk;
|
||||||
import de.bluecolored.bluemap.core.world.ChunkConsumer;
|
import de.bluecolored.bluemap.core.world.ChunkConsumer;
|
||||||
import de.bluecolored.bluemap.core.world.Region;
|
import de.bluecolored.bluemap.core.world.Region;
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
import de.bluecolored.bluemap.core.map.BmMap;
|
import de.bluecolored.bluemap.core.map.BmMap;
|
||||||
import de.bluecolored.bluemap.core.metrics.Metrics;
|
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 de.bluecolored.bluemap.core.util.FileHelper;
|
||||||
import org.apache.commons.cli.*;
|
import org.apache.commons.cli.*;
|
||||||
import org.apache.commons.lang3.time.DurationFormatUtils;
|
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 {
|
@Nullable String mapsToRender) throws ConfigurationException, IOException, InterruptedException {
|
||||||
|
|
||||||
//metrics report
|
//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())
|
if (blueMap.getConfig().getWebappConfig().isEnabled())
|
||||||
blueMap.createOrUpdateWebApp(forceGenerateWebapp);
|
blueMap.createOrUpdateWebApp(forceGenerateWebapp);
|
||||||
@ -201,12 +204,13 @@ public void startWebserver(BlueMapService blueMap, boolean verbose) throws IOExc
|
|||||||
|
|
||||||
// map route
|
// map route
|
||||||
for (var mapConfigEntry : blueMap.getConfig().getMapConfigs().entrySet()) {
|
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(
|
routingRequestHandler.register(
|
||||||
"maps/" + Pattern.quote(mapConfigEntry.getKey()) + "/(.*)",
|
"maps/" + Pattern.quote(mapConfigEntry.getKey()) + "/(.*)",
|
||||||
"$1",
|
"$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" +
|
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);
|
"This usually happens when the configured port (" + config.getPort() + ") is already in use by some other program.", ex);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new ConfigurationException("BlueMap failed to initialize the webserver.\n" +
|
throw new ConfigurationException("""
|
||||||
"Check your webserver-config if everything is configured correctly.\n" +
|
BlueMap failed to initialize the webserver.
|
||||||
"(Make sure you DON'T use the same port for bluemap that you also use for your minecraft server)", ex);
|
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.common.serverinterface.ServerWorld;
|
||||||
import de.bluecolored.bluemap.core.world.World;
|
import de.bluecolored.bluemap.core.world.World;
|
||||||
import net.md_5.bungee.chat.ComponentSerializer;
|
import net.md_5.bungee.chat.ComponentSerializer;
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.command.BlockCommandSender;
|
import org.bukkit.command.BlockCommandSender;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
|
Loading…
Reference in New Issue
Block a user