finder-wpf / FastSeekWpf /Core /IndexStore.cs
anshdadhich's picture
Fix IndexStore: rename Finalize() to CompleteIndex() to fix CS0465 collision with Object.Finalize
daf680a verified
raw
history blame
10.9 kB
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FastSeekWpf.Core;
[Serializable]
public class CachedEntry
{
public ulong FileRef { get; set; }
public ulong ParentRef { get; set; }
public string Name { get; set; } = string.Empty;
public FileKind Kind { get; set; }
public byte DriveIdx { get; set; }
}
[Serializable]
public class CacheData
{
public List<CachedEntry> Entries { get; set; } = new();
public List<string> DriveRoots { get; set; } = new();
public List<JournalCheckpoint> Checkpoints { get; set; } = new();
}
public struct IndexEntry
{
public ulong FileRef;
public ulong ParentRef;
public uint NameOff;
public uint NameLowerOff;
public ushort NameLen;
public ushort NameLowerLen;
public byte Flags; // bit 0 = is_dir
public byte DriveIdx; // index into IndexStore.DriveRoots
public readonly bool IsDir => (Flags & 1) != 0;
public readonly FileKind Kind => IsDir ? FileKind.Directory : FileKind.File;
}
public class IndexStore
{
public List<IndexEntry> Entries = new();
public List<byte> NameArena = new();
public List<byte> NameLowerArena = new();
public List<string> NameCache = new();
public List<string> NameLowerCache = new();
public List<(ulong fileRef, int idx)> RefLookup = new();
public List<string> DriveRoots = new();
public List<JournalCheckpoint> Checkpoints = new();
public int Count => Entries.Count;
public string NameAt(int i) => NameCache[i];
public string NameLowerAt(int i) => NameLowerCache[i];
public string DriveRootAt(int i) => DriveRoots[Entries[i].DriveIdx];
public uint? LookupIdx(ulong fileRef)
{
int lo = 0, hi = RefLookup.Count - 1;
while (lo <= hi)
{
int mid = (lo + hi) >>> 1;
var r = RefLookup[mid].fileRef;
if (r == fileRef) return (uint)RefLookup[mid].idx;
if (r < fileRef) lo = mid + 1;
else hi = mid - 1;
}
return null;
}
private void RebuildRefLookup()
{
RefLookup.Clear();
RefLookup.Capacity = Entries.Count;
for (int i = 0; i < Entries.Count; i++)
RefLookup.Add((Entries[i].FileRef, i));
RefLookup.Sort((a, b) => a.fileRef.CompareTo(b.fileRef));
}
public void PopulateFromScan(ScanResult scan, string driveRoot)
{
byte driveIdx = 0;
int existing = DriveRoots.IndexOf(driveRoot);
if (existing >= 0)
{
driveIdx = (byte)existing;
}
else
{
driveIdx = (byte)DriveRoots.Count;
DriveRoots.Add(driveRoot);
}
int count = scan.Records.Count;
Entries.Capacity = Math.Max(Entries.Capacity, Entries.Count + count);
NameArena.Capacity = Math.Max(NameArena.Capacity, NameArena.Count + count * 30);
NameLowerArena.Capacity = Math.Max(NameLowerArena.Capacity, NameLowerArena.Count + count * 30);
NameCache.Capacity = Math.Max(NameCache.Capacity, NameCache.Count + count);
NameLowerCache.Capacity = Math.Max(NameLowerCache.Capacity, NameLowerCache.Count + count);
foreach (var r in scan.Records)
{
int start = (int)r.NameOff;
int len = r.NameLen;
var nameChars = new char[len];
for (int i = 0; i < len; i++)
nameChars[i] = scan.NameData[start + i];
string name = new string(nameChars);
string nameLower = name.ToLowerInvariant();
byte[] nameBytes = Encoding.UTF8.GetBytes(name);
byte[] lowerBytes = Encoding.UTF8.GetBytes(nameLower);
uint nOff = (uint)NameArena.Count;
ushort nLen = (ushort)nameBytes.Length;
NameArena.AddRange(nameBytes);
uint nlOff = (uint)NameLowerArena.Count;
ushort nlLen = (ushort)lowerBytes.Length;
NameLowerArena.AddRange(lowerBytes);
Entries.Add(new IndexEntry
{
FileRef = r.FileRef,
ParentRef = r.ParentRef,
NameOff = nOff,
NameLowerOff = nlOff,
NameLen = nLen,
NameLowerLen = nlLen,
Flags = r.IsDir ? (byte)1 : (byte)0,
DriveIdx = driveIdx
});
NameCache.Add(name);
NameLowerCache.Add(nameLower);
}
}
/// <summary>
/// Sort entries by lowercase name and rebuild lookup tables.
/// Matches Rust IndexStore::finalize().
/// </summary>
public void CompleteIndex()
{
var indices = Enumerable.Range(0, Entries.Count).ToArray();
Array.Sort(indices, (a, b) => string.CompareOrdinal(NameLowerCache[a], NameLowerCache[b]));
var sortedEntries = new List<IndexEntry>(Entries.Count);
var sortedNames = new List<string>(Entries.Count);
var sortedLower = new List<string>(Entries.Count);
foreach (var i in indices)
{
sortedEntries.Add(Entries[i]);
sortedNames.Add(NameCache[i]);
sortedLower.Add(NameLowerCache[i]);
}
Entries = sortedEntries;
NameCache = sortedNames;
NameLowerCache = sortedLower;
RebuildRefLookup();
NameArena.TrimExcess();
NameLowerArena.TrimExcess();
}
public CacheData ToCache()
{
return new CacheData
{
Entries = Entries.Select((e, i) => new CachedEntry
{
FileRef = e.FileRef,
ParentRef = e.ParentRef,
Name = NameAt(i),
Kind = e.Kind,
DriveIdx = e.DriveIdx
}).ToList(),
DriveRoots = new List<string>(DriveRoots),
Checkpoints = new List<JournalCheckpoint>(Checkpoints)
};
}
public static IndexStore FromCache(CacheData cache)
{
int count = cache.Entries.Count;
var store = new IndexStore
{
DriveRoots = new List<string>(cache.DriveRoots),
Checkpoints = new List<JournalCheckpoint>(cache.Checkpoints)
};
store.Entries.Capacity = count;
store.NameArena.Capacity = count * 30;
store.NameLowerArena.Capacity = count * 30;
store.NameCache.Capacity = count;
store.NameLowerCache.Capacity = count;
store.RefLookup.Capacity = count;
foreach (var c in cache.Entries)
{
string nameLower = c.Name.ToLowerInvariant();
byte[] nameBytes = Encoding.UTF8.GetBytes(c.Name);
byte[] lowerBytes = Encoding.UTF8.GetBytes(nameLower);
uint nOff = (uint)store.NameArena.Count;
ushort nLen = (ushort)nameBytes.Length;
store.NameArena.AddRange(nameBytes);
uint nlOff = (uint)store.NameLowerArena.Count;
ushort nlLen = (ushort)lowerBytes.Length;
store.NameLowerArena.AddRange(lowerBytes);
store.Entries.Add(new IndexEntry
{
FileRef = c.FileRef,
ParentRef = c.ParentRef,
NameOff = nOff,
NameLowerOff = nlOff,
NameLen = nLen,
NameLowerLen = nlLen,
Flags = c.Kind == FileKind.Directory ? (byte)1 : (byte)0,
DriveIdx = c.DriveIdx
});
store.NameCache.Add(c.Name);
store.NameLowerCache.Add(nameLower);
}
store.RebuildRefLookup();
store.NameArena.TrimExcess();
store.NameLowerArena.TrimExcess();
return store;
}
public void Insert(FileRecord record)
{
string nameLower = record.Name.ToLowerInvariant();
var nameBytes = Encoding.UTF8.GetBytes(record.Name);
var lowerBytes = Encoding.UTF8.GetBytes(nameLower);
uint nOff = (uint)NameArena.Count;
ushort nLen = (ushort)nameBytes.Length;
NameArena.AddRange(nameBytes);
uint nlOff = (uint)NameLowerArena.Count;
ushort nlLen = (ushort)lowerBytes.Length;
NameLowerArena.AddRange(lowerBytes);
byte driveIdx = 0;
if (DriveRoots.Count == 0)
DriveRoots.Add("C:\\");
var entry = new IndexEntry
{
FileRef = record.FileRef,
ParentRef = record.ParentRef,
NameOff = nOff,
NameLowerOff = nlOff,
NameLen = nLen,
NameLowerLen = nlLen,
Flags = record.Kind == FileKind.Directory ? (byte)1 : (byte)0,
DriveIdx = driveIdx
};
int pos = 0;
for (; pos < Entries.Count; pos++)
{
if (string.CompareOrdinal(nameLower, NameLowerAt(pos)) < 0)
break;
}
Entries.Insert(pos, entry);
NameCache.Insert(pos, record.Name);
NameLowerCache.Insert(pos, nameLower);
RebuildRefLookup();
}
public void Remove(ulong fileRef)
{
int idx = -1;
for (int i = 0; i < Entries.Count; i++)
{
if (Entries[i].FileRef == fileRef)
{
idx = i;
break;
}
}
if (idx < 0) return;
Entries.RemoveAt(idx);
NameCache.RemoveAt(idx);
NameLowerCache.RemoveAt(idx);
RebuildRefLookup();
}
public void Rename(ulong oldRef, FileRecord newRecord)
{
Remove(oldRef);
Insert(newRecord);
}
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 string BuildPath(ulong fileRef)
{
string[] components = new string[64];
int compCount = 0;
ulong current = fileRef;
byte driveIdx = 0;
for (int i = 0; i < 64; i++)
{
var idx = LookupIdx(current);
if (idx == null) break;
int eIdx = (int)idx;
components[compCount++] = NameAt(eIdx);
var entry = Entries[eIdx];
driveIdx = entry.DriveIdx;
if (entry.ParentRef == current) break;
current = entry.ParentRef;
}
string driveRoot = driveIdx < DriveRoots.Count ? DriveRoots[driveIdx] : "C:\\";
var sb = new StringBuilder(driveRoot.Length + compCount * 32);
sb.Append(driveRoot);
for (int i = compCount - 1; i >= 0; i--)
{
if (sb.Length > 0 && sb[sb.Length - 1] != '\\' && sb[sb.Length - 1] != '/')
sb.Append('\\');
sb.Append(components[i]);
}
return sb.ToString();
}
}