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);
}