tgstation-server 6.19.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
SynchronousIOManager.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Globalization;
4using System.IO;
5using System.IO.Abstractions;
6using System.Linq;
7using System.Security.Cryptography;
8using System.Threading;
9
10using Microsoft.Extensions.Logging;
11
13{
16 {
20 readonly IFileSystem fileSystem;
21
25 readonly ILogger<SynchronousIOManager> logger;
26
32 public SynchronousIOManager(IFileSystem fileSystem, ILogger<SynchronousIOManager> logger)
33 {
34 this.fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
35 this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
36 }
37
39 public bool CreateDirectory(string path, CancellationToken cancellationToken)
40 {
41 if (IsDirectory(path))
42 return true;
43 cancellationToken.ThrowIfCancellationRequested();
44 fileSystem.Directory.CreateDirectory(path);
45 return false;
46 }
47
49 public bool DeleteDirectory(string path)
50 {
51 if (fileSystem.File.Exists(path))
52 return false;
53
54 if (!fileSystem.Directory.Exists(path))
55 return true;
56
57 if (fileSystem.Directory.EnumerateFileSystemEntries(path).Any())
58 return false;
59
60 fileSystem.Directory.Delete(path);
61 return true;
62 }
63
65 public IEnumerable<string> GetDirectories(string path, CancellationToken cancellationToken)
66 {
67 foreach (var directoryName in fileSystem.Directory.EnumerateDirectories(path))
68 {
69 yield return fileSystem.Path.GetFileName(directoryName);
70 cancellationToken.ThrowIfCancellationRequested();
71 }
72 }
73
75 public IEnumerable<string> GetFiles(string path, CancellationToken cancellationToken)
76 {
77 foreach (var fileName in fileSystem.Directory.EnumerateFiles(path))
78 {
79 yield return fileSystem.Path.GetFileName(fileName);
80 cancellationToken.ThrowIfCancellationRequested();
81 }
82 }
83
85 public bool IsDirectory(string path)
86 {
87 ArgumentNullException.ThrowIfNull(path);
88 return fileSystem.Directory.Exists(path);
89 }
90
92 public byte[] ReadFile(string path)
93 {
94 ArgumentNullException.ThrowIfNull(path);
95 return fileSystem.File.ReadAllBytes(path);
96 }
97
99 public bool WriteFileChecked(string path, Stream data, ref string? sha1InOut, CancellationToken cancellationToken)
100 {
101 ArgumentNullException.ThrowIfNull(path);
102 ArgumentNullException.ThrowIfNull(data);
103
104 cancellationToken.ThrowIfCancellationRequested();
105 var directory = fileSystem.Path.GetDirectoryName(path) ?? throw new ArgumentException("path cannot be rooted!", nameof(path));
106 fileSystem.Directory.CreateDirectory(directory);
107
108 var newFile = !fileSystem.File.Exists(path);
109
110 cancellationToken.ThrowIfCancellationRequested();
111
112 logger.LogTrace("Starting checked write to {path} ({fileType} file)", path, newFile ? "New" : "Pre-existing");
113
114 using (var file = fileSystem.File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
115 {
116 cancellationToken.ThrowIfCancellationRequested();
117
118 // no sha1? no write
119 if (file.Length != 0 && sha1InOut == null)
120 {
121 logger.LogDebug("Aborting checked write due to missing SHA!");
122 return false;
123 }
124
125 // suppressed due to only using for consistency checks
126 using (var sha1 = SHA1.Create())
127 {
128 string? GetSha1(Stream dataToHash)
129 {
130 if (dataToHash == null)
131 return null;
132
133 byte[] sha1Computed = dataToHash.Length != 0
134 ? sha1.ComputeHash(dataToHash)
135 : sha1.ComputeHash(Array.Empty<byte>());
136
137 return String.Join(String.Empty, sha1Computed.Select(b => b.ToString("x2", CultureInfo.InvariantCulture)));
138 }
139
140 var originalSha1 = GetSha1(file);
141
142 if (!newFile)
143 logger.LogTrace("Existing SHA calculated to be {sha}", originalSha1);
144
145 if (originalSha1 != sha1InOut && !(newFile && sha1InOut == null))
146 {
147 sha1InOut = originalSha1;
148 return false;
149 }
150
151 sha1InOut = GetSha1(data);
152 }
153
154 cancellationToken.ThrowIfCancellationRequested();
155
156 if (data.Length != 0)
157 {
158 logger.LogDebug("Writing file of length {size}", data.Length);
159 file.Seek(0, SeekOrigin.Begin);
160 data.Seek(0, SeekOrigin.Begin);
161
162 cancellationToken.ThrowIfCancellationRequested();
163 file.SetLength(data.Length);
164 data.CopyTo(file);
165 }
166 }
167
168 if (data.Length == 0)
169 {
170 logger.LogDebug("Stream is empty, deleting file");
171 fileSystem.File.Delete(path);
172 }
173
174 return true;
175 }
176
178 public Stream GetFileStream(string path)
179 => fileSystem.FileStream.New(
180 path,
181 FileMode.Open,
182 FileAccess.Read,
183 FileShare.Read | FileShare.Delete,
185 true);
186 }
187}
IIOManager that resolves paths to Environment.CurrentDirectory.
const int DefaultBufferSize
Default FileStream buffer size used by .NET.
bool DeleteDirectory(string path)
Deletes a directory at path if it's empty.true if the directory does not exist or is empty and was d...
IEnumerable< string > GetDirectories(string path, CancellationToken cancellationToken)
Enumerate directories in a given path .A IEnumerable<T> of directory names in path .
byte[] ReadFile(string path)
Read the bytes of a file at a given path .A byte array representing the contents of the file at path ...
bool CreateDirectory(string path, CancellationToken cancellationToken)
Create an empty directory at path .true if the directory already existed, false otherwise.
SynchronousIOManager(IFileSystem fileSystem, ILogger< SynchronousIOManager > logger)
Initializes a new instance of the SynchronousIOManager class.
bool WriteFileChecked(string path, Stream data, ref string? sha1InOut, CancellationToken cancellationToken)
Write data to a file at a given path .true on success, false if the operation failed due to sha1InOu...
readonly ILogger< SynchronousIOManager > logger
The ILogger for the SynchronousIOManager.
bool IsDirectory(string path)
Checks if a given path is a directory.true if path is a directory, false otherwise.
IEnumerable< string > GetFiles(string path, CancellationToken cancellationToken)
Enumerate files in a given path .A IEnumerable<T> of file names in path .
Stream GetFileStream(string path)
Gets the Stream for a given file path without write share.The Stream of the file.
readonly IFileSystem fileSystem
The IFileSystem to use.
For accessing the disk in a synchronous manner.