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. :) 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! - 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 :) 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: 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! 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 ## 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 ## 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.Tristate; import de.bluecolored.bluemap.core.util.Tristate;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -396,12 +393,28 @@ private ConfigTemplate createEndMapTemplate(String name, Path worldFolder, int i
} }
private String formatPath(Path path) { private String formatPath(Path path) {
return Path.of("") // normalize path
path = Path.of("")
.toAbsolutePath() .toAbsolutePath()
.relativize(path.toAbsolutePath()) .relativize(path.toAbsolutePath())
.normalize() .normalize();
.toString() String pathString = path.toString();
.replace("\\", "\\\\");
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 @@
public enum StorageType { public enum StorageType {
FILE (FileConfig.class, FileStorage::new), FILE (FileConfig.class, FileStorage::new),
SQL (SQLConfig.class, SQLStorage::new); SQL (SQLConfig.class, SQLStorage::create);
private final Class<? extends StorageConfig> configType; private final Class<? extends StorageConfig> configType;
private final StorageFactory<? extends StorageConfig> storageFactory; private final StorageFactory<? extends StorageConfig> storageFactory;

View File

@ -55,6 +55,7 @@
import java.io.OutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.Writer; import java.io.Writer;
import java.net.BindException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.file.Path; import java.nio.file.Path;
@ -194,6 +195,9 @@ private void load(@Nullable ResourcePack preloadedResourcePack) throws IOExcepti
} catch (UnknownHostException ex) { } catch (UnknownHostException ex) {
throw new ConfigurationException("BlueMap failed to resolve the ip in your webserver-config.\n" + throw new ConfigurationException("BlueMap failed to resolve the ip in your webserver-config.\n" +
"Check if that is correctly configured.", ex); "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) { } catch (IOException ex) {
throw new ConfigurationException("BlueMap failed to initialize the webserver.\n" + throw new ConfigurationException("BlueMap failed to initialize the webserver.\n" +
"Check your webserver-config if everything is configured correctly.\n" + "Check your webserver-config if everything is configured correctly.\n" +

View File

@ -34,10 +34,18 @@
import org.apache.commons.lang3.time.DurationFormatUtils; import org.apache.commons.lang3.time.DurationFormatUtils;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
public class CommandHelper { 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 Plugin plugin;
private final Map<String, WeakReference<RenderTask>> taskRefMap; private final Map<String, WeakReference<RenderTask>> taskRefMap;
@ -67,7 +75,9 @@ public List<Text> createStatusMessage(){
lines.add(Text.of(TextColor.WHITE, " Render-Threads are ", status, TextColor.WHITE, "!")); 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() + "):")); lines.add(Text.of(TextColor.WHITE, " Queued Tasks (" + tasks.size() + "):"));
for (int i = 0; i < tasks.size(); i++) { for (int i = 0; i < tasks.size(); i++) {
if (i >= 10){ if (i >= 10){
@ -76,20 +86,18 @@ public List<Text> createStatusMessage(){
} }
RenderTask task = tasks.get(i); 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) { if (i == 0) {
String detail = task.getDetail().orElse(null); task.getDetail().ifPresent(detail ->
if (detail != null) { lines.add(Text.of(TextColor.GRAY, "\u00A0\u00A0\u00A0Detail: ", TextColor.WHITE, detail)));
lines.add(Text.of(TextColor.GRAY, " Detail: ", 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) + "%")); (Math.round(task.estimateProgress() * 10000) / 100.0) + "%"));
long etaMs = renderer.estimateCurrentRenderTaskTimeRemaining(); long etaMs = renderer.estimateCurrentRenderTaskTimeRemaining();
if (etaMs > 0) { 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 List<Text> createStatusMessage(){
if (plugin.checkPausedByPlayerCount()) { if (plugin.checkPausedByPlayerCount()) {
lines.add(Text.of(TextColor.WHITE, " Render-Threads are ", lines.add(Text.of(TextColor.WHITE, " Render-Threads are ",
Text.of(TextColor.GOLD, "paused"))); 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 { } else {
lines.add(Text.of(TextColor.WHITE, " Render-Threads are ", lines.add(Text.of(TextColor.WHITE, " Render-Threads are ",
Text.of(TextColor.RED, "stopped") Text.of(TextColor.RED, "stopped")
@ -176,4 +184,9 @@ private String randomRef() {
return ref.subSequence(0, 4).toString(); 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 int worldsCommand(CommandContext<S> context) {
} }
public int mapsCommand(CommandContext<S> context) { 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()) { for (BmMap map : plugin.getMaps().values()) {
boolean unfrozen = plugin.getPluginState().getMapState(map).isUpdateEnabled(); boolean frozen = !plugin.getPluginState().getMapState(map).isUpdateEnabled();
if (unfrozen) {
source.sendMessage(Text.of( lines.add(Text.of(TextColor.GRAY, " - ",
TextColor.GRAY, " - ", TextColor.WHITE, map.getId(),
TextColor.WHITE, map.getId(), TextColor.GRAY, " (" + map.getName() + ")"));
TextColor.GRAY, " (" + map.getName() + ")"
).setHoverText(Text.of(TextColor.WHITE, "World: ", TextColor.GRAY, map.getWorld().getName()))); lines.add(Text.of(TextColor.GRAY, "\u00A0\u00A0\u00A0World: ",
} else { TextColor.DARK_GRAY, map.getWorld().getName()));
source.sendMessage(Text.of( lines.add(Text.of(TextColor.GRAY, "\u00A0\u00A0\u00A0Last Update: ",
TextColor.GRAY, " - ", TextColor.DARK_GRAY, helper.formatTime(map.getRenderState().getLatestRenderTime())));
TextColor.WHITE, map.getId(),
TextColor.GRAY, " (" + map.getName() + ") - ", if (frozen)
TextColor.AQUA, TextFormat.ITALIC, "frozen!" lines.add(Text.of(TextColor.AQUA, TextFormat.ITALIC, "This map is frozen!"));
).setHoverText(Text.of(TextColor.WHITE, "World: ", TextColor.GRAY, map.getWorld().getName())));
}
} }
CommandSource source = commandSourceInterface.apply(context.getSource());
source.sendMessages(lines);
return 1; return 1;
} }

View File

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

View File

@ -26,5 +26,6 @@
{ locale: "zh_TW", name: "中文(台灣)" } { locale: "zh_TW", name: "中文(台灣)" }
{ locale: "zh_HK", name: "中文(香港)" } { locale: "zh_HK", name: "中文(香港)" }
{ locale: "ko", 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> <template>
<div id="app" :class="{'theme-light': appState.theme === 'light', 'theme-dark': appState.theme === 'dark', 'theme-contrast': appState.theme === 'contrast'}"> <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'" /> <ZoomButtons v-if="showMapMenu && appState.controls.showZoomButtons && appState.controls.state !== 'free'" />
<ControlBar /> <ControlBar />
<div v-if="mapViewer.mapState !== 'loaded'" class="map-state-message">{{ $t("map." + mapViewer.mapState) }}</div> <div v-if="mapViewer.mapState !== 'loaded'" class="map-state-message">{{ $t("map." + mapViewer.mapState) }}</div>
@ -50,7 +50,7 @@ export default {
width: 100%; width: 100%;
height: 100%; height: 100%;
z-index: 100; // put over bluemap markers z-index: 10000; // put over bluemap markers
pointer-events: none; pointer-events: none;

View File

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

View File

@ -22,41 +22,85 @@
* 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.
*/ */
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/Map";
export * from "./map/Tile"; export * from "./map/Tile";
export * from "./map/TileLoader"; export * from "./map/TileLoader";
export * from "./map/TileManager"; export * from "./map/TileManager";
export * from "./map/TileMap"; 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/ExtrudeMarker";
export * from "./markers/HtmlMarker"; export * from "./markers/HtmlMarker";
export * from "./markers/LineMarker"; export * from "./markers/LineMarker";
export * from "./markers/Marker"; export * from "./markers/Marker";
export * from "./markers/MarkerFillFragmentShader";
export * from "./markers/MarkerFillVertexShader";
export * from "./markers/MarkerManager"; export * from "./markers/MarkerManager";
export * from "./markers/MarkerSet"; export * from "./markers/MarkerSet";
export * from "./markers/PlayerMarkerSet"; export * from "./markers/NormalMarkerManager";
export * from "./markers/ObjectMarker"; export * from "./markers/ObjectMarker";
export * from "./markers/PlayerMarker"; export * from "./markers/PlayerMarker";
export * from "./markers/PlayerMarkerManager";
export * from "./markers/PlayerMarkerSet";
export * from "./markers/PoiMarker"; export * from "./markers/PoiMarker";
export * from "./markers/ShapeMarker"; export * from "./markers/ShapeMarker";
export * from "./controls/map/MapControls"; export * from "./skybox/SkyFragmentShader";
export * from "./controls/freeflight/FreeFlightControls"; export * from "./skybox/SkyVertexShader";
export * from "./skybox/SkyboxScene";
export * from "./util/CSS2DRenderer";
export * from "./util/CombinedCamera"; export * from "./util/CombinedCamera";
export * from "./util/LineShader";
export * from "./util/Stats";
export * from "./util/Utils"; export * from "./util/Utils";
export * from "./BlueMapApp";
export * from "./MainMenu";
export * from "./MapViewer";
export * from "./PopupMarker";
export * from "./Utils";
/** /**
* @param event {object} * @param event {object}
* @return {boolean} - whether the event has been consumed (true) or not (false) * @return {boolean} - whether the event has been consumed (true) or not (false)
*/ */
Object3D.prototype.onClick = function(event) { Object3D.prototype.onClick = function (event) {
if (this.parent) {
if (this.parent){
if (!Array.isArray(event.eventStack)) event.eventStack = []; if (!Array.isArray(event.eventStack)) event.eventStack = [];
event.eventStack.push(this); event.eventStack.push(this);

View File

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

View File

@ -23,7 +23,7 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
import {MathUtils, Vector2} from "three"; import {MathUtils, Vector2, Vector3} from "three";
import {Manager, Pan, DIRECTION_ALL} from "hammerjs"; import {Manager, Pan, DIRECTION_ALL} from "hammerjs";
import {animate, EasingFunctions} from "../../util/Utils"; import {animate, EasingFunctions} from "../../util/Utils";
import {KeyMoveControls} from "./keyboard/KeyMoveControls"; import {KeyMoveControls} from "./keyboard/KeyMoveControls";
@ -32,9 +32,12 @@ import {MouseAngleControls} from "./mouse/MouseAngleControls";
import {KeyHeightControls} from "./keyboard/KeyHeightControls"; import {KeyHeightControls} from "./keyboard/KeyHeightControls";
import {TouchPanControls} from "./touch/TouchPanControls"; import {TouchPanControls} from "./touch/TouchPanControls";
import {reactive} from "vue"; import {reactive} from "vue";
import {DEG2RAD} from "three/src/math/MathUtils";
export class FreeFlightControls { export class FreeFlightControls {
static _beforeMoveTemp = new Vector3();
/** /**
* @param target {Element} * @param target {Element}
*/ */
@ -43,7 +46,7 @@ export class FreeFlightControls {
this.manager = null; this.manager = null;
this.data = reactive({ this.data = reactive({
followingPlayer: null
}); });
this.hammer = new Manager(this.target); this.hammer = new Manager(this.target);
@ -99,12 +102,32 @@ export class FreeFlightControls {
* @param map {Map} * @param map {Map}
*/ */
update(delta, 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.keyMove.update(delta, map);
this.keyHeight.update(delta, map); this.keyHeight.update(delta, map);
this.mouseRotate.update(delta, map); this.mouseRotate.update(delta, map);
this.mouseAngle.update(delta, map); this.mouseAngle.update(delta, map);
this.touchPan.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.angle = MathUtils.clamp(this.manager.angle, 0, Math.PI);
this.manager.distance = 0; this.manager.distance = 0;
this.manager.ortho = 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 => { onWheel = evt => {
evt.preventDefault(); evt.preventDefault();

View File

@ -66,11 +66,13 @@ export class KeyHeightControls {
window.addEventListener("keydown", this.onKeyDown); window.addEventListener("keydown", this.onKeyDown);
window.addEventListener("keyup", this.onKeyUp); window.addEventListener("keyup", this.onKeyUp);
window.addEventListener("blur", this.onStop)
} }
stop() { stop() {
window.removeEventListener("keydown", this.onKeyDown); window.removeEventListener("keydown", this.onKeyDown);
window.removeEventListener("keyup", this.onKeyUp); 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("keydown", this.onKeyDown);
window.addEventListener("keyup", this.onKeyUp); window.addEventListener("keyup", this.onKeyUp);
window.addEventListener("blur", this.onStop)
} }
stop() { stop() {
window.removeEventListener("keydown", this.onKeyDown); window.removeEventListener("keydown", this.onKeyDown);
window.removeEventListener("keyup", this.onKeyUp); 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 rootElement {Element}
* @param scrollCaptureElement {Element}
*/ */
constructor(rootElement) { constructor(rootElement, scrollCaptureElement) {
this.rootElement = rootElement; this.rootElement = rootElement;
this.scrollCaptureElement = scrollCaptureElement;
this.data = reactive({ this.data = reactive({
followingPlayer: null followingPlayer: null
@ -68,7 +70,7 @@ export class MapControls {
this.mouseMove = new MouseMoveControls(this.rootElement, 1.5,0.3); this.mouseMove = new MouseMoveControls(this.rootElement, 1.5,0.3);
this.mouseRotate = new MouseRotateControls(this.rootElement, 6, 0.3); this.mouseRotate = new MouseRotateControls(this.rootElement, 6, 0.3);
this.mouseAngle = new MouseAngleControls(this.rootElement, 3, 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.keyMove = new KeyMoveControls(this.rootElement, 0.025, 0.2);
this.keyRotate = new KeyRotateControls(this.rootElement, 0.06, 0.15); 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("keydown", this.onKeyDown);
window.addEventListener("keyup", this.onKeyUp); window.addEventListener("keyup", this.onKeyUp);
window.addEventListener("blur", this.onStop)
} }
stop() { stop() {
window.removeEventListener("keydown", this.onKeyDown); window.removeEventListener("keydown", this.onKeyDown);
window.removeEventListener("keyup", this.onKeyUp); 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("keydown", this.onKeyDown);
window.addEventListener("keyup", this.onKeyUp); window.addEventListener("keyup", this.onKeyUp);
window.addEventListener("blur", this.onStop)
} }
stop() { stop() {
window.removeEventListener("keydown", this.onKeyDown); window.removeEventListener("keydown", this.onKeyDown);
window.removeEventListener("keyup", this.onKeyUp); 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("keydown", this.onKeyDown);
window.addEventListener("keyup", this.onKeyUp); window.addEventListener("keyup", this.onKeyUp);
window.addEventListener("blur", this.onStop)
} }
stop() { stop() {
window.removeEventListener("keydown", this.onKeyDown); window.removeEventListener("keydown", this.onKeyDown);
window.removeEventListener("keyup", this.onKeyUp); 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("keydown", this.onKeyDown);
window.addEventListener("keyup", this.onKeyUp); window.addEventListener("keyup", this.onKeyUp);
window.addEventListener("blur", this.onStop)
} }
stop() { stop() {
window.removeEventListener("keydown", this.onKeyDown); window.removeEventListener("keydown", this.onKeyDown);
window.removeEventListener("keyup", this.onKeyUp); 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() { update() {
return this.loadMarkerFile() 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 {HtmlMarker} from "./HtmlMarker";
import {PoiMarker} from "./PoiMarker"; import {PoiMarker} from "./PoiMarker";
import {reactive} from "vue"; import {reactive} from "vue";
import {getLocalStorage, setLocalStorage} from "../Utils";
export class MarkerSet extends Scene { export class MarkerSet extends Scene {
/** /**
* @param id {string} * @param id {string}
*/ */
constructor(id) { constructor(id, data = null) {
super(); super();
Object.defineProperty(this, 'isMarkerSet', {value: true}); Object.defineProperty(this, 'isMarkerSet', {value: true});
@ -58,6 +59,9 @@ export class MarkerSet extends Scene {
return this.toggleable || return this.toggleable ||
this.markers.filter(marker => marker.listed).length > 0 || this.markers.filter(marker => marker.listed).length > 0 ||
this.markerSets.filter(markerSet => markerSet.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 }, get() { return this.data.visible },
set(value) { this.data.visible = value } 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) { updateFromData(data) {
@ -108,18 +125,14 @@ export class MarkerSet extends Scene {
updateMarkerSetFromData(markerSetId, data) { updateMarkerSetFromData(markerSetId, data) {
let markerSet = this.markerSets.get(markerSetId); let markerSet = this.markerSets.get(markerSetId);
// create new if not existent
if (!markerSet) { if (!markerSet) {
markerSet = new MarkerSet(markerSetId); // create new if not existent
markerSet = new MarkerSet(markerSetId, data);
this.add(markerSet); this.add(markerSet);
} else {
if (data.defaultHidden) { // update
markerSet.visible = false; markerSet.updateFromData(data);
}
} }
// update
markerSet.updateFromData(data);
} }
updateMarkersFromData(data = {}, ignore = []) { updateMarkersFromData(data = {}, ignore = []) {
@ -174,8 +187,8 @@ export class MarkerSet extends Scene {
* Removes all markers and marker-sets * Removes all markers and marker-sets
*/ */
clear() { clear() {
[...this.data.markerSets].forEach(markerSet => this.remove(markerSet)); [...this.markerSets.values()].forEach(markerSet => this.remove(markerSet));
[...this.data.markers].forEach(marker => this.remove(marker)); [...this.markers.values()].forEach(marker => this.remove(marker));
} }
add(...object) { 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; 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.playerUuid = playerUuid;
this.data.name = playerUuid; this.data.name = playerUuid;
this.data.playerHead = playerHead; this.data.playerHead = playerHead;
this.data.rotation = {
pitch: 0,
yaw: 0
};
this.elementObject = new CSS2DObject(htmlToElement(` this.elementObject = new CSS2DObject(htmlToElement(`
<div id="bm-marker-${this.data.id}" class="bm-marker-${this.data.type}"> <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 // animate position update
let pos = markerData.position || {}; let pos = markerData.position || {};
let rot = markerData.rotation || {};
if (!this.position.x && !this.position.y && !this.position.z) { if (!this.position.x && !this.position.y && !this.position.z) {
this.position.set( this.position.set(
pos.x || 0, pos.x || 0,
(pos.y || 0) + 1.8, (pos.y || 0) + 1.8,
pos.z || 0 pos.z || 0
); );
this.data.rotation.pitch = rot.pitch || 0;
this.data.rotation.yaw = rot.yaw || 0;
} else { } else {
let startPos = { let startPos = {
x: this.position.x, x: this.position.x,
y: this.position.y, y: this.position.y,
z: this.position.z, z: this.position.z,
pitch: this.data.rotation.pitch,
yaw: this.data.rotation.yaw,
} }
let deltaPos = { let deltaPos = {
x: (pos.x || 0) - startPos.x, x: (pos.x || 0) - startPos.x,
y: ((pos.y || 0) + 1.8) - startPos.y, y: ((pos.y || 0) + 1.8) - startPos.y,
z: (pos.z || 0) - startPos.z, 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 => { animate(progress => {
let ease = EasingFunctions.easeInOutCubic(progress); let ease = EasingFunctions.easeInOutCubic(progress);
this.position.set( this.position.set(
@ -127,7 +141,9 @@ export class PlayerMarker extends Marker {
startPos.y + deltaPos.y * ease || 0, startPos.y + deltaPos.y * ease || 0,
startPos.z + deltaPos.z * 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) 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 { export class PlayerMarkerSet extends MarkerSet {
constructor(id, playerheadsUrl) { constructor(id, playerheadsUrl, data = null) {
super(id); super(id, data);
this.data.label = "Player"; this.data.label = "Player";
this.data.toggleable = true; this.data.toggleable = true;
this.data.defaultHide = false; this.data.defaultHide = false;

View File

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

View File

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

View File

@ -219,7 +219,7 @@ public synchronized void saveMarkerState() {
public synchronized void savePlayerState() { public synchronized void savePlayerState() {
try ( try (
OutputStream out = storage.writeMeta(id, META_FILE_PLAYERS); 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) {

View File

@ -37,6 +37,7 @@
public class MapRenderState { public class MapRenderState {
private final Map<Vector2i, Long> regionRenderTimes; private final Map<Vector2i, Long> regionRenderTimes;
private transient long latestRenderTime = -1;
public MapRenderState() { public MapRenderState() {
regionRenderTimes = new HashMap<>(); regionRenderTimes = new HashMap<>();
@ -44,6 +45,13 @@ public MapRenderState() {
public synchronized void setRenderTime(Vector2i regionPos, long renderTime) { public synchronized void setRenderTime(Vector2i regionPos, long renderTime) {
regionRenderTimes.put(regionPos, renderTime); regionRenderTimes.put(regionPos, renderTime);
if (latestRenderTime != -1) {
if (renderTime > latestRenderTime)
latestRenderTime = renderTime;
else
latestRenderTime = -1;
}
} }
public synchronized long getRenderTime(Vector2i regionPos) { public synchronized long getRenderTime(Vector2i regionPos) {
@ -52,6 +60,19 @@ public synchronized long getRenderTime(Vector2i regionPos) {
else return renderTime; 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() { public synchronized void reset() {
regionRenderTimes.clear(); regionRenderTimes.clear();
} }

View File

@ -55,7 +55,9 @@ public ChunkAnvil113(MCAWorld world, CompoundTag chunkTag) {
CompoundTag levelData = chunkTag.getCompoundTag("Level"); CompoundTag levelData = chunkTag.getCompoundTag("Level");
String status = levelData.getString("Status"); 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.hasLight = isGenerated;
this.inhabitedTime = levelData.getLong("InhabitedTime"); 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 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.*; 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 de.bluecolored.bluemap.core.util.WrappedOutputStream;
import org.apache.commons.dbcp2.*; import org.apache.commons.dbcp2.*;
import org.apache.commons.pool2.ObjectPool; import org.apache.commons.pool2.ObjectPool;
@ -48,10 +50,12 @@
import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionException;
import java.util.function.Function; import java.util.function.Function;
public class SQLStorage extends Storage { public abstract class SQLStorage extends Storage {
private final DataSource dataSource; private final DataSource dataSource;
private final Compression hiresCompression;
protected final Dialect dialect;
protected final Compression hiresCompression;
private final LoadingCache<String, Integer> mapFKs = Caffeine.newBuilder() private final LoadingCache<String, Integer> mapFKs = Caffeine.newBuilder()
.executor(BlueMap.THREAD_POOL) .executor(BlueMap.THREAD_POOL)
@ -62,9 +66,9 @@ public class SQLStorage extends Storage {
private volatile boolean closed; 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; this.closed = false;
try { try {
if (config.getDriverClass().isPresent()) { if (config.getDriverClass().isPresent()) {
if (config.getDriverJar().isPresent()) { if (config.getDriverJar().isPresent()) {
@ -115,9 +119,7 @@ public OutputStream writeMapTile(String mapId, int lod, Vector2i tile) throws IO
byteOut.writeTo(blobOut); byteOut.writeTo(blobOut);
} }
executeUpdate(connection, executeUpdate(connection, this.dialect.writeMapTile(),
"REPLACE INTO `bluemap_map_tile` (`map`, `lod`, `x`, `z`, `compression`, `data`) " +
"VALUES (?, ?, ?, ?, ?, ?)",
mapFK, mapFK,
lod, lod,
tile.getX(), tile.getX(),
@ -139,17 +141,7 @@ public Optional<CompressedInputStream> readMapTile(String mapId, int lod, Vector
try { try {
byte[] data = recoveringConnection(connection -> { byte[] data = recoveringConnection(connection -> {
ResultSet result = executeQuery(connection, ResultSet result = executeQuery(connection,
"SELECT t.`data` " + this.dialect.readMapTile(),
"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` = ?",
mapId, mapId,
lod, lod,
tile.getX(), tile.getX(),
@ -179,17 +171,7 @@ public Optional<TileInfo> readMapTileInfo(final String mapId, int lod, final Vec
try { try {
TileInfo tileInfo = recoveringConnection(connection -> { TileInfo tileInfo = recoveringConnection(connection -> {
ResultSet result = executeQuery(connection, ResultSet result = executeQuery(connection,
"SELECT t.`changed`, LENGTH(t.`data`) as 'size' " + this.dialect.readMapTileInfo(),
"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` = ?",
mapId, mapId,
lod, lod,
tile.getX(), tile.getX(),
@ -238,15 +220,7 @@ public long getLastModified() {
public void deleteMapTile(String mapId, int lod, Vector2i tile) throws IOException { public void deleteMapTile(String mapId, int lod, Vector2i tile) throws IOException {
try { try {
recoveringConnection(connection -> recoveringConnection(connection ->
executeUpdate(connection, executeUpdate(connection,this.dialect.deleteMapTile(),
"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` = ?",
mapId, mapId,
lod, lod,
tile.getX(), tile.getX(),
@ -271,8 +245,7 @@ public OutputStream writeMeta(String mapId, String name) {
} }
executeUpdate(connection, executeUpdate(connection,
"REPLACE INTO `bluemap_map_meta` (`map`, `key`, `value`) " + this.dialect.writeMeta(),
"VALUES (?, ?, ?)",
mapFK, mapFK,
escapeMetaName(name), escapeMetaName(name),
dataBlob dataBlob
@ -289,12 +262,7 @@ public Optional<InputStream> readMeta(String mapId, String name) throws IOExcept
try { try {
byte[] data = recoveringConnection(connection -> { byte[] data = recoveringConnection(connection -> {
ResultSet result = executeQuery(connection, ResultSet result = executeQuery(connection,
"SELECT t.`value` " + this.dialect.readMeta(),
"FROM `bluemap_map_meta` t " +
" INNER JOIN `bluemap_map` m " +
" ON t.`map` = m.`id` " +
"WHERE m.`map_id` = ? " +
"AND t.`key` = ?",
mapId, mapId,
escapeMetaName(name) escapeMetaName(name)
); );
@ -319,12 +287,7 @@ public Optional<MetaInfo> readMetaInfo(String mapId, String name) throws IOExcep
try { try {
MetaInfo tileInfo = recoveringConnection(connection -> { MetaInfo tileInfo = recoveringConnection(connection -> {
ResultSet result = executeQuery(connection, ResultSet result = executeQuery(connection,
"SELECT LENGTH(t.`value`) as 'size' " + this.dialect.readMetaSize(),
"FROM `bluemap_map_meta` t " +
" INNER JOIN `bluemap_map` m " +
" ON t.`map` = m.`id` " +
"WHERE m.`map_id` = ? " +
"AND t.`key` = ?",
mapId, mapId,
escapeMetaName(name) escapeMetaName(name)
); );
@ -361,12 +324,7 @@ public void deleteMeta(String mapId, String name) throws IOException {
try { try {
recoveringConnection(connection -> recoveringConnection(connection ->
executeUpdate(connection, executeUpdate(connection,
"DELETE t " + this.dialect.deleteMeta(),
"FROM `bluemap_map_meta` t " +
" INNER JOIN `bluemap_map` m " +
" ON t.`map` = m.`id` " +
"WHERE m.`map_id` = ? " +
"AND t.`key` = ?",
mapId, mapId,
escapeMetaName(name) escapeMetaName(name)
), 2); ), 2);
@ -381,28 +339,18 @@ public void purgeMap(String mapId, Function<ProgressInfo, Boolean> onProgress) t
try { try {
recoveringConnection(connection -> { recoveringConnection(connection -> {
executeUpdate(connection, executeUpdate(connection,
"DELETE t " + this.dialect.purgeMapTile(),
"FROM `bluemap_map_tile` t " +
" INNER JOIN `bluemap_map` m " +
" ON t.`map` = m.`id` " +
"WHERE m.`map_id` = ?",
mapId mapId
); );
executeUpdate(connection, executeUpdate(connection,
"DELETE t " + this.dialect.purgeMapMeta(),
"FROM `bluemap_map_meta` t " +
" INNER JOIN `bluemap_map` m " +
" ON t.`map` = m.`id` " +
"WHERE m.`map_id` = ?",
mapId mapId
); );
executeUpdate(connection, executeUpdate(connection,
"DELETE " + this.dialect.purgeMap(),
"FROM `bluemap_map` " +
"WHERE `map_id` = ?",
mapId mapId
); );
}, 2); }, 2);
@ -420,7 +368,7 @@ public Collection<String> collectMapIds() throws IOException {
try { try {
return recoveringConnection(connection -> { return recoveringConnection(connection -> {
ResultSet result = executeQuery(connection, ResultSet result = executeQuery(connection,
"SELECT `map_id` FROM `bluemap_map`" this.dialect.selectMapIds()
); );
Collection<String> mapIds = new ArrayList<>(); Collection<String> mapIds = new ArrayList<>();
while (result.next()) { while (result.next()) {
@ -440,15 +388,10 @@ public void initialize() throws IOException {
// initialize and get schema-version // initialize and get schema-version
String schemaVersionString = recoveringConnection(connection -> { String schemaVersionString = recoveringConnection(connection -> {
connection.createStatement().executeUpdate( connection.createStatement().executeUpdate(
"CREATE TABLE IF NOT EXISTS `bluemap_storage_meta` (" + this.dialect.initializeStorageMeta());
"`key` varchar(255) NOT NULL, " +
"`value` varchar(255) DEFAULT NULL, " +
"PRIMARY KEY (`key`)" +
")");
ResultSet result = executeQuery(connection, ResultSet result = executeQuery(connection,
"SELECT `value` FROM `bluemap_storage_meta` " + this.dialect.selectStorageMeta(),
"WHERE `key` = ?",
"schema_version" "schema_version"
); );
@ -456,8 +399,7 @@ public void initialize() throws IOException {
return result.getString("value"); return result.getString("value");
} else { } else {
executeUpdate(connection, executeUpdate(connection,
"INSERT INTO `bluemap_storage_meta` (`key`, `value`) " + this.dialect.insertStorageMeta(),
"VALUES (?, ?)",
"schema_version", "0" "schema_version", "0"
); );
return "0"; return "0";
@ -482,51 +424,22 @@ public void initialize() throws IOException {
recoveringConnection(connection -> { recoveringConnection(connection -> {
connection.createStatement().executeUpdate( connection.createStatement().executeUpdate(
"CREATE TABLE `bluemap_map` (" + this.dialect.initializeMap()
"`id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT," +
"`map_id` VARCHAR(255) NOT NULL," +
"PRIMARY KEY (`id`)," +
"UNIQUE INDEX `map_id` (`map_id`)" +
");"
); );
connection.createStatement().executeUpdate( connection.createStatement().executeUpdate(
"CREATE TABLE `bluemap_map_tile_compression` (" + this.dialect.initializeMapTileCompression()
"`id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT," +
"`compression` VARCHAR(255) NOT NULL," +
"PRIMARY KEY (`id`)," +
"UNIQUE INDEX `compression` (`compression`)" +
");"
); );
connection.createStatement().executeUpdate( connection.createStatement().executeUpdate(
"CREATE TABLE `bluemap_map_meta` (" + this.dialect.initializeMapMeta());
"`map` SMALLINT UNSIGNED NOT NULL," +
"`key` varchar(255) NOT NULL," +
"`value` LONGBLOB NOT NULL," +
"PRIMARY KEY (`map`, `key`)," +
"CONSTRAINT `fk_bluemap_map_meta_map` FOREIGN KEY (`map`) REFERENCES `bluemap_map` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT" +
")");
connection.createStatement().executeUpdate( connection.createStatement().executeUpdate(
"CREATE TABLE `bluemap_map_tile` (" + this.dialect.initializeMapTile()
"`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" +
");"
); );
executeUpdate(connection, executeUpdate(connection,
"UPDATE `bluemap_storage_meta` " + this.dialect.updateStorageMeta(),
"SET `value` = ? " +
"WHERE `key` = ?",
"3", "schema_version" "3", "schema_version"
); );
}, 2); }, 2);
@ -544,36 +457,27 @@ public void initialize() throws IOException {
// delete potential files that are already in the new format to avoid constraint-issues // delete potential files that are already in the new format to avoid constraint-issues
executeUpdate(connection, executeUpdate(connection,
"DELETE FROM `bluemap_map_meta`" + this.dialect.deleteMapMeta(),
"WHERE `key` IN (?, ?, ?)",
"settings.json", "textures.json", ".rstate" "settings.json", "textures.json", ".rstate"
); );
// rename files // rename files
executeUpdate(connection, executeUpdate(connection,
"UPDATE `bluemap_map_meta` " + this.dialect.updateMapMeta(),
"SET `key` = ? " +
"WHERE `key` = ?",
"settings.json", "settings" "settings.json", "settings"
); );
executeUpdate(connection, executeUpdate(connection,
"UPDATE `bluemap_map_meta` " + this.dialect.updateMapMeta(),
"SET `key` = ? " +
"WHERE `key` = ?",
"textures.json", "textures" "textures.json", "textures"
); );
executeUpdate(connection, executeUpdate(connection,
"UPDATE `bluemap_map_meta` " + this.dialect.updateMapMeta(),
"SET `key` = ? " +
"WHERE `key` = ?",
".rstate", "render_state" ".rstate", "render_state"
); );
// update schemaVersion // update schemaVersion
executeUpdate(connection, executeUpdate(connection,
"UPDATE `bluemap_storage_meta` " + this.dialect.updateStorageMeta(),
"SET `value` = ? " +
"WHERE `key` = ?",
"3", "schema_version" "3", "schema_version"
); );
}, 2); }, 2);
@ -603,32 +507,31 @@ public void close() throws IOException {
} }
} }
private ResultSet executeQuery(Connection connection, @Language("sql") String sql, Object... parameters) throws SQLException { protected 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 return prepareStatement(connection, sql, parameters).executeQuery();
PreparedStatement statement = connection.prepareStatement(sql);
for (int i = 0; i < parameters.length; i++) {
statement.setObject(i+1, parameters[i]);
}
return statement.executeQuery();
} }
@SuppressWarnings("UnusedReturnValue") @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 // we only use this prepared statement once, but the DB-Driver caches those and reuses them
PreparedStatement statement = connection.prepareStatement(sql); PreparedStatement statement = connection.prepareStatement(sql);
for (int i = 0; i < parameters.length; i++) { 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") @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); recoveringConnection((ConnectionFunction<Void>) action, tries);
} }
@SuppressWarnings("SameParameterValue") @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; SQLException sqlException = null;
try { try {
@ -657,7 +560,7 @@ private <R> R recoveringConnection(ConnectionFunction<R> action, int tries) thro
throw sqlException; throw sqlException;
} }
private int getMapFK(String mapId) throws SQLException { protected int getMapFK(String mapId) throws SQLException {
try { try {
return Objects.requireNonNull(mapFKs.get(mapId)); return Objects.requireNonNull(mapFKs.get(mapId));
} catch (CompletionException ex) { } catch (CompletionException ex) {
@ -670,7 +573,7 @@ private int getMapFK(String mapId) throws SQLException {
} }
} }
private int getMapTileCompressionFK(Compression compression) throws SQLException { int getMapTileCompressionFK(Compression compression) throws SQLException {
try { try {
return Objects.requireNonNull(mapTileCompressionFKs.get(compression)); return Objects.requireNonNull(mapTileCompressionFKs.get(compression));
} catch (CompletionException ex) { } catch (CompletionException ex) {
@ -698,9 +601,7 @@ private int lookupFK(String table, String idField, String valueField, String val
return recoveringConnection(connection -> { return recoveringConnection(connection -> {
int key; int key;
ResultSet result = executeQuery(connection, ResultSet result = executeQuery(connection,
//language=SQL this.dialect.lookupFK(table,idField,valueField),
"SELECT `" + idField + "` FROM `" + table + "` " +
"WHERE `" + valueField + "` = ?",
value value
); );
@ -708,8 +609,7 @@ private int lookupFK(String table, String idField, String valueField, String val
key = result.getInt("id"); key = result.getInt("id");
} else { } else {
PreparedStatement statement = connection.prepareStatement( PreparedStatement statement = connection.prepareStatement(
"INSERT INTO `" + table + "` (`" + valueField + "`) " + this.dialect.insertFK(table,valueField),
"VALUES (?)",
Statement.RETURN_GENERATED_KEYS Statement.RETURN_GENERATED_KEYS
); );
statement.setString(1, value); statement.setString(1, value);
@ -774,6 +674,12 @@ private DataSource createDataSource(ConnectionFactory connectionFactory, int max
return new PoolingDataSource<>(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 @FunctionalInterface
public interface ConnectionConsumer extends ConnectionFunction<Void> { 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 java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.BindException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -204,12 +206,24 @@ public void startWebserver(BlueMapService blueMap, boolean verbose) throws IOExc
HttpRequestHandler handler = new BlueMapResponseModifier(routingRequestHandler); HttpRequestHandler handler = new BlueMapResponseModifier(routingRequestHandler);
if (verbose) handler = new LoggingRequestHandler(handler); if (verbose) handler = new LoggingRequestHandler(handler);
HttpServer webServer = new HttpServer(handler); try {
webServer.bind(new InetSocketAddress( HttpServer webServer = new HttpServer(handler);
config.resolveIp(), webServer.bind(new InetSocketAddress(
config.getPort() config.resolveIp(),
)); config.getPort()
webServer.start(); ));
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 @Override