namespace IFSRenderer.Shared;
/// <summary>
/// Locates the plugin library and transform library at runtime.
/// </summary>
/// <remarks>
/// <para>
/// These assets live in the source tree under <c>WpfDisplay/Library</c> and
/// <c>IFSTransforms/Transforms</c>, but a packaged build has neither of those
/// directories - it has a flat <c>Library/</c> and <c>Transforms/</c> beside the
/// executable. Resolution therefore tries the installed layout FIRST and only then
/// falls back to walking up for the solution file, so a developer running from
/// <c>bin/</c> and a user running an unpacked release both work.
/// </para>
/// <para>
/// Deliberately does not throw when nothing is found: the caller reports a missing
/// library far more usefully than a static constructor can.
/// </para>
/// </remarks>
public static class AssetPaths
{
private const string SolutionFile = "IFSRenderer.slnx";
/// <summary>Directory holding Includes/, PostEffects/, Templates/, Palettes/ and the preset json.</summary>
public static string Library { get; } = Resolve("Library", Path.Combine("WpfDisplay", "Library"));
/// <summary>Directory holding the *.ifstf transform plugins.</summary>
/// <remarks>
/// In the source tree these are vendored at <c>Transforms/</c> (the release pipeline
/// builds from a git archive export, which omits submodule contents); the
/// <c>IFSTransforms/</c> submodule is kept as a fallback for upstream syncing.
/// </remarks>
public static string Transforms { get; } = Resolve("Transforms", "Transforms",
Path.Combine("IFSTransforms", "Transforms"));
/// <summary>True when both asset directories were found.</summary>
public static bool Found => Directory.Exists(Library) && Directory.Exists(Transforms);
/// <summary>Human-readable resolution result, for startup diagnostics.</summary>
public static string Describe() =>
$"Library: {Library} ({(Directory.Exists(Library) ? "ok" : "MISSING")}), " +
$"Transforms: {Transforms} ({(Directory.Exists(Transforms) ? "ok" : "MISSING")})";
private static string Resolve(string installedName, params string[] sourceRelativePaths)
{
// 1. Packaged / published layout: beside the executable.
string beside = Path.Combine(AppContext.BaseDirectory, installedName);
if (Directory.Exists(beside))
return beside;
// 2. Development layout: walk up to the solution file.
var dir = new DirectoryInfo(AppContext.BaseDirectory);
while (dir is not null && !File.Exists(Path.Combine(dir.FullName, SolutionFile)))
dir = dir.Parent;
if (dir is not null)
{
foreach (string candidate in sourceRelativePaths)
{
string inSource = Path.Combine(dir.FullName, candidate);
if (Directory.Exists(inSource))
return inSource;
}
}
// 3. Nothing found - return the packaged path so the error names the place
// a user would actually look.
return beside;
}
}