Merge branch 'master' into fix/heightmaps

This commit is contained in:
Lukas Rieger (Blue) 2023-06-19 21:19:56 +02:00
commit c55d5849ca
No known key found for this signature in database
GPG Key ID: 2D09EC5ED2687FF2
44 changed files with 1588 additions and 249 deletions

View File

@ -19,7 +19,7 @@ So, if something doesn't work because it is not implemented yet, its not a bug.
If you are not sure, you can briefly ask about it in our [Discord](https://discord.gg/zmkyJa3) before creating an Issue. :)
- Make sure you tested it well enough to be sure it's not an issue on your end. If something doesn't work for you but for everyone else, its probably **not** a bug!
Also, please make sure noone else has already reported the same or a very similar bug!
Also, please make sure no one else has already reported the same or a very similar bug!
If you have additional information for an existing bug-report, you can add a comment to the already existing Issue :)
To report your bug, please open a [new Issue](https://github.com/BlueMap-Minecraft/BlueMap/issues/new?template=bug_report.md) with the `Bug report`-template and follow these guidlines:
@ -53,7 +53,20 @@ Make sure your Issue is easy to read and not a mess:
Create a separate Issue for each bug you find! Issues that contain more than one bug will be closed!
## Suggesting a new feature or change
**(Todo)**
Please use our [discord](https://discord.gg/zmkyJa3)s #suggestions channel to pitch new ideas.
We will discuss them there and if they are considered, I'll add an issue/note to out [TODO](https://github.com/orgs/BlueMap-Minecraft/projects/2/views/1)-Board!
## Creating a Pull-Request
**(Todo)**
If you want to develop a new PR, please run your Idea by me first in our [discord](https://discord.gg/zmkyJa3)!
We can discuss details there, since I have a lot of future plans in my head that are not written anywhere, and they might need to be considered
when implementing your feature!
*(Also, I tend to be quite picky about certain implementation styles and details ^^')*
**Please keep in mind that any feature you implement will need to be maintained in the future by me.
For this reason I will only accept PR's for features that I deem to be useful, maintainable, in-scope of the project and
worth it's maintenance-workload!**
Ofc the usual "good code quality..." stuff, i think that's common sense.
Try to match the existing code-style.
Don't add new libraries/dependencies without my ok.
Hacky stuff is not allowed =)

View File

@ -1,14 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Why do you want this feature
**Describe the solution you'd like**
A clear and concise description of what you want to happen.

View File

@ -34,10 +34,7 @@ import de.bluecolored.bluemap.core.util.FileHelper;
import de.bluecolored.bluemap.core.util.Tristate;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.file.*;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.HashMap;
@ -396,12 +393,28 @@ public class BlueMapConfigs implements BlueMapConfigProvider {
}
private String formatPath(Path path) {
return Path.of("")
// normalize path
path = Path.of("")
.toAbsolutePath()
.relativize(path.toAbsolutePath())
.normalize()
.toString()
.replace("\\", "\\\\");
.normalize();
String pathString = path.toString();
String formatted = pathString;
String separator = FileSystems.getDefault().getSeparator();
// try to replace separator with standardized forward slash
if (!separator.equals("/"))
formatted = pathString.replace(separator, "/");
// sanity check forward slash compatibility
if (!Path.of(formatted).equals(path))
formatted = pathString;
// escape all backslashes
formatted = formatted.replace("\\", "\\\\");
return formatted;
}
}

View File

@ -31,7 +31,7 @@ import de.bluecolored.bluemap.core.storage.sql.SQLStorage;
public enum StorageType {
FILE (FileConfig.class, FileStorage::new),
SQL (SQLConfig.class, SQLStorage::new);
SQL (SQLConfig.class, SQLStorage::create);
private final Class<? extends StorageConfig> configType;
private final StorageFactory<? extends StorageConfig> storageFactory;

View File

@ -55,6 +55,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.file.Path;
@ -194,6 +195,9 @@ public class Plugin implements ServerEventListener {
} catch (UnknownHostException ex) {
throw new ConfigurationException("BlueMap failed to resolve the ip in your webserver-config.\n" +
"Check if that is correctly configured.", ex);
} catch (BindException ex) {
throw new ConfigurationException("BlueMap failed to bind to the configured address.\n" +
"This usually happens when the configured port (" + webserverConfig.getPort() + ") is already in use by some other program.", ex);
} catch (IOException ex) {
throw new ConfigurationException("BlueMap failed to initialize the webserver.\n" +
"Check your webserver-config if everything is configured correctly.\n" +

View File

@ -34,10 +34,18 @@ import de.bluecolored.bluemap.core.world.World;
import org.apache.commons.lang3.time.DurationFormatUtils;
import java.lang.ref.WeakReference;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
public class CommandHelper {
private static final DateTimeFormatter TIME_FORMAT =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
.withLocale(Locale.ROOT)
.withZone(ZoneId.systemDefault());
private final Plugin plugin;
private final Map<String, WeakReference<RenderTask>> taskRefMap;
@ -67,7 +75,9 @@ public class CommandHelper {
lines.add(Text.of(TextColor.WHITE, " Render-Threads are ", status, TextColor.WHITE, "!"));
if (!tasks.isEmpty()) {
if (tasks.isEmpty()) {
lines.add(Text.of(TextColor.GRAY, " Last time running: ", TextColor.DARK_GRAY, formatTime(renderer.getLastTimeBusy())));
} else {
lines.add(Text.of(TextColor.WHITE, " Queued Tasks (" + tasks.size() + "):"));
for (int i = 0; i < tasks.size(); i++) {
if (i >= 10){
@ -76,20 +86,18 @@ public class CommandHelper {
}
RenderTask task = tasks.get(i);
lines.add(Text.of(TextColor.GRAY, " [" + getRefForTask(task) + "] ", TextColor.GOLD, task.getDescription()));
lines.add(Text.of(TextColor.GRAY, "\u00A0\u00A0[" + getRefForTask(task) + "] ", TextColor.GOLD, task.getDescription()));
if (i == 0) {
String detail = task.getDetail().orElse(null);
if (detail != null) {
lines.add(Text.of(TextColor.GRAY, " Detail: ", TextColor.WHITE, detail));
}
task.getDetail().ifPresent(detail ->
lines.add(Text.of(TextColor.GRAY, "\u00A0\u00A0\u00A0Detail: ", TextColor.WHITE, detail)));
lines.add(Text.of(TextColor.GRAY, " Progress: ", TextColor.WHITE,
lines.add(Text.of(TextColor.GRAY, "\u00A0\u00A0\u00A0Progress: ", TextColor.WHITE,
(Math.round(task.estimateProgress() * 10000) / 100.0) + "%"));
long etaMs = renderer.estimateCurrentRenderTaskTimeRemaining();
if (etaMs > 0) {
lines.add(Text.of(TextColor.GRAY, " ETA: ", TextColor.WHITE, DurationFormatUtils.formatDuration(etaMs, "HH:mm:ss")));
lines.add(Text.of(TextColor.GRAY, "\u00A0\u00A0\u00A0ETA: ", TextColor.WHITE, DurationFormatUtils.formatDuration(etaMs, "HH:mm:ss")));
}
}
}
@ -98,7 +106,7 @@ public class CommandHelper {
if (plugin.checkPausedByPlayerCount()) {
lines.add(Text.of(TextColor.WHITE, " Render-Threads are ",
Text.of(TextColor.GOLD, "paused")));
lines.add(Text.of(TextColor.GRAY, TextFormat.ITALIC, " (there are " + plugin.getConfigs().getPluginConfig().getPlayerRenderLimit() + " or more players online)"));
lines.add(Text.of(TextColor.GRAY, TextFormat.ITALIC, "\u00A0\u00A0\u00A0(there are " + plugin.getConfigs().getPluginConfig().getPlayerRenderLimit() + " or more players online)"));
} else {
lines.add(Text.of(TextColor.WHITE, " Render-Threads are ",
Text.of(TextColor.RED, "stopped")
@ -176,4 +184,9 @@ public class CommandHelper {
return ref.subSequence(0, 4).toString();
}
public String formatTime(long timestamp) {
if (timestamp < 0) return "-";
return TIME_FORMAT.format(Instant.ofEpochMilli(timestamp));
}
}

View File

@ -838,27 +838,28 @@ public class Commands<S> {
}
public int mapsCommand(CommandContext<S> context) {
CommandSource source = commandSourceInterface.apply(context.getSource());
List<Text> lines = new ArrayList<>();
lines.add(Text.of(TextColor.BLUE, "Maps loaded by BlueMap:"));
source.sendMessage(Text.of(TextColor.BLUE, "Maps loaded by BlueMap:"));
for (BmMap map : plugin.getMaps().values()) {
boolean unfrozen = plugin.getPluginState().getMapState(map).isUpdateEnabled();
if (unfrozen) {
source.sendMessage(Text.of(
TextColor.GRAY, " - ",
TextColor.WHITE, map.getId(),
TextColor.GRAY, " (" + map.getName() + ")"
).setHoverText(Text.of(TextColor.WHITE, "World: ", TextColor.GRAY, map.getWorld().getName())));
} else {
source.sendMessage(Text.of(
TextColor.GRAY, " - ",
TextColor.WHITE, map.getId(),
TextColor.GRAY, " (" + map.getName() + ") - ",
TextColor.AQUA, TextFormat.ITALIC, "frozen!"
).setHoverText(Text.of(TextColor.WHITE, "World: ", TextColor.GRAY, map.getWorld().getName())));
}
boolean frozen = !plugin.getPluginState().getMapState(map).isUpdateEnabled();
lines.add(Text.of(TextColor.GRAY, " - ",
TextColor.WHITE, map.getId(),
TextColor.GRAY, " (" + map.getName() + ")"));
lines.add(Text.of(TextColor.GRAY, "\u00A0\u00A0\u00A0World: ",
TextColor.DARK_GRAY, map.getWorld().getName()));
lines.add(Text.of(TextColor.GRAY, "\u00A0\u00A0\u00A0Last Update: ",
TextColor.DARK_GRAY, helper.formatTime(map.getRenderState().getLatestRenderTime())));
if (frozen)
lines.add(Text.of(TextColor.AQUA, TextFormat.ITALIC, "This map is frozen!"));
}
CommandSource source = commandSourceInterface.apply(context.getSource());
source.sendMessages(lines);
return 1;
}

View File

@ -38,6 +38,8 @@ public class RenderManager {
@DebugDump private final int id;
@DebugDump private volatile boolean running;
@DebugDump private long lastTimeBusy;
private final AtomicInteger nextWorkerThreadIndex;
@DebugDump private final Collection<WorkerThread> workerThreads;
private final AtomicInteger busyCount;
@ -55,6 +57,8 @@ public class RenderManager {
this.workerThreads = new ConcurrentLinkedDeque<>();
this.busyCount = new AtomicInteger(0);
this.lastTimeBusy = -1;
this.progressTracker = null;
this.newTask = true;
@ -249,6 +253,10 @@ public class RenderManager {
return workerThreads.size();
}
public long getLastTimeBusy() {
return lastTimeBusy;
}
private void removeTasksThatAreContainedIn(RenderTask containingTask) {
synchronized (this.renderTasks) {
if (renderTasks.size() < 2) return;
@ -290,13 +298,15 @@ public class RenderManager {
}
this.busyCount.incrementAndGet();
this.lastTimeBusy = System.currentTimeMillis();
}
try {
task.doWork();
} finally {
synchronized (renderTasks) {
this.busyCount.decrementAndGet();
int busyCount = this.busyCount.decrementAndGet();
if (busyCount > 0) this.lastTimeBusy = System.currentTimeMillis();
this.renderTasks.notifyAll();
}
}

View File

@ -26,5 +26,6 @@
{ locale: "zh_TW", name: "中文(台灣)" }
{ locale: "zh_HK", name: "中文(香港)" }
{ locale: "ko", name: "한국어" }
{ locale: "vi", name: "Tiếng Việt"}
]
}

View File

@ -0,0 +1,171 @@
{
pageTitle: "BlueMap - {map}"
menu: {
title: "Menu"
tooltip: "Menu"
}
map: {
unloaded: "Không có bản đồ."
loading: "Đang tải bản đồ..."
errored: "Có lồi khi tải bản đồ!"
}
maps: {
title: "Bản đồ"
button: "Bản đồ"
tooltip: "Mọi bản đồ"
}
markers: {
title: "Đánh dấu"
button: "Đánh dấu"
tooltip: "Mọi đánh dấu"
marker: "đánh dấu | các đánh dấu"
markerSet: "cụm đánh dấu | các cụm đánh dấu"
searchPlaceholder: "Tìm..."
followPlayerTitle: "Bám theo"
sort {
title: "Sắp xếp"
by {
default: "mặc định"
label: "tên"
distance: "khoảng cách"
}
}
}
settings: {
title: "Cài đặt"
button: "Cài đặt"
}
goFullscreen: {
button: "Toản màn hình"
}
resetCamera: {
button: "Đặt lại camera"
tooltip: "Đặt lại camera và vị trí"
}
updateMap: {
button: "Cập nhật bản đồ"
tooltip: "Xóa bộ nhớ đệm"
}
lighting: {
title: "Ánh sáng"
dayNightSwitch: {
tooltip: "Ngày/Đêm"
}
sunlight: "Nhật quang"
ambientLight: "Phát quang"
}
resolution: {
title: "Độ phân giải"
high: "Cao (SSAA x2)"
normal: "Thường (Native x1)"
low: "Thấp (Upscaling x0.5)"
}
mapControls: {
title: "Điều khiển"
showZoomButtons: "Hiện nút thu phóng"
}
freeFlightControls: {
title: "Chế độ bay"
mouseSensitivity: "Độ nhạy chuột"
invertMouseY: "Đảo trục dọc"
}
renderDistance: {
title: "Khoảng cách kết xuất"
hiresLayer: "Vùng chất lượng cao"
lowersLayer: "Vùng chất lượng thấp"
loadHiresWhileMoving: "Tải vùng chất lượng cao khi di chuyển"
off: "Tắt"
}
theme: {
title: "Giao diện"
default: "Mặc định (hệ thống)"
dark: "Tối"
light: "Sáng"
contrast: "Tương phản"
}
debug: {
button: "Gỡ lỗi"
}
resetAllSettings: {
button: "Thiết đặt lại"
}
players: {
title: "Người chơi"
tooltip: "Danh sách người chơi"
}
compass: {
tooltip: "Hướng / chỉ bắc"
}
screenshot: {
title: "Chụp màn hình"
button: "Chụp màn hình"
clipboard: "Sao chép"
}
controls: {
title: "Chế độ"
perspective: {
button: "Xung quanh"
tooltip: "Góc nhìn xung quanh"
}
flatView: {
button: "Phẳng"
tooltip: "Góc nhìn từ trên xuống"
}
freeFlight: {
button: "Bay"
tooltip: "Góc nhìn chim bay"
}
}
language: {
title: "Ngôn ngữ"
}
blockTooltip: {
block: "Khối"
position: "Vị chí"
chunk: "Vùng"
region: {
region: "Khu vực"
file: "File"
}
light: {
light: "Ánh sáng"
sun: "Nhật quang"
block: "Phát quang"
}
}
info: {
title: "Thông tin"
button: "Thông tin"
content: """
<img src="assets/logo.png" style="display: block; width: 40%; margin: 3em auto; border-radius: 50%">
<p>
<h2>Điều khiển chuột:</h2>
<table>
<tr><th>di chuyển</th><td><kbd>chuột trái</kbd> + kéo</td></tr>
<tr><th>thu phóng</th><td><kbd>lăn chuột</kbd></td></tr>
<tr><th>xoay/nghiêng</th><td><kbd>chuột phải</kbd> + kéo</td></tr>
</table>
</p>
<p>
<h2>Điều khiển bàn phím:</h2>
<table>
<tr><th>di chuyển</th><td><kbd>wasd</kbd> / <kbd>phím mũi tên</kbd></td></tr>
<tr><th>thu phóng</th><td>Bàn phím số: <kbd>+</kbd>/<kbd>-</kbd> or <kbd>Ins</kbd>/<kbd>Home</kbd></td></tr>
<tr><th>xoay/nghiêng</th><td><kbd>Alt trái</kbd> + <kbd>wasd</kbd> / <kbd>phím mũi tên</kbd> hoặc <kbd>Delete</kbd>/<kbd>End</kbd>/<kbd>Page Up</kbd>/<kbd>Page Down</kbd></td></tr>
</table>
</p>
<p>
<h2>Điều khiển cảm ứng:</h2>
<table>
<tr><th>di chuyển</th><td>chạm + kéo</td></tr>
<tr><th>thu phóng</th><td>chạm 2 ngón + nhón</td></tr>
<tr><th>xoay/nghiêng</th><td>chạm 2 ngón + di chuyển / xoay</td></tr>
</table>
</p>
<br><hr>
<p class="info-footer">
Trang được tạo &#9829; bởi <a href="https://bluecolo.red/bluemap">BlueMap</a> {version}
</p>
"""
}
}

View File

@ -1,6 +1,6 @@
<template>
<div id="app" :class="{'theme-light': appState.theme === 'light', 'theme-dark': appState.theme === 'dark', 'theme-contrast': appState.theme === 'contrast'}">
<FreeFlightMobileControls v-if="mapViewer.mapLoaded && appState.controls.state === 'free'" />
<FreeFlightMobileControls v-if="mapViewer.mapState === 'loaded' && appState.controls.state === 'free'" />
<ZoomButtons v-if="showMapMenu && appState.controls.showZoomButtons && appState.controls.state !== 'free'" />
<ControlBar />
<div v-if="mapViewer.mapState !== 'loaded'" class="map-state-message">{{ $t("map." + mapViewer.mapState) }}</div>
@ -50,7 +50,7 @@ export default {
width: 100%;
height: 100%;
z-index: 100; // put over bluemap markers
z-index: 10000; // put over bluemap markers
pointer-events: none;

View File

@ -65,6 +65,7 @@ export default {
if (this.markerSet.toggleable) {
// eslint-disable-next-line vue/no-mutating-props
this.markerSet.visible = !this.markerSet.visible
this.markerSet.saveState();
}
},
more(event) {

View File

@ -22,41 +22,85 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import {Object3D} from "three";
import { Object3D } from "three";
export * from "./MapViewer";
export * as Three from "three";
export * from "./controls/freeflight/FreeFlightControls";
export * from "./controls/freeflight/keyboard/KeyHeightControls";
// class name conflicts with map controls
export { KeyMoveControls as FreeFlightKeyMoveControls } from "./controls/freeflight/keyboard/KeyMoveControls";
export { MouseAngleControls as FreeFlightMouseAngleControls } from "./controls/freeflight/mouse/MouseAngleControls";
export { MouseRotateControls as FreeFlightMouseRotateControls } from "./controls/freeflight/mouse/MouseRotateControls";
export * from "./controls/freeflight/touch/TouchPanControls";
export * from "./controls/map/MapControls";
export * from "./controls/map/MapHeightControls";
export * from "./controls/map/keyboard/KeyAngleControls";
export { KeyMoveControls as MapKeyMoveControls } from "./controls/map/keyboard/KeyMoveControls";
export * from "./controls/map/keyboard/KeyRotateControls";
export * from "./controls/map/keyboard/KeyZoomControls";
export { MouseAngleControls as MapMouseAngleControls } from "./controls/map/mouse/MouseAngleControls";
export * from "./controls/map/mouse/MouseMoveControls";
export { MouseRotateControls as MapMouseRotateControls } from "./controls/map/mouse/MouseRotateControls";
export * from "./controls/map/mouse/MouseZoomControls";
export * from "./controls/map/touch/TouchAngleControls";
export * from "./controls/map/touch/TouchMoveControls";
export * from "./controls/map/touch/TouchRotateControls";
export * from "./controls/map/touch/TouchZoomControls";
export * from "./controls/ControlsManager";
export * from "./controls/KeyCombination";
export * from "./map/LowresTileLoader";
export * from "./map/Map";
export * from "./map/Tile";
export * from "./map/TileLoader";
export * from "./map/TileManager";
export * from "./map/TileMap";
export * from "./map/hires/HiresFragmentShader";
export * from "./map/hires/HiresVertexShader";
export * from "./map/lowres/LowresFragmentShader";
export * from "./map/lowres/LowresVertexShader";
export * from "./markers/ExtrudeMarker";
export * from "./markers/HtmlMarker";
export * from "./markers/LineMarker";
export * from "./markers/Marker";
export * from "./markers/MarkerFillFragmentShader";
export * from "./markers/MarkerFillVertexShader";
export * from "./markers/MarkerManager";
export * from "./markers/MarkerSet";
export * from "./markers/PlayerMarkerSet";
export * from "./markers/NormalMarkerManager";
export * from "./markers/ObjectMarker";
export * from "./markers/PlayerMarker";
export * from "./markers/PlayerMarkerManager";
export * from "./markers/PlayerMarkerSet";
export * from "./markers/PoiMarker";
export * from "./markers/ShapeMarker";
export * from "./controls/map/MapControls";
export * from "./controls/freeflight/FreeFlightControls";
export * from "./skybox/SkyFragmentShader";
export * from "./skybox/SkyVertexShader";
export * from "./skybox/SkyboxScene";
export * from "./util/CSS2DRenderer";
export * from "./util/CombinedCamera";
export * from "./util/LineShader";
export * from "./util/Stats";
export * from "./util/Utils";
export * from "./BlueMapApp";
export * from "./MainMenu";
export * from "./MapViewer";
export * from "./PopupMarker";
export * from "./Utils";
/**
* @param event {object}
* @return {boolean} - whether the event has been consumed (true) or not (false)
*/
Object3D.prototype.onClick = function(event) {
if (this.parent){
Object3D.prototype.onClick = function (event) {
if (this.parent) {
if (!Array.isArray(event.eventStack)) event.eventStack = [];
event.eventStack.push(this);

View File

@ -48,7 +48,7 @@ export class BlueMapApp {
this.mapViewer = new MapViewer(rootElement, this.events);
this.mapControls = new MapControls(this.mapViewer.renderer.domElement);
this.mapControls = new MapControls(this.mapViewer.renderer.domElement, rootElement);
this.freeFlightControls = new FreeFlightControls(this.mapViewer.renderer.domElement);
/** @type {PlayerMarkerManager} */
@ -251,6 +251,9 @@ export class BlueMapApp {
let map = this.mapsMap.get(mapId);
if (!map) return Promise.reject(`There is no map with the id "${mapId}" loaded!`);
if (this.playerMarkerManager) this.playerMarkerManager.dispose();
if (this.markerFileManager) this.markerFileManager.dispose();
await this.mapViewer.switchMap(map)
if (resetCamera) this.resetCamera();
@ -353,10 +356,8 @@ export class BlueMapApp {
}
initPlayerMarkerManager() {
if (this.playerMarkerManager){
this.playerMarkerManager.clear();
if (this.playerMarkerManager)
this.playerMarkerManager.dispose()
}
const map = this.mapViewer.map;
if (!map) return;
@ -369,16 +370,13 @@ export class BlueMapApp {
})
.catch(e => {
alert(this.events, e, "warning");
this.playerMarkerManager.clear();
this.playerMarkerManager.dispose();
});
}
initMarkerFileManager() {
if (this.markerFileManager) {
this.markerFileManager.clear();
if (this.markerFileManager)
this.markerFileManager.dispose();
}
const map = this.mapViewer.map;
if (!map) return;
@ -390,7 +388,6 @@ export class BlueMapApp {
})
.catch(e => {
alert(this.events, e, "warning");
this.markerFileManager.clear();
this.markerFileManager.dispose();
});
}

View File

@ -23,7 +23,7 @@
* THE SOFTWARE.
*/
import {MathUtils, Vector2} from "three";
import {MathUtils, Vector2, Vector3} from "three";
import {Manager, Pan, DIRECTION_ALL} from "hammerjs";
import {animate, EasingFunctions} from "../../util/Utils";
import {KeyMoveControls} from "./keyboard/KeyMoveControls";
@ -32,9 +32,12 @@ import {MouseAngleControls} from "./mouse/MouseAngleControls";
import {KeyHeightControls} from "./keyboard/KeyHeightControls";
import {TouchPanControls} from "./touch/TouchPanControls";
import {reactive} from "vue";
import {DEG2RAD} from "three/src/math/MathUtils";
export class FreeFlightControls {
static _beforeMoveTemp = new Vector3();
/**
* @param target {Element}
*/
@ -43,7 +46,7 @@ export class FreeFlightControls {
this.manager = null;
this.data = reactive({
followingPlayer: null
});
this.hammer = new Manager(this.target);
@ -99,12 +102,32 @@ export class FreeFlightControls {
* @param map {Map}
*/
update(delta, map) {
FreeFlightControls._beforeMoveTemp.copy(this.manager.position);
let beforeMoveRot = this.manager.rotation;
let beforeMoveAngle = this.manager.angle;
this.keyMove.update(delta, map);
this.keyHeight.update(delta, map);
this.mouseRotate.update(delta, map);
this.mouseAngle.update(delta, map);
this.touchPan.update(delta, map);
// if moved, stop following the marker and give back control
if (this.data.followingPlayer && (
!FreeFlightControls._beforeMoveTemp.equals(this.manager.position) ||
beforeMoveRot !== this.manager.rotation ||
beforeMoveAngle !== this.manager.angle
)) {
this.stopFollowingPlayerMarker();
}
// follow player marker
if (this.data.followingPlayer) {
this.manager.position.copy(this.data.followingPlayer.position);
this.manager.rotation = (this.data.followingPlayer.rotation.yaw - 180) * DEG2RAD;
this.manager.angle = -(this.data.followingPlayer.rotation.pitch - 90) * DEG2RAD;
}
this.manager.angle = MathUtils.clamp(this.manager.angle, 0, Math.PI);
this.manager.distance = 0;
this.manager.ortho = 0;
@ -133,6 +156,19 @@ export class FreeFlightControls {
});
}
/**
* @param marker {object}
*/
followPlayerMarker(marker) {
if (marker.isPlayerMarker) marker = marker.data;
this.data.followingPlayer = marker;
this.keyMove.deltaPosition.set(0, 0);
}
stopFollowingPlayerMarker() {
this.data.followingPlayer = null;
}
onWheel = evt => {
evt.preventDefault();

View File

@ -66,11 +66,13 @@ export class KeyHeightControls {
window.addEventListener("keydown", this.onKeyDown);
window.addEventListener("keyup", this.onKeyUp);
window.addEventListener("blur", this.onStop)
}
stop() {
window.removeEventListener("keydown", this.onKeyDown);
window.removeEventListener("keyup", this.onKeyUp);
window.removeEventListener("blur", this.onStop)
}
/**
@ -120,4 +122,9 @@ export class KeyHeightControls {
}
}
onStop = evt => {
this.up = false;
this.down = false;
}
}

View File

@ -78,11 +78,13 @@ export class KeyMoveControls {
window.addEventListener("keydown", this.onKeyDown);
window.addEventListener("keyup", this.onKeyUp);
window.addEventListener("blur", this.onStop)
}
stop() {
window.removeEventListener("keydown", this.onKeyDown);
window.removeEventListener("keyup", this.onKeyUp);
window.removeEventListener("blur", this.onStop)
}
/**
@ -152,4 +154,11 @@ export class KeyMoveControls {
}
}
onStop = evt => {
this.up = false;
this.down = false;
this.left = false;
this.right = false;
}
}

View File

@ -50,9 +50,11 @@ export class MapControls {
/**
* @param rootElement {Element}
* @param scrollCaptureElement {Element}
*/
constructor(rootElement) {
constructor(rootElement, scrollCaptureElement) {
this.rootElement = rootElement;
this.scrollCaptureElement = scrollCaptureElement;
this.data = reactive({
followingPlayer: null
@ -68,7 +70,7 @@ export class MapControls {
this.mouseMove = new MouseMoveControls(this.rootElement, 1.5,0.3);
this.mouseRotate = new MouseRotateControls(this.rootElement, 6, 0.3);
this.mouseAngle = new MouseAngleControls(this.rootElement, 3, 0.3);
this.mouseZoom = new MouseZoomControls(this.rootElement, 1, 0.2);
this.mouseZoom = new MouseZoomControls(this.scrollCaptureElement, 1, 0.2);
this.keyMove = new KeyMoveControls(this.rootElement, 0.025, 0.2);
this.keyRotate = new KeyRotateControls(this.rootElement, 0.06, 0.15);

View File

@ -67,11 +67,13 @@ export class KeyAngleControls {
window.addEventListener("keydown", this.onKeyDown);
window.addEventListener("keyup", this.onKeyUp);
window.addEventListener("blur", this.onStop)
}
stop() {
window.removeEventListener("keydown", this.onKeyDown);
window.removeEventListener("keyup", this.onKeyUp);
window.removeEventListener("blur", this.onStop)
}
/**
@ -121,4 +123,9 @@ export class KeyAngleControls {
}
}
onStop = evt => {
this.up = false;
this.down = false;
}
}

View File

@ -78,11 +78,13 @@ export class KeyMoveControls {
window.addEventListener("keydown", this.onKeyDown);
window.addEventListener("keyup", this.onKeyUp);
window.addEventListener("blur", this.onStop)
}
stop() {
window.removeEventListener("keydown", this.onKeyDown);
window.removeEventListener("keyup", this.onKeyUp);
window.removeEventListener("blur", this.onStop)
}
/**
@ -152,4 +154,11 @@ export class KeyMoveControls {
}
}
onStop = evt => {
this.up = false;
this.down = false;
this.left = false;
this.right = false;
}
}

View File

@ -67,11 +67,13 @@ export class KeyRotateControls {
window.addEventListener("keydown", this.onKeyDown);
window.addEventListener("keyup", this.onKeyUp);
window.addEventListener("blur", this.onStop)
}
stop() {
window.removeEventListener("keydown", this.onKeyDown);
window.removeEventListener("keyup", this.onKeyUp);
window.removeEventListener("blur", this.onStop)
}
/**
@ -121,4 +123,9 @@ export class KeyRotateControls {
}
}
onStop = evt => {
this.left = false;
this.right = false;
}
}

View File

@ -65,11 +65,13 @@ export class KeyZoomControls {
window.addEventListener("keydown", this.onKeyDown);
window.addEventListener("keyup", this.onKeyUp);
window.addEventListener("blur", this.onStop)
}
stop() {
window.removeEventListener("keydown", this.onKeyDown);
window.removeEventListener("keyup", this.onKeyUp);
window.removeEventListener("blur", this.onStop)
}
/**
@ -119,4 +121,9 @@ export class KeyZoomControls {
}
}
onStop = evt => {
this.in = false;
this.out = false;
}
}

View File

@ -83,7 +83,8 @@ export class MarkerManager {
*/
update() {
return this.loadMarkerFile()
.then(markerFileData => this.updateFromData(markerFileData));
.then(markerFileData => this.updateFromData(markerFileData))
.catch(() => this.clear());
}
/**

View File

@ -30,13 +30,14 @@ import {LineMarker} from "./LineMarker";
import {HtmlMarker} from "./HtmlMarker";
import {PoiMarker} from "./PoiMarker";
import {reactive} from "vue";
import {getLocalStorage, setLocalStorage} from "../Utils";
export class MarkerSet extends Scene {
/**
* @param id {string}
*/
constructor(id) {
constructor(id, data = null) {
super();
Object.defineProperty(this, 'isMarkerSet', {value: true});
@ -58,6 +59,9 @@ export class MarkerSet extends Scene {
return this.toggleable ||
this.markers.filter(marker => marker.listed).length > 0 ||
this.markerSets.filter(markerSet => markerSet.listed).length > 0
},
saveState: () => {
setLocalStorage(this.localStorageKey("visible"), this.visible);
}
});
@ -65,6 +69,19 @@ export class MarkerSet extends Scene {
get() { return this.data.visible },
set(value) { this.data.visible = value }
});
if (data) {
this.updateFromData(data);
}
if (this.data.toggleable) {
let storedVisible = getLocalStorage(this.localStorageKey("visible"));
if (storedVisible !== undefined) {
this.visible = !!storedVisible;
} else if (this.data.defaultHide) {
this.visible = false;
}
}
}
updateFromData(data) {
@ -108,18 +125,14 @@ export class MarkerSet extends Scene {
updateMarkerSetFromData(markerSetId, data) {
let markerSet = this.markerSets.get(markerSetId);
// create new if not existent
if (!markerSet) {
markerSet = new MarkerSet(markerSetId);
// create new if not existent
markerSet = new MarkerSet(markerSetId, data);
this.add(markerSet);
if (data.defaultHidden) {
markerSet.visible = false;
}
} else {
// update
markerSet.updateFromData(data);
}
// update
markerSet.updateFromData(data);
}
updateMarkersFromData(data = {}, ignore = []) {
@ -174,8 +187,8 @@ export class MarkerSet extends Scene {
* Removes all markers and marker-sets
*/
clear() {
[...this.data.markerSets].forEach(markerSet => this.remove(markerSet));
[...this.data.markers].forEach(marker => this.remove(marker));
[...this.markerSets.values()].forEach(markerSet => this.remove(markerSet));
[...this.markers.values()].forEach(marker => this.remove(marker));
}
add(...object) {
@ -220,4 +233,8 @@ export class MarkerSet extends Scene {
});
}
localStorageKey(key) {
return "bluemap-markerset-" + encodeURIComponent(this.data.id) + "-" + key;
}
}

View File

@ -48,4 +48,8 @@ export class NormalMarkerManager extends MarkerManager {
return true;
}
clear() {
this.root.updateMarkerSetsFromData({}, [PLAYER_MARKER_SET_ID, "bm-popup-set"]);
}
}

View File

@ -41,6 +41,10 @@ export class PlayerMarker extends Marker {
this.data.playerUuid = playerUuid;
this.data.name = playerUuid;
this.data.playerHead = playerHead;
this.data.rotation = {
pitch: 0,
yaw: 0
};
this.elementObject = new CSS2DObject(htmlToElement(`
<div id="bm-marker-${this.data.id}" class="bm-marker-${this.data.type}">
@ -102,24 +106,34 @@ export class PlayerMarker extends Marker {
// animate position update
let pos = markerData.position || {};
let rot = markerData.rotation || {};
if (!this.position.x && !this.position.y && !this.position.z) {
this.position.set(
pos.x || 0,
(pos.y || 0) + 1.8,
pos.z || 0
);
this.data.rotation.pitch = rot.pitch || 0;
this.data.rotation.yaw = rot.yaw || 0;
} else {
let startPos = {
x: this.position.x,
y: this.position.y,
z: this.position.z,
pitch: this.data.rotation.pitch,
yaw: this.data.rotation.yaw,
}
let deltaPos = {
x: (pos.x || 0) - startPos.x,
y: ((pos.y || 0) + 1.8) - startPos.y,
z: (pos.z || 0) - startPos.z,
pitch: (rot.pitch || 0) - startPos.pitch,
yaw: (rot.yaw || 0) - startPos.yaw
}
if (deltaPos.x || deltaPos.y || deltaPos.z) {
while (deltaPos.yaw > 180) deltaPos.yaw -= 360;
while (deltaPos.yaw < -180) deltaPos.yaw += 360;
if (deltaPos.x || deltaPos.y || deltaPos.z || deltaPos.pitch || deltaPos.yaw) {
animate(progress => {
let ease = EasingFunctions.easeInOutCubic(progress);
this.position.set(
@ -127,7 +141,9 @@ export class PlayerMarker extends Marker {
startPos.y + deltaPos.y * ease || 0,
startPos.z + deltaPos.z * ease || 0
);
}, 500);
this.data.rotation.pitch = startPos.pitch + deltaPos.pitch * ease || 0;
this.data.rotation.yaw = startPos.yaw + deltaPos.yaw * ease || 0;
}, 1000);
}
}

View File

@ -78,4 +78,8 @@ export class PlayerMarkerManager extends MarkerManager {
return this.getPlayerMarkerSet().getPlayerMarker(playerUuid)
}
clear() {
this.getPlayerMarkerSet(false).clear();
}
}

View File

@ -29,8 +29,8 @@ import {PlayerMarker} from "./PlayerMarker";
export class PlayerMarkerSet extends MarkerSet {
constructor(id, playerheadsUrl) {
super(id);
constructor(id, playerheadsUrl, data = null) {
super(id, data);
this.data.label = "Player";
this.data.toggleable = true;
this.data.defaultHide = false;

View File

@ -25,6 +25,7 @@
import * as Vue from 'vue';
import App from './App.vue';
import * as BlueMap from "./js/BlueMap";
import {BlueMapApp} from "./js/BlueMapApp";
import {i18nModule, loadLanguageSettings} from "./i18n";
@ -38,6 +39,7 @@ async function load() {
try {
const bluemap = new BlueMapApp(document.getElementById("map-container"));
window.bluemap = bluemap;
window.BlueMap = BlueMap;
// init vue
const vue = Vue.createApp(App, {

View File

@ -27,6 +27,8 @@
.bm-marker-html {
position: relative;
user-select: none;
.bm-marker-poi-label {
position: absolute;
top: 0;

View File

@ -219,7 +219,7 @@ public class BmMap {
public synchronized void savePlayerState() {
try (
OutputStream out = storage.writeMeta(id, META_FILE_PLAYERS);
OutputStream out = storage.writeMeta(id, META_FILE_PLAYERS)
) {
out.write("{}".getBytes(StandardCharsets.UTF_8));
} catch (Exception ex) {

View File

@ -37,6 +37,7 @@ import java.util.zip.GZIPOutputStream;
public class MapRenderState {
private final Map<Vector2i, Long> regionRenderTimes;
private transient long latestRenderTime = -1;
public MapRenderState() {
regionRenderTimes = new HashMap<>();
@ -44,6 +45,13 @@ public class MapRenderState {
public synchronized void setRenderTime(Vector2i regionPos, long renderTime) {
regionRenderTimes.put(regionPos, renderTime);
if (latestRenderTime != -1) {
if (renderTime > latestRenderTime)
latestRenderTime = renderTime;
else
latestRenderTime = -1;
}
}
public synchronized long getRenderTime(Vector2i regionPos) {
@ -52,6 +60,19 @@ public class MapRenderState {
else return renderTime;
}
public long getLatestRenderTime() {
if (latestRenderTime == -1) {
synchronized (this) {
latestRenderTime = regionRenderTimes.values().stream()
.mapToLong(Long::longValue)
.max()
.orElse(-1);
}
}
return latestRenderTime;
}
public synchronized void reset() {
regionRenderTimes.clear();
}

View File

@ -55,7 +55,9 @@ public class ChunkAnvil113 extends MCAChunk {
CompoundTag levelData = chunkTag.getCompoundTag("Level");
String status = levelData.getString("Status");
this.isGenerated = status.equals("full");
this.isGenerated = status.equals("full") ||
status.equals("fullchunk") ||
status.equals("postprocessed");
this.hasLight = isGenerated;
this.inhabitedTime = levelData.getLong("InhabitedTime");

View File

@ -0,0 +1,13 @@
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);
}
}

View File

@ -0,0 +1,120 @@
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.WrappedOutputStream;
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 WrappedOutputStream(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 WrappedOutputStream(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);
}
}
}

View File

@ -30,6 +30,8 @@ import com.github.benmanes.caffeine.cache.LoadingCache;
import de.bluecolored.bluemap.core.BlueMap;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.storage.*;
import de.bluecolored.bluemap.core.storage.sql.dialect.DialectType;
import de.bluecolored.bluemap.core.storage.sql.dialect.Dialect;
import de.bluecolored.bluemap.core.util.WrappedOutputStream;
import org.apache.commons.dbcp2.*;
import org.apache.commons.pool2.ObjectPool;
@ -48,10 +50,12 @@ import java.util.*;
import java.util.concurrent.CompletionException;
import java.util.function.Function;
public class SQLStorage extends Storage {
public abstract class SQLStorage extends Storage {
private final DataSource dataSource;
private final Compression hiresCompression;
protected final Dialect dialect;
protected final Compression hiresCompression;
private final LoadingCache<String, Integer> mapFKs = Caffeine.newBuilder()
.executor(BlueMap.THREAD_POOL)
@ -62,9 +66,9 @@ public class SQLStorage extends Storage {
private volatile boolean closed;
public SQLStorage(SQLStorageSettings config) throws MalformedURLException, SQLDriverException {
public SQLStorage(Dialect dialect, SQLStorageSettings config) throws MalformedURLException, SQLDriverException {
this.dialect = dialect;
this.closed = false;
try {
if (config.getDriverClass().isPresent()) {
if (config.getDriverJar().isPresent()) {
@ -115,9 +119,7 @@ public class SQLStorage extends Storage {
byteOut.writeTo(blobOut);
}
executeUpdate(connection,
"REPLACE INTO `bluemap_map_tile` (`map`, `lod`, `x`, `z`, `compression`, `data`) " +
"VALUES (?, ?, ?, ?, ?, ?)",
executeUpdate(connection, this.dialect.writeMapTile(),
mapFK,
lod,
tile.getX(),
@ -139,17 +141,7 @@ public class SQLStorage extends Storage {
try {
byte[] data = recoveringConnection(connection -> {
ResultSet result = executeQuery(connection,
"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` = ?",
this.dialect.readMapTile(),
mapId,
lod,
tile.getX(),
@ -179,17 +171,7 @@ public class SQLStorage extends Storage {
try {
TileInfo tileInfo = recoveringConnection(connection -> {
ResultSet result = executeQuery(connection,
"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` = ?",
this.dialect.readMapTileInfo(),
mapId,
lod,
tile.getX(),
@ -238,15 +220,7 @@ public class SQLStorage extends Storage {
public void deleteMapTile(String mapId, int lod, Vector2i tile) throws IOException {
try {
recoveringConnection(connection ->
executeUpdate(connection,
"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` = ?",
executeUpdate(connection,this.dialect.deleteMapTile(),
mapId,
lod,
tile.getX(),
@ -271,8 +245,7 @@ public class SQLStorage extends Storage {
}
executeUpdate(connection,
"REPLACE INTO `bluemap_map_meta` (`map`, `key`, `value`) " +
"VALUES (?, ?, ?)",
this.dialect.writeMeta(),
mapFK,
escapeMetaName(name),
dataBlob
@ -289,12 +262,7 @@ public class SQLStorage extends Storage {
try {
byte[] data = recoveringConnection(connection -> {
ResultSet result = executeQuery(connection,
"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` = ?",
this.dialect.readMeta(),
mapId,
escapeMetaName(name)
);
@ -319,12 +287,7 @@ public class SQLStorage extends Storage {
try {
MetaInfo tileInfo = recoveringConnection(connection -> {
ResultSet result = executeQuery(connection,
"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` = ?",
this.dialect.readMetaSize(),
mapId,
escapeMetaName(name)
);
@ -361,12 +324,7 @@ public class SQLStorage extends Storage {
try {
recoveringConnection(connection ->
executeUpdate(connection,
"DELETE t " +
"FROM `bluemap_map_meta` t " +
" INNER JOIN `bluemap_map` m " +
" ON t.`map` = m.`id` " +
"WHERE m.`map_id` = ? " +
"AND t.`key` = ?",
this.dialect.deleteMeta(),
mapId,
escapeMetaName(name)
), 2);
@ -381,28 +339,18 @@ public class SQLStorage extends Storage {
try {
recoveringConnection(connection -> {
executeUpdate(connection,
"DELETE t " +
"FROM `bluemap_map_tile` t " +
" INNER JOIN `bluemap_map` m " +
" ON t.`map` = m.`id` " +
"WHERE m.`map_id` = ?",
this.dialect.purgeMapTile(),
mapId
);
executeUpdate(connection,
"DELETE t " +
"FROM `bluemap_map_meta` t " +
" INNER JOIN `bluemap_map` m " +
" ON t.`map` = m.`id` " +
"WHERE m.`map_id` = ?",
this.dialect.purgeMapMeta(),
mapId
);
executeUpdate(connection,
"DELETE " +
"FROM `bluemap_map` " +
"WHERE `map_id` = ?",
this.dialect.purgeMap(),
mapId
);
}, 2);
@ -420,7 +368,7 @@ public class SQLStorage extends Storage {
try {
return recoveringConnection(connection -> {
ResultSet result = executeQuery(connection,
"SELECT `map_id` FROM `bluemap_map`"
this.dialect.selectMapIds()
);
Collection<String> mapIds = new ArrayList<>();
while (result.next()) {
@ -440,15 +388,10 @@ public class SQLStorage extends Storage {
// initialize and get schema-version
String schemaVersionString = recoveringConnection(connection -> {
connection.createStatement().executeUpdate(
"CREATE TABLE IF NOT EXISTS `bluemap_storage_meta` (" +
"`key` varchar(255) NOT NULL, " +
"`value` varchar(255) DEFAULT NULL, " +
"PRIMARY KEY (`key`)" +
")");
this.dialect.initializeStorageMeta());
ResultSet result = executeQuery(connection,
"SELECT `value` FROM `bluemap_storage_meta` " +
"WHERE `key` = ?",
this.dialect.selectStorageMeta(),
"schema_version"
);
@ -456,8 +399,7 @@ public class SQLStorage extends Storage {
return result.getString("value");
} else {
executeUpdate(connection,
"INSERT INTO `bluemap_storage_meta` (`key`, `value`) " +
"VALUES (?, ?)",
this.dialect.insertStorageMeta(),
"schema_version", "0"
);
return "0";
@ -482,51 +424,22 @@ public class SQLStorage extends Storage {
recoveringConnection(connection -> {
connection.createStatement().executeUpdate(
"CREATE TABLE `bluemap_map` (" +
"`id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT," +
"`map_id` VARCHAR(255) NOT NULL," +
"PRIMARY KEY (`id`)," +
"UNIQUE INDEX `map_id` (`map_id`)" +
");"
this.dialect.initializeMap()
);
connection.createStatement().executeUpdate(
"CREATE TABLE `bluemap_map_tile_compression` (" +
"`id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT," +
"`compression` VARCHAR(255) NOT NULL," +
"PRIMARY KEY (`id`)," +
"UNIQUE INDEX `compression` (`compression`)" +
");"
this.dialect.initializeMapTileCompression()
);
connection.createStatement().executeUpdate(
"CREATE TABLE `bluemap_map_meta` (" +
"`map` SMALLINT UNSIGNED NOT NULL," +
"`key` varchar(255) NOT NULL," +
"`value` LONGBLOB NOT NULL," +
"PRIMARY KEY (`map`, `key`)," +
"CONSTRAINT `fk_bluemap_map_meta_map` FOREIGN KEY (`map`) REFERENCES `bluemap_map` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT" +
")");
this.dialect.initializeMapMeta());
connection.createStatement().executeUpdate(
"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" +
");"
this.dialect.initializeMapTile()
);
executeUpdate(connection,
"UPDATE `bluemap_storage_meta` " +
"SET `value` = ? " +
"WHERE `key` = ?",
this.dialect.updateStorageMeta(),
"3", "schema_version"
);
}, 2);
@ -544,36 +457,27 @@ public class SQLStorage extends Storage {
// delete potential files that are already in the new format to avoid constraint-issues
executeUpdate(connection,
"DELETE FROM `bluemap_map_meta`" +
"WHERE `key` IN (?, ?, ?)",
this.dialect.deleteMapMeta(),
"settings.json", "textures.json", ".rstate"
);
// rename files
executeUpdate(connection,
"UPDATE `bluemap_map_meta` " +
"SET `key` = ? " +
"WHERE `key` = ?",
this.dialect.updateMapMeta(),
"settings.json", "settings"
);
executeUpdate(connection,
"UPDATE `bluemap_map_meta` " +
"SET `key` = ? " +
"WHERE `key` = ?",
this.dialect.updateMapMeta(),
"textures.json", "textures"
);
executeUpdate(connection,
"UPDATE `bluemap_map_meta` " +
"SET `key` = ? " +
"WHERE `key` = ?",
this.dialect.updateMapMeta(),
".rstate", "render_state"
);
// update schemaVersion
executeUpdate(connection,
"UPDATE `bluemap_storage_meta` " +
"SET `value` = ? " +
"WHERE `key` = ?",
this.dialect.updateStorageMeta(),
"3", "schema_version"
);
}, 2);
@ -603,32 +507,31 @@ public class SQLStorage extends Storage {
}
}
private ResultSet executeQuery(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.executeQuery();
protected ResultSet executeQuery(Connection connection, @Language("sql") String sql, Object... parameters) throws SQLException {
return prepareStatement(connection, sql, parameters).executeQuery();
}
@SuppressWarnings("UnusedReturnValue")
private int executeUpdate(Connection connection, @Language("sql") String sql, Object... parameters) throws SQLException {
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]);
statement.setObject(i + 1, parameters[i]);
}
return statement.executeUpdate();
return statement;
}
@SuppressWarnings("SameParameterValue")
private void recoveringConnection(ConnectionConsumer action, int tries) throws SQLException, IOException {
protected void recoveringConnection(ConnectionConsumer action, int tries) throws SQLException, IOException {
recoveringConnection((ConnectionFunction<Void>) action, tries);
}
@SuppressWarnings("SameParameterValue")
private <R> R recoveringConnection(ConnectionFunction<R> action, int tries) throws SQLException, IOException {
protected <R> R recoveringConnection(ConnectionFunction<R> action, int tries) throws SQLException, IOException {
SQLException sqlException = null;
try {
@ -657,7 +560,7 @@ public class SQLStorage extends Storage {
throw sqlException;
}
private int getMapFK(String mapId) throws SQLException {
protected int getMapFK(String mapId) throws SQLException {
try {
return Objects.requireNonNull(mapFKs.get(mapId));
} catch (CompletionException ex) {
@ -670,7 +573,7 @@ public class SQLStorage extends Storage {
}
}
private int getMapTileCompressionFK(Compression compression) throws SQLException {
int getMapTileCompressionFK(Compression compression) throws SQLException {
try {
return Objects.requireNonNull(mapTileCompressionFKs.get(compression));
} catch (CompletionException ex) {
@ -698,9 +601,7 @@ public class SQLStorage extends Storage {
return recoveringConnection(connection -> {
int key;
ResultSet result = executeQuery(connection,
//language=SQL
"SELECT `" + idField + "` FROM `" + table + "` " +
"WHERE `" + valueField + "` = ?",
this.dialect.lookupFK(table,idField,valueField),
value
);
@ -708,8 +609,7 @@ public class SQLStorage extends Storage {
key = result.getInt("id");
} else {
PreparedStatement statement = connection.prepareStatement(
"INSERT INTO `" + table + "` (`" + valueField + "`) " +
"VALUES (?)",
this.dialect.insertFK(table,valueField),
Statement.RETURN_GENERATED_KEYS
);
statement.setString(1, value);
@ -774,6 +674,12 @@ public class SQLStorage extends Storage {
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> {

View File

@ -0,0 +1,13 @@
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);
}
}

View File

@ -0,0 +1,78 @@
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);
}

View File

@ -0,0 +1,42 @@
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;
}
}

View File

@ -0,0 +1,250 @@
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(255) NOT NULL, " +
"`value` varchar(255) DEFAULT NULL, " +
"PRIMARY KEY (`key`)" +
")";
}
@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(255) NOT NULL," +
"PRIMARY KEY (`id`)," +
"UNIQUE INDEX `map_id` (`map_id`)" +
");";
}
@Override
@Language("MySQL")
public String initializeMapTileCompression() {
return "CREATE TABLE `bluemap_map_tile_compression` (" +
"`id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT," +
"`compression` VARCHAR(255) NOT NULL," +
"PRIMARY KEY (`id`)," +
"UNIQUE INDEX `compression` (`compression`)" +
");";
}
@Override
@Language("MySQL")
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("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" +
");";
}
@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 (?)";
}
}

View File

@ -0,0 +1,240 @@
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 (?)";
}
}

View File

@ -0,0 +1,256 @@
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 (?)";
}
}

View File

@ -54,7 +54,9 @@ import org.apache.commons.lang3.time.DurationFormatUtils;
import java.io.File;
import java.io.IOException;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.TimeUnit;
@ -204,12 +206,24 @@ public class BlueMapCLI implements ServerInterface {
HttpRequestHandler handler = new BlueMapResponseModifier(routingRequestHandler);
if (verbose) handler = new LoggingRequestHandler(handler);
HttpServer webServer = new HttpServer(handler);
webServer.bind(new InetSocketAddress(
config.resolveIp(),
config.getPort()
));
webServer.start();
try {
HttpServer webServer = new HttpServer(handler);
webServer.bind(new InetSocketAddress(
config.resolveIp(),
config.getPort()
));
webServer.start();
} catch (UnknownHostException ex) {
throw new ConfigurationException("BlueMap failed to resolve the ip in your webserver-config.\n" +
"Check if that is correctly configured.", ex);
} catch (BindException ex) {
throw new ConfigurationException("BlueMap failed to bind to the configured address.\n" +
"This usually happens when the configured port (" + config.getPort() + ") is already in use by some other program.", ex);
} catch (IOException ex) {
throw new ConfigurationException("BlueMap failed to initialize the webserver.\n" +
"Check your webserver-config if everything is configured correctly.\n" +
"(Make sure you DON'T use the same port for bluemap that you also use for your minecraft server)", ex);
}
}
@Override