Upload FastSeekWpf/Core/IndexStore.cs
Browse files- FastSeekWpf/Core/IndexStore.cs +48 -25
FastSeekWpf/Core/IndexStore.cs
CHANGED
|
@@ -12,13 +12,14 @@ public class CachedEntry
|
|
| 12 |
public ulong ParentRef { get; set; }
|
| 13 |
public string Name { get; set; } = string.Empty;
|
| 14 |
public FileKind Kind { get; set; }
|
|
|
|
| 15 |
}
|
| 16 |
|
| 17 |
[Serializable]
|
| 18 |
public class CacheData
|
| 19 |
{
|
| 20 |
public List<CachedEntry> Entries { get; set; } = new();
|
| 21 |
-
public string
|
| 22 |
public List<JournalCheckpoint> Checkpoints { get; set; } = new();
|
| 23 |
}
|
| 24 |
|
|
@@ -31,6 +32,7 @@ public struct IndexEntry
|
|
| 31 |
public ushort NameLen;
|
| 32 |
public ushort NameLowerLen;
|
| 33 |
public byte Flags;
|
|
|
|
| 34 |
|
| 35 |
public readonly bool IsDir => (Flags & 1) != 0;
|
| 36 |
public readonly FileKind Kind => IsDir ? FileKind.Directory : FileKind.File;
|
|
@@ -41,6 +43,7 @@ public struct IndexEntry
|
|
| 41 |
/// - Arena-backed name storage (List<byte> for O(1) append)
|
| 42 |
/// - Parallel cached lowercase strings for zero-allocation search
|
| 43 |
/// - Sorted ref_lookup for O(log n) parent lookups
|
|
|
|
| 44 |
/// </summary>
|
| 45 |
public class IndexStore
|
| 46 |
{
|
|
@@ -53,7 +56,8 @@ public class IndexStore
|
|
| 53 |
public List<string> NameLowerCache = new();
|
| 54 |
// Sorted by file_ref for binary search
|
| 55 |
public List<(ulong fileRef, int idx)> RefLookup = new();
|
| 56 |
-
|
|
|
|
| 57 |
public List<JournalCheckpoint> Checkpoints = new();
|
| 58 |
|
| 59 |
public int Count => Entries.Count;
|
|
@@ -62,6 +66,7 @@ public class IndexStore
|
|
| 62 |
|
| 63 |
public string NameAt(int i) => NameCache[i];
|
| 64 |
public string NameLowerAt(int i) => NameLowerCache[i];
|
|
|
|
| 65 |
|
| 66 |
public uint? LookupIdx(ulong fileRef)
|
| 67 |
{
|
|
@@ -88,18 +93,25 @@ public class IndexStore
|
|
| 88 |
|
| 89 |
public void PopulateFromScan(ScanResult scan, string driveRoot)
|
| 90 |
{
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
int count = scan.Records.Count;
|
| 93 |
-
Entries.
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
NameLowerArena.Capacity = count * 30;
|
| 99 |
-
NameCache.Clear();
|
| 100 |
-
NameCache.Capacity = count;
|
| 101 |
-
NameLowerCache.Clear();
|
| 102 |
-
NameLowerCache.Capacity = count;
|
| 103 |
|
| 104 |
foreach (var r in scan.Records)
|
| 105 |
{
|
|
@@ -130,7 +142,8 @@ public class IndexStore
|
|
| 130 |
NameLowerOff = nlOff,
|
| 131 |
NameLen = nLen,
|
| 132 |
NameLowerLen = nlLen,
|
| 133 |
-
Flags = r.IsDir ? (byte)1 : (byte)0
|
|
|
|
| 134 |
});
|
| 135 |
|
| 136 |
NameCache.Add(name);
|
|
@@ -140,7 +153,6 @@ public class IndexStore
|
|
| 140 |
|
| 141 |
/// <summary>
|
| 142 |
/// Sort entries by lowercase name and rebuild lookup tables.
|
| 143 |
-
/// (Renamed from Finalize to avoid CS0465 warning — object.Finalize is the destructor.)
|
| 144 |
/// </summary>
|
| 145 |
public void CompleteIndex()
|
| 146 |
{
|
|
@@ -174,9 +186,10 @@ public class IndexStore
|
|
| 174 |
FileRef = e.FileRef,
|
| 175 |
ParentRef = e.ParentRef,
|
| 176 |
Name = NameAt(i),
|
| 177 |
-
Kind = e.Kind
|
|
|
|
| 178 |
}).ToList(),
|
| 179 |
-
|
| 180 |
Checkpoints = new List<JournalCheckpoint>(Checkpoints)
|
| 181 |
};
|
| 182 |
}
|
|
@@ -186,7 +199,7 @@ public class IndexStore
|
|
| 186 |
int count = cache.Entries.Count;
|
| 187 |
var store = new IndexStore
|
| 188 |
{
|
| 189 |
-
|
| 190 |
Checkpoints = new List<JournalCheckpoint>(cache.Checkpoints)
|
| 191 |
};
|
| 192 |
store.Entries.Capacity = count;
|
|
@@ -216,7 +229,8 @@ public class IndexStore
|
|
| 216 |
NameLowerOff = nlOff,
|
| 217 |
NameLen = nLen,
|
| 218 |
NameLowerLen = nlLen,
|
| 219 |
-
Flags = c.Kind == FileKind.Directory ? (byte)1 : (byte)0
|
|
|
|
| 220 |
});
|
| 221 |
|
| 222 |
store.NameCache.Add(c.Name);
|
|
@@ -242,6 +256,11 @@ public class IndexStore
|
|
| 242 |
ushort nlLen = (ushort)lowerBytes.Length;
|
| 243 |
NameLowerArena.AddRange(lowerBytes);
|
| 244 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
var entry = new IndexEntry
|
| 246 |
{
|
| 247 |
FileRef = record.FileRef,
|
|
@@ -250,7 +269,8 @@ public class IndexStore
|
|
| 250 |
NameLowerOff = nlOff,
|
| 251 |
NameLen = nLen,
|
| 252 |
NameLowerLen = nlLen,
|
| 253 |
-
Flags = record.Kind == FileKind.Directory ? (byte)1 : (byte)0
|
|
|
|
| 254 |
};
|
| 255 |
|
| 256 |
// Insert in sorted position by lowercase name
|
|
@@ -307,24 +327,27 @@ public class IndexStore
|
|
| 307 |
|
| 308 |
public string BuildPath(ulong fileRef)
|
| 309 |
{
|
| 310 |
-
// string is a managed type, can't stackalloc it. Use small heap array.
|
| 311 |
string[] components = new string[64];
|
| 312 |
int compCount = 0;
|
| 313 |
ulong current = fileRef;
|
|
|
|
| 314 |
|
| 315 |
for (int i = 0; i < 64; i++)
|
| 316 |
{
|
| 317 |
var idx = LookupIdx(current);
|
| 318 |
if (idx == null) break;
|
| 319 |
|
| 320 |
-
|
| 321 |
-
components[compCount++] = NameAt(
|
|
|
|
|
|
|
| 322 |
if (entry.ParentRef == current) break;
|
| 323 |
current = entry.ParentRef;
|
| 324 |
}
|
| 325 |
|
| 326 |
-
|
| 327 |
-
sb
|
|
|
|
| 328 |
for (int i = compCount - 1; i >= 0; i--)
|
| 329 |
{
|
| 330 |
if (sb.Length > 0 && sb[sb.Length - 1] != '\\' && sb[sb.Length - 1] != '/')
|
|
|
|
| 12 |
public ulong ParentRef { get; set; }
|
| 13 |
public string Name { get; set; } = string.Empty;
|
| 14 |
public FileKind Kind { get; set; }
|
| 15 |
+
public byte DriveIdx { get; set; }
|
| 16 |
}
|
| 17 |
|
| 18 |
[Serializable]
|
| 19 |
public class CacheData
|
| 20 |
{
|
| 21 |
public List<CachedEntry> Entries { get; set; } = new();
|
| 22 |
+
public List<string> DriveRoots { get; set; } = new();
|
| 23 |
public List<JournalCheckpoint> Checkpoints { get; set; } = new();
|
| 24 |
}
|
| 25 |
|
|
|
|
| 32 |
public ushort NameLen;
|
| 33 |
public ushort NameLowerLen;
|
| 34 |
public byte Flags;
|
| 35 |
+
public byte DriveIdx; // index into IndexStore.DriveRoots
|
| 36 |
|
| 37 |
public readonly bool IsDir => (Flags & 1) != 0;
|
| 38 |
public readonly FileKind Kind => IsDir ? FileKind.Directory : FileKind.File;
|
|
|
|
| 43 |
/// - Arena-backed name storage (List<byte> for O(1) append)
|
| 44 |
/// - Parallel cached lowercase strings for zero-allocation search
|
| 45 |
/// - Sorted ref_lookup for O(log n) parent lookups
|
| 46 |
+
/// - Multi-drive support via per-entry DriveIdx
|
| 47 |
/// </summary>
|
| 48 |
public class IndexStore
|
| 49 |
{
|
|
|
|
| 56 |
public List<string> NameLowerCache = new();
|
| 57 |
// Sorted by file_ref for binary search
|
| 58 |
public List<(ulong fileRef, int idx)> RefLookup = new();
|
| 59 |
+
// One root per drive — entries reference via DriveIdx
|
| 60 |
+
public List<string> DriveRoots = new();
|
| 61 |
public List<JournalCheckpoint> Checkpoints = new();
|
| 62 |
|
| 63 |
public int Count => Entries.Count;
|
|
|
|
| 66 |
|
| 67 |
public string NameAt(int i) => NameCache[i];
|
| 68 |
public string NameLowerAt(int i) => NameLowerCache[i];
|
| 69 |
+
public string DriveRootAt(int i) => DriveRoots[Entries[i].DriveIdx];
|
| 70 |
|
| 71 |
public uint? LookupIdx(ulong fileRef)
|
| 72 |
{
|
|
|
|
| 93 |
|
| 94 |
public void PopulateFromScan(ScanResult scan, string driveRoot)
|
| 95 |
{
|
| 96 |
+
// Find or add drive root
|
| 97 |
+
byte driveIdx = 0;
|
| 98 |
+
int existing = DriveRoots.IndexOf(driveRoot);
|
| 99 |
+
if (existing >= 0)
|
| 100 |
+
{
|
| 101 |
+
driveIdx = (byte)existing;
|
| 102 |
+
}
|
| 103 |
+
else
|
| 104 |
+
{
|
| 105 |
+
driveIdx = (byte)DriveRoots.Count;
|
| 106 |
+
DriveRoots.Add(driveRoot);
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
int count = scan.Records.Count;
|
| 110 |
+
Entries.Capacity = Math.Max(Entries.Capacity, Entries.Count + count);
|
| 111 |
+
NameArena.Capacity = Math.Max(NameArena.Capacity, NameArena.Count + count * 30);
|
| 112 |
+
NameLowerArena.Capacity = Math.Max(NameLowerArena.Capacity, NameLowerArena.Count + count * 30);
|
| 113 |
+
NameCache.Capacity = Math.Max(NameCache.Capacity, NameCache.Count + count);
|
| 114 |
+
NameLowerCache.Capacity = Math.Max(NameLowerCache.Capacity, NameLowerCache.Count + count);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
|
| 116 |
foreach (var r in scan.Records)
|
| 117 |
{
|
|
|
|
| 142 |
NameLowerOff = nlOff,
|
| 143 |
NameLen = nLen,
|
| 144 |
NameLowerLen = nlLen,
|
| 145 |
+
Flags = r.IsDir ? (byte)1 : (byte)0,
|
| 146 |
+
DriveIdx = driveIdx
|
| 147 |
});
|
| 148 |
|
| 149 |
NameCache.Add(name);
|
|
|
|
| 153 |
|
| 154 |
/// <summary>
|
| 155 |
/// Sort entries by lowercase name and rebuild lookup tables.
|
|
|
|
| 156 |
/// </summary>
|
| 157 |
public void CompleteIndex()
|
| 158 |
{
|
|
|
|
| 186 |
FileRef = e.FileRef,
|
| 187 |
ParentRef = e.ParentRef,
|
| 188 |
Name = NameAt(i),
|
| 189 |
+
Kind = e.Kind,
|
| 190 |
+
DriveIdx = e.DriveIdx
|
| 191 |
}).ToList(),
|
| 192 |
+
DriveRoots = new List<string>(DriveRoots),
|
| 193 |
Checkpoints = new List<JournalCheckpoint>(Checkpoints)
|
| 194 |
};
|
| 195 |
}
|
|
|
|
| 199 |
int count = cache.Entries.Count;
|
| 200 |
var store = new IndexStore
|
| 201 |
{
|
| 202 |
+
DriveRoots = new List<string>(cache.DriveRoots),
|
| 203 |
Checkpoints = new List<JournalCheckpoint>(cache.Checkpoints)
|
| 204 |
};
|
| 205 |
store.Entries.Capacity = count;
|
|
|
|
| 229 |
NameLowerOff = nlOff,
|
| 230 |
NameLen = nLen,
|
| 231 |
NameLowerLen = nlLen,
|
| 232 |
+
Flags = c.Kind == FileKind.Directory ? (byte)1 : (byte)0,
|
| 233 |
+
DriveIdx = c.DriveIdx
|
| 234 |
});
|
| 235 |
|
| 236 |
store.NameCache.Add(c.Name);
|
|
|
|
| 256 |
ushort nlLen = (ushort)lowerBytes.Length;
|
| 257 |
NameLowerArena.AddRange(lowerBytes);
|
| 258 |
|
| 259 |
+
// Default to first drive if unknown
|
| 260 |
+
byte driveIdx = 0;
|
| 261 |
+
if (DriveRoots.Count == 0)
|
| 262 |
+
DriveRoots.Add("C:\\");
|
| 263 |
+
|
| 264 |
var entry = new IndexEntry
|
| 265 |
{
|
| 266 |
FileRef = record.FileRef,
|
|
|
|
| 269 |
NameLowerOff = nlOff,
|
| 270 |
NameLen = nLen,
|
| 271 |
NameLowerLen = nlLen,
|
| 272 |
+
Flags = record.Kind == FileKind.Directory ? (byte)1 : (byte)0,
|
| 273 |
+
DriveIdx = driveIdx
|
| 274 |
};
|
| 275 |
|
| 276 |
// Insert in sorted position by lowercase name
|
|
|
|
| 327 |
|
| 328 |
public string BuildPath(ulong fileRef)
|
| 329 |
{
|
|
|
|
| 330 |
string[] components = new string[64];
|
| 331 |
int compCount = 0;
|
| 332 |
ulong current = fileRef;
|
| 333 |
+
byte driveIdx = 0;
|
| 334 |
|
| 335 |
for (int i = 0; i < 64; i++)
|
| 336 |
{
|
| 337 |
var idx = LookupIdx(current);
|
| 338 |
if (idx == null) break;
|
| 339 |
|
| 340 |
+
int eIdx = (int)idx;
|
| 341 |
+
components[compCount++] = NameAt(eIdx);
|
| 342 |
+
var entry = Entries[eIdx];
|
| 343 |
+
driveIdx = entry.DriveIdx;
|
| 344 |
if (entry.ParentRef == current) break;
|
| 345 |
current = entry.ParentRef;
|
| 346 |
}
|
| 347 |
|
| 348 |
+
string driveRoot = driveIdx < DriveRoots.Count ? DriveRoots[driveIdx] : "C:\\";
|
| 349 |
+
var sb = new StringBuilder(driveRoot.Length + compCount * 32);
|
| 350 |
+
sb.Append(driveRoot);
|
| 351 |
for (int i = compCount - 1; i >= 0; i--)
|
| 352 |
{
|
| 353 |
if (sb.Length > 0 && sb[sb.Length - 1] != '\\' && sb[sb.Length - 1] != '/')
|