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:
2026-08-22 12:43:05 +02:00
commit e9aba73552
130 changed files with 15110 additions and 0 deletions

View File

@@ -0,0 +1,144 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using Explorer.Domain;
using Explorer.Domain.Abstractions;
using Microsoft.Extensions.Logging;
namespace Explorer.Windows;
public sealed class WindowsShellFileOperations : IShellFileOperations
{
private readonly ILogger<WindowsShellFileOperations> _logger;
public WindowsShellFileOperations(ILogger<WindowsShellFileOperations> logger) => _logger = logger;
public void Open(string path)
{
var psi = new ProcessStartInfo
{
FileName = PathRules.FromExtended(path),
UseShellExecute = true
};
Process.Start(psi);
}
public bool DeleteToRecycleBin(IReadOnlyList<string> paths, out string? error)
{
error = null;
if (paths.Count == 0)
{
return true;
}
var joined = string.Join("\0", paths.Select(PathRules.FromExtended)) + "\0\0";
var op = new NativeMethods.ShFileOpStruct
{
hwnd = 0,
wFunc = NativeMethods.FoDelete,
pFrom = joined,
pTo = null!,
fFlags = (ushort)(NativeMethods.FofAllowUndo | NativeMethods.FofNoConfirmation | NativeMethods.FofNoErrorUi | NativeMethods.FofSilent),
fAnyOperationsAborted = 0,
hNameMappings = 0,
lpszProgressTitle = null
};
var rc = NativeMethods.SHFileOperation(ref op);
if (rc != 0)
{
error = $"Recycle failed ({rc})";
_logger.LogWarning("SHFileOperation delete returned {Code}", rc);
return false;
}
return true;
}
public bool CopyFileWithProgress(string source, string destination, bool overwrite, IProgress<long>? progress, CancellationToken cancellationToken, out string? error)
{
error = null;
Directory.CreateDirectory(Path.GetDirectoryName(PathRules.FromExtended(destination))!);
var cancel = 0;
NativeMethods.CopyProgressRoutine cb = (total, transferred, _, _, _, _, _, _, _) =>
{
progress?.Report(transferred);
return cancellationToken.IsCancellationRequested ? NativeMethods.ProgressCancel : NativeMethods.ProgressContinue;
};
var flags = overwrite ? 0u : NativeMethods.CopyFileFailIfExists;
var ok = NativeMethods.CopyFileEx(
PathRules.ToExtended(source),
PathRules.ToExtended(destination),
cb,
0,
ref cancel,
flags);
if (!ok)
{
var code = Marshal.GetLastWin32Error();
if (cancellationToken.IsCancellationRequested || code == 1235)
{
error = "Cancelled";
return false;
}
error = new System.ComponentModel.Win32Exception(code).Message;
return false;
}
return true;
}
public bool MoveFileWithProgress(string source, string destination, bool overwrite, IProgress<long>? progress, CancellationToken cancellationToken, out string? error)
{
error = null;
Directory.CreateDirectory(Path.GetDirectoryName(PathRules.FromExtended(destination))!);
NativeMethods.CopyProgressRoutine cb = (total, transferred, _, _, _, _, _, _, _) =>
{
progress?.Report(transferred);
return cancellationToken.IsCancellationRequested ? NativeMethods.ProgressCancel : NativeMethods.ProgressContinue;
};
var flags = NativeMethods.MoveFileCopyAllowed | NativeMethods.MoveFileWriteThrough;
if (overwrite)
{
flags |= NativeMethods.MoveFileReplaceExisting;
}
var ok = NativeMethods.MoveFileWithProgress(
PathRules.ToExtended(source),
PathRules.ToExtended(destination),
cb,
0,
flags);
if (!ok)
{
var code = Marshal.GetLastWin32Error();
if (cancellationToken.IsCancellationRequested)
{
error = "Cancelled";
return false;
}
error = new System.ComponentModel.Win32Exception(code).Message;
return false;
}
return true;
}
}
public static class BackgroundIo
{
public static IDisposable Begin()
{
NativeMethods.SetPriorityClass(NativeMethods.GetCurrentProcess(), NativeMethods.ProcessModeBackgroundBegin);
return new Reset();
}
private sealed class Reset : IDisposable
{
public void Dispose()
=> NativeMethods.SetPriorityClass(NativeMethods.GetCurrentProcess(), NativeMethods.ProcessModeBackgroundEnd);
}
}