From 2470b1ab06a891ee11e09cdbd93fb675eca10f49 Mon Sep 17 00:00:00 2001 From: Noah Stolk <31079637+NoahStolk@users.noreply.github.com> Date: Wed, 5 Apr 2023 21:03:00 +0200 Subject: [PATCH] Stop doing file IO directly in web server --- .../Utils/TestData.cs | 79 +++++++++++- .../CustomEntryProcessorTests.cs | 2 - .../ModArchiveProcessorTests.cs | 9 -- .../Services/CustomEntryService.cs | 4 +- .../Services/CustomLeaderboardService.cs | 4 +- .../Services/SpawnsetService.cs | 10 +- .../Services/ToolService.cs | 4 +- .../Repositories/CustomEntryRepository.cs | 10 +- .../Repositories/ToolRepository.cs | 8 +- .../Caching/LeaderboardHistoryCache.cs | 10 +- .../Services/Caching/ModArchiveCache.cs | 20 +-- .../Services/Caching/SpawnsetHashCache.cs | 4 +- .../Services/Caching/SpawnsetSummaryCache.cs | 14 ++- .../Services/CustomEntryProcessor.cs | 8 +- .../Services/Inversion/IFileSystemService.cs | 48 ++++++- .../Services/ModArchiveAccessor.cs | 6 +- .../Services/ModArchiveProcessor.cs | 17 +-- .../Services/ModScreenshotProcessor.cs | 16 ++- .../Controllers/App/SpawnsetsController.cs | 8 +- .../Controllers/Ddae/AssetsController.cs | 4 +- .../Controllers/Ddse/SpawnsetsController.cs | 2 +- .../Main/CustomEntriesController.cs | 2 +- .../Main/ModScreenshotsController.cs | 4 +- .../Controllers/Main/ModsController.cs | 4 +- .../Controllers/Main/SpawnsetsController.cs | 16 +-- .../LeaderboardHistoryBackgroundService.cs | 4 +- .../Services/FileSystemService.cs | 117 +++++++++++++++++- .../DevilDaggersInfo.Web.Server/_Imports.cs | 1 - 28 files changed, 329 insertions(+), 106 deletions(-) delete mode 100644 src/web-server/DevilDaggersInfo.Web.Server/_Imports.cs diff --git a/src/tests/DevilDaggersInfo.Web.Server.Domain.Main.Tests/Utils/TestData.cs b/src/tests/DevilDaggersInfo.Web.Server.Domain.Main.Tests/Utils/TestData.cs index 2edd40b657..1cf0a76a31 100644 --- a/src/tests/DevilDaggersInfo.Web.Server.Domain.Main.Tests/Utils/TestData.cs +++ b/src/tests/DevilDaggersInfo.Web.Server.Domain.Main.Tests/Utils/TestData.cs @@ -88,10 +88,87 @@ public LeaderboardHistory GetLeaderboardHistoryByFilePath(string filePath) } #pragma warning disable SA1201 - public string Root => throw new NotImplementedException(); public string GetLeaderboardHistoryPathFromDate(DateTime dateTime) => throw new NotImplementedException(); public string GetPath(DataSubDirectory subDirectory) => throw new NotImplementedException(); public string GetToolDistributionPath(string name, ToolPublishMethod publishMethod, ToolBuildType buildType, string version) => throw new NotImplementedException(); + public bool DeleteFileIfExists(string path) => throw new NotImplementedException(); + public bool FileExists(string path) + { + throw new NotImplementedException(); + } + public bool DeleteDirectoryIfExists(string path, bool recursive) => throw new NotImplementedException(); + public bool DirectoryExists(string path) + { + throw new NotImplementedException(); + } + public void MoveDirectory(string sourcePath, string destinationPath) + { + throw new NotImplementedException(); + } + public void CreateDirectory(string path) + { + throw new NotImplementedException(); + } + public string[] GetFiles(string path) + { + throw new NotImplementedException(); + } + public string[] GetFiles(string path, string searchPattern) + { + throw new NotImplementedException(); + } + public byte[] ReadAllBytes(string path) + { + throw new NotImplementedException(); + } + public Task ReadAllBytesAsync(string path) + { + throw new NotImplementedException(); + } + public Task ReadAllBytesAsync(string path, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + public void WriteAllBytes(string path, byte[] bytes) + { + throw new NotImplementedException(); + } + public Task WriteAllBytesAsync(string path, byte[] bytes) + { + throw new NotImplementedException(); + } + public Task WriteAllBytesAsync(string path, byte[] bytes, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + public void MoveFile(string sourcePath, string destinationPath) + { + throw new NotImplementedException(); + } + public string ReadAllText(string path) + { + throw new NotImplementedException(); + } + public Task ReadAllTextAsync(string path) + { + throw new NotImplementedException(); + } + public Task ReadAllTextAsync(string path, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + public void WriteAllText(string path, string text) + { + throw new NotImplementedException(); + } + public Task WriteAllTextAsync(string path, string text) + { + throw new NotImplementedException(); + } + public Task WriteAllTextAsync(string path, string text, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } public int GetCount() => throw new NotImplementedException(); public void Clear() => throw new NotImplementedException(); #pragma warning restore SA1201 diff --git a/src/tests/DevilDaggersInfo.Web.Server.Domain.Tests/CustomEntryProcessorTests.cs b/src/tests/DevilDaggersInfo.Web.Server.Domain.Tests/CustomEntryProcessorTests.cs index e3646a8d54..082a85841c 100644 --- a/src/tests/DevilDaggersInfo.Web.Server.Domain.Tests/CustomEntryProcessorTests.cs +++ b/src/tests/DevilDaggersInfo.Web.Server.Domain.Tests/CustomEntryProcessorTests.cs @@ -44,8 +44,6 @@ public CustomEntryProcessorTests() fileSystemService.Setup(m => m.GetPath(DataSubDirectory.Spawnsets)).Returns(spawnsetsPath); fileSystemService.Setup(m => m.GetPath(DataSubDirectory.CustomEntryReplays)).Returns(replaysPath); - Directory.CreateDirectory(replaysPath); - Mock> spawnsetHashCacheLogger = new(); Mock spawnsetHashCache = new(fileSystemService.Object, spawnsetHashCacheLogger.Object); Mock> customEntryProcessorLogger = new(); diff --git a/src/tests/DevilDaggersInfo.Web.Server.Domain.Tests/ModArchiveProcessorTests.cs b/src/tests/DevilDaggersInfo.Web.Server.Domain.Tests/ModArchiveProcessorTests.cs index 76b1dd255a..a3607b3830 100644 --- a/src/tests/DevilDaggersInfo.Web.Server.Domain.Tests/ModArchiveProcessorTests.cs +++ b/src/tests/DevilDaggersInfo.Web.Server.Domain.Tests/ModArchiveProcessorTests.cs @@ -18,19 +18,10 @@ protected ModArchiveProcessorTests() string modsPath = Path.Combine(TestUtils.ResourcePath, "Mods"); string modArchiveCachePath = Path.Combine(TestUtils.ResourcePath, "ModArchiveCache"); - if (Directory.Exists(modsPath)) - Directory.Delete(modsPath, true); - - if (Directory.Exists(modArchiveCachePath)) - Directory.Delete(modArchiveCachePath, true); - Mock fileSystemService = new(); fileSystemService.Setup(m => m.GetPath(DataSubDirectory.Mods)).Returns(modsPath); fileSystemService.Setup(m => m.GetPath(DataSubDirectory.ModArchiveCache)).Returns(modArchiveCachePath); - Directory.CreateDirectory(modsPath); - Directory.CreateDirectory(modArchiveCachePath); - Cache = new(fileSystemService.Object); Accessor = new(fileSystemService.Object, Cache); Processor = new(fileSystemService.Object, Cache, Accessor); diff --git a/src/web-server/DevilDaggersInfo.Web.Server.Domain.Admin/Services/CustomEntryService.cs b/src/web-server/DevilDaggersInfo.Web.Server.Domain.Admin/Services/CustomEntryService.cs index 4960f22185..b69ebae445 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server.Domain.Admin/Services/CustomEntryService.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server.Domain.Admin/Services/CustomEntryService.cs @@ -104,8 +104,6 @@ public async Task DeleteCustomEntryAsync(int id) await _dbContext.SaveChangesAsync(); string path = Path.Combine(_fileSystemService.GetPath(DataSubDirectory.CustomEntryReplays), $"{id}.ddreplay"); - bool fileExists = File.Exists(path); - if (fileExists) - File.Delete(path); + _fileSystemService.DeleteFileIfExists(path); } } diff --git a/src/web-server/DevilDaggersInfo.Web.Server.Domain.Admin/Services/CustomLeaderboardService.cs b/src/web-server/DevilDaggersInfo.Web.Server.Domain.Admin/Services/CustomLeaderboardService.cs index 35d8625984..88fef3f152 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server.Domain.Admin/Services/CustomLeaderboardService.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server.Domain.Admin/Services/CustomLeaderboardService.cs @@ -461,10 +461,10 @@ private void ValidateCustomLeaderboard( throw new CustomLeaderboardValidationException($"Spawnset with ID '{spawnsetId}' does not exist."); string spawnsetFilePath = Path.Combine(_fileSystemService.GetPath(DataSubDirectory.Spawnsets), spawnset.Name); - if (!File.Exists(spawnsetFilePath)) + if (!_fileSystemService.FileExists(spawnsetFilePath)) throw new InvalidOperationException($"Spawnset file '{spawnset.Name}' does not exist. Spawnset with ID '{spawnsetId}' does not have a file which should never happen."); - if (!SpawnsetBinary.TryParse(File.ReadAllBytes(spawnsetFilePath), out SpawnsetBinary? spawnsetBinary)) + if (!SpawnsetBinary.TryParse(_fileSystemService.ReadAllBytes(spawnsetFilePath), out SpawnsetBinary? spawnsetBinary)) throw new InvalidOperationException($"Could not parse survival file '{spawnset.Name}'. Please review the file. Also review how this file ended up in the 'spawnsets' directory, as it should not be possible to upload non-survival files from the Admin API."); GameMode requiredGameMode = category.RequiredGameModeForCategory(); diff --git a/src/web-server/DevilDaggersInfo.Web.Server.Domain.Admin/Services/SpawnsetService.cs b/src/web-server/DevilDaggersInfo.Web.Server.Domain.Admin/Services/SpawnsetService.cs index 6717ff9668..39b032324b 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server.Domain.Admin/Services/SpawnsetService.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server.Domain.Admin/Services/SpawnsetService.cs @@ -45,7 +45,7 @@ public async Task AddSpawnsetAsync(AddSpawnset addSpawnset) // Add file. string path = Path.Combine(_fileSystemService.GetPath(DataSubDirectory.Spawnsets), addSpawnset.Name); - await File.WriteAllBytesAsync(path, addSpawnset.FileContents); + await _fileSystemService.WriteAllBytesAsync(path, addSpawnset.FileContents); // Add entity. SpawnsetEntity spawnset = new() @@ -80,7 +80,7 @@ public async Task EditSpawnsetAsync(int id, EditSpawnset editSpawnset) string directory = _fileSystemService.GetPath(DataSubDirectory.Spawnsets); string oldPath = Path.Combine(directory, spawnset.Name); string newPath = Path.Combine(directory, editSpawnset.Name); - File.Move(oldPath, newPath); + _fileSystemService.MoveFile(oldPath, newPath); _spawnsetHashCache.Clear(); } @@ -104,12 +104,8 @@ public async Task DeleteSpawnsetAsync(int id) throw new AdminDomainException("Spawnset with custom leaderboard cannot be deleted."); string path = Path.Combine(_fileSystemService.GetPath(DataSubDirectory.Spawnsets), spawnset.Name); - bool fileExists = File.Exists(path); - if (fileExists) - { - File.Delete(path); + if (_fileSystemService.DeleteFileIfExists(path)) _spawnsetHashCache.Clear(); - } _dbContext.Spawnsets.Remove(spawnset); await _dbContext.SaveChangesAsync(); diff --git a/src/web-server/DevilDaggersInfo.Web.Server.Domain.Admin/Services/ToolService.cs b/src/web-server/DevilDaggersInfo.Web.Server.Domain.Admin/Services/ToolService.cs index ad1a354f98..e6cc842768 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server.Domain.Admin/Services/ToolService.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server.Domain.Admin/Services/ToolService.cs @@ -34,10 +34,10 @@ public async Task AddDistribution(string name, ToolPublishMethod publishMethod, throw new AdminDomainException("Distribution already exists."); string path = _fileSystemService.GetToolDistributionPath(name, publishMethod, buildType, version); - if (File.Exists(path)) + if (_fileSystemService.FileExists(path)) throw new AdminDomainException("File for distribution already exists, but does not exist in the database. Please review the database and the file system."); - await File.WriteAllBytesAsync(path, zipFileContents); + await _fileSystemService.WriteAllBytesAsync(path, zipFileContents); if (updateVersion) tool.CurrentVersionNumber = version; diff --git a/src/web-server/DevilDaggersInfo.Web.Server.Domain/Repositories/CustomEntryRepository.cs b/src/web-server/DevilDaggersInfo.Web.Server.Domain/Repositories/CustomEntryRepository.cs index f3d027a1ab..e48d024424 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server.Domain/Repositories/CustomEntryRepository.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server.Domain/Repositories/CustomEntryRepository.cs @@ -20,16 +20,16 @@ public CustomEntryRepository(ApplicationDbContext dbContext, IFileSystemService public byte[] GetCustomEntryReplayBufferById(int id) { string path = Path.Combine(_fileSystemService.GetPath(DataSubDirectory.CustomEntryReplays), $"{id}.ddreplay"); - if (!File.Exists(path)) + if (!_fileSystemService.FileExists(path)) throw new NotFoundException($"Replay file with ID '{id}' could not be found."); - return File.ReadAllBytes(path); + return _fileSystemService.ReadAllBytes(path); } public (string FileName, byte[] Contents) GetCustomEntryReplayById(int id) { string path = Path.Combine(_fileSystemService.GetPath(DataSubDirectory.CustomEntryReplays), $"{id}.ddreplay"); - if (!File.Exists(path)) + if (!_fileSystemService.FileExists(path)) throw new NotFoundException($"Replay file with ID '{id}' could not be found."); // ! Navigation property. @@ -48,13 +48,13 @@ public byte[] GetCustomEntryReplayBufferById(int id) throw new NotFoundException($"Custom entry replay '{id}' could not be found."); string fileName = $"{customEntry.SpawnsetId}-{customEntry.SpawnsetName}-{customEntry.PlayerId}-{customEntry.PlayerName}.ddreplay"; - return (fileName, File.ReadAllBytes(path)); + return (fileName, _fileSystemService.ReadAllBytes(path)); } public List GetExistingCustomEntryReplayIds(List ids) { return ids - .Where(id => File.Exists(Path.Combine(_fileSystemService.GetPath(DataSubDirectory.CustomEntryReplays), $"{id}.ddreplay"))) + .Where(id => _fileSystemService.FileExists(Path.Combine(_fileSystemService.GetPath(DataSubDirectory.CustomEntryReplays), $"{id}.ddreplay"))) .Select(id => id) .ToList(); } diff --git a/src/web-server/DevilDaggersInfo.Web.Server.Domain/Repositories/ToolRepository.cs b/src/web-server/DevilDaggersInfo.Web.Server.Domain/Repositories/ToolRepository.cs index a18c32eebb..93cb4ab65d 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server.Domain/Repositories/ToolRepository.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server.Domain/Repositories/ToolRepository.cs @@ -26,7 +26,7 @@ public ToolRepository(ApplicationDbContext dbContext, IFileSystemService fileSys private async Task GetJsonString(string name) { - return await File.ReadAllTextAsync(Path.Combine(_fileSystemService.GetPath(DataSubDirectory.Tools), $"{name}.json")); + return await _fileSystemService.ReadAllTextAsync(Path.Combine(_fileSystemService.GetPath(DataSubDirectory.Tools), $"{name}.json")); } public async Task GetToolAsync(string name) @@ -69,13 +69,13 @@ private async Task GetJsonString(string name) public byte[] GetToolDistributionFile(string name, ToolPublishMethod publishMethod, ToolBuildType buildType, string version) { string path = _fileSystemService.GetToolDistributionPath(name, publishMethod, buildType, version); - if (!File.Exists(path)) + if (!_fileSystemService.FileExists(path)) { _logger.LogError("Tool distribution file at '{path}' does not exist!", path); throw new NotFoundException("Tool distribution file does not exist."); } - return File.ReadAllBytes(path); + return _fileSystemService.ReadAllBytes(path); } public async Task GetLatestToolDistributionAsync(string name, ToolPublishMethod publishMethod, ToolBuildType buildType) @@ -125,7 +125,7 @@ public async Task UpdateToolDistributionStatisticsAsync(string name, ToolPublish private int GetToolDistributionFileSize(string name, ToolPublishMethod publishMethod, ToolBuildType buildType, string version) { string path = _fileSystemService.GetToolDistributionPath(name, publishMethod, buildType, version); - if (File.Exists(path)) + if (_fileSystemService.FileExists(path)) return (int)new FileInfo(path).Length; return 0; diff --git a/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/Caching/LeaderboardHistoryCache.cs b/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/Caching/LeaderboardHistoryCache.cs index c5379d09ee..72151ff211 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/Caching/LeaderboardHistoryCache.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/Caching/LeaderboardHistoryCache.cs @@ -1,4 +1,5 @@ using DevilDaggersInfo.Web.Server.Domain.Models.LeaderboardHistory; +using DevilDaggersInfo.Web.Server.Domain.Services.Inversion; using System.Collections.Concurrent; namespace DevilDaggersInfo.Web.Server.Domain.Services.Caching; @@ -7,13 +8,20 @@ public class LeaderboardHistoryCache : ILeaderboardHistoryCache { private readonly ConcurrentDictionary _cache = new(); + private readonly IFileSystemService _fileSystemService; + + public LeaderboardHistoryCache(IFileSystemService fileSystemService) + { + _fileSystemService = fileSystemService; + } + public LeaderboardHistory GetLeaderboardHistoryByFilePath(string filePath) { string name = Path.GetFileNameWithoutExtension(filePath); if (_cache.TryGetValue(name, out LeaderboardHistory? value)) return value; - LeaderboardHistory lb = LeaderboardHistory.CreateFromFile(File.ReadAllBytes(filePath)); + LeaderboardHistory lb = LeaderboardHistory.CreateFromFile(_fileSystemService.ReadAllBytes(filePath)); _cache.TryAdd(name, lb); return lb; } diff --git a/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/Caching/ModArchiveCache.cs b/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/Caching/ModArchiveCache.cs index 34726d3801..a4dff3bd62 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/Caching/ModArchiveCache.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/Caching/ModArchiveCache.cs @@ -25,8 +25,8 @@ public ModArchiveCache(IFileSystemService fileSystemService) public ModArchiveCacheData GetArchiveDataByBytes(string name, byte[] bytes) { // Check memory cache. - if (_cache.ContainsKey(name)) - return _cache[name]; + if (_cache.TryGetValue(name, out ModArchiveCacheData? value)) + return value; // Check file cache. ModArchiveCacheData? fileCache = LoadFromFileCache(name); @@ -42,8 +42,8 @@ public ModArchiveCacheData GetArchiveDataByFilePath(string filePath) { // Check memory cache. string name = Path.GetFileNameWithoutExtension(filePath); - if (_cache.ContainsKey(name)) - return _cache[name]; + if (_cache.TryGetValue(name, out ModArchiveCacheData? value)) + return value; // Check file cache. ModArchiveCacheData? fileCache = LoadFromFileCache(name); @@ -61,10 +61,10 @@ public ModArchiveCacheData GetArchiveDataByFilePath(string filePath) private ModArchiveCacheData? LoadFromFileCache(string name) { string fileCachePath = Path.Combine(_fileSystemService.GetPath(DataSubDirectory.ModArchiveCache), $"{name}.json"); - if (!File.Exists(fileCachePath)) + if (!_fileSystemService.FileExists(fileCachePath)) return null; - ModArchiveCacheData? fileCacheArchiveData = JsonConvert.DeserializeObject(File.ReadAllText(fileCachePath)); + ModArchiveCacheData? fileCacheArchiveData = JsonConvert.DeserializeObject(_fileSystemService.ReadAllText(fileCachePath)); if (fileCacheArchiveData == null) return null; @@ -114,17 +114,17 @@ private ModArchiveCacheData CreateModArchiveCacheDataFromStream(string name, Str private void WriteToFileCache(string name, ModArchiveCacheData archiveData) { string fileCacheDirectory = _fileSystemService.GetPath(DataSubDirectory.ModArchiveCache); - Directory.CreateDirectory(fileCacheDirectory); + _fileSystemService.CreateDirectory(fileCacheDirectory); - File.WriteAllText(Path.Combine(fileCacheDirectory, $"{name}.json"), JsonConvert.SerializeObject(archiveData)); + _fileSystemService.WriteAllText(Path.Combine(fileCacheDirectory, $"{name}.json"), JsonConvert.SerializeObject(archiveData)); } public void LoadEntireFileCache() { string fileCacheDirectory = _fileSystemService.GetPath(DataSubDirectory.ModArchiveCache); - Directory.CreateDirectory(fileCacheDirectory); + _fileSystemService.CreateDirectory(fileCacheDirectory); - foreach (string path in Directory.GetFiles(fileCacheDirectory, "*.json")) + foreach (string path in _fileSystemService.GetFiles(fileCacheDirectory, "*.json")) { string name = Path.GetFileNameWithoutExtension(path); LoadFromFileCache(name); diff --git a/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/Caching/SpawnsetHashCache.cs b/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/Caching/SpawnsetHashCache.cs index c119436675..fa3b625b99 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/Caching/SpawnsetHashCache.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/Caching/SpawnsetHashCache.cs @@ -28,9 +28,9 @@ public SpawnsetHashCache(IFileSystemService fileSystemService, ILogger _cache = new(); + private readonly IFileSystemService _fileSystemService; + + public SpawnsetSummaryCache(IFileSystemService fileSystemService) + { + _fileSystemService = fileSystemService; + } + public SpawnsetSummary GetSpawnsetSummaryByFilePath(string filePath) { string name = Path.GetFileNameWithoutExtension(filePath); - if (_cache.ContainsKey(name)) - return _cache[name]; + if (_cache.TryGetValue(name, out SpawnsetSummary? value)) + return value; - if (!SpawnsetSummary.TryParse(File.ReadAllBytes(filePath), out SpawnsetSummary? spawnsetSummary)) + if (!SpawnsetSummary.TryParse(_fileSystemService.ReadAllBytes(filePath), out SpawnsetSummary? spawnsetSummary)) throw new($"Failed to get spawnset summary from spawnset file: '{name}'."); _cache.TryAdd(name, spawnsetSummary); diff --git a/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/CustomEntryProcessor.cs b/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/CustomEntryProcessor.cs index 7082c980b4..a533e59587 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/CustomEntryProcessor.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/CustomEntryProcessor.cs @@ -591,7 +591,7 @@ private void LogAndThrowValidationException(UploadRequest uploadRequest, string private async Task WriteReplayFile(int customEntryId, byte[] replayData) { - await File.WriteAllBytesAsync(Path.Combine(_fileSystemService.GetPath(DataSubDirectory.CustomEntryReplays), $"{customEntryId}.ddreplay"), replayData); + await _fileSystemService.WriteAllBytesAsync(Path.Combine(_fileSystemService.GetPath(DataSubDirectory.CustomEntryReplays), $"{customEntryId}.ddreplay"), replayData); } private static bool IsReplayTimeAlmostTheSame(int requestTimeAsInt, int databaseTime) @@ -611,10 +611,10 @@ private static bool IsReplayTimeAlmostTheSame(int requestTimeAsInt, int database private async Task IsReplayFileTheSame(int customEntryId, byte[] newReplay) { string path = Path.Combine(_fileSystemService.GetPath(DataSubDirectory.CustomEntryReplays), $"{customEntryId}.ddreplay"); - if (!File.Exists(path)) + if (!_fileSystemService.FileExists(path)) return false; - byte[] originalReplay = await File.ReadAllBytesAsync(path); + byte[] originalReplay = await _fileSystemService.ReadAllBytesAsync(path); return ArrayUtils.AreEqual(originalReplay, newReplay); } @@ -663,7 +663,7 @@ private void Log(UploadRequest uploadRequest, string? spawnsetName, string? erro private List GetExistingReplayIds(List customEntryIds) { - return customEntryIds.Where(id => File.Exists(Path.Combine(_fileSystemService.GetPath(DataSubDirectory.CustomEntryReplays), $"{id}.ddreplay"))).ToList(); + return customEntryIds.Where(id => _fileSystemService.FileExists(Path.Combine(_fileSystemService.GetPath(DataSubDirectory.CustomEntryReplays), $"{id}.ddreplay"))).ToList(); } // ! Navigation property. diff --git a/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/Inversion/IFileSystemService.cs b/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/Inversion/IFileSystemService.cs index a8856c1aaf..d3f2c84600 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/Inversion/IFileSystemService.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/Inversion/IFileSystemService.cs @@ -5,8 +5,6 @@ namespace DevilDaggersInfo.Web.Server.Domain.Services.Inversion; public interface IFileSystemService { - string Root { get; } - string[] TryGetFiles(DataSubDirectory subDirectory); string GetLeaderboardHistoryPathFromDate(DateTime dateTime); @@ -14,4 +12,50 @@ public interface IFileSystemService string GetPath(DataSubDirectory subDirectory); string GetToolDistributionPath(string name, ToolPublishMethod publishMethod, ToolBuildType buildType, string version); + + bool DeleteFileIfExists(string path); + + bool FileExists(string path); + + bool DeleteDirectoryIfExists(string path, bool recursive); + + bool DirectoryExists(string path); + + void MoveDirectory(string sourcePath, string destinationPath); + + void CreateDirectory(string path); + + string[] GetFiles(string path); + + string[] GetFiles(string path, string searchPattern); + + byte[] ReadAllBytes(string path); + + Task ReadAllBytesAsync(string path); + + Task ReadAllBytesAsync(string path, CancellationToken cancellationToken); + + void WriteAllBytes(string path, byte[] bytes); + + Task WriteAllBytesAsync(string path, byte[] bytes); + + Task WriteAllBytesAsync(string path, byte[] bytes, CancellationToken cancellationToken); + + void MoveFile(string sourcePath, string destinationPath); + + string ReadAllText(string path); + + Task ReadAllTextAsync(string path); + + Task ReadAllTextAsync(string path, CancellationToken cancellationToken); + + void WriteAllText(string path, string text); + + Task WriteAllTextAsync(string path, string text); + + Task WriteAllTextAsync(string path, string text, CancellationToken cancellationToken); + + // TODO: Add ZipFile methods. + + // TODO: Add FileInfo methods. } diff --git a/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/ModArchiveAccessor.cs b/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/ModArchiveAccessor.cs index 5f37d01972..7fd8acd458 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/ModArchiveAccessor.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/ModArchiveAccessor.cs @@ -23,18 +23,18 @@ public ModFileSystemData GetModFileSystemData(string modName) string modArchivePath = GetModArchivePath(modName); string modScreenshotsDirectory = Path.Combine(_fileSystemService.GetPath(DataSubDirectory.ModScreenshots), modName); - ModArchiveCacheData? modArchiveCacheData = File.Exists(modArchivePath) ? _modArchiveCache.GetArchiveDataByFilePath(modArchivePath) : null; + ModArchiveCacheData? modArchiveCacheData = _fileSystemService.FileExists(modArchivePath) ? _modArchiveCache.GetArchiveDataByFilePath(modArchivePath) : null; return new() { ModArchive = modArchiveCacheData, - ScreenshotFileNames = !Directory.Exists(modScreenshotsDirectory) ? null : GetScreenshotFileNames(modScreenshotsDirectory), + ScreenshotFileNames = !_fileSystemService.DirectoryExists(modScreenshotsDirectory) ? null : GetScreenshotFileNames(modScreenshotsDirectory), }; List GetScreenshotFileNames(string s) { // ! Path.GetFileName will never return null because modScreenshotsDirectory is not null. - return Directory.GetFiles(s).Select(Path.GetFileName).ToList()!; + return _fileSystemService.GetFiles(s).Select(Path.GetFileName).ToList()!; } } } diff --git a/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/ModArchiveProcessor.cs b/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/ModArchiveProcessor.cs index 62fd571880..e8d8b4d3e9 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/ModArchiveProcessor.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/ModArchiveProcessor.cs @@ -47,14 +47,13 @@ public async Task ProcessModBinaryUploadAsync(string modName, Dictionary TransformBinariesInModArchiveAsync(string originalModNam // Determine which binaries to keep. Dictionary keptBinaries = new(); string originalArchivePath = _modArchiveAccessor.GetModArchivePath(originalModName); - if (File.Exists(originalArchivePath)) + if (_fileSystemService.FileExists(originalArchivePath)) { using ZipArchive originalArchive = ZipFile.Open(originalArchivePath, ZipArchiveMode.Read); foreach (ZipArchiveEntry entry in originalArchive.Entries) @@ -143,17 +141,14 @@ public void DeleteModFilesAndClearCache(string modName) { // Delete archive zip file. string archivePath = _modArchiveAccessor.GetModArchivePath(modName); - if (File.Exists(archivePath)) + if (_fileSystemService.DeleteFileIfExists(archivePath)) { - File.Delete(archivePath); - // Clear entire memory cache (can't clear individual entries). _modArchiveCache.Clear(); } // Clear file cache for this mod. string cachePath = Path.Combine(_fileSystemService.GetPath(DataSubDirectory.ModArchiveCache), $"{modName}.json"); - if (File.Exists(cachePath)) - File.Delete(cachePath); + _fileSystemService.DeleteFileIfExists(cachePath); } } diff --git a/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/ModScreenshotProcessor.cs b/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/ModScreenshotProcessor.cs index 33658545f0..5fd1b31320 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/ModScreenshotProcessor.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server.Domain/Services/ModScreenshotProcessor.cs @@ -19,7 +19,7 @@ public void ProcessModScreenshotUpload(string modName, Dictionary kvp.Key).Select(kvp => kvp.Value)) { @@ -32,9 +32,9 @@ public void ProcessModScreenshotUpload(string modName, Dictionary GetSpawnsetById([Required] int id) return NotFound(); string path = Path.Combine(_fileSystemService.GetPath(DataSubDirectory.Spawnsets), spawnsetEntity.Name); - if (!IoFile.Exists(path)) + if (!_fileSystemService.FileExists(path)) return NotFound(); var customLeaderboard = _dbContext.CustomLeaderboards @@ -51,7 +51,7 @@ public ActionResult GetSpawnsetById([Required] int id) .Select(cl => new { cl.Id, cl.SpawnsetId }) .FirstOrDefault(cl => cl.SpawnsetId == spawnsetEntity.Id); - return spawnsetEntity.ToGetSpawnset(customLeaderboard?.Id, IoFile.ReadAllBytes(path)); + return spawnsetEntity.ToGetSpawnset(customLeaderboard?.Id, _fileSystemService.ReadAllBytes(path)); } [HttpGet("{id}/buffer")] @@ -68,12 +68,12 @@ public async Task> GetSpawnsetBufferById([Requir throw new NotFoundException(); string path = Path.Combine(_fileSystemService.GetPath(DataSubDirectory.Spawnsets), spawnset.Name); - if (!IoFile.Exists(path)) + if (!_fileSystemService.FileExists(path)) throw new NotFoundException(); return new GetSpawnsetBuffer { - Data = await IoFile.ReadAllBytesAsync(path), + Data = await _fileSystemService.ReadAllBytesAsync(path), }; } diff --git a/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Ddae/AssetsController.cs b/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Ddae/AssetsController.cs index 4a76f4b1d5..09a066143c 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Ddae/AssetsController.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Ddae/AssetsController.cs @@ -23,11 +23,11 @@ public AssetsController(IFileSystemService fileSystemService) [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult>> GetAssetInfoForDdae() { - return Directory.GetFiles(_fileSystemService.GetPath(DataSubDirectory.AssetInfo)) + return _fileSystemService.GetFiles(_fileSystemService.GetPath(DataSubDirectory.AssetInfo)) .Select(p => { string fileName = Path.GetFileNameWithoutExtension(p); - List assetInfo = JsonConvert.DeserializeObject?>(IoFile.ReadAllText(p)) ?? throw new($"Could not deserialize asset info from file '{fileName}'."); + List assetInfo = JsonConvert.DeserializeObject?>(_fileSystemService.ReadAllText(p)) ?? throw new($"Could not deserialize asset info from file '{fileName}'."); return (fileName, assetInfo); }). ToDictionary(t => t.fileName, t => t.assetInfo); diff --git a/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Ddse/SpawnsetsController.cs b/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Ddse/SpawnsetsController.cs index 441d34bf6b..ae32ea6d74 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Ddse/SpawnsetsController.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Ddse/SpawnsetsController.cs @@ -48,7 +48,7 @@ public List GetSpawnsetsObsolete(string? authorFilter = null, s .ToList(); return query - .Where(s => IoFile.Exists(Path.Combine(_fileSystemService.GetPath(DataSubDirectory.Spawnsets), s.Name))) + .Where(s => _fileSystemService.FileExists(Path.Combine(_fileSystemService.GetPath(DataSubDirectory.Spawnsets), s.Name))) .Select(s => { SpawnsetSummary spawnsetSummary = _spawnsetSummaryCache.GetSpawnsetSummaryByFilePath(Path.Combine(_fileSystemService.GetPath(DataSubDirectory.Spawnsets), s.Name)); diff --git a/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Main/CustomEntriesController.cs b/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Main/CustomEntriesController.cs index a0b4d14f3e..bc5251b602 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Main/CustomEntriesController.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Main/CustomEntriesController.cs @@ -72,6 +72,6 @@ public ActionResult GetCustomEntryDataById([Required] int id // ! Navigation property. SpawnsetSummary ss = _spawnsetSummaryCache.GetSpawnsetSummaryByFilePath(Path.Combine(_fileSystemService.GetPath(DataSubDirectory.Spawnsets), customEntry.CustomLeaderboard!.Spawnset!.Name)); - return customEntry.ToGetCustomEntryData(customEntryData, ss.EffectivePlayerSettings.HandLevel, IoFile.Exists(Path.Combine(_fileSystemService.GetPath(DataSubDirectory.CustomEntryReplays), $"{id}.ddreplay"))); + return customEntry.ToGetCustomEntryData(customEntryData, ss.EffectivePlayerSettings.HandLevel, _fileSystemService.FileExists(Path.Combine(_fileSystemService.GetPath(DataSubDirectory.CustomEntryReplays), $"{id}.ddreplay"))); } } diff --git a/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Main/ModScreenshotsController.cs b/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Main/ModScreenshotsController.cs index 71db18b97d..7dc734ffcb 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Main/ModScreenshotsController.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Main/ModScreenshotsController.cs @@ -22,9 +22,9 @@ public ModScreenshotsController(IFileSystemService fileSystemService) public IActionResult GetScreenshotByFilePath(string modName, string fileName) { string path = Path.Combine(_fileSystemService.GetPath(DataSubDirectory.ModScreenshots), modName, fileName); - if (!IoFile.Exists(path)) + if (!_fileSystemService.FileExists(path)) return NotFound(); - return File(IoFile.ReadAllBytes(path), "image/png"); + return File(_fileSystemService.ReadAllBytes(path), "image/png"); } } diff --git a/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Main/ModsController.cs b/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Main/ModsController.cs index 997e51821b..186ddf44cc 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Main/ModsController.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Main/ModsController.cs @@ -136,10 +136,10 @@ public ActionResult GetModFile([Required] string modName) string fileName = $"{modName}.zip"; string path = Path.Combine(_fileSystemService.GetPath(DataSubDirectory.Mods), fileName); - if (!IoFile.Exists(path)) + if (!_fileSystemService.FileExists(path)) return BadRequest($"Mod file '{fileName}' does not exist."); - return File(IoFile.ReadAllBytes(path), MediaTypeNames.Application.Zip, fileName); + return File(_fileSystemService.ReadAllBytes(path), MediaTypeNames.Application.Zip, fileName); } [HttpGet("by-author")] diff --git a/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Main/SpawnsetsController.cs b/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Main/SpawnsetsController.cs index 29ae6bb6d1..b8af74bb48 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Main/SpawnsetsController.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server/Controllers/Main/SpawnsetsController.cs @@ -175,10 +175,10 @@ public ActionResult GetSpawnsetByHash([FromQuery] byte[] hash public ActionResult GetSpawnsetHash([Required] string fileName) { string path = Path.Combine(_fileSystemService.GetPath(DataSubDirectory.Spawnsets), fileName); - if (!IoFile.Exists(path)) + if (!_fileSystemService.FileExists(path)) return NotFound(); - byte[] spawnsetBytes = IoFile.ReadAllBytes(path); + byte[] spawnsetBytes = _fileSystemService.ReadAllBytes(path); return MD5.HashData(spawnsetBytes); } @@ -200,10 +200,10 @@ public ActionResult GetTotalSpawnsetData() public ActionResult GetSpawnsetFile([Required] string fileName) { string path = Path.Combine(_fileSystemService.GetPath(DataSubDirectory.Spawnsets), fileName); - if (!IoFile.Exists(path)) + if (!_fileSystemService.FileExists(path)) return NotFound(); - return File(IoFile.ReadAllBytes(path), MediaTypeNames.Application.Octet, fileName); + return File(_fileSystemService.ReadAllBytes(path), MediaTypeNames.Application.Octet, fileName); } [HttpGet("{id}")] @@ -220,7 +220,7 @@ public ActionResult GetSpawnsetById([Required] int id) return NotFound(); string path = Path.Combine(_fileSystemService.GetPath(DataSubDirectory.Spawnsets), spawnsetEntity.Name); - if (!IoFile.Exists(path)) + if (!_fileSystemService.FileExists(path)) return NotFound(); var customLeaderboard = _dbContext.CustomLeaderboards @@ -228,7 +228,7 @@ public ActionResult GetSpawnsetById([Required] int id) .Select(cl => new { cl.Id, cl.SpawnsetId }) .FirstOrDefault(cl => cl.SpawnsetId == spawnsetEntity.Id); - return spawnsetEntity.ToGetSpawnset(customLeaderboard?.Id, IoFile.ReadAllBytes(path)); + return spawnsetEntity.ToGetSpawnset(customLeaderboard?.Id, _fileSystemService.ReadAllBytes(path)); } [HttpGet("default")] @@ -244,13 +244,13 @@ public ActionResult GetDefaultSpawnset(GameVersion gameVersion) }; string path = Path.Combine(_fileSystemService.GetPath(DataSubDirectory.Spawnsets), fileName); - if (!IoFile.Exists(path)) + if (!_fileSystemService.FileExists(path)) { _logger.LogError("Default spawnset {name} does not exist in the file system.", fileName); return NotFound(); } - return IoFile.ReadAllBytes(path); + return _fileSystemService.ReadAllBytes(path); } [HttpGet("by-author")] diff --git a/src/web-server/DevilDaggersInfo.Web.Server/HostedServices/LeaderboardHistoryBackgroundService.cs b/src/web-server/DevilDaggersInfo.Web.Server/HostedServices/LeaderboardHistoryBackgroundService.cs index 1ff090431d..ffbff75922 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server/HostedServices/LeaderboardHistoryBackgroundService.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server/HostedServices/LeaderboardHistoryBackgroundService.cs @@ -61,13 +61,13 @@ protected override async Task ExecuteTaskAsync(CancellationToken stoppingToken) string fileName = $"{DateTime.UtcNow:yyyyMMddHHmm}.bin"; string fullPath = Path.Combine(_fileSystemService.GetPath(DataSubDirectory.LeaderboardHistory), fileName); - await File.WriteAllBytesAsync(fullPath, historyModel.ToBytes(), stoppingToken); + await _fileSystemService.WriteAllBytesAsync(fullPath, historyModel.ToBytes(), stoppingToken); Logger.LogInformation("Task execution for `{service}` succeeded. `{fileName}` with {entries} entries was created.", nameof(LeaderboardHistoryBackgroundService), fullPath, entries.Count); } private bool HistoryFileExistsForDate(DateTime dateTime) { - foreach (string path in Directory.GetFiles(_fileSystemService.GetPath(DataSubDirectory.LeaderboardHistory), "*.bin")) + foreach (string path in _fileSystemService.GetFiles(_fileSystemService.GetPath(DataSubDirectory.LeaderboardHistory), "*.bin")) { string fileName = Path.GetFileNameWithoutExtension(path); if (HistoryUtils.HistoryFileNameToDateTime(fileName).Date == dateTime.Date) diff --git a/src/web-server/DevilDaggersInfo.Web.Server/Services/FileSystemService.cs b/src/web-server/DevilDaggersInfo.Web.Server/Services/FileSystemService.cs index c17339f184..54e46671ae 100644 --- a/src/web-server/DevilDaggersInfo.Web.Server/Services/FileSystemService.cs +++ b/src/web-server/DevilDaggersInfo.Web.Server/Services/FileSystemService.cs @@ -13,8 +13,6 @@ public FileSystemService() Directory.CreateDirectory(GetPath(e)); } - public string Root => "Data"; - public string[] TryGetFiles(DataSubDirectory subDirectory) { try @@ -40,10 +38,123 @@ public string GetLeaderboardHistoryPathFromDate(DateTime dateTime) } public string GetPath(DataSubDirectory subDirectory) - => Path.Combine(Root, subDirectory.ToString()); + => Path.Combine("Data", subDirectory.ToString()); public string GetToolDistributionPath(string name, ToolPublishMethod publishMethod, ToolBuildType buildType, string version) { return Path.Combine(GetPath(DataSubDirectory.Tools), $"{name}-{version}-{buildType}-{publishMethod}.zip"); } + + public bool DeleteFileIfExists(string path) + { + bool exists = File.Exists(path); + if (exists) + File.Delete(path); + + return exists; + } + + public bool FileExists(string path) + { + return File.Exists(path); + } + + public bool DeleteDirectoryIfExists(string path, bool recursive) + { + bool exists = Directory.Exists(path); + if (exists) + Directory.Delete(path, recursive); + + return exists; + } + + public bool DirectoryExists(string path) + { + return Directory.Exists(path); + } + + public void MoveDirectory(string sourcePath, string destinationPath) + { + Directory.Move(sourcePath, destinationPath); + } + + public void CreateDirectory(string path) + { + Directory.CreateDirectory(path); + } + + public string[] GetFiles(string path) + { + return Directory.GetFiles(path); + } + + public string[] GetFiles(string path, string searchPattern) + { + return Directory.GetFiles(path, searchPattern); + } + + public byte[] ReadAllBytes(string path) + { + return File.ReadAllBytes(path); + } + + public async Task ReadAllBytesAsync(string path) + { + return await File.ReadAllBytesAsync(path); + } + + public async Task ReadAllBytesAsync(string path, CancellationToken cancellationToken) + { + return await File.ReadAllBytesAsync(path, cancellationToken); + } + + public void WriteAllBytes(string path, byte[] bytes) + { + File.WriteAllBytes(path, bytes); + } + + public async Task WriteAllBytesAsync(string path, byte[] bytes) + { + await File.WriteAllBytesAsync(path, bytes); + } + + public async Task WriteAllBytesAsync(string path, byte[] bytes, CancellationToken cancellationToken) + { + await File.WriteAllBytesAsync(path, bytes, cancellationToken); + } + + public void MoveFile(string sourcePath, string destinationPath) + { + File.Move(sourcePath, destinationPath); + } + + public string ReadAllText(string path) + { + return File.ReadAllText(path); + } + + public async Task ReadAllTextAsync(string path) + { + return await File.ReadAllTextAsync(path); + } + + public async Task ReadAllTextAsync(string path, CancellationToken cancellationToken) + { + return await File.ReadAllTextAsync(path, cancellationToken); + } + + public void WriteAllText(string path, string text) + { + File.WriteAllText(path, text); + } + + public async Task WriteAllTextAsync(string path, string text) + { + await File.WriteAllTextAsync(path, text); + } + + public async Task WriteAllTextAsync(string path, string text, CancellationToken cancellationToken) + { + await File.WriteAllTextAsync(path, text, cancellationToken); + } } diff --git a/src/web-server/DevilDaggersInfo.Web.Server/_Imports.cs b/src/web-server/DevilDaggersInfo.Web.Server/_Imports.cs deleted file mode 100644 index 58f4ccd4d7..0000000000 --- a/src/web-server/DevilDaggersInfo.Web.Server/_Imports.cs +++ /dev/null @@ -1 +0,0 @@ -global using IoFile = System.IO.File;