using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Windows; using System.Windows.Interop; using FastSeekWpf.Core; using FastSeekWpf.NativeInterop; namespace FastSeekWpf; public partial class App : Application { private const string MutexName = "FastSeekWpf_SingleInstance_Mutex"; private const uint WM_TOGGLE_WINDOW = Win32Api.WM_USER + 3; private Mutex? _mutex; private bool _ownsMutex; private volatile bool _shutdown; private MainWindow? _mainWindow; private Thread? _hotkeyThread; [DllImport("kernel32.dll", SetLastError = true)] private static extern bool AttachConsole(int dwProcessId); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool AllocConsole(); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool FreeConsole(); private const int ATTACH_PARENT_PROCESS = -1; private void OnStartup(object sender, StartupEventArgs e) { if (e.Args.Contains("--cli")) { RunCliMode(); Shutdown(); return; } if (e.Args.Contains("--clear-cache")) { RunClearCache(); Shutdown(); return; } Logger.Log("=== App startup ==="); _mutex = new Mutex(true, MutexName, out bool createdNew); if (!createdNew) { IntPtr hwnd = Win32Api.FindWindowW(null, "FastSeek"); if (hwnd != IntPtr.Zero) Win32Api.PostMessageW(hwnd, WM_TOGGLE_WINDOW, IntPtr.Zero, IntPtr.Zero); Shutdown(); return; } _ownsMutex = true; _mainWindow = new MainWindow(); _mainWindow.Show(); _hotkeyThread = new Thread(HotkeyLoop) { IsBackground = true, Name = "HotkeyThread" }; var helper = new WindowInteropHelper(_mainWindow); _hotkeyThread.Start(helper.Handle); } private static void EnsureConsole() { if (!AttachConsole(ATTACH_PARENT_PROCESS)) AllocConsole(); Console.OutputEncoding = Encoding.UTF8; var stdout = Console.OpenStandardOutput(); var writer = new StreamWriter(stdout, Encoding.UTF8) { AutoFlush = true }; Console.SetOut(writer); Console.SetError(writer); } private static void RunClearCache() { EnsureConsole(); var cachePath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "FastSeek", "cache.dat"); if (File.Exists(cachePath)) { var size = new FileInfo(cachePath).Length; File.Delete(cachePath); Console.WriteLine($"Cache cleared ({size:N0} bytes)"); } else { Console.WriteLine("No cache found."); } Console.WriteLine("Press Enter..."); Console.ReadLine(); FreeConsole(); } private static void RunCliMode() { EnsureConsole(); Console.Clear(); PrintHeader("FastSeek CLI"); Console.WriteLine(); bool elevated = Elevation.IsElevated(); if (elevated) PrintLine(ConsoleColor.Green, "Elevation", "Administrator ✓"); else { PrintLine(ConsoleColor.Red, "Elevation", $"{Elevation.StatusText()} ✗"); PrintError("NOT RUNNING AS ADMINISTRATOR — MFT read will fail."); PrintInfo("To fix: Build the project, then run the compiled .exe:"); PrintDetail(" dotnet publish -c Release -r win-x64"); PrintDetail(" .\\bin\\Release\\net8.0-windows\\win-x64\\FastSeekWpf.exe --cli"); Console.WriteLine(); } var drives = DriveDiscovery.GetNtfsDrives(); PrintLine(ConsoleColor.Cyan, "Drives", $"{drives.Count} NTFS found"); foreach (var d in drives) PrintDetail($" {d.Letter}: {d.Root}"); Console.WriteLine(); if (drives.Count == 0) { PrintError("No NTFS drives found."); if (!elevated) PrintInfo("This is likely because you are not elevated. Try running as Administrator."); WaitExit(); return; } if (!elevated) PrintInfo("Attempting scan anyway (will likely fail without elevation)..."); PrintLine(ConsoleColor.Cyan, "Scan", "Reading MFT..."); var index = new IndexStore(); long totalRecords = 0; var sw = Stopwatch.StartNew(); foreach (var drive in drives) { try { using var reader = new MftReader(drive); var (scan, method) = reader.ScanAny(); if (scan.Records.Count > 0) { index.PopulateFromScan(scan, drive.Root); totalRecords += scan.Records.Count; PrintLine(ConsoleColor.Green, $" {drive.Letter}", $"{scan.Records.Count:N0} records ({method})"); } else { PrintError($" {drive.Letter}: 0 records"); if (!elevated) PrintInfo(" ↑ Expected when not running as Administrator."); } } catch (Exception ex) { PrintError($" {drive.Letter}: {ex.Message}"); } } index.Finalize(); sw.Stop(); PrintLine(ConsoleColor.White, "Index", $"{index.Count:N0} entries | {sw.ElapsedMilliseconds}ms | {totalRecords / Math.Max(sw.ElapsedMilliseconds, 1):N0} rec/ms"); Console.WriteLine(); if (index.Count > 0) { var cacheSw = Stopwatch.StartNew(); CacheManager.SaveCache(index); cacheSw.Stop(); var cachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "FastSeek", "cache.dat"); long cacheSize = File.Exists(cachePath) ? new FileInfo(cachePath).Length : 0; PrintLine(ConsoleColor.Cyan, "Cache", $"saved {FormatBytes(cacheSize)} in {cacheSw.ElapsedMilliseconds}ms"); } else PrintLine(ConsoleColor.DarkGray, "Cache", "skipped (empty index)"); Console.WriteLine(); if (index.Count > 0) { PrintLine(ConsoleColor.Cyan, "Benchmark", "1,000 random queries..."); var benchSw = Stopwatch.StartNew(); var rnd = new Random(42); var exclusions = CacheManager.LoadExclusions(); long benchResults = 0; for (int i = 0; i < 1000; i++) { if (index.Entries.Count == 0) break; var entry = index.Entries[rnd.Next(index.Entries.Count)]; var name = index.Name(entry); if (name.Length < 2) continue; int start = rnd.Next(name.Length - 1); int len = rnd.Next(1, Math.Min(name.Length - start, 6)); var q = name.Substring(start, len).ToLowerInvariant(); var r = SearchEngine.Search(index, q, 50, false, exclusions); benchResults += r.Count; } benchSw.Stop(); double qps = 1000.0 / Math.Max(benchSw.ElapsedMilliseconds, 1) * 1000; PrintLine(ConsoleColor.White, "Result", $"{benchSw.ElapsedMilliseconds}ms total | {qps:N0} qps | {benchResults / 1000.0:F1} avg results"); } else PrintLine(ConsoleColor.DarkGray, "Benchmark", "skipped (empty index)"); Console.WriteLine(); PrintDivider(); Console.WriteLine(); if (index.Count == 0) { PrintError("INDEX IS EMPTY — GUI will show no results."); if (!elevated) { PrintInfo("ROOT CAUSE: You must run the COMPILED .exe as Administrator."); PrintInfo("'dotnet run' does NOT trigger UAC elevation."); PrintInfo("Steps:"); PrintDetail(" 1. dotnet publish -c Release -r win-x64"); PrintDetail(" 2. .\\bin\\Release\\net8.0-windows\\win-x64\\FastSeekWpf.exe --cli"); PrintDetail(" 3. Accept the UAC prompt"); } else { PrintInfo("Possible causes:"); PrintDetail(" • MFT direct read blocked by antivirus/EDR"); PrintDetail(" • Volume is ReFS/BitLocker/VM shared folder"); PrintDetail(" • chkdsk / Windows Update holding volume lock"); } Console.WriteLine(); } if (index.Count > 0) { PrintHeader("Interactive Search (type 'exit' to quit)"); Console.WriteLine(); while (true) { Console.ForegroundColor = ConsoleColor.DarkGray; Console.Write("> "); Console.ResetColor(); var query = Console.ReadLine()?.Trim(); if (string.IsNullOrEmpty(query)) continue; if (query.Equals("exit", StringComparison.OrdinalIgnoreCase)) break; var searchSw = Stopwatch.StartNew(); var exclusions = CacheManager.LoadExclusions(); var results = SearchEngine.Search(index, query, 20, false, exclusions); searchSw.Stop(); Console.ForegroundColor = ConsoleColor.DarkGray; Console.WriteLine($" {results.Count} results | {searchSw.Elapsed.TotalMilliseconds:F3}ms"); Console.ResetColor(); foreach (var r in results.Take(10)) { var color = r.Kind switch { ResultKind.App => ConsoleColor.Green, ResultKind.Document => ConsoleColor.Yellow, ResultKind.Image => ConsoleColor.Magenta, ResultKind.Video => ConsoleColor.Red, ResultKind.Audio => ConsoleColor.Blue, ResultKind.Archive => ConsoleColor.DarkYellow, ResultKind.Folder => ConsoleColor.Cyan, _ => ConsoleColor.Gray }; Console.ForegroundColor = color; Console.Write($" [{GetBadgeLabel(r.Kind),-4}] "); Console.ResetColor(); Console.WriteLine($"{r.Name} → {r.FullPath}"); } if (results.Count > 10) { Console.ForegroundColor = ConsoleColor.DarkGray; Console.WriteLine($" ... and {results.Count - 10} more"); Console.ResetColor(); } Console.WriteLine(); } } WaitExit(); } private static void WaitExit() { Console.WriteLine(); Console.ForegroundColor = ConsoleColor.DarkGray; Console.WriteLine("Press Enter to exit..."); Console.ResetColor(); Console.ReadLine(); FreeConsole(); } private static void PrintHeader(string text) { Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine($"━━━ {text} ━━━"); Console.ResetColor(); } private static void PrintDivider() { Console.ForegroundColor = ConsoleColor.DarkGray; Console.WriteLine(new string('─', 60)); Console.ResetColor(); } private static void PrintLine(ConsoleColor color, string label, string value) { Console.ForegroundColor = ConsoleColor.DarkGray; Console.Write($"{label,-10}: "); Console.ForegroundColor = color; Console.WriteLine(value); Console.ResetColor(); } private static void PrintDetail(string text) { Console.ForegroundColor = ConsoleColor.DarkGray; Console.WriteLine(text); Console.ResetColor(); } private static void PrintError(string text) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"✗ {text}"); Console.ResetColor(); } private static void PrintInfo(string text) { Console.ForegroundColor = ConsoleColor.DarkCyan; Console.WriteLine($"ℹ {text}"); Console.ResetColor(); } private static string FormatBytes(long bytes) { if (bytes < 1024) return $"{bytes} B"; if (bytes < 1024 * 1024) return $"{bytes / 1024.0:F1} KB"; if (bytes < 1024 * 1024 * 1024) return $"{bytes / (1024.0 * 1024):F1} MB"; return $"{bytes / (1024.0 * 1024 * 1024):F2} GB"; } private static string GetBadgeLabel(ResultKind kind) => kind switch { ResultKind.App => "APP", ResultKind.Document => "DOC", ResultKind.Image => "IMG", ResultKind.Video => "VID", ResultKind.Audio => "AUD", ResultKind.Archive => "ZIP", ResultKind.Folder => "DIR", _ => "FILE" }; private void OnExit(object sender, ExitEventArgs e) { _shutdown = true; _hotkeyThread?.Join(500); if (_ownsMutex) _mutex?.ReleaseMutex(); _mutex?.Dispose(); Logger.Log("=== App exit ==="); } private void HotkeyLoop(object? param) { IntPtr targetHwnd = (IntPtr)(param ?? IntPtr.Zero); if (targetHwnd == IntPtr.Zero) return; uint modifiers = 0x0002 | 0x0004; if (!Win32Api.RegisterHotKey(IntPtr.Zero, 1, modifiers, 0x20)) { Logger.Log($"RegisterHotKey failed: {Marshal.GetLastWin32Error()}"); return; } try { while (!_shutdown) { int result = Win32Api.GetMessageW(out Win32Api.MSG msg, IntPtr.Zero, 0, 0); if (result == 0 || result == -1) break; if (msg.message == Win32Api.WM_HOTKEY) Win32Api.PostMessageW(targetHwnd, WM_TOGGLE_WINDOW, IntPtr.Zero, IntPtr.Zero); else { Win32Api.TranslateMessage(ref msg); Win32Api.DispatchMessageW(ref msg); } } } finally { Win32Api.UnregisterHotKey(IntPtr.Zero, 1); } } }