Skip to content

Commit defd44b

Browse files
feat: add CreateSymbolicLink support (#871)
* Adds CreateSymbolicLink support Adds a FileSystemInfo extension for wrapping convenience. Adds FEATURE_CREATE_SYMBOLIC_LINK to the build properties. Includes mock implementations and mock tests. Updates the ApiParityTests to no longer ignore CreateSymbolicLink for .net 6.0. Bumps the version in version.json. * Skip path tests on non-Windows platform Co-authored-by: Florian Greinacher <[email protected]>
1 parent df30ba4 commit defd44b

File tree

15 files changed

+541
-7
lines changed

15 files changed

+541
-7
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<DefineConstants Condition="'$(TargetFramework)' != 'net461'">$(DefineConstants);FEATURE_FILE_SYSTEM_ACL_EXTENSIONS</DefineConstants>
1414
<DefineConstants Condition="'$(TargetFramework)' == 'net6.0' OR '$(TargetFramework)' == 'net5.0' OR '$(TargetFramework)' == 'netcoreapp3.1' OR '$(TargetFramework)' == 'netstandard2.1'">$(DefineConstants);FEATURE_ASYNC_FILE;FEATURE_ENUMERATION_OPTIONS;FEATURE_ADVANCED_PATH_OPERATIONS;FEATURE_PATH_JOIN_WITH_SPAN</DefineConstants>
1515
<DefineConstants Condition="'$(TargetFramework)' == 'net6.0' OR '$(TargetFramework)' == 'net5.0'">$(DefineConstants);FEATURE_FILE_MOVE_WITH_OVERWRITE;FEATURE_SUPPORTED_OS_ATTRIBUTE;FEATURE_FILE_SYSTEM_WATCHER_FILTERS;FEATURE_ENDS_IN_DIRECTORY_SEPARATOR;FEATURE_PATH_JOIN_WITH_PARAMS;FEATURE_PATH_JOIN_WITH_FOUR_PATHS</DefineConstants>
16-
<DefineConstants Condition="'$(TargetFramework)' == 'net6.0'">$(DefineConstants);FEATURE_FILE_SYSTEM_INFO_LINK_TARGET</DefineConstants>
16+
<DefineConstants Condition="'$(TargetFramework)' == 'net6.0'">$(DefineConstants);FEATURE_FILE_SYSTEM_INFO_LINK_TARGET;FEATURE_CREATE_SYMBOLIC_LINK</DefineConstants>
1717
</PropertyGroup>
1818
<ItemGroup>
1919
<PackageReference Include="Nerdbank.GitVersioning" Version="3.5.109">

src/System.IO.Abstractions.TestingHelpers/MockDirectory.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,30 @@ private IDirectoryInfo CreateDirectoryInternal(string path, DirectorySecurity di
8484
return created;
8585
}
8686

87+
#if FEATURE_CREATE_SYMBOLIC_LINK
88+
/// <inheritdoc />
89+
public override IFileSystemInfo CreateSymbolicLink(string path, string pathToTarget)
90+
{
91+
mockFileDataAccessor.PathVerifier.IsLegalAbsoluteOrRelative(path, nameof(path));
92+
mockFileDataAccessor.PathVerifier.IsLegalAbsoluteOrRelative(pathToTarget, nameof(pathToTarget));
93+
94+
if (Exists(path))
95+
{
96+
throw CommonExceptions.FileAlreadyExists(nameof(path));
97+
}
98+
99+
var targetExists = Exists(pathToTarget);
100+
if (!targetExists)
101+
{
102+
throw CommonExceptions.FileNotFound(pathToTarget);
103+
}
104+
105+
mockFileDataAccessor.AddDirectory(path);
106+
mockFileDataAccessor.GetFile(path).LinkTarget = pathToTarget;
107+
108+
return new MockDirectoryInfo(mockFileDataAccessor, path);
109+
}
110+
#endif
87111

88112
/// <inheritdoc />
89113
public override void Delete(string path)

src/System.IO.Abstractions.TestingHelpers/MockFile.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,46 @@ private Stream CreateInternal(string path, FileAccess access, FileOptions option
155155
return OpenInternal(path, FileMode.Open, access, options);
156156
}
157157

158+
#if FEATURE_CREATE_SYMBOLIC_LINK
159+
/// <inheritdoc />
160+
public override IFileSystemInfo CreateSymbolicLink(string path, string pathToTarget)
161+
{
162+
if (path == null)
163+
{
164+
throw CommonExceptions.FilenameCannotBeNull(nameof(path));
165+
}
166+
167+
if (pathToTarget == null)
168+
{
169+
throw CommonExceptions.FilenameCannotBeNull(nameof(pathToTarget));
170+
}
171+
172+
mockFileDataAccessor.PathVerifier.IsLegalAbsoluteOrRelative(path, nameof(path));
173+
mockFileDataAccessor.PathVerifier.IsLegalAbsoluteOrRelative(pathToTarget, nameof(pathToTarget));
174+
175+
if (Exists(path))
176+
{
177+
throw CommonExceptions.FileAlreadyExists(nameof(path));
178+
}
179+
180+
VerifyDirectoryExists(path);
181+
182+
var fileExists = mockFileDataAccessor.FileExists(pathToTarget);
183+
if (!fileExists)
184+
{
185+
throw CommonExceptions.FileNotFound(pathToTarget);
186+
}
187+
188+
var sourceFileData = mockFileDataAccessor.GetFile(pathToTarget);
189+
sourceFileData.CheckFileAccess(pathToTarget, FileAccess.Read);
190+
var destFileData = new MockFileData(new byte[0]);
191+
destFileData.CreationTime = destFileData.LastAccessTime = DateTime.Now;
192+
destFileData.LinkTarget = pathToTarget;
193+
mockFileDataAccessor.AddFile(path, destFileData);
194+
195+
return new MockFileInfo(mockFileDataAccessor, path);
196+
}
197+
#endif
158198
/// <inheritdoc />
159199
public override StreamWriter CreateText(string path)
160200
{

src/System.IO.Abstractions/Converters.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ internal static IEnumerable<FileSystemInfoBase> WrapFileSystemInfos(this IEnumer
1212
internal static FileSystemInfoBase[] WrapFileSystemInfos(this FileSystemInfo[] input, IFileSystem fileSystem)
1313
=> input.Select(info => WrapFileSystemInfo(fileSystem, info)).ToArray();
1414

15+
internal static FileSystemInfoBase WrapFileSystemInfo(this FileSystemInfo input, IFileSystem fileSystem)
16+
=> WrapFileSystemInfo(fileSystem, input);
17+
1518
internal static IEnumerable<DirectoryInfoBase> WrapDirectories(this IEnumerable<DirectoryInfo> input, IFileSystem fileSystem)
1619
=> input.Select(info => WrapDirectoryInfo(fileSystem, info));
1720

src/System.IO.Abstractions/DirectoryBase.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ internal DirectoryBase() { }
2828
/// <inheritdoc cref="IDirectory.CreateDirectory(string,DirectorySecurity)"/>
2929
[SupportedOSPlatform("windows")]
3030
public abstract IDirectoryInfo CreateDirectory(string path, DirectorySecurity directorySecurity);
31-
31+
#if FEATURE_CREATE_SYMBOLIC_LINK
32+
/// <inheritdoc cref="IDirectory.CreateSymbolicLink(string, string)"/>
33+
public abstract IFileSystemInfo CreateSymbolicLink(string path, string pathToTarget);
34+
#endif
3235
/// <inheritdoc cref="IDirectory.Delete(string)"/>
3336
public abstract void Delete(string path);
3437

src/System.IO.Abstractions/DirectoryWrapper.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,13 @@ public override IDirectoryInfo CreateDirectory(string path, DirectorySecurity di
2929
directoryInfo.Create(directorySecurity);
3030
return new DirectoryInfoWrapper(FileSystem, directoryInfo);
3131
}
32-
32+
#if FEATURE_CREATE_SYMBOLIC_LINK
33+
/// <inheritdoc />
34+
public override IFileSystemInfo CreateSymbolicLink(string path, string pathToTarget)
35+
{
36+
return Directory.CreateSymbolicLink(path, pathToTarget).WrapFileSystemInfo(FileSystem);
37+
}
38+
#endif
3339
/// <inheritdoc />
3440
public override void Delete(string path)
3541
{

src/System.IO.Abstractions/FileBase.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ internal FileBase() { }
5252

5353
/// <inheritdoc cref="IFile.Create(string,int,FileOptions)"/>
5454
public abstract Stream Create(string path, int bufferSize, FileOptions options);
55-
55+
#if FEATURE_CREATE_SYMBOLIC_LINK
56+
/// <inheritdoc cref="IFile.CreateSymbolicLink(string, string)"/>
57+
public abstract IFileSystemInfo CreateSymbolicLink(string path, string pathToTarget);
58+
#endif
5659
/// <inheritdoc cref="IFile.CreateText"/>
5760
public abstract StreamWriter CreateText(string path);
5861
/// <inheritdoc cref="IFile.Decrypt"/>

src/System.IO.Abstractions/FileWrapper.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ public override Stream Create(string path, int bufferSize, FileOptions options)
7575
return File.Create(path, bufferSize, options);
7676
}
7777

78+
#if FEATURE_CREATE_SYMBOLIC_LINK
79+
/// <inheritdoc />
80+
public override IFileSystemInfo CreateSymbolicLink(string path, string pathToTarget)
81+
{
82+
return File.CreateSymbolicLink(path, pathToTarget).WrapFileSystemInfo(FileSystem);
83+
}
84+
#endif
7885
/// <inheritdoc />
7986
public override StreamWriter CreateText(string path)
8087
{

src/System.IO.Abstractions/IDirectory.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ public interface IDirectory
2020
/// <inheritdoc cref="Directory.CreateDirectory(string,DirectorySecurity)"/>
2121
#endif
2222
IDirectoryInfo CreateDirectory(string path, DirectorySecurity directorySecurity);
23+
#if FEATURE_CREATE_SYMBOLIC_LINK
24+
/// <inheritdoc cref="Directory.CreateSymbolicLink"/>
25+
IFileSystemInfo CreateSymbolicLink(string path, string pathToTarget);
26+
#endif
2327
/// <inheritdoc cref="Directory.Delete(string)"/>
2428
void Delete(string path);
2529
/// <inheritdoc cref="Directory.Delete(string,bool)"/>

src/System.IO.Abstractions/IFile.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ public partial interface IFile
3333
Stream Create(string path, int bufferSize);
3434
/// <inheritdoc cref="File.Create(string,int,FileOptions)"/>
3535
Stream Create(string path, int bufferSize, FileOptions options);
36+
#if FEATURE_CREATE_SYMBOLIC_LINK
37+
/// <inheritdoc cref="File.CreateSymbolicLink"/>
38+
IFileSystemInfo CreateSymbolicLink(string path, string pathToTarget);
39+
#endif
3640
/// <inheritdoc cref="File.CreateText"/>
3741
StreamWriter CreateText(string path);
3842
/// <inheritdoc cref="File.Decrypt"/>

0 commit comments

Comments
 (0)