using Sunfish.Config;
namespace Sunfish.Shares;
/// <summary>Resultado de resolver una ruta contra los shares.</summary>
public sealed class Resolution
{
/// <summary>Share que atiende la ruta (location de prefijo mas largo); null si ninguno.</summary>
public ShareConfig? Share { get; init; }
/// <summary>Componentes de la ruta dentro del share.</summary>
public IReadOnlyList<string> Relative { get; init; } = [];
/// <summary>
/// Locations mas especificas que cuelgan de esta ruta, como carpetas virtuales
/// (nombre del siguiente componente). Tapan a las carpetas reales del mismo nombre.
/// </summary>
public IReadOnlyList<string> VirtualChildren { get; init; } = [];
}
/// <summary>
/// Arbol de locations. Gana el prefijo mas largo, comparando por componentes completos y sin
/// distinguir mayusculas (como las locations de prefijo de nginx).
/// </summary>
public sealed class ShareTree
{
private readonly List<(ShareConfig Share, string[] Segments)> _entries;
public ShareTree(IEnumerable<ShareConfig> shares)
{
_entries = shares
.Where(s => s.Enabled)
.Select(s => (s, LocationPath.Segments(s.Location)))
.OrderByDescending(e => e.Item2.Length)
.ToList();
}
public IEnumerable<ShareConfig> Shares => _entries.Select(e => e.Share);
public Resolution Resolve(IReadOnlyList<string> segments)
{
ShareConfig? share = null;
int matched = 0;
foreach (var (candidate, location) in _entries)
{
if (IsPrefix(location, segments))
{
share = candidate;
matched = location.Length;
break; // ordenadas de mas a menos componentes
}
}
return new Resolution
{
Share = share,
Relative = segments.Skip(matched).ToArray(),
VirtualChildren = ChildrenOf(segments),
};
}
/// <summary>Nombres de las locations que cuelgan directamente o indirectamente de la ruta.</summary>
public IReadOnlyList<string> ChildrenOf(IReadOnlyList<string> segments)
{
var names = new List<string>();
foreach (var (_, location) in _entries)
{
if (location.Length > segments.Count && IsPrefix(segments, location))
{
string child = location[segments.Count];
if (!names.Contains(child, StringComparer.OrdinalIgnoreCase))
names.Add(child);
}
}
names.Sort(StringComparer.OrdinalIgnoreCase);
return names;
}
/// <summary>La location del share, para construir URLs.</summary>
public static string UrlOf(ShareConfig share) => share.Location == "/" ? "/" : share.Location + "/";
private static bool IsPrefix(IReadOnlyList<string> prefix, IReadOnlyList<string> path)
{
if (prefix.Count > path.Count)
return false;
for (int i = 0; i < prefix.Count; i++)
{
if (!string.Equals(prefix[i], path[i], StringComparison.OrdinalIgnoreCase))
return false;
}
return true;
}
}