OverviewFilesCommitsIssuesDocsReleasesPeers

ifsrenderer-linux / Shared / AssetPaths.cs

70 lines · csharp ref:
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;
    }
}