Implement native addon loader

This commit is contained in:
Lukas Rieger (Blue) 2024-05-27 23:10:20 +02:00
parent 01b1ac513c
commit 6ad50a89cb
No known key found for this signature in database
GPG Key ID: AA33883B1BBA03E6
11 changed files with 364 additions and 112 deletions

View File

@ -0,0 +1,6 @@
package de.bluecolored.bluemap.common.addons;
import lombok.experimental.StandardException;
@StandardException
public class AddonException extends Exception {}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -0,0 +1,7 @@
package de.bluecolored.bluemap.common.addons;
public record LoadedAddon (
AddonInfo addonInfo,
ClassLoader classLoader,
Object instance
) {}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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)