Skip to content

Commit cb9e2db

Browse files
committed
Optimize case-insensitive file existence check in PetroglyphFileSystem and add additional test cases
1 parent f16178a commit cb9e2db

2 files changed

Lines changed: 49 additions & 31 deletions

File tree

src/PetroglyphTools/PG.StarWarsGame.Engine.FileSystem.Test/IO/PetroglyphFileSystemTests.Exist.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ public void FileExists_RelativePath()
8484
[InlineData("a/b/c.txt", "A/B/C.txt")]
8585
[InlineData("A/B/C.txt", "a/b/c.txt")]
8686
[InlineData("a/B/c.txt", "A/b/C.txt")]
87+
[InlineData("a/B/C.txt", "a/B/c.txt")]
88+
[InlineData("a/b/C/D.txt", "a/b/c/d.txt")]
8789
public void FileExists_CaseInsensitive(string inputPath, string actualPathOnDisk)
8890
{
8991
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());

src/PetroglyphTools/PG.StarWarsGame.Engine.FileSystem/IO/PetroglyphFileSystem.Exist.cs

Lines changed: 47 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -52,55 +52,71 @@ in stringBuilder.GetPinnableReference(true),
5252
// NB: This method assumes backslashes have been normalized to forward slashes
5353
// NB: This method operates on the actual file system
5454
private bool FileExistsCaseInsensitive(ReadOnlySpan<char> filePath, ref ValueStringBuilder stringBuilder)
55-
{
56-
Debug.Assert(!RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
57-
58-
var pathString = filePath.ToString();
59-
if (_underlyingFileSystem.File.Exists(pathString))
60-
return true;
55+
{
56+
Debug.Assert(!RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
6157

62-
var directory = _underlyingFileSystem.Path.GetDirectoryName(pathString);
63-
var fileName = _underlyingFileSystem.Path.GetFileName(pathString);
58+
var pathString = filePath.ToString();
59+
if (_underlyingFileSystem.File.Exists(pathString))
60+
return true;
6461

65-
if (string.IsNullOrEmpty(directory) || string.IsNullOrEmpty(fileName))
66-
return false;
62+
var segments = pathString.Split('/');
63+
var currentPath = segments[0].Length == 0 ? "/" : segments[0];
6764

68-
if (!_underlyingFileSystem.Directory.Exists(directory))
69-
{
70-
if (!FileExistsCaseInsensitive(directory.AsSpan(), ref stringBuilder))
71-
return false;
65+
var lastSegmentIndex = segments.Length - 1;
66+
while (lastSegmentIndex > 0 && string.IsNullOrEmpty(segments[lastSegmentIndex]))
67+
lastSegmentIndex--;
7268

73-
directory = stringBuilder.AsSpan().ToString();
74-
}
69+
for (var i = 1; i < segments.Length; i++)
70+
{
71+
var segment = segments[i];
72+
if (string.IsNullOrEmpty(segment))
73+
continue;
7574

76-
var files = _underlyingFileSystem.Directory.GetFiles(directory);
77-
var directories = _underlyingFileSystem.Directory.GetDirectories(directory);
75+
var isLastSegment = i == lastSegmentIndex;
7876

79-
foreach (var file in files)
77+
// Guard: if currentPath doesn't exist as a directory, bail out cheaply
78+
if (!_underlyingFileSystem.Directory.Exists(currentPath))
79+
return false;
80+
81+
if (isLastSegment)
8082
{
81-
var name = _underlyingFileSystem.Path.GetFileName(file);
82-
if (name.Equals(fileName, StringComparison.OrdinalIgnoreCase))
83+
// Single IO call instead of GetFiles + GetDirectories
84+
var entries = _underlyingFileSystem.Directory.GetFileSystemEntries(currentPath);
85+
foreach (var entry in entries)
8386
{
84-
stringBuilder.Length = 0;
85-
stringBuilder.Append(file);
86-
return true;
87+
var name = _underlyingFileSystem.Path.GetFileName(entry);
88+
if (name.Equals(segment, StringComparison.OrdinalIgnoreCase))
89+
{
90+
stringBuilder.Length = 0;
91+
stringBuilder.Append(entry);
92+
return true;
93+
}
8794
}
95+
return false;
8896
}
8997

90-
foreach (var dir in directories)
98+
// Intermediate segment: resolve case-insensitively
99+
var subDirs = _underlyingFileSystem.Directory.GetDirectories(currentPath);
100+
string? resolved = null;
101+
foreach (var dir in subDirs)
91102
{
92103
var name = _underlyingFileSystem.Path.GetFileName(dir);
93-
if (name.Equals(fileName, StringComparison.OrdinalIgnoreCase))
104+
if (name.Equals(segment, StringComparison.OrdinalIgnoreCase))
94105
{
95-
stringBuilder.Length = 0;
96-
stringBuilder.Append(dir);
97-
return true;
106+
resolved = dir;
107+
break;
98108
}
99109
}
100110

101-
return false;
111+
if (resolved is null)
112+
return false;
113+
114+
currentPath = resolved;
102115
}
103-
116+
117+
return false;
118+
}
119+
104120
private bool IsPathFullyQualified_Exists(ReadOnlySpan<char> path)
105121
{
106122
// This is really tricky, because under Windows "/" or "\" do NOT

0 commit comments

Comments
 (0)