namespace Explorer.Domain; public static class PathRules { public const string ExtendedPrefix = @"\\?\"; public const string ExtendedUncPrefix = @"\\?\UNC\"; public static string ToExtended(string path) { if (string.IsNullOrWhiteSpace(path)) { return path; } if (path.StartsWith(ExtendedPrefix, StringComparison.Ordinal)) { return path; } if (path.StartsWith(@"\\", StringComparison.Ordinal)) { return ExtendedUncPrefix + path[2..]; } return ExtendedPrefix + path; } public static string FromExtended(string path) { if (path.StartsWith(ExtendedUncPrefix, StringComparison.OrdinalIgnoreCase)) { return @"\\" + path[ExtendedUncPrefix.Length..]; } if (path.StartsWith(ExtendedPrefix, StringComparison.Ordinal)) { return path[ExtendedPrefix.Length..]; } return path; } public static bool IsUnc(string path) { var p = FromExtended(path); return p.StartsWith(@"\\", StringComparison.Ordinal); } public static string NormalizeDirectorySeparators(string path) => path.Replace('/', '\\'); public static string Combine(string root, string relative) { root = FromExtended(root).TrimEnd('\\'); relative = relative.Replace('/', '\\').TrimStart('\\'); if (string.IsNullOrEmpty(relative)) { return EnsureDirectoryTrailingSlashIfRoot(root); } return root + "\\" + relative; } public static string MakeRelative(string root, string fullPath) { var r = FromExtended(root).TrimEnd('\\'); var f = FromExtended(fullPath).TrimEnd('\\'); if (f.Equals(r, StringComparison.OrdinalIgnoreCase)) { return string.Empty; } if (f.StartsWith(r + "\\", StringComparison.OrdinalIgnoreCase)) { return f[(r.Length + 1)..]; } return f; } public static string CanonicalUncRoot(string path) { var p = FromExtended(path).TrimEnd('\\'); if (!p.StartsWith(@"\\", StringComparison.Ordinal)) { return p; } var parts = p[2..].Split('\\', StringSplitOptions.RemoveEmptyEntries); if (parts.Length < 2) { return @"\\" + p[2..].ToLowerInvariant(); } return @"\\" + parts[0].ToLowerInvariant() + "\\" + parts[1]; } public static string Parent(string path) { var p = FromExtended(path).TrimEnd('\\'); if (p.Length >= 2 && p[1] == ':' && p.Length <= 3) { return p.Length == 2 ? p + "\\" : p + (p.EndsWith('\\') ? "" : "\\"); } if (IsUnc(p)) { var root = CanonicalUncRoot(p); if (p.Equals(root, StringComparison.OrdinalIgnoreCase)) { return root; } } var idx = p.LastIndexOf('\\'); if (idx <= 0) { return p; } if (idx == 2 && p[1] == ':') { return p[..3]; } return p[..idx]; } public static bool IsDriveRoot(string path) { var p = FromExtended(path).TrimEnd('\\'); return p.Length == 2 && p[1] == ':'; } public static string EnsureDirectoryTrailingSlashIfRoot(string path) { var p = FromExtended(path); if (p.Length == 2 && p[1] == ':') { return p + "\\"; } return p; } public static string GetFileName(string path) { var p = FromExtended(path).TrimEnd('\\'); var idx = p.LastIndexOf('\\'); return idx < 0 ? p : p[(idx + 1)..]; } public static string RelativeParent(string pathRel) { if (string.IsNullOrEmpty(pathRel)) { return string.Empty; } var n = NormalizeDirectorySeparators(pathRel).TrimEnd('\\'); var idx = n.LastIndexOf('\\'); return idx < 0 ? string.Empty : n[..idx]; } public static string JoinDisplay(string? root, string pathRel) { if (string.IsNullOrWhiteSpace(root)) { return pathRel; } return string.IsNullOrWhiteSpace(pathRel) ? EnsureDirectoryTrailingSlashIfRoot(root) : Combine(root, pathRel); } public static string ShortenDisplay(string path, int maxChars = 64) { var p = FromExtended(path); if (p.Length <= maxChars) { return p; } var unc = IsUnc(p); var parts = p.TrimEnd('\\').Split('\\', StringSplitOptions.RemoveEmptyEntries); if (parts.Length <= 2) { return p; } string prefix; var start = 0; if (unc) { prefix = @"\\" + parts[0] + "\\" + parts[1]; start = 2; } else { prefix = parts.Length > 2 ? parts[0] + "\\" + parts[1] : parts[0]; start = parts.Length > 2 ? 2 : 1; } var leaf = parts[^1]; var parent = parts.Length - 1 > start ? parts[^2] + "\\" + leaf : leaf; var candidate = prefix + @"\…\" + parent; if (candidate.Length <= maxChars) { return candidate; } candidate = prefix + @"\…\" + leaf; return candidate.Length <= maxChars ? candidate : prefix + @"\…"; } }