File size: 4,796 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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
using FastSeek.Core.Mft;

namespace FastSeek.Core.Index;

public sealed class IndexEntry
{
    public ulong FileRef;
    public ulong ParentRef;
    public required string Name;
    public required string NameLower;
    public byte Flags;
    public bool IsDir => (Flags & 1) != 0;
    public FileKind Kind => IsDir ? FileKind.Directory : FileKind.File;
}

public sealed class CachedEntry
{
    public ulong FileRef { get; set; }
    public ulong ParentRef { get; set; }
    public required string Name { get; set; }
    public FileKind Kind { get; set; }
}

public sealed class CacheData
{
    public List<CachedEntry> Entries { get; set; } = new();
    public string DriveRoot { get; set; } = string.Empty;
    public List<JournalCheckpoint> Checkpoints { get; set; } = new();
}

public sealed class IndexStore
{
    public List<IndexEntry> Entries { get; } = new();
    public List<(ulong fileRef, int idx)> RefLookup { get; } = new();
    public string DriveRoot { get; set; } = string.Empty;
    public List<JournalCheckpoint> Checkpoints { get; set; } = new();

    public string Name(IndexEntry e) => e.Name;
    public string NameLower(IndexEntry e) => e.NameLower;

    public int? LookupIdx(ulong fileRef)
    {
        var lo = 0;
        var hi = RefLookup.Count - 1;
        while (lo <= hi)
        {
            var mid = lo + ((hi - lo) >> 1);
            var cmp = RefLookup[mid].fileRef.CompareTo(fileRef);
            if (cmp == 0) return RefLookup[mid].idx;
            if (cmp < 0) lo = mid + 1; else hi = mid - 1;
        }
        return null;
    }

    internal void PopulateFromScan(ScanResult scan, string driveRoot)
    {
        DriveRoot = driveRoot;
        var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(scan.NameData);
        Entries.Capacity = Math.Max(Entries.Capacity, Entries.Count + scan.Records.Count);
        foreach (var r in scan.Records)
        {
            var name = new string(span.Slice((int)r.NameOff, r.NameLen));
            AddEntry(r.FileRef, r.ParentRef, name, r.IsDir ? FileKind.Directory : FileKind.File);
        }
    }

    public void FinalizeStore()
    {
        Entries.Sort(static (a, b) => string.CompareOrdinal(a.NameLower, b.NameLower));
        RebuildRefLookup();
    }

    public void Insert(FileRecord record) => AddSorted(record.FileRef, record.ParentRef, record.Name, record.Kind);
    public void Remove(ulong fileRef) { Entries.RemoveAll(e => e.FileRef == fileRef); RebuildRefLookup(); }
    public void Rename(ulong oldRef, FileRecord r) { Remove(oldRef); Insert(r); }
    public void ApplyMove(ulong fileRef, ulong newParentRef, string name, FileKind kind) { Remove(fileRef); Insert(new FileRecord { FileRef = fileRef, ParentRef = newParentRef, Name = name, Kind = kind }); }
    public int Len() => Entries.Count;

    public CacheData ToCache() => new()
    {
        DriveRoot = DriveRoot,
        Checkpoints = Checkpoints,
        Entries = Entries.Select(e => new CachedEntry { FileRef = e.FileRef, ParentRef = e.ParentRef, Name = e.Name, Kind = e.Kind }).ToList()
    };

    public static IndexStore FromCache(CacheData cache)
    {
        var s = new IndexStore { DriveRoot = cache.DriveRoot, Checkpoints = cache.Checkpoints };
        s.Entries.Capacity = cache.Entries.Count;
        foreach (var c in cache.Entries) s.AddEntry(c.FileRef, c.ParentRef, c.Name, c.Kind);
        s.RebuildRefLookup();
        return s;
    }

    private void AddSorted(ulong fileRef, ulong parentRef, string name, FileKind kind)
    {
        var lower = name.ToLowerInvariant();
        var pos = Entries.BinarySearch(new IndexEntry { FileRef = fileRef, ParentRef = parentRef, Name = name, NameLower = lower, Flags = kind == FileKind.Directory ? (byte)1 : (byte)0 },
            Comparer<IndexEntry>.Create(static (a, b) => string.CompareOrdinal(a.NameLower, b.NameLower)));
        if (pos < 0) pos = ~pos;
        Entries.Insert(pos, new IndexEntry { FileRef = fileRef, ParentRef = parentRef, Name = name, NameLower = lower, Flags = kind == FileKind.Directory ? (byte)1 : (byte)0 });
        RebuildRefLookup();
    }

    private void AddEntry(ulong fileRef, ulong parentRef, string name, FileKind kind)
    {
        Entries.Add(new IndexEntry
        {
            FileRef = fileRef,
            ParentRef = parentRef,
            Name = name,
            NameLower = name.ToLowerInvariant(),
            Flags = kind == FileKind.Directory ? (byte)1 : (byte)0
        });
    }

    private void RebuildRefLookup()
    {
        RefLookup.Clear();
        RefLookup.Capacity = Math.Max(RefLookup.Capacity, Entries.Count);
        for (var i = 0; i < Entries.Count; i++) RefLookup.Add((Entries[i].FileRef, i));
        RefLookup.Sort(static (a, b) => a.fileRef.CompareTo(b.fileRef));
    }
}