tgstation-server 6.19.3
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 bool WriteFileChecked(string path, Stream data, ref string? sha1InOut, CancellationToken cancellationToken)
93 {
94 ArgumentNullException.ThrowIfNull(path);
95 ArgumentNullException.ThrowIfNull(data);
96
97 cancellationToken.ThrowIfCancellationRequested();
98 var directory = fileSystem.Path.GetDirectoryName(path) ?? throw new ArgumentException("path cannot be rooted!", nameof(path));
99 fileSystem.Directory.CreateDirectory(directory);
100
101 var newFile = !fileSystem.File.Exists(path);
102
103 cancellationToken.ThrowIfCancellationRequested();
104
105 logger.LogTrace("Starting checked write to {path} ({fileType} file)", path, newFile ? "New" : "Pre-existing");
106
107 using (var file = fileSystem.File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
108 {
109 cancellationToken.ThrowIfCancellationRequested();
110
111 // no sha1? no write
112 if (file.Length != 0 && sha1InOut == null)
113 {
114 logger.LogDebug("Aborting checked write due to missing SHA!");
115 return false;
116 }
117
118 // suppressed due to only using for consistency checks
119 using (var sha1 = SHA1.Create())
120 {
121 string? GetSha1(Stream dataToHash)
122 {
123 if (dataToHash == null)
124 return null;
125
126 byte[] sha1Computed = dataToHash.Length != 0
127 ? sha1.ComputeHash(dataToHash)
128 : sha1.ComputeHash(Array.Empty<byte>());
129
130 return String.Join(String.Empty, sha1Computed.Select(b => b.ToString("x2", CultureInfo.InvariantCulture)));
131 }
132
133 var originalSha1 = GetSha1(file);
134
135 if (!newFile)
136 logger.LogTrace("Existing SHA calculated to be {sha}", originalSha1);
137
138 if (originalSha1 != sha1InOut && !(newFile && sha1InOut == null))
139 {
140 sha1InOut = originalSha1;
141 return false;
142 }
143
144 sha1InOut = GetSha1(data);
145 }
146
147 cancellationToken.ThrowIfCancellationRequested();
148
149 if (data.Length != 0)
150 {
151 logger.LogDebug("Writing file of length {size}", data.Length);
152 file.Seek(0, SeekOrigin.Begin);
153 data.Seek(0, SeekOrigin.Begin);
154
155 cancellationToken.ThrowIfCancellationRequested();
156 file.SetLength(data.Length);
157 data.CopyTo(file);
158 }
159 }
160
161 if (data.Length == 0)
162 {
163 logger.LogDebug("Stream is empty, deleting file");
164 fileSystem.File.Delete(path);
165 }
166
167 return true;
168 }
169
171 public Stream GetFileStream(string path)
172 => fileSystem.FileStream.New(
173 path,
174 FileMode.Open,
175 FileAccess.Read,
176 FileShare.Read | FileShare.Delete,
178 true);
179 }
180}
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 .
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.