| 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)); |
| } |
| } |
|
|