tgstation-server  4.3.2
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.IO;
4 using System.IO.Compression;
5 using System.Linq;
6 using System.Net;
7 using System.Threading;
8 using System.Threading.Tasks;
9 
10 namespace Tgstation.Server.Host.IO
11 {
16  {
20  public const string CurrentDirectory = ".";
21 
25  public const int DefaultBufferSize = 4096;
26 
33  static async Task NormalizeAndDelete(DirectoryInfo dir, CancellationToken cancellationToken)
34  {
35  var tasks = new List<Task>();
36 
37  // check if we are a symbolic link
38  if (!dir.Attributes.HasFlag(FileAttributes.Directory) || dir.Attributes.HasFlag(FileAttributes.ReparsePoint))
39  {
40  dir.Delete();
41  return;
42  }
43 
44  foreach (var subDir in dir.EnumerateDirectories())
45  {
46  cancellationToken.ThrowIfCancellationRequested();
47  tasks.Add(NormalizeAndDelete(subDir, cancellationToken));
48  }
49 
50  foreach (var file in dir.EnumerateFiles())
51  {
52  cancellationToken.ThrowIfCancellationRequested();
53  file.Attributes = FileAttributes.Normal;
54  file.Delete();
55  }
56 
57  await Task.WhenAll(tasks).ConfigureAwait(false);
58  cancellationToken.ThrowIfCancellationRequested();
59  dir.Delete(true);
60  }
61 
67  static FileStream OpenWriteStream(string path) => new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite, DefaultBufferSize, true);
68 
77  IEnumerable<Task> CopyDirectoryImpl(string src, string dest, IEnumerable<string> ignore, CancellationToken cancellationToken)
78  {
79  var dir = new DirectoryInfo(src);
80  var atLeastOneSubDir = false;
81  foreach (var I in dir.EnumerateDirectories())
82  {
83  if (ignore != null && ignore.Contains(I.Name))
84  continue;
85  foreach (var J in CopyDirectoryImpl(I.FullName, Path.Combine(dest, I.Name), null, cancellationToken))
86  {
87  atLeastOneSubDir = true;
88  yield return J;
89  }
90  }
91 
92  async Task CopyThisDirectory()
93  {
94  if (!atLeastOneSubDir)
95  await CreateDirectory(dest, cancellationToken).ConfigureAwait(false); // save on createdir calls
96 
97  var tasks = new List<Task>();
98 
99  await dir.EnumerateFiles().ToAsyncEnumerable().ForEachAsync(fileInfo =>
100  {
101  if (ignore != null && ignore.Contains(fileInfo.Name))
102  return;
103  tasks.Add(CopyFile(fileInfo.FullName, Path.Combine(dest, fileInfo.Name), cancellationToken));
104  }).ConfigureAwait(false);
105 
106  await Task.WhenAll(tasks).ConfigureAwait(false);
107  }
108 
109  yield return CopyThisDirectory();
110  }
111 
113  public async Task CopyDirectory(string src, string dest, IEnumerable<string> ignore, CancellationToken cancellationToken)
114  {
115  if (dest == null)
116  throw new ArgumentNullException(nameof(src));
117  if (dest == null)
118  throw new ArgumentNullException(nameof(src));
119 
120  src = ResolvePath(src);
121  dest = ResolvePath(dest);
122  foreach (var directoryCopy in CopyDirectoryImpl(src, dest, ignore, cancellationToken))
123  await directoryCopy.ConfigureAwait(false);
124  }
125 
127  public string ConcatPath(params string[] paths)
128  {
129  if (paths == null)
130  throw new ArgumentNullException(nameof(paths));
131  return Path.Combine(paths);
132  }
133 
135  public async Task CopyFile(string src, string dest, CancellationToken cancellationToken)
136  {
137  if (src == null)
138  throw new ArgumentNullException(nameof(src));
139  if (dest == null)
140  throw new ArgumentNullException(nameof(dest));
141  using var srcStream = new FileStream(ResolvePath(src), FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, DefaultBufferSize, true);
142  using var destStream = new FileStream(ResolvePath(dest), FileMode.Create, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete, DefaultBufferSize, true);
143  await srcStream.CopyToAsync(destStream, 81920, cancellationToken).ConfigureAwait(false);
144  }
145 
147  public Task CreateDirectory(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => Directory.CreateDirectory(ResolvePath(path)), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
148 
150  public Task DeleteDirectory(string path, CancellationToken cancellationToken)
151  {
152  path = ResolvePath(path);
153  var di = new DirectoryInfo(path);
154  if (!di.Exists)
155  return Task.CompletedTask;
156 
157  return Task.Factory.StartNew(
158  () => NormalizeAndDelete(di, cancellationToken),
159  cancellationToken,
160  TaskCreationOptions.LongRunning,
161  TaskScheduler.Current);
162  }
163 
165  public Task DeleteFile(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => File.Delete(ResolvePath(path)), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
166 
168  public Task<bool> FileExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => File.Exists(ResolvePath(path)), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
169 
171  public Task<bool> DirectoryExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => Directory.Exists(ResolvePath(path)), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
172 
174  public string GetDirectoryName(string path) => Path.GetDirectoryName(path ?? throw new ArgumentNullException(nameof(path)));
175 
177  public string GetFileName(string path) => Path.GetFileName(path ?? throw new ArgumentNullException(nameof(path)));
178 
180  public string GetFileNameWithoutExtension(string path) => Path.GetFileNameWithoutExtension(path ?? throw new ArgumentNullException(nameof(path)));
181 
183  public Task<List<string>> GetFilesWithExtension(string path, string extension, bool recursive, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
184  {
185  path = ResolvePath(path);
186  if (extension == null)
187  throw new ArgumentNullException(extension);
188  var results = new List<string>();
189  foreach (var I in Directory.EnumerateFiles(
190  path,
191  $"*.{extension}",
192  recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly))
193  {
194  cancellationToken.ThrowIfCancellationRequested();
195  results.Add(I);
196  }
197 
198  return results;
199  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
200 
202  public Task MoveFile(string source, string destination, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
203  {
204  if (destination == null)
205  throw new ArgumentNullException(nameof(destination));
206  source = ResolvePath(source ?? throw new ArgumentNullException(nameof(source)));
207  destination = ResolvePath(destination);
208  File.Move(source, destination);
209  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
210 
212  public Task MoveDirectory(string source, string destination, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
213  {
214  if (destination == null)
215  throw new ArgumentNullException(nameof(destination));
216  source = ResolvePath(source ?? throw new ArgumentNullException(nameof(source)));
217  destination = ResolvePath(destination);
218  Directory.Move(source, destination);
219  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
220 
222  public async Task<byte[]> ReadAllBytes(string path, CancellationToken cancellationToken)
223  {
224  path = ResolvePath(path);
225  using var file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, DefaultBufferSize, true);
226  byte[] buf;
227  buf = new byte[file.Length];
228  await file.ReadAsync(buf, 0, (int)file.Length, cancellationToken).ConfigureAwait(false);
229  return buf;
230  }
231 
233  public string ResolvePath() => ResolvePath(CurrentDirectory);
234 
236  public virtual string ResolvePath(string path) => Path.GetFullPath(path ?? throw new ArgumentNullException(nameof(path)));
237 
239  public async Task WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken)
240  {
241  path = ResolvePath(path);
242  using var file = OpenWriteStream(path);
243  await file.WriteAsync(contents, 0, contents.Length, cancellationToken).ConfigureAwait(false);
244  }
245 
247  public Task<IReadOnlyList<string>> GetDirectories(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
248  {
249  path = ResolvePath(path);
250  var results = new List<string>();
251  cancellationToken.ThrowIfCancellationRequested();
252  foreach (var I in Directory.EnumerateDirectories(path))
253  {
254  results.Add(I);
255  cancellationToken.ThrowIfCancellationRequested();
256  }
257 
258  return (IReadOnlyList<string>)results;
259  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
260 
262  public Task<IReadOnlyList<string>> GetFiles(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
263  {
264  path = ResolvePath(path);
265  var results = new List<string>();
266  cancellationToken.ThrowIfCancellationRequested();
267  foreach (var I in Directory.EnumerateFiles(path))
268  {
269  results.Add(I);
270  cancellationToken.ThrowIfCancellationRequested();
271  }
272 
273  return (IReadOnlyList<string>)results;
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  var tcs = new TaskCompletionSource<byte[]>();
282  wc.DownloadDataCompleted += (a, b) =>
283  {
284  if (b.Error != null)
285  tcs.TrySetException(b.Error);
286  else if (b.Cancelled)
287  tcs.TrySetCanceled();
288  else
289  tcs.TrySetResult(b.Result);
290  };
291  wc.DownloadDataAsync(url);
292  using (cancellationToken.Register(() =>
293  {
294  wc.CancelAsync();
295  tcs.TrySetCanceled();
296  }))
297  return await tcs.Task.ConfigureAwait(false);
298  }
299 
301  public Task ZipToDirectory(string path, byte[] zipFileBytes, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
302  {
303  path = ResolvePath(path);
304  if (zipFileBytes == null)
305  throw new ArgumentNullException(nameof(zipFileBytes));
306 
307  using var ms = new MemoryStream(zipFileBytes);
308  using var archive = new ZipArchive(ms, ZipArchiveMode.Read);
309  archive.ExtractToDirectory(path);
310  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
311 
313  public bool PathContainsParentAccess(string path) => path?.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }).Any(x => x == "..") ?? throw new ArgumentNullException(nameof(path));
314  }
315 }
Task DeleteDirectory(string path, CancellationToken cancellationToken)
Recursively delete a directory, removes and does not enter any symlinks encounterd.
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 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 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