tgstation-server
The /tg/station 13 server suite
DefaultIOManager.cs
Go to the documentation of this file.
1 using System;
2 using System.Collections.Generic;
3 using System.Globalization;
4 using System.IO;
5 using System.IO.Compression;
6 using System.Linq;
7 using System.Net;
8 using System.Text;
9 using System.Threading;
10 using System.Threading.Tasks;
11 
12 namespace Tgstation.Server.Host.IO
13 {
18  {
22  public const int DefaultBufferSize = 4096;
23 
30  static async Task NormalizeAndDelete(DirectoryInfo dir, CancellationToken cancellationToken)
31  {
32  var tasks = new List<Task>();
33 
34  foreach (var subDir in dir.EnumerateDirectories())
35  {
36  cancellationToken.ThrowIfCancellationRequested();
37  if (!subDir.Attributes.HasFlag(FileAttributes.Directory) || subDir.Attributes.HasFlag(FileAttributes.ReparsePoint))
38  //this is probably a symlink
39  subDir.Delete();
40  else
41  tasks.Add(NormalizeAndDelete(subDir, cancellationToken));
42  }
43  foreach (var file in dir.EnumerateFiles())
44  {
45  cancellationToken.ThrowIfCancellationRequested();
46  file.Attributes = FileAttributes.Normal;
47  file.Delete();
48  }
49  await Task.WhenAll(tasks).ConfigureAwait(false);
50  cancellationToken.ThrowIfCancellationRequested();
51  dir.Delete(true);
52  }
53 
59  static FileStream OpenWriteStream(string path) => new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite, DefaultBufferSize, true);
60 
69  IEnumerable<Task> CopyDirectoryImpl(string src, string dest, IEnumerable<string> ignore, CancellationToken cancellationToken)
70  {
71  var dir = new DirectoryInfo(src);
72  var atLeastOneSubDir = false;
73  foreach (var I in dir.EnumerateDirectories())
74  {
75  if (ignore != null && ignore.Contains(I.Name))
76  continue;
77  foreach (var J in CopyDirectoryImpl(I.FullName, Path.Combine(dest, I.Name), null, cancellationToken))
78  {
79  atLeastOneSubDir = true;
80  yield return J;
81  }
82  }
83 
84  async Task CopyThisDirectory()
85  {
86  if (!atLeastOneSubDir)
87  await CreateDirectory(dest, cancellationToken).ConfigureAwait(false); //save on createdir calls
88 
89  var tasks = new List<Task>();
90 
91  await dir.EnumerateFiles().ToAsyncEnumerable().ForEachAsync(I =>
92  {
93  if (ignore != null && ignore.Contains(I.Name))
94  return;
95  tasks.Add(CopyFile(I.FullName, Path.Combine(dest, I.Name), cancellationToken));
96  }).ConfigureAwait(false);
97 
98  await Task.WhenAll(tasks).ConfigureAwait(false);
99  };
100 
101  yield return CopyThisDirectory();
102  }
103 
105  public async Task CopyDirectory(string src, string dest, IEnumerable<string> ignore, CancellationToken cancellationToken)
106  {
107  if (dest == null)
108  throw new ArgumentNullException(nameof(src));
109  if (dest == null)
110  throw new ArgumentNullException(nameof(src));
111 
112  src = ResolvePath(src);
113  dest = ResolvePath(dest);
114  foreach (var directoryCopy in CopyDirectoryImpl(src, dest, ignore, cancellationToken))
115  await directoryCopy.ConfigureAwait(false);
116  }
117 
119  public async Task AppendAllText(string path, string additional_contents, CancellationToken cancellationToken)
120  {
121  if (additional_contents == null)
122  throw new ArgumentNullException(nameof(additional_contents));
123  using (var destStream = new FileStream(ResolvePath(path), FileMode.Append, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete, DefaultBufferSize, true))
124  {
125  var buf = Encoding.UTF8.GetBytes(additional_contents);
126  await destStream.WriteAsync(buf, 0, buf.Length, cancellationToken).ConfigureAwait(false);
127  }
128  }
129 
131  public string ConcatPath(params string[] paths)
132  {
133  if (paths == null)
134  throw new ArgumentNullException(nameof(paths));
135  return Path.Combine(paths);
136  }
137 
139  public async Task CopyFile(string src, string dest, CancellationToken cancellationToken)
140  {
141  if (src == null)
142  throw new ArgumentNullException(nameof(src));
143  if (dest == null)
144  throw new ArgumentNullException(nameof(dest));
145  using (var srcStream = new FileStream(ResolvePath(src), FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, DefaultBufferSize, true))
146  using (var destStream = new FileStream(ResolvePath(dest), FileMode.Create, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete, DefaultBufferSize, true))
147  await srcStream.CopyToAsync(destStream, 81920, cancellationToken).ConfigureAwait(false);
148  }
149 
151  public Task CreateDirectory(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => Directory.CreateDirectory(ResolvePath(path)), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
152 
154  public async Task DeleteDirectory(string path, CancellationToken cancellationToken)
155  {
156  path = ResolvePath(path);
157  var di = new DirectoryInfo(path);
158  if (!di.Exists)
159  return;
160  await NormalizeAndDelete(di, cancellationToken).ConfigureAwait(false);
161  }
162 
164  public Task DeleteFile(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => File.Delete(ResolvePath(path)), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
165 
167  public Task<bool> FileExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => File.Exists(ResolvePath(path)), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
168 
170  public Task<bool> DirectoryExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => Directory.Exists(ResolvePath(path)), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
171 
173  public string GetDirectoryName(string path) => Path.GetDirectoryName(path ?? throw new ArgumentNullException(nameof(path)));
174 
176  public string GetFileName(string path) => Path.GetFileName(path ?? throw new ArgumentNullException(nameof(path)));
177 
179  public string GetFileNameWithoutExtension(string path) => Path.GetFileNameWithoutExtension(path ?? throw new ArgumentNullException(nameof(path)));
180 
182  public Task<List<string>> GetFilesWithExtension(string path, string extension, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
183  {
184  path = ResolvePath(path);
185  if (extension == null)
186  throw new ArgumentNullException(extension);
187  var results = new List<string>();
188  foreach (var I in Directory.EnumerateFiles(path, String.Format(CultureInfo.InvariantCulture, "*.{0}", extension), SearchOption.TopDirectoryOnly))
189  {
190  cancellationToken.ThrowIfCancellationRequested();
191  results.Add(I);
192  }
193  return results;
194  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
195 
197  public Task MoveFile(string source, string destination, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
198  {
199  if (destination == null)
200  throw new ArgumentNullException(nameof(destination));
201  source = ResolvePath(source ?? throw new ArgumentNullException(nameof(source)));
202  destination = ResolvePath(destination);
203  File.Move(source, destination);
204  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
205 
207  public Task MoveDirectory(string source, string destination, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
208  {
209  if (destination == null)
210  throw new ArgumentNullException(nameof(destination));
211  source = ResolvePath(source ?? throw new ArgumentNullException(nameof(source)));
212  destination = ResolvePath(destination);
213  Directory.Move(source, destination);
214  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
215 
217  public async Task<byte[]> ReadAllBytes(string path, CancellationToken cancellationToken)
218  {
219  path = ResolvePath(path);
220  using (var file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, DefaultBufferSize, true))
221  {
222  byte[] buf;
223  buf = new byte[file.Length];
224  await file.ReadAsync(buf, 0, (int)file.Length, cancellationToken).ConfigureAwait(false);
225  return buf;
226  }
227  }
228 
230  public virtual string ResolvePath(string path) => Path.GetFullPath(path ?? throw new ArgumentNullException(nameof(path)));
231 
233  public async Task WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken)
234  {
235  path = ResolvePath(path);
236  using (var file = OpenWriteStream(path))
237  await file.WriteAsync(contents, 0, contents.Length, cancellationToken).ConfigureAwait(false);
238  }
239 
241  public Task<IReadOnlyList<string>> GetDirectories(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
242  {
243  path = ResolvePath(path);
244  var results = new List<string>();
245  cancellationToken.ThrowIfCancellationRequested();
246  foreach (var I in Directory.EnumerateDirectories(path))
247  {
248  results.Add(I);
249  cancellationToken.ThrowIfCancellationRequested();
250  }
251  return (IReadOnlyList<string>)results;
252  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
253 
255  public Task<IReadOnlyList<string>> GetFiles(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
256  {
257  path = ResolvePath(path);
258  var results = new List<string>();
259  cancellationToken.ThrowIfCancellationRequested();
260  foreach (var I in Directory.EnumerateFiles(path))
261  {
262  results.Add(I);
263  cancellationToken.ThrowIfCancellationRequested();
264  }
265  return (IReadOnlyList<string>)results;
266  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
267 
269  public Task CreateSymlink(string target, string link, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
270  {
271  target = ResolvePath(target);
272  link = ResolvePath(link);
273 
274  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
275 
277  public async Task<byte[]> DownloadFile(Uri url, CancellationToken cancellationToken)
278  {
279  //DownloadDataTaskAsync can't be cancelled and is shittily written, don't use it
280  using (var wc = new WebClient())
281  {
282  var tcs = new TaskCompletionSource<byte[]>();
283  wc.DownloadDataCompleted += (a, b) =>
284  {
285  if (b.Error != null)
286  tcs.TrySetException(b.Error);
287  else if (b.Cancelled)
288  tcs.TrySetCanceled();
289  else
290  tcs.TrySetResult(b.Result);
291  };
292  wc.DownloadDataAsync(url);
293  using (cancellationToken.Register(() =>
294  {
295  wc.CancelAsync();
296  tcs.TrySetCanceled();
297  }))
298  return await tcs.Task.ConfigureAwait(false);
299  }
300  }
301 
303  public Task ZipToDirectory(string path, byte[] zipFileBytes, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
304  {
305  path = ResolvePath(path);
306  if (zipFileBytes == null)
307  throw new ArgumentNullException(nameof(zipFileBytes));
308 
309  using (var ms = new MemoryStream(zipFileBytes))
310  using (var archive = new ZipArchive(ms, ZipArchiveMode.Read))
311  archive.ExtractToDirectory(path);
312  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
313 
315  public bool PathContainsParentAccess(string path) => path?.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }).Any(x => x == "..") ?? throw new ArgumentNullException(nameof(path));
316  }
317 }
async Task WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken)
Writes some contents to a file at path overwriting previous content
string ConcatPath(params string[] paths)
Combines an array of strings into a path
async Task DeleteDirectory(string path, CancellationToken cancellationToken)
Recursively delete a directory
async Task CopyFile(string src, string dest, CancellationToken cancellationToken)
Copy a file from src to dest
static async Task NormalizeAndDelete(DirectoryInfo dir, CancellationToken cancellationToken)
Recursively empty a directory
async Task AppendAllText(string path, string additional_contents, CancellationToken cancellationToken)
async Task CopyDirectory(string src, string dest, IEnumerable< string > ignore, CancellationToken cancellationToken)
Copies a directory from src to dest
async Task< byte[]> ReadAllBytes(string path, CancellationToken cancellationToken)
Returns all the contents of a file at path as a byte array
IEnumerable< Task > CopyDirectoryImpl(string src, string dest, IEnumerable< string > ignore, CancellationToken cancellationToken)
Copies a directory from src to dest
async Task< byte[]> DownloadFile(Uri url, CancellationToken cancellationToken)
Downloads a file from url
IIOManager that resolves paths to Environment.CurrentDirectory
Interface for using filesystems
Definition: IIOManager.cs:11