forked from mirror/BlueMap
Implement native addon loader
This commit is contained in:
parent
01b1ac513c
commit
6ad50a89cb
@ -0,0 +1,6 @@
|
||||
package de.bluecolored.bluemap.common.addons;
|
||||
|
||||
import lombok.experimental.StandardException;
|
||||
|
||||
@StandardException
|
||||
public class AddonException extends Exception {}
|
@ -0,0 +1,12 @@
|
||||
package de.bluecolored.bluemap.common.addons;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class AddonInfo {
|
||||
public static final String ADDON_INFO_FILE = "bluemap.addon.json";
|
||||
|
||||
private String id;
|
||||
private String entrypoint;
|
||||
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
package de.bluecolored.bluemap.common.addons;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import de.bluecolored.bluemap.core.BlueMap;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static de.bluecolored.bluemap.common.addons.AddonInfo.ADDON_INFO_FILE;
|
||||
|
||||
public final class AddonManager {
|
||||
|
||||
private static final Gson GSON = new GsonBuilder().create();
|
||||
private static final Map<String, LoadedAddon> LOADED_ADDONS = new ConcurrentHashMap<>();
|
||||
|
||||
private AddonManager() {
|
||||
throw new UnsupportedOperationException("Utility class");
|
||||
}
|
||||
|
||||
public static void tryLoadAddons(Path root) {
|
||||
tryLoadAddons(root, false);
|
||||
}
|
||||
|
||||
public static void tryLoadAddons(Path root, boolean expectOnlyAddons) {
|
||||
try (Stream<Path> files = Files.list(root)) {
|
||||
files
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(f -> f.getFileName().toString().endsWith(".jar"))
|
||||
.forEach(expectOnlyAddons ? AddonManager::tryLoadAddon : AddonManager::tryLoadJar);
|
||||
} catch (IOException e) {
|
||||
Logger.global.logError("Failed to load addons from '%s'".formatted(root), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void tryLoadAddon(Path addonJarFile) {
|
||||
try {
|
||||
AddonInfo addonInfo = loadAddonInfo(addonJarFile);
|
||||
if (addonInfo == null) throw new AddonException("No %s found in '%s'".formatted(ADDON_INFO_FILE, addonJarFile));
|
||||
|
||||
if (LOADED_ADDONS.containsKey(addonInfo.getId())) return;
|
||||
|
||||
loadAddon(addonJarFile, addonInfo);
|
||||
} catch (IOException | AddonException e) {
|
||||
Logger.global.logError("Failed to load addon '%s'".formatted(addonJarFile), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void tryLoadJar(Path addonJarFile) {
|
||||
try {
|
||||
AddonInfo addonInfo = loadAddonInfo(addonJarFile);
|
||||
if (addonInfo == null) {
|
||||
Logger.global.logDebug("No %s found in '%s', skipping...".formatted(ADDON_INFO_FILE, addonJarFile));
|
||||
return;
|
||||
}
|
||||
|
||||
if (LOADED_ADDONS.containsKey(addonInfo.getId())) return;
|
||||
|
||||
loadAddon(addonJarFile, addonInfo);
|
||||
} catch (IOException | AddonException e) {
|
||||
Logger.global.logError("Failed to load addon '%s'".formatted(addonJarFile), e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized static void loadAddon(Path jarFile, AddonInfo addonInfo) throws AddonException {
|
||||
Logger.global.logInfo("Loading BlueMap Addon: %s (%s)".formatted(addonInfo.getId(), jarFile));
|
||||
|
||||
if (LOADED_ADDONS.containsKey(addonInfo.getId()))
|
||||
throw new AddonException("Addon with id '%s' is already loaded".formatted(addonInfo.getId()));
|
||||
|
||||
try {
|
||||
ClassLoader addonClassLoader = BlueMap.class.getClassLoader();
|
||||
Class<?> entrypointClass;
|
||||
|
||||
// try to find entrypoint class and load jar with new classloader if needed
|
||||
try {
|
||||
entrypointClass = BlueMap.class.getClassLoader().loadClass(addonInfo.getEntrypoint());
|
||||
} catch (ClassNotFoundException e) {
|
||||
addonClassLoader = new URLClassLoader(
|
||||
new URL[]{ jarFile.toUri().toURL() },
|
||||
BlueMap.class.getClassLoader()
|
||||
);
|
||||
entrypointClass = addonClassLoader.loadClass(addonInfo.getEntrypoint());
|
||||
}
|
||||
|
||||
// create addon instance
|
||||
Object instance = entrypointClass.getConstructor().newInstance();
|
||||
LoadedAddon addon = new LoadedAddon(
|
||||
addonInfo,
|
||||
addonClassLoader,
|
||||
instance
|
||||
);
|
||||
LOADED_ADDONS.put(addonInfo.getId(), addon);
|
||||
|
||||
// run addon
|
||||
if (instance instanceof Runnable runnable)
|
||||
runnable.run();
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new AddonException("Failed to load addon '%s'".formatted(jarFile), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static @Nullable AddonInfo loadAddonInfo(Path addonJarFile) throws IOException, AddonException {
|
||||
try (FileSystem fileSystem = FileSystems.newFileSystem(addonJarFile, (ClassLoader) null)) {
|
||||
for (Path root : fileSystem.getRootDirectories()) {
|
||||
Path addonInfoFile = root.resolve(ADDON_INFO_FILE);
|
||||
if (!Files.exists(addonInfoFile)) continue;
|
||||
|
||||
try (Reader reader = Files.newBufferedReader(addonInfoFile, StandardCharsets.UTF_8)) {
|
||||
AddonInfo addonInfo = GSON.fromJson(reader, AddonInfo.class);
|
||||
|
||||
if (addonInfo.getId() == null)
|
||||
throw new AddonException("'id' is missing");
|
||||
|
||||
if (addonInfo.getEntrypoint() == null)
|
||||
throw new AddonException("'entrypoint' is missing");
|
||||
|
||||
return addonInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package de.bluecolored.bluemap.common.addons;
|
||||
|
||||
public record LoadedAddon (
|
||||
AddonInfo addonInfo,
|
||||
ClassLoader classLoader,
|
||||
Object instance
|
||||
) {}
|
@ -29,12 +29,14 @@
|
||||
import de.bluecolored.bluemap.api.BlueMapAPI;
|
||||
import de.bluecolored.bluemap.api.BlueMapMap;
|
||||
import de.bluecolored.bluemap.api.BlueMapWorld;
|
||||
import de.bluecolored.bluemap.common.BlueMapService;
|
||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||
import de.bluecolored.bluemap.common.serverinterface.ServerWorld;
|
||||
import de.bluecolored.bluemap.core.BlueMap;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
@ -43,12 +45,28 @@
|
||||
|
||||
public class BlueMapAPIImpl extends BlueMapAPI {
|
||||
|
||||
private final Plugin plugin;
|
||||
private final BlueMapService blueMapService;
|
||||
private final @Nullable Plugin plugin;
|
||||
|
||||
private final WebAppImpl webAppImpl;
|
||||
private final @Nullable RenderManagerImpl renderManagerImpl;
|
||||
private final @Nullable PluginImpl pluginImpl;
|
||||
|
||||
private final LoadingCache<Object, Optional<BlueMapWorld>> worldCache;
|
||||
private final LoadingCache<String, Optional<BlueMapMap>> mapCache;
|
||||
|
||||
public BlueMapAPIImpl(Plugin plugin) {
|
||||
this(plugin.getBlueMap(), plugin);
|
||||
}
|
||||
|
||||
public BlueMapAPIImpl(BlueMapService blueMapService, @Nullable Plugin plugin) {
|
||||
this.blueMapService = blueMapService;
|
||||
this.plugin = plugin;
|
||||
|
||||
this.renderManagerImpl = plugin != null ? new RenderManagerImpl(this, plugin) : null;
|
||||
this.webAppImpl = new WebAppImpl(blueMapService, plugin);
|
||||
this.pluginImpl = plugin != null ? new PluginImpl(plugin) : null;
|
||||
|
||||
this.worldCache = Caffeine.newBuilder()
|
||||
.executor(BlueMap.THREAD_POOL)
|
||||
.weakKeys()
|
||||
@ -59,24 +77,9 @@ public BlueMapAPIImpl(Plugin plugin) {
|
||||
.build(this::getMapUncached);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RenderManagerImpl getRenderManager() {
|
||||
return new RenderManagerImpl(this, plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebAppImpl getWebApp() {
|
||||
return new WebAppImpl(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public de.bluecolored.bluemap.api.plugin.Plugin getPlugin() {
|
||||
return new PluginImpl(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<BlueMapMap> getMaps() {
|
||||
Map<String, BmMap> maps = plugin.getBlueMap().getMaps();
|
||||
Map<String, BmMap> maps = blueMapService.getMaps();
|
||||
return maps.keySet().stream()
|
||||
.map(this::getMap)
|
||||
.filter(Optional::isPresent)
|
||||
@ -86,7 +89,7 @@ public Collection<BlueMapMap> getMaps() {
|
||||
|
||||
@Override
|
||||
public Collection<BlueMapWorld> getWorlds() {
|
||||
Map<String, World> worlds = plugin.getBlueMap().getWorlds();
|
||||
Map<String, World> worlds = blueMapService.getWorlds();
|
||||
return worlds.keySet().stream()
|
||||
.map(this::getWorld)
|
||||
.filter(Optional::isPresent)
|
||||
@ -102,21 +105,24 @@ public Optional<BlueMapWorld> getWorld(Object world) {
|
||||
public Optional<BlueMapWorld> getWorldUncached(Object world) {
|
||||
|
||||
if (world instanceof String) {
|
||||
var coreWorld = plugin.getBlueMap().getWorlds().get(world);
|
||||
var coreWorld = blueMapService.getWorlds().get(world);
|
||||
if (coreWorld != null) world = coreWorld;
|
||||
}
|
||||
|
||||
if (world instanceof World coreWorld) {
|
||||
return Optional.of(new BlueMapWorldImpl(plugin, coreWorld));
|
||||
return Optional.of(new BlueMapWorldImpl(coreWorld, blueMapService, plugin));
|
||||
}
|
||||
|
||||
if (plugin == null) return Optional.empty();
|
||||
|
||||
ServerWorld serverWorld = plugin.getServerInterface().getServerWorld(world).orElse(null);
|
||||
if (serverWorld == null) return Optional.empty();
|
||||
|
||||
World coreWorld = plugin.getWorld(serverWorld);
|
||||
if (coreWorld == null) return Optional.empty();
|
||||
|
||||
return Optional.of(new BlueMapWorldImpl(plugin, coreWorld));
|
||||
return Optional.of(new BlueMapWorldImpl(coreWorld, blueMapService, plugin));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -125,7 +131,7 @@ public Optional<BlueMapMap> getMap(String id) {
|
||||
}
|
||||
|
||||
public Optional<BlueMapMap> getMapUncached(String id) {
|
||||
var maps = plugin.getBlueMap().getMaps();
|
||||
var maps = blueMapService.getMaps();
|
||||
|
||||
var map = maps.get(id);
|
||||
if (map == null) return Optional.empty();
|
||||
@ -133,7 +139,7 @@ public Optional<BlueMapMap> getMapUncached(String id) {
|
||||
var world = getWorld(map.getWorld()).orElse(null);
|
||||
if (world == null) return Optional.empty();
|
||||
|
||||
return Optional.of(new BlueMapMapImpl(plugin, map, (BlueMapWorldImpl) world));
|
||||
return Optional.of(new BlueMapMapImpl(map, (BlueMapWorldImpl) world, plugin));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -141,6 +147,23 @@ public String getBlueMapVersion() {
|
||||
return BlueMap.VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebAppImpl getWebApp() {
|
||||
return webAppImpl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RenderManagerImpl getRenderManager() {
|
||||
if (renderManagerImpl == null) throw new UnsupportedOperationException("RenderManager API is not supported on this platform");
|
||||
return renderManagerImpl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public de.bluecolored.bluemap.api.plugin.Plugin getPlugin() {
|
||||
if (pluginImpl == null) throw new UnsupportedOperationException("Plugin API is not supported on this platform");
|
||||
return pluginImpl;
|
||||
}
|
||||
|
||||
public void register() {
|
||||
try {
|
||||
BlueMapAPI.registerInstance(this);
|
||||
@ -157,4 +180,26 @@ public void unregister() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Easy-access method for addons depending on BlueMapCommon:<br>
|
||||
* <blockquote><pre>
|
||||
* BlueMapService bluemap = ((BlueMapAPIImpl) blueMapAPI).blueMapService();
|
||||
* </pre></blockquote>
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public BlueMapService blueMapService() {
|
||||
return blueMapService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Easy-access method for addons depending on BlueMapCommon:<br>
|
||||
* <blockquote><pre>
|
||||
* Plugin plugin = ((BlueMapAPIImpl) blueMapAPI).plugin();
|
||||
* </pre></blockquote>
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public @Nullable Plugin plugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,16 +25,16 @@
|
||||
package de.bluecolored.bluemap.common.api;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import de.bluecolored.bluemap.api.AssetStorage;
|
||||
import de.bluecolored.bluemap.api.BlueMapMap;
|
||||
import de.bluecolored.bluemap.api.BlueMapWorld;
|
||||
import de.bluecolored.bluemap.api.AssetStorage;
|
||||
import de.bluecolored.bluemap.api.markers.MarkerSet;
|
||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||
import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask;
|
||||
import de.bluecolored.bluemap.common.rendermanager.WorldRegionRenderTask;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@ -42,27 +42,16 @@
|
||||
|
||||
public class BlueMapMapImpl implements BlueMapMap {
|
||||
|
||||
private final WeakReference<Plugin> plugin;
|
||||
private final String mapId;
|
||||
private final WeakReference<BmMap> map;
|
||||
private final BlueMapWorldImpl world;
|
||||
private final String mapId;
|
||||
private final WeakReference<Plugin> plugin;
|
||||
|
||||
public BlueMapMapImpl(Plugin plugin, BmMap map) throws IOException {
|
||||
this.plugin = new WeakReference<>(plugin);
|
||||
this.map = new WeakReference<>(map);
|
||||
this.world = new BlueMapWorldImpl(plugin, map.getWorld());
|
||||
public BlueMapMapImpl(BmMap map, BlueMapWorldImpl world, @Nullable Plugin plugin) {
|
||||
this.mapId = map.getId();
|
||||
}
|
||||
|
||||
public BlueMapMapImpl(Plugin plugin, BmMap map, BlueMapWorldImpl world) {
|
||||
this.plugin = new WeakReference<>(plugin);
|
||||
this.map = new WeakReference<>(map);
|
||||
this.world = world;
|
||||
this.mapId = map.getId();
|
||||
}
|
||||
|
||||
public BmMap getBmMap() {
|
||||
return unpack(map);
|
||||
this.plugin = new WeakReference<>(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -122,7 +111,9 @@ public synchronized void setFrozen(boolean frozen) {
|
||||
}
|
||||
|
||||
private synchronized void unfreeze() {
|
||||
Plugin plugin = unpack(this.plugin);
|
||||
Plugin plugin = this.plugin.get();
|
||||
if (plugin == null) return; // fail silently: not supported on non-plugin platforms
|
||||
|
||||
BmMap map = unpack(this.map);
|
||||
plugin.startWatchingMap(map);
|
||||
plugin.getPluginState().getMapState(map).setUpdateEnabled(true);
|
||||
@ -130,7 +121,9 @@ private synchronized void unfreeze() {
|
||||
}
|
||||
|
||||
private synchronized void freeze() {
|
||||
Plugin plugin = unpack(this.plugin);
|
||||
Plugin plugin = this.plugin.get();
|
||||
if (plugin == null) return; // fail silently: not supported on non-plugin platforms
|
||||
|
||||
BmMap map = unpack(this.map);
|
||||
plugin.stopWatchingMap(map);
|
||||
plugin.getPluginState().getMapState(map).setUpdateEnabled(false);
|
||||
@ -147,7 +140,10 @@ private synchronized void freeze() {
|
||||
|
||||
@Override
|
||||
public boolean isFrozen() {
|
||||
return !unpack(plugin).getPluginState().getMapState(unpack(map)).isUpdateEnabled();
|
||||
Plugin plugin = this.plugin.get();
|
||||
if (plugin == null) return false; // fail silently: not supported on non-plugin platforms
|
||||
|
||||
return !plugin.getPluginState().getMapState(unpack(map)).isUpdateEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -169,4 +165,14 @@ private <T> T unpack(WeakReference<T> ref) {
|
||||
return Objects.requireNonNull(ref.get(), "Reference lost to delegate object. Most likely BlueMap got reloaded and this instance is no longer valid.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Easy-access method for addons depending on BlueMapCore:<br>
|
||||
* <blockquote><pre>
|
||||
* BmMap map = ((BlueMapMapImpl) blueMapMap).map();
|
||||
* </pre></blockquote>
|
||||
*/
|
||||
public BmMap map() {
|
||||
return unpack(map);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,9 +26,11 @@
|
||||
|
||||
import de.bluecolored.bluemap.api.BlueMapMap;
|
||||
import de.bluecolored.bluemap.api.BlueMapWorld;
|
||||
import de.bluecolored.bluemap.common.BlueMapService;
|
||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.nio.file.Path;
|
||||
@ -39,17 +41,15 @@
|
||||
public class BlueMapWorldImpl implements BlueMapWorld {
|
||||
|
||||
private final String id;
|
||||
private final WeakReference<Plugin> plugin;
|
||||
private final WeakReference<World> world;
|
||||
private final WeakReference<BlueMapService> blueMapService;
|
||||
private final WeakReference<Plugin> plugin;
|
||||
|
||||
public BlueMapWorldImpl(Plugin plugin, World world) {
|
||||
public BlueMapWorldImpl(World world, BlueMapService blueMapService, @Nullable Plugin plugin) {
|
||||
this.id = world.getId();
|
||||
this.plugin = new WeakReference<>(plugin);
|
||||
this.world = new WeakReference<>(world);
|
||||
}
|
||||
|
||||
public World getWorld() {
|
||||
return unpack(world);
|
||||
this.blueMapService = new WeakReference<>(blueMapService);
|
||||
this.plugin = new WeakReference<>(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -70,11 +70,10 @@ public Path getSaveFolder() {
|
||||
|
||||
@Override
|
||||
public Collection<BlueMapMap> getMaps() {
|
||||
Plugin plugin = unpack(this.plugin);
|
||||
World world = unpack(this.world);
|
||||
return plugin.getBlueMap().getMaps().values().stream()
|
||||
return unpack(blueMapService).getMaps().values().stream()
|
||||
.filter(map -> map.getWorld().equals(world))
|
||||
.map(map -> new BlueMapMapImpl(plugin, map, this))
|
||||
.map(map -> new BlueMapMapImpl(map, this, plugin.get()))
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
@ -97,4 +96,14 @@ private <T> T unpack(WeakReference<T> ref) {
|
||||
return Objects.requireNonNull(ref.get(), "Reference lost to delegate object. Most likely BlueMap got reloaded and this instance is no longer valid.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Easy-access method for addons depending on BlueMapCore:<br>
|
||||
* <blockquote><pre>
|
||||
* World world = ((BlueMapWorldImpl) blueMapWorld).world();
|
||||
* </pre></blockquote>
|
||||
*/
|
||||
public World world() {
|
||||
return unpack(world);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -48,19 +48,19 @@ public RenderManagerImpl(BlueMapAPIImpl api, Plugin plugin) {
|
||||
@Override
|
||||
public boolean scheduleMapUpdateTask(BlueMapMap map, boolean force) {
|
||||
BlueMapMapImpl cmap = castMap(map);
|
||||
return renderManager.scheduleRenderTask(new MapUpdateTask(cmap.getBmMap(), s -> force));
|
||||
return renderManager.scheduleRenderTask(new MapUpdateTask(cmap.map(), s -> force));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean scheduleMapUpdateTask(BlueMapMap map, Collection<Vector2i> regions, boolean force) {
|
||||
BlueMapMapImpl cmap = castMap(map);
|
||||
return renderManager.scheduleRenderTask(new MapUpdateTask(cmap.getBmMap(), regions, s -> force));
|
||||
return renderManager.scheduleRenderTask(new MapUpdateTask(cmap.map(), regions, s -> force));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean scheduleMapPurgeTask(BlueMapMap map) {
|
||||
BlueMapMapImpl cmap = castMap(map);
|
||||
return renderManager.scheduleRenderTask(new MapPurgeTask(cmap.getBmMap()));
|
||||
return renderManager.scheduleRenderTask(new MapPurgeTask(cmap.map()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -25,35 +25,47 @@
|
||||
package de.bluecolored.bluemap.common.api;
|
||||
|
||||
import de.bluecolored.bluemap.api.WebApp;
|
||||
import de.bluecolored.bluemap.common.BlueMapService;
|
||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.util.FileHelper;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class WebAppImpl implements WebApp {
|
||||
|
||||
private final Plugin plugin;
|
||||
private final BlueMapService blueMapService;
|
||||
private final @Nullable Plugin plugin;
|
||||
|
||||
private final Timer timer = new Timer();
|
||||
private @Nullable TimerTask scheduledWebAppSettingsUpdate;
|
||||
|
||||
public WebAppImpl(BlueMapService blueMapService, @Nullable Plugin plugin) {
|
||||
this.blueMapService = blueMapService;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public WebAppImpl(Plugin plugin) {
|
||||
this.blueMapService = plugin.getBlueMap();
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getWebRoot() {
|
||||
return plugin.getBlueMap().getConfig().getWebappConfig().getWebroot();
|
||||
return blueMapService.getConfig().getWebappConfig().getWebroot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerVisibility(UUID player, boolean visible) {
|
||||
if (plugin == null) return; // fail silently: not supported on non-plugin platforms
|
||||
|
||||
if (visible) {
|
||||
plugin.getPluginState().removeHiddenPlayer(player);
|
||||
} else {
|
||||
@ -63,19 +75,48 @@ public void setPlayerVisibility(UUID player, boolean visible) {
|
||||
|
||||
@Override
|
||||
public boolean getPlayerVisibility(UUID player) {
|
||||
if (plugin == null) return false; // fail silently: not supported on non-plugin platforms
|
||||
|
||||
return !plugin.getPluginState().isPlayerHidden(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerScript(String url) {
|
||||
public synchronized void registerScript(String url) {
|
||||
Logger.global.logDebug("Registering script from API: " + url);
|
||||
plugin.getBlueMap().getWebFilesManager().getScripts().add(url);
|
||||
blueMapService.getWebFilesManager().getScripts().add(url);
|
||||
scheduleUpdateWebAppSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerStyle(String url) {
|
||||
public synchronized void registerStyle(String url) {
|
||||
Logger.global.logDebug("Registering style from API: " + url);
|
||||
plugin.getBlueMap().getWebFilesManager().getStyles().add(url);
|
||||
blueMapService.getWebFilesManager().getStyles().add(url);
|
||||
scheduleUpdateWebAppSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save webapp-settings after a short delay, if no other save is already scheduled.
|
||||
* (to bulk-save changes in case there is a lot of scripts being registered at once)
|
||||
*/
|
||||
private synchronized void scheduleUpdateWebAppSettings() {
|
||||
if (!blueMapService.getConfig().getWebappConfig().isEnabled()) return;
|
||||
if (scheduledWebAppSettingsUpdate != null) return;
|
||||
|
||||
timer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (WebAppImpl.this) {
|
||||
try {
|
||||
if (blueMapService.getConfig().getWebappConfig().isEnabled())
|
||||
blueMapService.getWebFilesManager().saveSettings();
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logError("Failed to update webapp settings", ex);
|
||||
} finally {
|
||||
scheduledWebAppSettingsUpdate = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -28,6 +28,7 @@
|
||||
import de.bluecolored.bluemap.common.BlueMapService;
|
||||
import de.bluecolored.bluemap.common.InterruptableReentrantLock;
|
||||
import de.bluecolored.bluemap.common.MissingResourcesException;
|
||||
import de.bluecolored.bluemap.common.addons.AddonManager;
|
||||
import de.bluecolored.bluemap.common.api.BlueMapAPIImpl;
|
||||
import de.bluecolored.bluemap.common.config.*;
|
||||
import de.bluecolored.bluemap.common.live.LivePlayersDataSupplier;
|
||||
@ -50,6 +51,8 @@
|
||||
import de.bluecolored.bluemap.core.util.Tristate;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.configurate.gson.GsonConfigurationLoader;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
@ -61,6 +64,7 @@
|
||||
import java.net.BindException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
@ -70,6 +74,7 @@
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Getter
|
||||
public class Plugin implements ServerEventListener {
|
||||
|
||||
public static final String PLUGIN_ID = "bluemap";
|
||||
@ -77,25 +82,23 @@ public class Plugin implements ServerEventListener {
|
||||
|
||||
private static final String DEBUG_FILE_LOG_NAME = "file-debug-log";
|
||||
|
||||
@Getter(AccessLevel.NONE)
|
||||
private final InterruptableReentrantLock loadingLock = new InterruptableReentrantLock();
|
||||
|
||||
private final String implementationType;
|
||||
private final Server serverInterface;
|
||||
|
||||
private BlueMapService blueMap;
|
||||
|
||||
private PluginState pluginState;
|
||||
|
||||
private RenderManager renderManager;
|
||||
private HttpServer webServer;
|
||||
private Logger webLogger;
|
||||
|
||||
private BlueMapAPIImpl api;
|
||||
|
||||
private HttpServer webServer;
|
||||
private RoutingRequestHandler webRequestHandler;
|
||||
private Logger webLogger;
|
||||
|
||||
private Timer daemonTimer;
|
||||
|
||||
private Map<String, MapUpdateService> mapUpdateServices;
|
||||
|
||||
private PlayerSkinUpdater skinUpdater;
|
||||
|
||||
private boolean loaded = false;
|
||||
@ -119,6 +122,12 @@ private void load(@Nullable ResourcePack preloadedResourcePack) throws IOExcepti
|
||||
if (loaded) return;
|
||||
unload(); //ensure nothing is left running (from a failed load or something)
|
||||
|
||||
//load addons
|
||||
Path addonsFolder = serverInterface.getConfigFolder().resolve("addons");
|
||||
Files.createDirectories(addonsFolder);
|
||||
AddonManager.tryLoadAddons(addonsFolder, true);
|
||||
serverInterface.getModsFolder().ifPresent(AddonManager::tryLoadAddons);
|
||||
|
||||
//load configs
|
||||
BlueMapConfigManager configManager = BlueMapConfigManager.builder()
|
||||
.minecraftVersion(serverInterface.getMinecraftVersion())
|
||||
@ -184,10 +193,10 @@ private void load(@Nullable ResourcePack preloadedResourcePack) throws IOExcepti
|
||||
Path webroot = webserverConfig.getWebroot();
|
||||
FileHelper.createDirectories(webroot);
|
||||
|
||||
RoutingRequestHandler routingRequestHandler = new RoutingRequestHandler();
|
||||
this.webRequestHandler = new RoutingRequestHandler();
|
||||
|
||||
// default route
|
||||
routingRequestHandler.register(".*", new FileRequestHandler(webroot));
|
||||
webRequestHandler.register(".*", new FileRequestHandler(webroot));
|
||||
|
||||
// map route
|
||||
for (var mapConfigEntry : configManager.getMapConfigs().entrySet()) {
|
||||
@ -203,7 +212,7 @@ private void load(@Nullable ResourcePack preloadedResourcePack) throws IOExcepti
|
||||
mapRequestHandler = new MapRequestHandler(storage.map(id));
|
||||
}
|
||||
|
||||
routingRequestHandler.register(
|
||||
webRequestHandler.register(
|
||||
"maps/" + Pattern.quote(id) + "/(.*)",
|
||||
"$1",
|
||||
new BlueMapResponseModifier(mapRequestHandler)
|
||||
@ -223,7 +232,7 @@ private void load(@Nullable ResourcePack preloadedResourcePack) throws IOExcepti
|
||||
|
||||
try {
|
||||
webServer = new HttpServer(new LoggingRequestHandler(
|
||||
routingRequestHandler,
|
||||
webRequestHandler,
|
||||
webserverConfig.getLog().getFormat(),
|
||||
webLogger
|
||||
));
|
||||
@ -361,10 +370,6 @@ public void run() {
|
||||
this.api = new BlueMapAPIImpl(this);
|
||||
this.api.register();
|
||||
|
||||
//save webapp settings again (for api-registered scripts and styles)
|
||||
if (webappConfig.isEnabled())
|
||||
this.getBlueMap().getWebFilesManager().saveSettings();
|
||||
|
||||
//start render-manager
|
||||
if (pluginState.isRenderThreadsEnabled()) {
|
||||
checkPausedByPlayerCount(); // <- this also starts the render-manager if it should start
|
||||
@ -638,38 +643,6 @@ public boolean checkPausedByPlayerCount() {
|
||||
return getBlueMap().getWorlds().get(id);
|
||||
}
|
||||
|
||||
public Server getServerInterface() {
|
||||
return serverInterface;
|
||||
}
|
||||
|
||||
public BlueMapService getBlueMap() {
|
||||
return blueMap;
|
||||
}
|
||||
|
||||
public PluginState getPluginState() {
|
||||
return pluginState;
|
||||
}
|
||||
|
||||
public RenderManager getRenderManager() {
|
||||
return renderManager;
|
||||
}
|
||||
|
||||
public HttpServer getWebServer() {
|
||||
return webServer;
|
||||
}
|
||||
|
||||
public boolean isLoaded() {
|
||||
return loaded;
|
||||
}
|
||||
|
||||
public String getImplementationType() {
|
||||
return implementationType;
|
||||
}
|
||||
|
||||
public PlayerSkinUpdater getSkinUpdater() {
|
||||
return skinUpdater;
|
||||
}
|
||||
|
||||
private void initFileWatcherTasks() {
|
||||
var maps = blueMap.getMaps();
|
||||
if (maps != null) {
|
||||
|
@ -27,6 +27,8 @@
|
||||
import de.bluecolored.bluemap.common.BlueMapConfiguration;
|
||||
import de.bluecolored.bluemap.common.BlueMapService;
|
||||
import de.bluecolored.bluemap.common.MissingResourcesException;
|
||||
import de.bluecolored.bluemap.common.addons.AddonManager;
|
||||
import de.bluecolored.bluemap.common.api.BlueMapAPIImpl;
|
||||
import de.bluecolored.bluemap.common.config.BlueMapConfigManager;
|
||||
import de.bluecolored.bluemap.common.config.ConfigurationException;
|
||||
import de.bluecolored.bluemap.common.config.CoreConfig;
|
||||
@ -113,6 +115,10 @@ public void renderMaps(BlueMapService blueMap, boolean watch, boolean forceRende
|
||||
totalRegions += updateTask.getRegions().size();
|
||||
}
|
||||
|
||||
// enable api
|
||||
BlueMapAPIImpl api = new BlueMapAPIImpl(blueMap, null);
|
||||
api.register();
|
||||
|
||||
Logger.global.logInfo("Start updating " + maps.size() + " maps (" + totalRegions + " regions, ~" + totalRegions * 1024L + " chunks)...");
|
||||
|
||||
// start rendering
|
||||
@ -150,6 +156,8 @@ public void run() {
|
||||
|
||||
Runnable shutdown = () -> {
|
||||
Logger.global.logInfo("Stopping...");
|
||||
api.unregister();
|
||||
|
||||
updateInfoTask.cancel();
|
||||
saveTask.cancel();
|
||||
|
||||
@ -301,6 +309,12 @@ public static void main(String[] args) {
|
||||
cli.minecraftVersion = cmd.getOptionValue("v");
|
||||
}
|
||||
|
||||
// load addons
|
||||
Path addonsFolder = cli.configFolder.resolve("addons");
|
||||
Files.createDirectories(addonsFolder);
|
||||
AddonManager.tryLoadAddons(cli.configFolder.resolve("addons"), true);
|
||||
|
||||
// load configs
|
||||
BlueMapConfigManager configs = BlueMapConfigManager.builder()
|
||||
.minecraftVersion(cli.minecraftVersion)
|
||||
.configRoot(cli.configFolder)
|
||||
|
Loading…
Reference in New Issue
Block a user