1- namespace System . IO . Abstractions . Extensions
1+ // S3874: "out" and "ref" parameters should not be used
2+ // https://rules.sonarsource.com/csharp/RSPEC-3874/
3+ //
4+ // Our CreateDisposableDirectory / CreateDisposableFile extensions
5+ // intentionally use an out param so that the DirectoryInfo / FileInfo
6+ // is passed out (similar to a Try* method) to the caller while the
7+ // returned object implements IDisposable to leverage the using statement.
8+ #pragma warning disable S3874
9+
10+ namespace System . IO . Abstractions . Extensions
211{
312 public static class IFileSystemExtensions
413 {
@@ -11,5 +20,147 @@ public static IDirectoryInfo CurrentDirectory(this IFileSystem fileSystem)
1120 {
1221 return fileSystem . DirectoryInfo . New ( fileSystem . Directory . GetCurrentDirectory ( ) ) ;
1322 }
23+
24+ /// <summary>
25+ /// Creates a new <see cref="IDirectoryInfo"/> using a random name from the temp path, and returns an <see cref="IDisposable"/>
26+ /// that deletes the directory when disposed.
27+ /// </summary>
28+ /// <param name="fileSystem">
29+ /// The <see cref="IFileSystem"/> in use.
30+ /// </param>
31+ /// <param name="directoryInfo">
32+ /// The <see cref="IDirectoryInfo"/> for the directory that was created.
33+ /// </param>
34+ /// <returns>
35+ /// An <see cref="IDisposable"/> to manage the directory's lifetime.
36+ /// </returns>
37+ public static IDisposable CreateDisposableDirectory ( this IFileSystem fileSystem , out IDirectoryInfo directoryInfo )
38+ {
39+ return fileSystem . CreateDisposableDirectory ( fileSystem . Path . GetRandomTempPath ( ) , out directoryInfo ) ;
40+ }
41+
42+ /// <inheritdoc cref="CreateDisposableDirectory(IFileSystem, out IDirectoryInfo)"/>
43+ /// <summary>
44+ /// Creates a new <see cref="IDirectoryInfo"/> using a path provided by <paramref name="path"/>, and returns an
45+ /// <see cref="IDisposable"/> that deletes the directory when disposed.
46+ /// </summary>
47+ /// <param name="path">
48+ /// The full path to the directory to create.
49+ /// </param>
50+ /// <exception cref="ArgumentException">
51+ /// If the directory already exists.
52+ /// </exception>
53+ public static IDisposable CreateDisposableDirectory ( this IFileSystem fileSystem , string path , out IDirectoryInfo directoryInfo )
54+ {
55+ return fileSystem . CreateDisposableDirectory ( path , dir => new DisposableDirectory ( dir ) , out directoryInfo ) ;
56+ }
57+
58+ /// <inheritdoc cref="CreateDisposableDirectory(IFileSystem, string, out IDirectoryInfo)"/>
59+ /// <summary>
60+ /// Creates a new <see cref="IDirectoryInfo"/> using a path provided by <paramref name="path"/>, and returns an
61+ /// <see cref="IDisposable"/> created by <paramref name="disposableFactory"/>, that should delete the directory when disposed.
62+ /// </summary>
63+ /// <param name="disposableFactory">
64+ /// A <see cref="Func{T, TResult}"/> that acts as a factory method. Given the <see cref="IDirectoryInfo"/>, create the
65+ /// <see cref="IDisposable"/> that will manage the its lifetime.
66+ /// </param>
67+ public static T CreateDisposableDirectory < T > (
68+ this IFileSystem fileSystem ,
69+ string path ,
70+ Func < IDirectoryInfo , T > disposableFactory ,
71+ out IDirectoryInfo directoryInfo ) where T : IDisposable
72+ {
73+ directoryInfo = fileSystem . DirectoryInfo . New ( path ) ;
74+
75+ if ( directoryInfo . Exists )
76+ {
77+ throw CreateAlreadyExistsException ( nameof ( path ) , path ) ;
78+ }
79+
80+ directoryInfo . Create ( ) ;
81+
82+ return disposableFactory ( directoryInfo ) ;
83+ }
84+
85+ /// <summary>
86+ /// Creates a new <see cref="IFileInfo"/> using a random name from the temp path, and returns an <see cref="IDisposable"/>
87+ /// that deletes the file when disposed.
88+ /// </summary>
89+ /// <param name="fileSystem">
90+ /// The <see cref="IFileSystem"/> in use.
91+ /// </param>
92+ /// <param name="fileInfo">
93+ /// The <see cref="IFileInfo"/> for the file that was created.
94+ /// </param>
95+ /// <returns>
96+ /// An <see cref="IDisposable"/> to manage the file's lifetime.
97+ /// </returns>
98+ public static IDisposable CreateDisposableFile ( this IFileSystem fileSystem , out IFileInfo fileInfo )
99+ {
100+ return fileSystem . CreateDisposableFile ( fileSystem . Path . GetRandomTempPath ( ) , out fileInfo ) ;
101+ }
102+
103+ /// <inheritdoc cref="CreateDisposableFile(IFileSystem, out IFileInfo)"/>
104+ /// <summary>
105+ /// Creates a new <see cref="IFileInfo"/> using a path provided by <paramref name="path"/>, and returns an
106+ /// <see cref="IDisposable"/> that deletes the file when disposed.
107+ /// </summary>
108+ /// <param name="path">
109+ /// The full path to the file to create.
110+ /// </param>
111+ /// <exception cref="ArgumentException">
112+ /// If the file already exists.
113+ /// </exception>
114+ public static IDisposable CreateDisposableFile ( this IFileSystem fileSystem , string path , out IFileInfo fileInfo )
115+ {
116+ return fileSystem . CreateDisposableFile ( path , file => new DisposableFile ( file ) , out fileInfo ) ;
117+ }
118+
119+ /// <inheritdoc cref="CreateDisposableFile(IFileSystem, string, out IFileInfo)"/>
120+ /// <summary>
121+ /// Creates a new <see cref="IFileInfo"/> using a path provided by <paramref name="path"/>, and returns an
122+ /// <see cref="IDisposable"/> created by <paramref name="disposableFactory"/>, that should delete the file when disposed.
123+ /// </summary>
124+ /// <param name="disposableFactory">
125+ /// A <see cref="Func{T, TResult}"/> that acts as a factory method. Given the <see cref="IFileInfo"/>, create the
126+ /// <see cref="IDisposable"/> that will manage the its lifetime.
127+ /// </param>
128+ public static T CreateDisposableFile < T > (
129+ this IFileSystem fileSystem ,
130+ string path ,
131+ Func < IFileInfo , T > disposableFactory ,
132+ out IFileInfo fileInfo ) where T : IDisposable
133+ {
134+ fileInfo = fileSystem . FileInfo . New ( path ) ;
135+
136+ if ( fileInfo . Exists )
137+ {
138+ throw CreateAlreadyExistsException ( nameof ( path ) , path ) ;
139+ }
140+
141+ // Ensure we close the handle to the file after we create it, otherwise
142+ // callers may get an access denied error.
143+ fileInfo . Create ( ) . Dispose ( ) ;
144+
145+ return disposableFactory ( fileInfo ) ;
146+ }
147+
148+ private static string GetRandomTempPath ( this IPath path )
149+ {
150+ var temp = path . GetTempPath ( ) ;
151+ var fileName = path . GetRandomFileName ( ) ;
152+ return path . Combine ( temp , fileName ) ;
153+ }
154+
155+ private static ArgumentException CreateAlreadyExistsException ( string argumentName , string path )
156+ {
157+ // Having the colliding path availabe as part of the exception is very useful for debugging.
158+ // However, paths can be considered sensitive information in some contexts (like web servers).
159+ // Thus, we add the path to the exception's data , rather than the message.
160+ var ex = new ArgumentException ( "File already exists" , argumentName ) ;
161+ ex . Data . Add ( "path" , path ) ;
162+
163+ return ex ;
164+ }
14165 }
15166}
0 commit comments