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 Entries { get; set; } = new(); public string DriveRoot { get; set; } = string.Empty; public List Checkpoints { get; set; } = new(); } public sealed class IndexStore { public List Entries { get; } = new(); public List<(ulong fileRef, int idx)> RefLookup { get; } = new(); public string DriveRoot { get; set; } = string.Empty; public List 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.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)); } }