Skip to content

Commit e7b89d8

Browse files
committed
Put GetCanonicalPath into its own utility class
1 parent 064888c commit e7b89d8

2 files changed

Lines changed: 82 additions & 66 deletions

File tree

engine/Sandbox.SolutionGenerator/Generator.cs

Lines changed: 5 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Linq;
5-
using System.Runtime.InteropServices;
65
using System.Text.Json;
6+
using Sandbox.Utility;
77

88
namespace Sandbox.SolutionGenerator
99
{
@@ -26,87 +26,26 @@ private string NormalizePath( string path )
2626
return path.Replace( '\\', '/' );
2727
}
2828

29-
30-
// Importing necessary Win32 APIs for getting the canonical path
31-
[DllImport( "kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true )]
32-
private static extern IntPtr CreateFileW( string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile );
33-
34-
[DllImport( "kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true )]
35-
private static extern uint GetFinalPathNameByHandleW( IntPtr hFile, char[] lpszFilePath, uint cchFilePath, uint dwFlags );
36-
37-
[DllImport( "kernel32.dll", SetLastError = true )]
38-
private static extern bool CloseHandle( IntPtr hObject );
39-
40-
// Define magic numbers with proper names
41-
private const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
42-
private const uint OPEN_EXISTING = 3;
43-
private const uint FILE_SHARE_READ = 1;
44-
private const uint FILE_SHARE_WRITE = 2;
45-
46-
/// <summary>
47-
/// Get the proper path casing for the given path
48-
/// </summary>
49-
private static string GetCanonicalPath( string path )
50-
{
51-
if ( !string.IsNullOrWhiteSpace( path ) && !Path.IsPathRooted( path ) ) return path;
52-
53-
try
54-
{
55-
var handle = CreateFileW( path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero );
56-
if ( handle == IntPtr.Zero || handle == new IntPtr( -1 ) )
57-
return path;
58-
59-
try
60-
{
61-
var buffer = new char[512];
62-
var len = GetFinalPathNameByHandleW( handle, buffer, (uint)buffer.Length, 0 );
63-
if ( len > 0 && len < buffer.Length )
64-
{
65-
var finalPath = new string( buffer, 0, (int)len );
66-
// Remove the \\?\ prefix added by Windows API
67-
if ( finalPath.StartsWith( @"\\?\" ) )
68-
{
69-
finalPath = finalPath.Substring( 4 );
70-
}
71-
return finalPath;
72-
}
73-
else
74-
{
75-
return path;
76-
}
77-
}
78-
finally
79-
{
80-
CloseHandle( handle );
81-
}
82-
}
83-
catch
84-
{
85-
// Ignore errors and return original path
86-
return path;
87-
}
88-
}
89-
9029
/// <summary>
9130
/// Converts a path to be relative to a base path, always returning forward slashes.
9231
/// </summary>
9332
private string AttemptAbsoluteToRelative( string basePath, string targetPath )
9433
{
9534
string targetFileName = string.Empty;
96-
string baseDir = GetCanonicalPath( basePath );
97-
string targetDir = GetCanonicalPath( targetPath );
35+
string baseDir = NativeFileSystem.GetCanonicalPath( basePath );
36+
string targetDir = NativeFileSystem.GetCanonicalPath( targetPath );
9837

9938
// If target is a file, extract the filename
10039
if ( Path.HasExtension( targetPath ) )
10140
{
10241
targetFileName = Path.GetFileName( targetPath );
103-
targetDir = GetCanonicalPath( Path.GetDirectoryName( targetPath ) ?? targetPath );
42+
targetDir = NativeFileSystem.GetCanonicalPath( Path.GetDirectoryName( targetPath ) ?? targetPath );
10443
}
10544

10645
// If base is a file, use its directory
10746
if ( Path.HasExtension( basePath ) )
10847
{
109-
baseDir = GetCanonicalPath( Path.GetDirectoryName( basePath ) ?? basePath );
48+
baseDir = NativeFileSystem.GetCanonicalPath( Path.GetDirectoryName( basePath ) ?? basePath );
11049
}
11150

11251
// Calculate relative path from base to target - this preserves casing when inputs are cased
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using System;
2+
using System.IO;
3+
using System.Runtime.InteropServices;
4+
5+
namespace Sandbox.Utility;
6+
7+
/// <summary>
8+
/// Provides native file system operations with platform-specific implementations.
9+
/// </summary>
10+
public static partial class NativeFileSystem
11+
{
12+
[DllImport( "kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true )]
13+
private static extern IntPtr CreateFileW( string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile );
14+
15+
[DllImport( "kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true )]
16+
private static extern uint GetFinalPathNameByHandleW( IntPtr hFile, char[] lpszFilePath, uint cchFilePath, uint dwFlags );
17+
18+
[DllImport( "kernel32.dll", SetLastError = true )]
19+
private static extern bool CloseHandle( IntPtr hObject );
20+
21+
private const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
22+
private const uint OPEN_EXISTING = 3;
23+
private const uint FILE_SHARE_READ = 1;
24+
private const uint FILE_SHARE_WRITE = 2;
25+
26+
/// <summary>
27+
/// Gets the canonical path with proper casing for the given path.
28+
/// On Windows, this resolves the true filesystem path including correct casing.
29+
/// On Linux, this returns the path unchanged since the filesystem is case-sensitive.
30+
/// </summary>
31+
/// <param name="path">The path to canonicalize.</param>
32+
/// <returns>The canonical path, or the original path if canonicalization fails.</returns>
33+
public static string GetCanonicalPath( string path )
34+
{
35+
if ( string.IsNullOrWhiteSpace( path ) || !Path.IsPathRooted( path ) )
36+
return path;
37+
38+
if ( !OperatingSystem.IsWindows() )
39+
return path;
40+
41+
try
42+
{
43+
var handle = CreateFileW( path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero );
44+
if ( handle == IntPtr.Zero || handle == new IntPtr( -1 ) )
45+
return path;
46+
47+
try
48+
{
49+
var buffer = new char[512];
50+
var len = GetFinalPathNameByHandleW( handle, buffer, (uint)buffer.Length, 0 );
51+
if ( len > 0 && len < buffer.Length )
52+
{
53+
var finalPath = new string( buffer, 0, (int)len );
54+
// Remove the \\?\ prefix added by Windows API
55+
if ( finalPath.StartsWith( @"\\?\" ) )
56+
{
57+
finalPath = finalPath.Substring( 4 );
58+
}
59+
return finalPath;
60+
}
61+
else
62+
{
63+
return path;
64+
}
65+
}
66+
finally
67+
{
68+
CloseHandle( handle );
69+
}
70+
}
71+
catch
72+
{
73+
// Ignore errors and return original path
74+
return path;
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)