fix(furnidata): prefer renderer config source

Resolve furnidata from the renderer config and asset base before falling back to the legacy items.furnidata.path override. This keeps the emulator aligned with the same furnidata URL the UI/renderer already consume.

Keep the legacy path as a compatibility fallback for older installs, but stop exposing absolute furnidata file paths in the startup log. The provider now reports a compact manager-style source label instead.

Add coverage proving renderer-config furnidata.url wins over the legacy path when both are present.
This commit is contained in:
simoleo89
2026-06-13 17:08:17 +02:00
parent fdcd3a7323
commit aec61064ae
3 changed files with 57 additions and 18 deletions
@@ -36,30 +36,36 @@ public final class FurnidataSourceResolver {
public static Source resolve() { public static Source resolve() {
try { try {
String override = Emulator.getConfig().getValue("items.furnidata.path", ""); String override = Emulator.getConfig().getValue("items.furnidata.path", "");
if (!override.isEmpty()) {
Path p = Paths.get(override);
if (Files.exists(p)) return new Source(p, Files.isDirectory(p), Status.RESOLVED, "items.furnidata.path");
return new Source(p, Files.isDirectory(p), Status.SOURCE_MISSING, "items.furnidata.path does not exist");
}
String rendererConfigPath = Emulator.getConfig().getValue("furni.editor.renderer.config.path", ""); String rendererConfigPath = Emulator.getConfig().getValue("furni.editor.renderer.config.path", "");
String assetBasePath = Emulator.getConfig().getValue("furni.editor.asset.base.path", ""); String assetBasePath = Emulator.getConfig().getValue("furni.editor.asset.base.path", "");
if (!rendererConfigPath.isEmpty()) { return resolveConfigured(override, rendererConfigPath, assetBasePath);
Source fromRenderer = resolveFromRendererConfig(Paths.get(rendererConfigPath), assetBasePath.isEmpty() ? null : Paths.get(assetBasePath));
if (fromRenderer.ok() || fromRenderer.status() == Status.UNRESOLVED_PLACEHOLDER) return fromRenderer;
}
Source fallback = resolveFromAssetBase(assetBasePath);
if (fallback != null) return fallback;
return new Source(null, false, Status.CONFIG_MISSING, "No furnidata source config found");
} catch (Exception e) { } catch (Exception e) {
LOGGER.warn("FurnidataSourceResolver failed", e); LOGGER.warn("FurnidataSourceResolver failed", e);
return new Source(null, false, Status.ERROR, e.getMessage() != null ? e.getMessage() : "Resolver error"); return new Source(null, false, Status.ERROR, e.getMessage() != null ? e.getMessage() : "Resolver error");
} }
} }
public static Source resolveConfigured(String legacyOverridePath, String rendererConfigPath, String assetBasePath) {
if (rendererConfigPath != null && !rendererConfigPath.isEmpty()) {
Source fromRenderer = resolveFromRendererConfig(Paths.get(rendererConfigPath), assetBasePath == null || assetBasePath.isEmpty() ? null : Paths.get(assetBasePath));
if (fromRenderer.ok() || fromRenderer.status() == Status.UNRESOLVED_PLACEHOLDER) return fromRenderer;
}
Source fromAssetBase = resolveFromAssetBase(assetBasePath);
if (fromAssetBase != null && fromAssetBase.ok()) return fromAssetBase;
if (legacyOverridePath != null && !legacyOverridePath.isEmpty()) {
Path p = Paths.get(legacyOverridePath);
if (Files.exists(p)) return new Source(p, Files.isDirectory(p), Status.RESOLVED, "items.furnidata.path fallback");
return new Source(p, Files.isDirectory(p), Status.SOURCE_MISSING, "items.furnidata.path fallback does not exist");
}
if (fromAssetBase != null) return fromAssetBase;
return new Source(null, false, Status.CONFIG_MISSING, "No furnidata source config found");
}
public static Source resolveFromRendererConfig(Path rendererConfig, Path assetBase) { public static Source resolveFromRendererConfig(Path rendererConfig, Path assetBase) {
try { try {
if (rendererConfig == null || !Files.exists(rendererConfig)) { if (rendererConfig == null || !Files.exists(rendererConfig)) {
@@ -27,6 +27,7 @@ public class FurnitureTextProvider {
private final boolean enabled; private final boolean enabled;
private volatile Map<String, FurniText> index = Map.of(); private volatile Map<String, FurniText> index = Map.of();
private volatile Path source; private volatile Path source;
private volatile String sourceDescription = "unknown";
private FurnidataWatcher watcher; private FurnidataWatcher watcher;
public FurnitureTextProvider(boolean enabled) { public FurnitureTextProvider(boolean enabled) {
@@ -47,7 +48,7 @@ public class FurnitureTextProvider {
return; return;
} }
reindex(new FurnidataReader(this.source, DEFAULT_MAX_BYTES).read()); reindex(new FurnidataReader(this.source, DEFAULT_MAX_BYTES).read());
LOGGER.info("FurnitureTextProvider: indexed {} furnidata names from {}", this.index.size(), this.source); LOGGER.info("Furniture Text Provider -> Indexed! ({} names, source: {})", this.index.size(), this.sourceDescription);
if (Boolean.parseBoolean(Emulator.getConfig().getValue("items.furnidata.watch.enabled", "true"))) { if (Boolean.parseBoolean(Emulator.getConfig().getValue("items.furnidata.watch.enabled", "true"))) {
if (this.watcher != null) this.watcher.stop(); if (this.watcher != null) this.watcher.stop();
@@ -88,9 +89,12 @@ public class FurnitureTextProvider {
} }
} }
private static Path resolveSource() { private Path resolveSource() {
FurnidataSourceResolver.Source source = FurnidataSourceResolver.resolve(); FurnidataSourceResolver.Source source = FurnidataSourceResolver.resolve();
if (source.ok()) return source.path(); if (source.ok()) {
this.sourceDescription = source.message();
return source.path();
}
LOGGER.warn("FurnitureTextProvider: no furnidata source resolved ({}) - {}", source.status(), source.message()); LOGGER.warn("FurnitureTextProvider: no furnidata source resolved ({}) - {}", source.status(), source.message());
return null; return null;
} }
@@ -78,4 +78,33 @@ class FurniDataManagerTest {
assertEquals(assetBase.resolve("gamedata").resolve("FurnitureData.json"), source.path()); assertEquals(assetBase.resolve("gamedata").resolve("FurnitureData.json"), source.path());
assertFalse(source.directory()); assertFalse(source.directory());
} }
@Test
void prefersRendererConfigOverLegacyFurnidataPath(@TempDir Path dir) throws Exception {
Path legacy = dir.resolve("legacy").resolve("FurnitureData.json");
Files.createDirectories(legacy.getParent());
Files.writeString(legacy, "{}");
Path assetBase = dir.resolve("nitro-assets");
Path rendererSource = assetBase.resolve("gamedata").resolve("FurnitureData.json");
Files.createDirectories(rendererSource.getParent());
Files.writeString(rendererSource, "{}");
Path rendererConfig = dir.resolve("renderer-config.json");
Files.writeString(rendererConfig, """
{
"gamedata.url": "http://localhost:5173/nitro-assets/gamedata",
"furnidata.url": "${gamedata.url}/FurnitureData.json?t=%timestamp%"
}
""");
FurnidataSourceResolver.Source source = FurnidataSourceResolver.resolveConfigured(
legacy.toString(),
rendererConfig.toString(),
assetBase.toString());
assertTrue(source.ok());
assertEquals(rendererSource, source.path());
assertEquals("renderer-config furnidata.url", source.message());
}
} }