Add Explorer Workbench with hierarchical, off-UI Storage analysis.
Storage queries run in the background with cancellation and covering indexes so switching views no longer freezes the UI. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
202
src/Explorer.Domain/PathRules.cs
Normal file
202
src/Explorer.Domain/PathRules.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
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 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 + @"\…";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user