Newer
Older
Sunfish / src / Sunfish.Core / Config / ShareSettings.cs
@Ivan Dominguez Ivan Dominguez 21 hours ago 5 KB 3.0
using System.Globalization;

namespace Sunfish.Config;

/// <summary>
/// Cambiar opciones de un share por nombre de clave (las del fichero INI). Lo usan los
/// subcomandos, la TUI y el panel web, para que todos acepten lo mismo.
/// </summary>
public static class ShareSettings
{
    /// <summary>Claves que se pueden cambiar con <see cref="TrySet"/>.</summary>
    public static readonly string[] Keys =
    [
        "Location", "Path", "Index", "Enabled", "Listing", "Upload", "Overwrite", "Rename", "Delete",
        "Zip", "Ftp", "WebDav", "Mac", "ShortNames", "Users",
    ];

    /// <summary>Aplica "clave=valor". Devuelve un mensaje de error o null si fue bien.</summary>
    public static string? TrySet(ShareConfig share, string key, string value)
    {
        value = value.Trim();
        switch (key.ToLowerInvariant())
        {
            case "location":
                if (!Shares.LocationPath.TryNormalize(value, out var location))
                    return Strings.ConfigBadLocation(share.Name, value);
                share.Location = location;
                return null;
            case "path":
                if (value.Length == 0)
                    return Strings.ConfigMissingPath(share.Name);
                share.Path = value;
                return null;
            case "index":
                share.Index = value;
                return null;
            case "users":
                share.Users = value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList();
                return null;
        }
        if (!TryParseBool(value, out bool flag))
            return Strings.ConfigBadBool(key, value);
        switch (key.ToLowerInvariant())
        {
            case "enabled": share.Enabled = flag; break;
            case "listing": share.Listing = flag; break;
            case "upload": share.Upload = flag; break;
            case "overwrite": share.Overwrite = flag; break;
            case "rename": share.Rename = flag; break;
            case "delete": share.Delete = flag; break;
            case "zip": share.Zip = flag; break;
            case "ftp": share.Ftp = flag; break;
            case "webdav": share.WebDav = flag; break;
            case "mac": share.Mac = flag; break;
            case "shortnames": share.ShortNames = flag; break;
            default: return Strings.UnknownShareKey(key);
        }
        return null;
    }

    public static bool TryParseBool(string text, out bool value)
    {
        switch (text.Trim().ToLower(CultureInfo.InvariantCulture))
        {
            case "true": case "yes": case "on": case "1": case "si": case "sí": value = true; return true;
            case "false": case "no": case "off": case "0": value = false; return true;
            default: value = false; return false;
        }
    }

    /// <summary>Location libre para un share nuevo a partir de un nombre: /fotos, /fotos-2...</summary>
    public static string SuggestLocation(SunfishConfig config, string name)
    {
        string baseName = new string(name.Where(c => !char.IsControl(c) && c is not ('/' or '\\' or ':' or '%')).ToArray()).Trim();
        if (baseName.Length == 0 || baseName.StartsWith('$'))
            baseName = "share";
        string candidate = "/" + baseName;
        for (int i = 2; config.Shares.Any(s => s.Location.Equals(candidate, StringComparison.OrdinalIgnoreCase)); i++)
            candidate = $"/{baseName}-{i}";
        return candidate;
    }

    /// <summary>Nombre libre para un share nuevo.</summary>
    public static string SuggestName(SunfishConfig config, string name)
    {
        string baseName = new string(name.Where(c => c > ' ' && c is not ('[' or ']')).ToArray());
        if (baseName.Length == 0)
            baseName = "share";
        string candidate = baseName;
        for (int i = 2; config.Shares.Any(s => s.Name.Equals(candidate, StringComparison.OrdinalIgnoreCase)); i++)
            candidate = $"{baseName}-{i}";
        return candidate;
    }

    /// <summary>
    /// Comprueba un share antes de guardarlo: nombre valido y libre, location no usada por otro
    /// share activo y carpeta existente. Devuelve el problema o null.
    /// </summary>
    public static string? Validate(SunfishConfig config, ShareConfig share, string? originalName)
    {
        if (!IsValidName(share.Name))
            return Strings.ErrorBadName(share.Name);
        var others = config.Shares.Where(x => !string.Equals(x.Name, originalName, StringComparison.OrdinalIgnoreCase)).ToList();
        if (others.Any(x => x.Name.Equals(share.Name, StringComparison.OrdinalIgnoreCase)))
            return Strings.ErrorNameInUse(share.Name);
        if (!Shares.LocationPath.TryNormalize(share.Location, out var location))
            return Strings.ConfigBadLocation(share.Name, share.Location);
        share.Location = location;
        if (share.Enabled && others.Any(x => x.Enabled && x.Location.Equals(location, StringComparison.OrdinalIgnoreCase)))
            return Strings.ErrorNameInUse(location);
        if (!Directory.Exists(share.Path))
            return Strings.ErrorNotAFolder(share.Path);
        return null;
    }

    /// <summary>Un nombre de share o de usuario valido en el INI: sin espacios, comas, ':' ni corchetes.</summary>
    public static bool IsValidName(string name) =>
        name.Length is > 0 and <= 64 && name.All(c => c > ' ' && c != ',' && c != ':' && c != '[' && c != ']' && c != 0x7F);
}