File size: 5,485 Bytes
1c4658f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
using System.Diagnostics;
using FastSeek.Core;

namespace FastSeek.Cli;

internal static class Program
{
    private static void Main()
    {
        Console.WriteLine("FastSeek - starting...");
        using var engine = new FastSeekEngine();
        engine.InitializeAsync(msg => Console.WriteLine($"[startup] {msg}")).GetAwaiter().GetResult();

        var configPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "fastsearch", "config.txt");
        Directory.CreateDirectory(Path.GetDirectoryName(configPath)!);
        engine.ExcludedDirs.AddRange(LoadExclusions(configPath));

        var resultLimit = 200;

        Console.WriteLine("FastSeek - File Search");
        PrintMetrics(engine);
        Console.WriteLine("Commands: case, count, metrics, options, limit <n>, clearcache, exclusions, exclude <path>, unexclude <path>, quit");

        while (true)
        {
            Console.Write("search> ");
            var input = Console.ReadLine()?.Trim() ?? string.Empty;
            if (string.IsNullOrEmpty(input)) continue;
            if (input is "quit" or "exit" or "q") break;
            if (input == "case") { engine.CaseSensitive = !engine.CaseSensitive; Console.WriteLine(engine.CaseSensitive ? "case sensitivity: ON" : "case sensitivity: OFF"); continue; }
            if (input == "count") { Console.WriteLine($"{engine.Count():N0} files in index"); continue; }
            if (input == "metrics") { PrintMetrics(engine); continue; }
            if (input == "options") { Console.WriteLine($"case={(engine.CaseSensitive ? "on" : "off")}, limit={resultLimit}, exclusions={engine.ExcludedDirs.Count}, cache={engine.CachePath}"); continue; }
            if (input == "clearcache")
            {
                try
                {
                    if (File.Exists(engine.CachePath)) File.Delete(engine.CachePath);
                    Console.WriteLine($"cache cleared: {engine.CachePath}");
                    Console.WriteLine("restart FastSeek to force full rescan");
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"failed to clear cache: {ex.Message}");
                }
                continue;
            }
            if (input.StartsWith("limit ", StringComparison.OrdinalIgnoreCase))
            {
                if (int.TryParse(input[6..].Trim(), out var parsed) && parsed > 0 && parsed <= 5000)
                {
                    resultLimit = parsed;
                    Console.WriteLine($"result limit set to {resultLimit}");
                }
                else Console.WriteLine("usage: limit <1..5000>");
                continue;
            }
            if (input.StartsWith("exclude ", StringComparison.OrdinalIgnoreCase)) { var p = NormalizeExclude(input[8..]); if (!engine.ExcludedDirs.Contains(p)) engine.ExcludedDirs.Add(p); SaveExclusions(configPath, engine.ExcludedDirs); Console.WriteLine($"excluded: {p}"); continue; }
            if (input.StartsWith("unexclude ", StringComparison.OrdinalIgnoreCase)) { var p = NormalizeExclude(input[10..]); engine.ExcludedDirs.RemoveAll(x => x == p); SaveExclusions(configPath, engine.ExcludedDirs); Console.WriteLine($"removed: {p}"); continue; }
            if (input == "exclusions") { Console.WriteLine(engine.ExcludedDirs.Count == 0 ? "no excluded directories" : string.Join(Environment.NewLine, engine.ExcludedDirs.Select(x => $"- {x}"))); continue; }

            var sw = Stopwatch.StartNew();
            var results = engine.Search(input, resultLimit);
            sw.Stop();

            if (results.Count == 0) Console.WriteLine($"no results for \"{input}\"");
            else
            {
                for (var i = 0; i < results.Count; i++) Console.WriteLine($"[{i + 1,4}] [{(results[i].IsDir ? "DIR " : "FILE")}] {results[i].FullPath}");
                Console.WriteLine($"\nResults: {results.Count:N0}   Time: {sw.Elapsed.TotalMilliseconds:F2} ms   Limit: {resultLimit:N0}\n");
            }
        }
    }

    private static void PrintMetrics(FastSeekEngine engine)
    {
        var m = engine.LastStartupMetrics;
        if (m is null) return;

        Console.WriteLine();
        Console.WriteLine("Startup Metrics");
        Console.WriteLine("---------------");
        Console.WriteLine($"Cache        : {(m.CacheLoaded ? "HIT" : "MISS")}");
        Console.WriteLine($"Total Files  : {m.TotalFiles:N0}");
        Console.WriteLine($"Drives       : {m.DriveDiscoveryMs,10:F2} ms");
        Console.WriteLine($"Cache Load   : {m.CacheLoadMs,10:F2} ms");
        Console.WriteLine($"Scan         : {m.ScanMs,10:F2} ms");
        Console.WriteLine($"Index        : {m.IndexMs,10:F2} ms");
        Console.WriteLine($"Cache Save   : {m.CacheSaveMs,10:F2} ms");
        Console.WriteLine($"Startup Total: {m.TotalStartupMs,10:F2} ms");
        Console.WriteLine($"Cache Path   : {engine.CachePath}");
        Console.WriteLine();
    }

    private static List<string> LoadExclusions(string path) => File.Exists(path) ? File.ReadAllLines(path).Select(l => l.Trim().ToLowerInvariant()).Where(l => l.Length > 0).ToList() : [];
    private static void SaveExclusions(string path, List<string> dirs) => File.WriteAllText(path, string.Join(Environment.NewLine, dirs));
    private static string NormalizeExclude(string path)
    {
        var p = path.Trim().ToLowerInvariant();
        return p.EndsWith("\\") || p.EndsWith("/") ? p : p + "\\";
    }
}