From 7b25b32a5382f7e94bb2370bd78e60c98bbcb6da Mon Sep 17 00:00:00 2001 From: Ruairidh MacLeod Date: Tue, 20 Aug 2024 16:04:24 +0100 Subject: [PATCH 1/4] rename TestableIO packages as suggested in their README --- Directory.Packages.props | 4 ++-- src/SmiServices/SmiServices.csproj | 2 +- .../SmiServices.IntegrationTests.csproj | 2 +- tests/SmiServices.UnitTests/SmiServices.UnitTests.csproj | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 11e42b335..cf59b4fe8 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -24,7 +24,7 @@ - + @@ -38,6 +38,6 @@ - + diff --git a/src/SmiServices/SmiServices.csproj b/src/SmiServices/SmiServices.csproj index 56c7ac171..e5ec15f80 100644 --- a/src/SmiServices/SmiServices.csproj +++ b/src/SmiServices/SmiServices.csproj @@ -24,7 +24,7 @@ - + diff --git a/tests/SmiServices.IntegrationTests/SmiServices.IntegrationTests.csproj b/tests/SmiServices.IntegrationTests/SmiServices.IntegrationTests.csproj index bbdeff3b6..ab7439191 100644 --- a/tests/SmiServices.IntegrationTests/SmiServices.IntegrationTests.csproj +++ b/tests/SmiServices.IntegrationTests/SmiServices.IntegrationTests.csproj @@ -5,7 +5,7 @@ - + diff --git a/tests/SmiServices.UnitTests/SmiServices.UnitTests.csproj b/tests/SmiServices.UnitTests/SmiServices.UnitTests.csproj index 31de220f2..a2b320fef 100644 --- a/tests/SmiServices.UnitTests/SmiServices.UnitTests.csproj +++ b/tests/SmiServices.UnitTests/SmiServices.UnitTests.csproj @@ -5,7 +5,7 @@ - + From 3b1d525f836d8bbb8785a30b74edabddda023793 Mon Sep 17 00:00:00 2001 From: Ruairidh MacLeod Date: Tue, 20 Aug 2024 16:04:59 +0100 Subject: [PATCH 2/4] remove obsolete System.IO.FileSystem package --- Directory.Packages.props | 1 - 1 file changed, 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index cf59b4fe8..1794e215c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -25,7 +25,6 @@ - From c0c50e3a68eeac8efad4b3c4e96575b8885da564 Mon Sep 17 00:00:00 2001 From: Ruairidh MacLeod Date: Tue, 20 Aug 2024 16:09:45 +0100 Subject: [PATCH 3/4] add TestableIO.System.IO.Abstractions.Analyzers --- Directory.Packages.props | 1 + src/SmiServices/SmiServices.csproj | 1 + 2 files changed, 2 insertions(+) diff --git a/Directory.Packages.props b/Directory.Packages.props index 1794e215c..5105a783f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -25,6 +25,7 @@ + diff --git a/src/SmiServices/SmiServices.csproj b/src/SmiServices/SmiServices.csproj index e5ec15f80..3217547c9 100644 --- a/src/SmiServices/SmiServices.csproj +++ b/src/SmiServices/SmiServices.csproj @@ -25,6 +25,7 @@ + From f9182150635e1ea065de133b1b5f18f4d7f493c7 Mon Sep 17 00:00:00 2001 From: Ruairidh MacLeod Date: Tue, 20 Aug 2024 20:34:37 +0100 Subject: [PATCH 4/4] WIP fix TestableIO.System.IO.Abstractions.Analyzers errors in SmiServices --- .../DicomDirectoryProcessor.cs | 11 ++++--- .../DicomDirectoryProcessorCliOptions.cs | 11 ------- .../DicomDirectoryProcessorHost.cs | 32 +++++++++++-------- .../DirectoryFinders/DicomDirectoryFinder.cs | 2 +- .../Applications/DicomLoader/DicomLoader.cs | 11 ++++--- .../Applications/DicomLoader/Loader.cs | 13 +++++--- .../DynamicRulesTester/DynamicRulesTester.cs | 4 +-- .../ExtractImages/ExtractImages.cs | 10 +++--- .../ExtractImages/ExtractImagesHost.cs | 22 ++++++------- .../Applications/Setup/EnvironmentProbe.cs | 3 +- .../Applications/Setup/MainWindow.cs | 7 ++-- .../TriggerUpdates/TriggerUpdates.cs | 10 +++--- .../TriggerUpdates/TriggerUpdatesHost.cs | 5 +-- .../Common/Execution/MicroserviceHost.cs | 9 +++++- .../Messages/AccessionDirectoryMessage.cs | 5 ++- .../Common/Messages/DicomFileMessage.cs | 26 --------------- .../Common/Options/GlobalOptions.cs | 5 ++- .../Common/Options/GlobalOptionsFactory.cs | 13 +++++--- src/SmiServices/Common/Options/SmiCliInit.cs | 15 +++++---- src/SmiServices/Common/SmiLogging.cs | 25 ++++++++------- src/SmiServices/Common/ZipHelper.cs | 12 ------- .../CohortExtractor/CohortExtractor.cs | 7 ++-- .../CohortExtractor/CohortExtractorHost.cs | 8 +++-- .../DefaultProjectPathResolver.cs | 16 +++++++--- .../NoSuffixProjectPathResolver.cs | 5 ++- .../CohortPackager/CohortPackager.cs | 14 ++++---- .../CohortPackager/CohortPackagerHost.cs | 6 ++-- .../DicomAnonymiser/DicomAnonymiser.cs | 8 +++-- .../DicomAnonymiser/DicomAnonymiserHost.cs | 8 ++--- .../DicomRelationalMapper.cs | 7 ++-- .../DicomRelationalMapperHost.cs | 5 +-- .../ExplicitListDicomProcessListProvider.cs | 6 ++++ .../DicomReprocessor/DicomReprocessor.cs | 7 ++-- .../DicomReprocessor/DicomReprocessorHost.cs | 8 ++--- .../DicomTagReader/DicomTagReader.cs | 10 +++--- .../DicomTagReaderCliOptions.cs | 2 +- .../Execution/DicomTagReaderHost.cs | 6 ++-- .../Execution/ParallelTagReader.cs | 2 +- .../Execution/SerialTagReader.cs | 4 +-- .../DicomTagReader/Execution/TagReaderBase.cs | 24 +++++++------- .../Messaging/DicomTagReaderConsumer.cs | 5 +-- .../Microservices/FileCopier/FileCopier.cs | 8 +++-- .../FileCopier/FileCopierHost.cs | 5 +-- .../IdentifierMapper/IdentifierMapper.cs | 7 ++-- .../IdentifierMapper/IdentifierMapperHost.cs | 5 +-- .../IsIdentifiable/Classifier.cs | 6 ++-- .../IsIdentifiable/IClassifier.cs | 3 +- .../IsIdentifiable/IsIdentifiable.cs | 7 ++-- .../IsIdentifiable/IsIdentifiableHost.cs | 9 +++--- .../IsIdentifiable/RejectAllClassifier.cs | 3 +- .../TesseractStanfordDicomFileClassifier.cs | 7 ++-- .../MongoDBPopulator/MongoDBPopulator.cs | 7 ++-- .../MongoDBPopulator/MongoDbPopulatorHost.cs | 5 +-- .../UpdateValues/UpdateValues.cs | 8 +++-- .../UpdateValues/UpdateValuesHost.cs | 5 +-- 55 files changed, 259 insertions(+), 235 deletions(-) diff --git a/src/SmiServices/Applications/DicomDirectoryProcessor/DicomDirectoryProcessor.cs b/src/SmiServices/Applications/DicomDirectoryProcessor/DicomDirectoryProcessor.cs index 8834e3ac5..9e9b3c113 100644 --- a/src/SmiServices/Applications/DicomDirectoryProcessor/DicomDirectoryProcessor.cs +++ b/src/SmiServices/Applications/DicomDirectoryProcessor/DicomDirectoryProcessor.cs @@ -1,6 +1,7 @@ using SmiServices.Common.Execution; using SmiServices.Common.Options; using System.Collections.Generic; +using System.IO.Abstractions; namespace SmiServices.Applications.DicomDirectoryProcessor { @@ -18,20 +19,22 @@ public static class DicomDirectoryProcessor /// Arguments. There should be exactly one argument that specified the /// path to the top level directory that is be searched. /// - public static int Main(IEnumerable args) + /// + public static int Main(IEnumerable args, IFileSystem? fileSystem = null) { int ret = SmiCliInit .ParseAndRun( args, typeof(DicomDirectoryProcessor), - OnParse + OnParse, + fileSystem ?? new FileSystem() ); return ret; } - private static int OnParse(GlobalOptions globals, DicomDirectoryProcessorCliOptions parsedOptions) + private static int OnParse(GlobalOptions globals, IFileSystem fileSystem, DicomDirectoryProcessorCliOptions parsedOptions) { - var bootstrapper = new MicroserviceHostBootstrapper(() => new DicomDirectoryProcessorHost(globals, parsedOptions)); + var bootstrapper = new MicroserviceHostBootstrapper(() => new DicomDirectoryProcessorHost(globals, parsedOptions, fileSystem)); int ret = bootstrapper.Main(); return ret; } diff --git a/src/SmiServices/Applications/DicomDirectoryProcessor/DicomDirectoryProcessorCliOptions.cs b/src/SmiServices/Applications/DicomDirectoryProcessor/DicomDirectoryProcessorCliOptions.cs index 52a7487c5..f3a20d9b7 100644 --- a/src/SmiServices/Applications/DicomDirectoryProcessor/DicomDirectoryProcessorCliOptions.cs +++ b/src/SmiServices/Applications/DicomDirectoryProcessor/DicomDirectoryProcessorCliOptions.cs @@ -16,17 +16,6 @@ public class DicomDirectoryProcessorCliOptions : CliOptions public string? DirectoryFormat { get; set; } - public DirectoryInfo? ToProcessDir - { - get - { - return ToProcess == null - ? null - : new DirectoryInfo(ToProcess); - } - set => ToProcess = value?.FullName ?? throw new ArgumentNullException(nameof(value)); - } - [Usage] public static IEnumerable Examples { diff --git a/src/SmiServices/Applications/DicomDirectoryProcessor/DicomDirectoryProcessorHost.cs b/src/SmiServices/Applications/DicomDirectoryProcessor/DicomDirectoryProcessorHost.cs index 88d3feee1..fdc75dd4b 100644 --- a/src/SmiServices/Applications/DicomDirectoryProcessor/DicomDirectoryProcessorHost.cs +++ b/src/SmiServices/Applications/DicomDirectoryProcessor/DicomDirectoryProcessorHost.cs @@ -4,6 +4,7 @@ using System; using System.Globalization; using System.IO; +using System.IO.Abstractions; namespace SmiServices.Applications.DicomDirectoryProcessor { @@ -19,32 +20,35 @@ public class DicomDirectoryProcessorHost : MicroserviceHost /// Constructor /// /// Common microservices options. Must contain details for an message exchange labelled as "accessionDirectories" + /// /// Configuration settings for the program - public DicomDirectoryProcessorHost(GlobalOptions globals, DicomDirectoryProcessorCliOptions cliOptions) - : base(globals) + public DicomDirectoryProcessorHost(GlobalOptions globals, DicomDirectoryProcessorCliOptions cliOptions, IFileSystem? fileSystem = null) + : base(globals, fileSystem ?? new FileSystem()) { _cliOptions = cliOptions; + IDirectoryInfo toProcessDir = FileSystem.DirectoryInfo.New(cliOptions.ToProcess); + if (!cliOptions.DirectoryFormat!.ToLower().Equals("list")) { // TODO(rkm 2020-02-12) I think we want to check this regardless of the mode // (bp 2020-02-13) By not doing this check on list means that the list of paths is not required to be in PACS and can be imported from anywhere - if (!Directory.Exists(globals.FileSystemOptions!.FileSystemRoot)) - throw new ArgumentException("Cannot find the FileSystemRoot specified in the given MicroservicesOptions (" + globals.FileSystemOptions.FileSystemRoot + ")"); + if (!FileSystem.Directory.Exists(globals.FileSystemOptions!.FileSystemRoot)) + throw new ArgumentException($"Cannot find the FileSystemRoot specified in the given MicroservicesOptions ({globals.FileSystemOptions.FileSystemRoot})"); - if (!cliOptions.ToProcessDir!.Exists) - throw new ArgumentException("Could not find directory " + cliOptions.ToProcessDir.FullName); + if (!toProcessDir.Exists) + throw new ArgumentException($"Could not find directory {toProcessDir.FullName}"); - if (!cliOptions.ToProcessDir.FullName.StartsWith(globals.FileSystemOptions.FileSystemRoot, true, CultureInfo.CurrentCulture)) - throw new ArgumentException("Directory parameter (" + cliOptions.ToProcessDir.FullName + ") must be below the FileSystemRoot (" + globals.FileSystemOptions.FileSystemRoot + ")"); + if (!toProcessDir.FullName.StartsWith(globals.FileSystemOptions.FileSystemRoot, true, CultureInfo.CurrentCulture)) + throw new ArgumentException($"Directory parameter ({toProcessDir.FullName}) must be below the FileSystemRoot ({globals.FileSystemOptions.FileSystemRoot})"); } else { - if (!File.Exists(cliOptions.ToProcessDir!.FullName)) - throw new ArgumentException("Could not find accession directory list file (" + cliOptions.ToProcessDir.FullName + ")"); + if (!FileSystem.File.Exists(toProcessDir.FullName)) + throw new ArgumentException($"Could not find accession directory list file ({toProcessDir.FullName})"); - if (!Path.GetExtension(cliOptions.ToProcessDir.FullName).Equals(".csv")) - throw new ArgumentException("When in 'list' mode, path to accession directory file of format .csv expected (" + cliOptions.ToProcessDir.FullName + ")"); + if (!FileSystem.Path.GetExtension(toProcessDir.FullName).Equals(".csv")) + throw new ArgumentException($"When in 'list' mode, path to accession directory file of format .csv expected ({toProcessDir.FullName})"); } switch (cliOptions.DirectoryFormat.ToLower()) @@ -84,9 +88,11 @@ public DicomDirectoryProcessorHost(GlobalOptions globals, DicomDirectoryProcesso /// public override void Start() { + IDirectoryInfo toProcessDir = FileSystem.DirectoryInfo.New(_cliOptions.ToProcess); + try { - _ddf.SearchForDicomDirectories(_cliOptions.ToProcessDir!.FullName); + _ddf.SearchForDicomDirectories(toProcessDir.FullName); } catch (Exception e) { diff --git a/src/SmiServices/Applications/DicomDirectoryProcessor/DirectoryFinders/DicomDirectoryFinder.cs b/src/SmiServices/Applications/DicomDirectoryProcessor/DirectoryFinders/DicomDirectoryFinder.cs index b8b7b8e49..71a4ad2e7 100644 --- a/src/SmiServices/Applications/DicomDirectoryProcessor/DirectoryFinders/DicomDirectoryFinder.cs +++ b/src/SmiServices/Applications/DicomDirectoryProcessor/DirectoryFinders/DicomDirectoryFinder.cs @@ -96,7 +96,7 @@ protected void FoundNewDicomDirectory(string dir) { Logger.Debug("DicomDirectoryFinder: Found " + dir); - string dirPath = Path.GetFullPath(dir).TrimEnd(Path.DirectorySeparatorChar); + string dirPath = FileSystem.Path.GetFullPath(dir).TrimEnd(FileSystem.Path.DirectorySeparatorChar); if (dirPath.StartsWith(FileSystemRoot)) dirPath = dirPath.Remove(0, FileSystemRoot.Length); diff --git a/src/SmiServices/Applications/DicomLoader/DicomLoader.cs b/src/SmiServices/Applications/DicomLoader/DicomLoader.cs index 77dfb200d..70d29e833 100644 --- a/src/SmiServices/Applications/DicomLoader/DicomLoader.cs +++ b/src/SmiServices/Applications/DicomLoader/DicomLoader.cs @@ -17,6 +17,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.IO.Abstractions; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -26,14 +27,14 @@ namespace SmiServices.Applications.DicomLoader; public static class DicomLoader { private static CancellationTokenSource? _cts; - public static int Main(IEnumerable args) + public static int Main(IEnumerable args, IFileSystem? fileSystem = null) { - return SmiCliInit.ParseAndRun(args, typeof(DicomLoader), OnParse); + return SmiCliInit.ParseAndRun(args, typeof(DicomLoader), OnParse, fileSystem ?? new FileSystem()); } - private static int OnParse(GlobalOptions g, DicomLoaderOptions cliOptions) + private static int OnParse(GlobalOptions g, IFileSystem fileSystem, DicomLoaderOptions cliOptions) { - return OnParse(g, cliOptions, null); + return OnParse(g, fileSystem, cliOptions, null); } private static void CancelHandler(object? _, ConsoleCancelEventArgs a) @@ -42,7 +43,7 @@ private static void CancelHandler(object? _, ConsoleCancelEventArgs a) a.Cancel = true; } - private static int OnParse(GlobalOptions go, DicomLoaderOptions dicomLoaderOptions, Stream? fileList) + private static int OnParse(GlobalOptions go, IFileSystem fileSystem, DicomLoaderOptions dicomLoaderOptions, Stream? fileList) { if (go.MongoDatabases?.DicomStoreOptions is null) throw new InvalidOperationException("MongoDatabases or DICOM store options not set"); diff --git a/src/SmiServices/Applications/DicomLoader/Loader.cs b/src/SmiServices/Applications/DicomLoader/Loader.cs index 347215d22..0101a6dc0 100644 --- a/src/SmiServices/Applications/DicomLoader/Loader.cs +++ b/src/SmiServices/Applications/DicomLoader/Loader.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.IO.Abstractions; using System.Linq; using System.Text; using System.Threading; @@ -257,9 +258,10 @@ public void Report(CancellationToken? cancellationToken = null) private readonly DicomLoaderOptions _loadOptions; private readonly ParallelDLEHost? _parallelDleHost; private readonly LoadMetadata? _lmd; + private readonly IFileSystem _fileSystem; public Loader(IMongoDatabase database, string imageCollection, string seriesCollection, - DicomLoaderOptions loadOptions, ParallelDLEHost? parallelDleHost, LoadMetadata? lmd) + DicomLoaderOptions loadOptions, ParallelDLEHost? parallelDleHost, LoadMetadata? lmd, IFileSystem? fileSystem = null) { _imageQueueLock = new object(); _seriesListLock = new ReaderWriterLockSlim(); @@ -282,6 +284,7 @@ public Loader(IMongoDatabase database, string imageCollection, string seriesColl _statsLock = new object(); _imageStore = database.GetCollection(imageCollection); _seriesStore = database.GetCollection(seriesCollection); + _fileSystem = fileSystem ?? new FileSystem(); } /// @@ -290,13 +293,13 @@ public Loader(IMongoDatabase database, string imageCollection, string seriesColl /// DICOM file or archive of DICOM files to load /// Cancellation token /// - private void Process(FileInfo fi, CancellationToken ct) + private void Process(IFileInfo fi, CancellationToken ct) { ct.ThrowIfCancellationRequested(); var dName = fi.DirectoryName ?? throw new ApplicationException($"No parent directory for '{fi.FullName}'"); var bBuffer = new byte[132]; var buffer = new Span(bBuffer); - using (var fileStream = File.OpenRead(fi.FullName)) + using (var fileStream = fi.OpenRead()) { if (fileStream.Read(buffer) == 132 && buffer[128..].SequenceEqual(_dicomMagic)) { @@ -429,7 +432,7 @@ private bool ExistingEntry(string filename) /// public ValueTask Load(string filename, CancellationToken ct) { - if (!File.Exists(filename)) + if (!_fileSystem.File.Exists(filename)) { Console.WriteLine($@"{filename} does not exist, skipping"); return ValueTask.CompletedTask; @@ -442,7 +445,7 @@ public ValueTask Load(string filename, CancellationToken ct) try { - Process(new FileInfo(filename), ct); + Process(_fileSystem.FileInfo.New(filename), ct); } catch (Exception e) { diff --git a/src/SmiServices/Applications/DynamicRulesTester/DynamicRulesTester.cs b/src/SmiServices/Applications/DynamicRulesTester/DynamicRulesTester.cs index bf3d57107..617b165a6 100644 --- a/src/SmiServices/Applications/DynamicRulesTester/DynamicRulesTester.cs +++ b/src/SmiServices/Applications/DynamicRulesTester/DynamicRulesTester.cs @@ -22,7 +22,7 @@ public static int Main(IEnumerable args, IFileSystem? fileSystem = null) try { - return SmiCliInit.ParseAndRun(args, typeof(DynamicRulesTester), OnParse); + return SmiCliInit.ParseAndRun(args, typeof(DynamicRulesTester), OnParse, _fileSystem); } catch (Exception e) { @@ -31,7 +31,7 @@ public static int Main(IEnumerable args, IFileSystem? fileSystem = null) } } - private static int OnParse(GlobalOptions _, DynamicRulesTesterCliOptions cliOptions) + private static int OnParse(GlobalOptions _, IFileSystem fileSystem, DynamicRulesTesterCliOptions cliOptions) { var dynamicRejector = new DynamicRejector(cliOptions.DynamicRulesFile, _fileSystem); diff --git a/src/SmiServices/Applications/ExtractImages/ExtractImages.cs b/src/SmiServices/Applications/ExtractImages/ExtractImages.cs index 7b81a98af..b6b73fe02 100644 --- a/src/SmiServices/Applications/ExtractImages/ExtractImages.cs +++ b/src/SmiServices/Applications/ExtractImages/ExtractImages.cs @@ -2,6 +2,7 @@ using SmiServices.Common.Options; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO.Abstractions; namespace SmiServices.Applications.ExtractImages @@ -9,21 +10,22 @@ namespace SmiServices.Applications.ExtractImages [ExcludeFromCodeCoverage] public static class ExtractImages { - public static int Main(IEnumerable args) + public static int Main(IEnumerable args, IFileSystem? fileSystem = null) { int ret = SmiCliInit .ParseAndRun( args, typeof(ExtractImages), - OnParse + OnParse, + fileSystem ?? new FileSystem() ); return ret; } - private static int OnParse(GlobalOptions globals, ExtractImagesCliOptions parsedOptions) + private static int OnParse(GlobalOptions globals, IFileSystem fileSystem, ExtractImagesCliOptions parsedOptions) { var bootstrapper = - new MicroserviceHostBootstrapper(() => new ExtractImagesHost(globals, parsedOptions)); + new MicroserviceHostBootstrapper(() => new ExtractImagesHost(globals, parsedOptions, fileSystem)); int ret = bootstrapper.Main(); return ret; } diff --git a/src/SmiServices/Applications/ExtractImages/ExtractImagesHost.cs b/src/SmiServices/Applications/ExtractImages/ExtractImagesHost.cs index 059e3fc8b..0d81c9d58 100644 --- a/src/SmiServices/Applications/ExtractImages/ExtractImagesHost.cs +++ b/src/SmiServices/Applications/ExtractImages/ExtractImagesHost.cs @@ -14,8 +14,6 @@ namespace SmiServices.Applications.ExtractImages { public class ExtractImagesHost : MicroserviceHost { - private readonly IFileSystem _fileSystem; - private readonly string _csvFilePath; private readonly IExtractionMessageSender _extractionMessageSender; @@ -26,37 +24,37 @@ public class ExtractImagesHost : MicroserviceHost public ExtractImagesHost( GlobalOptions globals, ExtractImagesCliOptions cliOptions, + IFileSystem? fileSystem = null, IExtractionMessageSender? extractionMessageSender = null, IMessageBroker? messageBroker = null, - IFileSystem? fileSystem = null, bool threaded = false ) : base( globals, + fileSystem ?? new FileSystem(), messageBroker, threaded ) { ExtractImagesOptions? options = Globals.ExtractImagesOptions ?? throw new ArgumentException(nameof(Globals.ExtractImagesOptions)); - _fileSystem = fileSystem ?? new FileSystem(); string extractRoot = Globals.FileSystemOptions?.ExtractRoot ?? throw new ArgumentException("Some part of Globals.FileSystemOptions.ExtractRoot was null"); - if (!_fileSystem.Directory.Exists(extractRoot)) + if (!FileSystem.Directory.Exists(extractRoot)) throw new DirectoryNotFoundException($"Could not find the extraction root '{extractRoot}'"); _csvFilePath = cliOptions.CohortCsvFile; if (string.IsNullOrWhiteSpace(_csvFilePath)) throw new ArgumentNullException(nameof(cliOptions)); - if (!_fileSystem.File.Exists(_csvFilePath)) + if (!FileSystem.File.Exists(_csvFilePath)) throw new FileNotFoundException($"Could not find the cohort CSV file '{_csvFilePath}'"); // TODO(rkm 2021-04-01) Now that all the extraction path code is in C#, we would benefit from refactoring it all out // to a helper class to support having multiple configurations (and probably prevent some bugs) - string extractionName = _fileSystem.Path.GetFileNameWithoutExtension(_csvFilePath); - string extractionDir = _fileSystem.Path.Join(cliOptions.ProjectId, "extractions", extractionName); - _absoluteExtractionDir = _fileSystem.Path.Join(extractRoot, extractionDir); + string extractionName = FileSystem.Path.GetFileNameWithoutExtension(_csvFilePath); + string extractionDir = FileSystem.Path.Join(cliOptions.ProjectId, "extractions", extractionName); + _absoluteExtractionDir = FileSystem.Path.Join(extractRoot, extractionDir); - if (_fileSystem.Directory.Exists(_absoluteExtractionDir)) + if (FileSystem.Directory.Exists(_absoluteExtractionDir)) throw new DirectoryNotFoundException($"Extraction directory already exists '{_absoluteExtractionDir}'"); if (extractionMessageSender == null) @@ -69,7 +67,7 @@ public ExtractImagesHost( cliOptions, extractionRequestProducer, extractionRequestInfoProducer, - _fileSystem, + FileSystem, extractRoot, extractionDir, new DateTimeProvider(), @@ -85,7 +83,7 @@ public ExtractImagesHost( public override void Start() { - var parser = new CohortCsvParser(_fileSystem); + var parser = new CohortCsvParser(FileSystem); (ExtractionKey extractionKey, List idList) = parser.Parse(_csvFilePath); _extractionMessageSender.SendMessages(extractionKey, idList); diff --git a/src/SmiServices/Applications/Setup/EnvironmentProbe.cs b/src/SmiServices/Applications/Setup/EnvironmentProbe.cs index 68d8dca4a..0f9c04197 100644 --- a/src/SmiServices/Applications/Setup/EnvironmentProbe.cs +++ b/src/SmiServices/Applications/Setup/EnvironmentProbe.cs @@ -20,6 +20,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO.Abstractions; using System.Linq; using System.Text; @@ -118,7 +119,7 @@ public EnvironmentProbe(string? yamlFile) if (string.IsNullOrWhiteSpace(yamlFile)) throw new Exception("You have not yet entered a path for yaml file"); - Options = new GlobalOptionsFactory().Load("Setup", yamlFile); + Options = new GlobalOptionsFactory().Load("Setup", new FileSystem(), yamlFile); DeserializeYaml = new CheckEventArgs("Deserialized Yaml File", CheckResult.Success); } catch (Exception ex) diff --git a/src/SmiServices/Applications/Setup/MainWindow.cs b/src/SmiServices/Applications/Setup/MainWindow.cs index 04db4b3fc..2e4fbf20a 100644 --- a/src/SmiServices/Applications/Setup/MainWindow.cs +++ b/src/SmiServices/Applications/Setup/MainWindow.cs @@ -18,6 +18,7 @@ namespace Setup using System; using System.Collections.Generic; using System.IO; + using System.IO.Abstractions; using Terminal.Gui; using Attribute = Terminal.Gui.Attribute; @@ -27,6 +28,8 @@ public partial class MainWindow { private readonly ColorScheme _goodScheme; private readonly ColorScheme _badScheme; + private readonly IFileSystem _fileSystem = new FileSystem(); + /// /// The currently selected yaml file /// @@ -114,9 +117,9 @@ private void RegisterTry(Button btn, Func probeFunc) private void ReloadYaml() { var f = tbDefaultYaml.Text.ToString(); - if(File.Exists(f)) + if(_fileSystem.File.Exists(f)) { - var newYaml = File.ReadAllText(f); + var newYaml = _fileSystem.File.ReadAllText(f); // no need to generate a new probe because the yaml has not changed if (string.Equals(newYaml, _lastYaml)) diff --git a/src/SmiServices/Applications/TriggerUpdates/TriggerUpdates.cs b/src/SmiServices/Applications/TriggerUpdates/TriggerUpdates.cs index c85aa269b..2b6c2b3ec 100644 --- a/src/SmiServices/Applications/TriggerUpdates/TriggerUpdates.cs +++ b/src/SmiServices/Applications/TriggerUpdates/TriggerUpdates.cs @@ -2,13 +2,14 @@ using SmiServices.Common.Options; using System; using System.Collections.Generic; +using System.IO.Abstractions; namespace SmiServices.Applications.TriggerUpdates { public static class TriggerUpdates { - public static int Main(IEnumerable args) + public static int Main(IEnumerable args, IFileSystem? fileSystem = null) { int ret = SmiCliInit .ParseAndRun( @@ -17,12 +18,13 @@ public static int Main(IEnumerable args) [ typeof(TriggerUpdatesFromMapperOptions), ], - OnParse + OnParse, + fileSystem ?? new FileSystem() ); return ret; } - private static int OnParse(GlobalOptions globals, object opts) + private static int OnParse(GlobalOptions globals, IFileSystem fileSystem, object opts) { var parsedOptions = SmiCliInit.Verify(opts); @@ -32,7 +34,7 @@ private static int OnParse(GlobalOptions globals, object opts) _ => throw new NotImplementedException($"No case for '{parsedOptions.GetType()}'") }; - var bootstrapper = new MicroserviceHostBootstrapper(() => new TriggerUpdatesHost(globals, source)); + var bootstrapper = new MicroserviceHostBootstrapper(() => new TriggerUpdatesHost(globals, source, messageBroker: null, fileSystem)); int ret = bootstrapper.Main(); return ret; } diff --git a/src/SmiServices/Applications/TriggerUpdates/TriggerUpdatesHost.cs b/src/SmiServices/Applications/TriggerUpdates/TriggerUpdatesHost.cs index 95bfe035c..2cc58e084 100644 --- a/src/SmiServices/Applications/TriggerUpdates/TriggerUpdatesHost.cs +++ b/src/SmiServices/Applications/TriggerUpdates/TriggerUpdatesHost.cs @@ -2,6 +2,7 @@ using SmiServices.Common.Execution; using SmiServices.Common.Messaging; using SmiServices.Common.Options; +using System.IO.Abstractions; namespace SmiServices.Applications.TriggerUpdates @@ -11,8 +12,8 @@ public class TriggerUpdatesHost : MicroserviceHost private readonly ITriggerUpdatesSource _source; private readonly IProducerModel _producer; - public TriggerUpdatesHost(GlobalOptions options, ITriggerUpdatesSource source, IMessageBroker? messageBroker = null) - : base(options, messageBroker) + public TriggerUpdatesHost(GlobalOptions options, ITriggerUpdatesSource source, IMessageBroker? messageBroker = null, IFileSystem? fileSystem = null) + : base(options, fileSystem ?? new FileSystem(), messageBroker) { _source = source; _producer = MessageBroker.SetupProducer(options.TriggerUpdatesOptions!, isBatch: false); diff --git a/src/SmiServices/Common/Execution/MicroserviceHost.cs b/src/SmiServices/Common/Execution/MicroserviceHost.cs index 0283a04bf..f089ffb33 100644 --- a/src/SmiServices/Common/Execution/MicroserviceHost.cs +++ b/src/SmiServices/Common/Execution/MicroserviceHost.cs @@ -7,6 +7,7 @@ using SmiServices.Common.Messaging; using SmiServices.Common.Options; using System; +using System.IO.Abstractions; namespace SmiServices.Common.Execution { @@ -19,6 +20,7 @@ public abstract class MicroserviceHost : IMicroserviceHost protected readonly GlobalOptions Globals; protected readonly ILogger Logger; + protected readonly IFileSystem FileSystem; protected readonly IMessageBroker MessageBroker; @@ -39,16 +41,21 @@ public abstract class MicroserviceHost : IMicroserviceHost /// Loads logging, sets up fatal behaviour, subscribes rabbit etc. /// /// Settings for the microservice (location of rabbit, queue names etc) + /// /// /// protected MicroserviceHost( GlobalOptions globals, + IFileSystem fileSystem, IMessageBroker? messageBroker = null, - bool threaded = false) + bool threaded = false + ) { if (globals == null || globals.FileSystemOptions == null || globals.RabbitOptions == null || globals.LoggingOptions == null) throw new ArgumentException("All or part of the global options are null"); + FileSystem = fileSystem; + // Disable fo-dicom's DICOM validation globally from here new DicomSetupBuilder().SkipValidation(); diff --git a/src/SmiServices/Common/Messages/AccessionDirectoryMessage.cs b/src/SmiServices/Common/Messages/AccessionDirectoryMessage.cs index e01876eee..2a9647e82 100644 --- a/src/SmiServices/Common/Messages/AccessionDirectoryMessage.cs +++ b/src/SmiServices/Common/Messages/AccessionDirectoryMessage.cs @@ -3,6 +3,7 @@ using Newtonsoft.Json; using System; using System.IO; +using System.IO.Abstractions; namespace SmiServices.Common.Messages { @@ -19,7 +20,7 @@ public sealed class AccessionDirectoryMessage : MemberwiseEquatable Path.Combine(rootPath, DirectoryPath); - public override string ToString() => $"AccessionDirectoryMessage[DirectoryPath={DirectoryPath}]"; } } diff --git a/src/SmiServices/Common/Messages/DicomFileMessage.cs b/src/SmiServices/Common/Messages/DicomFileMessage.cs index a500b6d5b..e72bbebc1 100644 --- a/src/SmiServices/Common/Messages/DicomFileMessage.cs +++ b/src/SmiServices/Common/Messages/DicomFileMessage.cs @@ -60,32 +60,6 @@ public DicomFileMessage(string root, string file) DicomFilePath = file[root.Length..].TrimStart(Path.DirectorySeparatorChar); } - public string GetAbsolutePath(string rootPath) - { - return Path.Combine(rootPath, DicomFilePath); - } - - public bool Validate(string fileSystemRoot) - { - var absolutePath = GetAbsolutePath(fileSystemRoot); - - if (string.IsNullOrWhiteSpace(absolutePath)) - return false; - - try - { - var dir = new FileInfo(absolutePath); - - //There file referenced must exist - return dir.Exists; - } - catch (Exception) - { - return false; - } - - } - public bool VerifyPopulated() { return !string.IsNullOrWhiteSpace(DicomFilePath) && diff --git a/src/SmiServices/Common/Options/GlobalOptions.cs b/src/SmiServices/Common/Options/GlobalOptions.cs index 8076a37f0..a550405cb 100644 --- a/src/SmiServices/Common/Options/GlobalOptions.cs +++ b/src/SmiServices/Common/Options/GlobalOptions.cs @@ -11,6 +11,7 @@ using SmiServices.Common.Messaging; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using System.Text; @@ -557,7 +558,9 @@ public IRDMPPlatformRepositoryServiceLocator GetRepositoryProvider() // if using file system backend for RDMP create that repo instead if (!string.IsNullOrWhiteSpace(YamlDir)) { - return new RepositoryProvider(new YamlRepository(new System.IO.DirectoryInfo(YamlDir))); +#pragma warning disable IO0007 // Replace DirectoryInfo class with IFileSystem.DirectoryInfo for improved testability + return new RepositoryProvider(new YamlRepository(new DirectoryInfo(YamlDir))); +#pragma warning restore IO0007 // Replace DirectoryInfo class with IFileSystem.DirectoryInfo for improved testability } // We are using database backend for RDMP (i.e. Sql Server) diff --git a/src/SmiServices/Common/Options/GlobalOptionsFactory.cs b/src/SmiServices/Common/Options/GlobalOptionsFactory.cs index ee93358ca..2d6eb0265 100644 --- a/src/SmiServices/Common/Options/GlobalOptionsFactory.cs +++ b/src/SmiServices/Common/Options/GlobalOptionsFactory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.IO.Abstractions; using YamlDotNet.Serialization; namespace SmiServices.Common.Options @@ -28,18 +29,19 @@ public GlobalOptionsFactory( /// /// /// + /// /// - public GlobalOptions Load(string hostProcessName, string configFilePath = "default.yaml") + public GlobalOptions Load(string hostProcessName, IFileSystem fileSystem, string configFilePath = "default.yaml") { IDeserializer deserializer = new DeserializerBuilder() .WithObjectFactory(GetGlobalOption) .IgnoreUnmatchedProperties() .Build(); - if (!File.Exists(configFilePath)) + if (!fileSystem.File.Exists(configFilePath)) throw new ArgumentException($"Could not find config file '{configFilePath}'"); - string yamlContents = File.ReadAllText(configFilePath); + string yamlContents = fileSystem.File.ReadAllText(configFilePath); using var sr = new StringReader(yamlContents); var globals = deserializer.Deserialize(sr); @@ -70,10 +72,11 @@ private GlobalOptions Decorate(GlobalOptions globals) /// /// /// + /// /// - public GlobalOptions Load(string hostProcessName, CliOptions cliOptions) + public GlobalOptions Load(string hostProcessName, CliOptions cliOptions, IFileSystem fileSystem) { - GlobalOptions globalOptions = Load(hostProcessName, cliOptions.YamlFile); + GlobalOptions globalOptions = Load(hostProcessName, fileSystem, cliOptions.YamlFile); // The above Load call does the decoration - don't do it here. return globalOptions; diff --git a/src/SmiServices/Common/Options/SmiCliInit.cs b/src/SmiServices/Common/Options/SmiCliInit.cs index f2e30ab53..f6eed04cd 100644 --- a/src/SmiServices/Common/Options/SmiCliInit.cs +++ b/src/SmiServices/Common/Options/SmiCliInit.cs @@ -4,6 +4,7 @@ using NLog.Targets; using System; using System.Collections.Generic; +using System.IO.Abstractions; using System.Linq; @@ -48,8 +49,9 @@ public static Parser GetDefaultParser() /// Arguments passed to Main /// /// The function to call on a successful parse + /// /// The return code from the onParse function - public static int ParseAndRun(IEnumerable args, Type programType, Func onParse) where T : CliOptions + public static int ParseAndRun(IEnumerable args, Type programType, Func onParse, IFileSystem fileSystem) where T : CliOptions { int ret = _parser .ParseArguments(args) @@ -58,7 +60,7 @@ public static int ParseAndRun(IEnumerable args, Type programType, Fun { string hostProcessName = GetHostProcessName(programType); - GlobalOptions globals = new GlobalOptionsFactory().Load(hostProcessName, parsed); + GlobalOptions globals = new GlobalOptionsFactory().Load(hostProcessName, parsed, fileSystem); if (InitSmiLogging) { @@ -66,7 +68,7 @@ public static int ParseAndRun(IEnumerable args, Type programType, Fun SmiLogging.Setup(globals.LoggingOptions, hostProcessName); } - return onParse(globals, parsed); + return onParse(globals, fileSystem, parsed); }, OnErrors ); @@ -80,8 +82,9 @@ public static int ParseAndRun(IEnumerable args, Type programType, Fun /// /// The list of possible target verb types to construct from the args /// The function to call on a successful parse + /// /// The return code from the onParse function - public static int ParseAndRun(IEnumerable args, Type programType, Type[] targetVerbTypes, Func onParse) + public static int ParseAndRun(IEnumerable args, Type programType, Type[] targetVerbTypes, Func onParse, IFileSystem fileSystem) { int ret = _parser .ParseArguments( @@ -94,7 +97,7 @@ public static int ParseAndRun(IEnumerable args, Type programType, Type[] string hostProcessName = GetHostProcessName(programType); var cliOptions = Verify(parsed); - GlobalOptions globals = new GlobalOptionsFactory().Load(hostProcessName, cliOptions); + GlobalOptions globals = new GlobalOptionsFactory().Load(hostProcessName, cliOptions, fileSystem); if (InitSmiLogging) { @@ -102,7 +105,7 @@ public static int ParseAndRun(IEnumerable args, Type programType, Type[] SmiLogging.Setup(globals.LoggingOptions, hostProcessName); } - return onParse(globals, parsed); + return onParse(globals, fileSystem, parsed); }, OnErrors ); diff --git a/src/SmiServices/Common/SmiLogging.cs b/src/SmiServices/Common/SmiLogging.cs index 9253a43b3..62ab5a0de 100644 --- a/src/SmiServices/Common/SmiLogging.cs +++ b/src/SmiServices/Common/SmiLogging.cs @@ -2,6 +2,7 @@ using SmiServices.Common.Options; using System; using System.IO; +using System.IO.Abstractions; namespace SmiServices.Common @@ -12,18 +13,20 @@ public static class SmiLogging private static bool _initialised; - public static void Setup(LoggingOptions loggingOptions, string hostProcessName) + public static void Setup(LoggingOptions loggingOptions, string hostProcessName, IFileSystem? fileSystem = null) { if (_initialised) throw new Exception("SmiLogging already initialised"); _initialised = true; - string localConfig = Path.Combine(Directory.GetCurrentDirectory(), DefaultLogConfigName); + fileSystem ??= new FileSystem(); + + string localConfig = fileSystem.Path.Combine(fileSystem.Directory.GetCurrentDirectory(), DefaultLogConfigName); string configFilePathToLoad = !string.IsNullOrWhiteSpace(loggingOptions.LogConfigFile) ? loggingOptions.LogConfigFile : localConfig; - if (!File.Exists(configFilePathToLoad)) + if (!fileSystem.File.Exists(configFilePathToLoad)) throw new FileNotFoundException($"Could not find the specified logging configuration '{configFilePathToLoad})'"); LogManager.ThrowConfigExceptions = true; @@ -31,10 +34,10 @@ public static void Setup(LoggingOptions loggingOptions, string hostProcessName) if (!string.IsNullOrWhiteSpace(loggingOptions.LogsRoot)) { - if (!Directory.Exists(loggingOptions.LogsRoot)) + if (!fileSystem.Directory.Exists(loggingOptions.LogsRoot)) throw new ApplicationException($"Invalid log root '{loggingOptions.LogsRoot}'"); - VerifyCanWrite(loggingOptions.LogsRoot); + VerifyCanWrite(loggingOptions.LogsRoot, fileSystem); LogManager.Configuration.Variables["baseFileName"] = $"{loggingOptions.LogsRoot}/" + @@ -42,7 +45,7 @@ public static void Setup(LoggingOptions loggingOptions, string hostProcessName) $"${{cached:cached=true:clearCache=None:inner=${{date:format=yyyy-MM-dd-HH-mm-ss}}}}-${{processid}}"; } else - VerifyCanWrite(Directory.GetCurrentDirectory()); + VerifyCanWrite(fileSystem.Directory.GetCurrentDirectory(), fileSystem); Logger logger = LogManager.GetLogger(nameof(SmiLogging)); LogManager.GlobalThreshold = LogLevel.Trace; @@ -54,13 +57,13 @@ public static void Setup(LoggingOptions loggingOptions, string hostProcessName) logger.Info($"Logging config loaded from {configFilePathToLoad}"); } - private static void VerifyCanWrite(string logsRoot) + private static void VerifyCanWrite(string logsRoot, IFileSystem fileSystem) { - string[] tmpFileParts = Path.GetTempFileName().Split(Path.DirectorySeparatorChar); - string tmpFileInLogsRoot = Path.Combine(logsRoot, tmpFileParts[^1]); + string[] tmpFileParts = fileSystem.Path.GetTempFileName().Split(fileSystem.Path.DirectorySeparatorChar); + string tmpFileInLogsRoot = fileSystem.Path.Combine(logsRoot, tmpFileParts[^1]); try { - File.WriteAllText(tmpFileInLogsRoot, ""); + fileSystem.File.WriteAllText(tmpFileInLogsRoot, ""); } catch (UnauthorizedAccessException e) { @@ -68,7 +71,7 @@ private static void VerifyCanWrite(string logsRoot) } finally { - File.Delete(tmpFileInLogsRoot); + fileSystem.File.Delete(tmpFileInLogsRoot); } } } diff --git a/src/SmiServices/Common/ZipHelper.cs b/src/SmiServices/Common/ZipHelper.cs index 8c388f768..c7d6371a3 100644 --- a/src/SmiServices/Common/ZipHelper.cs +++ b/src/SmiServices/Common/ZipHelper.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.IO; using System.IO.Abstractions; namespace SmiServices.Common @@ -21,16 +20,5 @@ public static bool IsZip(IFileInfo f) { return SupportedExtensions.Contains(f.Extension); } - - /// - /// Returns true if looks like a compressed archive compatible with smi e.g. zip, tar etc - /// - /// - /// - public static bool IsZip(string path) - { - return SupportedExtensions.Contains(Path.GetExtension(path)); - } - } } diff --git a/src/SmiServices/Microservices/CohortExtractor/CohortExtractor.cs b/src/SmiServices/Microservices/CohortExtractor/CohortExtractor.cs index c4db41ee8..9c8672e23 100644 --- a/src/SmiServices/Microservices/CohortExtractor/CohortExtractor.cs +++ b/src/SmiServices/Microservices/CohortExtractor/CohortExtractor.cs @@ -1,18 +1,19 @@ using SmiServices.Common.Execution; using SmiServices.Common.Options; using System.Collections.Generic; +using System.IO.Abstractions; namespace SmiServices.Microservices.CohortExtractor { public static class CohortExtractor { - public static int Main(IEnumerable args) + public static int Main(IEnumerable args, IFileSystem? fileSystem = null) { - int ret = SmiCliInit.ParseAndRun(args, typeof(CohortExtractor), OnParse); + int ret = SmiCliInit.ParseAndRun(args, typeof(CohortExtractor), OnParse, fileSystem ?? new FileSystem()); return ret; } - private static int OnParse(GlobalOptions globals, CliOptions opts) + private static int OnParse(GlobalOptions globals, IFileSystem? fileSystem, CliOptions opts) { //Use the auditor and request fullfilers specified in the yaml var bootstrapper = new MicroserviceHostBootstrapper( diff --git a/src/SmiServices/Microservices/CohortExtractor/CohortExtractorHost.cs b/src/SmiServices/Microservices/CohortExtractor/CohortExtractorHost.cs index ea35dd48f..001667045 100644 --- a/src/SmiServices/Microservices/CohortExtractor/CohortExtractorHost.cs +++ b/src/SmiServices/Microservices/CohortExtractor/CohortExtractorHost.cs @@ -15,6 +15,7 @@ using SmiServices.Microservices.CohortExtractor.ProjectPathResolvers; using SmiServices.Microservices.CohortExtractor.RequestFulfillers; using System; +using System.IO.Abstractions; using System.Linq; using System.Text.RegularExpressions; @@ -45,10 +46,11 @@ public class CohortExtractorHost : MicroserviceHost /// Creates a new instance of the host with the given /// /// Settings for the microservice (location of rabbit, queue names etc) + /// /// Optional override for the value specified in /// Optional override for the value specified in - public CohortExtractorHost(GlobalOptions options, IAuditExtractions? auditor, IExtractionRequestFulfiller? fulfiller) - : base(options) + public CohortExtractorHost(GlobalOptions options, IAuditExtractions? auditor, IExtractionRequestFulfiller? fulfiller, IFileSystem? fileSystem = null) + : base(options, fileSystem ?? new FileSystem()) { _consumerOptions = options.CohortExtractorOptions!; _consumerOptions.Validate(); @@ -151,7 +153,7 @@ private void InitializeExtractionSources(IRDMPPlatformRepositoryServiceLocator r } _pathResolver = string.IsNullOrWhiteSpace(_consumerOptions.ProjectPathResolverType) - ? new DefaultProjectPathResolver() + ? new DefaultProjectPathResolver(FileSystem) : ObjectFactory.CreateInstance( _consumerOptions.ProjectPathResolverType, typeof(IProjectPathResolver).Assembly, repositoryLocator); } diff --git a/src/SmiServices/Microservices/CohortExtractor/ProjectPathResolvers/DefaultProjectPathResolver.cs b/src/SmiServices/Microservices/CohortExtractor/ProjectPathResolvers/DefaultProjectPathResolver.cs index 64cb2b26d..5b6b30e84 100644 --- a/src/SmiServices/Microservices/CohortExtractor/ProjectPathResolvers/DefaultProjectPathResolver.cs +++ b/src/SmiServices/Microservices/CohortExtractor/ProjectPathResolvers/DefaultProjectPathResolver.cs @@ -1,17 +1,24 @@ using SmiServices.Common.Messages.Extraction; using SmiServices.Microservices.CohortExtractor.RequestFulfillers; using System; -using System.IO; +using System.IO.Abstractions; namespace SmiServices.Microservices.CohortExtractor.ProjectPathResolvers { public class DefaultProjectPathResolver : IProjectPathResolver { + private readonly IFileSystem _fileSystem; + public string AnonExt { get; protected set; } = "-an.dcm"; public string IdentExt { get; protected set; } = ".dcm"; private static readonly string[] _replaceableExtensions = [".dcm", ".dicom"]; + public DefaultProjectPathResolver(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + /// /// Returns the output path for the anonymised file, relative to the ExtractionDirectory /// @@ -23,7 +30,7 @@ public string GetOutputPath(QueryToExecuteResult result, ExtractionRequestMessag string extToUse = message.IsIdentifiableExtraction ? IdentExt : AnonExt; // The extension of the input DICOM file can be anything (or nothing), but here we try to standardise the output file name to have the required extension - string fileName = Path.GetFileName(result.FilePathValue); + string fileName = _fileSystem.Path.GetFileName(result.FilePathValue); if (string.IsNullOrWhiteSpace(fileName)) throw new ArgumentNullException(nameof(result)); @@ -43,10 +50,11 @@ public string GetOutputPath(QueryToExecuteResult result, ExtractionRequestMessag string? studyUID = result.StudyTagValue?.TrimStart('.'); string? seriesUID = result.SeriesTagValue?.TrimStart('.'); - return Path.Combine( + return _fileSystem.Path.Combine( studyUID ?? "unknown", seriesUID ?? "unknown", - fileName); + fileName + ); } } } diff --git a/src/SmiServices/Microservices/CohortExtractor/ProjectPathResolvers/NoSuffixProjectPathResolver.cs b/src/SmiServices/Microservices/CohortExtractor/ProjectPathResolvers/NoSuffixProjectPathResolver.cs index 5e49d9575..14dd70302 100644 --- a/src/SmiServices/Microservices/CohortExtractor/ProjectPathResolvers/NoSuffixProjectPathResolver.cs +++ b/src/SmiServices/Microservices/CohortExtractor/ProjectPathResolvers/NoSuffixProjectPathResolver.cs @@ -1,3 +1,5 @@ +using System.IO.Abstractions; + namespace SmiServices.Microservices.CohortExtractor.ProjectPathResolvers { /// @@ -5,7 +7,8 @@ namespace SmiServices.Microservices.CohortExtractor.ProjectPathResolvers /// public class NoSuffixProjectPathResolver : DefaultProjectPathResolver { - public NoSuffixProjectPathResolver() + public NoSuffixProjectPathResolver(IFileSystem fileSystem) + : base(fileSystem) { AnonExt = ".dcm"; } diff --git a/src/SmiServices/Microservices/CohortPackager/CohortPackager.cs b/src/SmiServices/Microservices/CohortPackager/CohortPackager.cs index 70992a0a1..992a9409a 100644 --- a/src/SmiServices/Microservices/CohortPackager/CohortPackager.cs +++ b/src/SmiServices/Microservices/CohortPackager/CohortPackager.cs @@ -7,31 +7,29 @@ using SmiServices.Microservices.CohortPackager.JobProcessing.Reporting; using System; using System.Collections.Generic; -using System.IO; using System.IO.Abstractions; - namespace SmiServices.Microservices.CohortPackager { public static class CohortPackager { - public static int Main(IEnumerable args) + public static int Main(IEnumerable args, IFileSystem? fileSystem = null) { - int ret = SmiCliInit.ParseAndRun(args, typeof(CohortPackager), OnParse); + int ret = SmiCliInit.ParseAndRun(args, typeof(CohortPackager), OnParse, fileSystem ?? new FileSystem()); return ret; } - private static int OnParse(GlobalOptions globals, CohortPackagerCliOptions opts) + private static int OnParse(GlobalOptions globals, IFileSystem fileSystem, CohortPackagerCliOptions opts) { if (opts.ExtractionId != default) - return RecreateReports(globals, opts); + return RecreateReports(globals, fileSystem, opts); var bootstrapper = new MicroserviceHostBootstrapper(() => new CohortPackagerHost(globals)); int ret = bootstrapper.Main(); return ret; } - private static int RecreateReports(GlobalOptions globalOptions, CohortPackagerCliOptions cliOptions) + private static int RecreateReports(GlobalOptions globalOptions, IFileSystem fileSystem, CohortPackagerCliOptions cliOptions) { Logger logger = LogManager.GetCurrentClassLogger(); @@ -58,7 +56,7 @@ private static int RecreateReports(GlobalOptions globalOptions, CohortPackagerCl var reporter = new JobReporter( jobStore, new FileSystem(), - Directory.GetCurrentDirectory(), + fileSystem.Directory.GetCurrentDirectory(), cliOptions.OutputNewLine ?? globalOptions.CohortPackagerOptions?.ReportNewLine ); diff --git a/src/SmiServices/Microservices/CohortPackager/CohortPackagerHost.cs b/src/SmiServices/Microservices/CohortPackager/CohortPackagerHost.cs index f132e05c9..c778d7960 100644 --- a/src/SmiServices/Microservices/CohortPackager/CohortPackagerHost.cs +++ b/src/SmiServices/Microservices/CohortPackager/CohortPackagerHost.cs @@ -42,14 +42,14 @@ public class CohortPackagerHost : MicroserviceHost /// public CohortPackagerHost( GlobalOptions globals, - IExtractJobStore? jobStore = null, IFileSystem? fileSystem = null, + IExtractJobStore? jobStore = null, IJobReporter? reporter = null, IJobCompleteNotifier? notifier = null, IMessageBroker? messageBroker = null, DateTimeProvider? dateTimeProvider = null ) - : base(globals, messageBroker) + : base(globals, fileSystem ?? new FileSystem(), messageBroker) { var cohortPackagerOptions = globals.CohortPackagerOptions ?? throw new ArgumentNullException(nameof(globals), "CohortPackagerOptions cannot be null"); @@ -77,7 +77,7 @@ public CohortPackagerHost( reporter = new JobReporter( jobStore, - fileSystem ?? new FileSystem(), + FileSystem, extractRoot, cohortPackagerOptions.ReportNewLine ); diff --git a/src/SmiServices/Microservices/DicomAnonymiser/DicomAnonymiser.cs b/src/SmiServices/Microservices/DicomAnonymiser/DicomAnonymiser.cs index 22c5d6cd4..c00bab5a7 100644 --- a/src/SmiServices/Microservices/DicomAnonymiser/DicomAnonymiser.cs +++ b/src/SmiServices/Microservices/DicomAnonymiser/DicomAnonymiser.cs @@ -1,6 +1,7 @@ using SmiServices.Common.Execution; using SmiServices.Common.Options; using System.Collections.Generic; +using System.IO.Abstractions; namespace SmiServices.Microservices.DicomAnonymiser { @@ -10,13 +11,14 @@ public static class DicomAnonymiser /// Program entry point when run from the command line /// /// - public static int Main(IEnumerable args) + /// + public static int Main(IEnumerable args, IFileSystem? fileSystem = null) { - int ret = SmiCliInit.ParseAndRun(args, typeof(DicomAnonymiser), OnParse); + int ret = SmiCliInit.ParseAndRun(args, typeof(DicomAnonymiser), OnParse, fileSystem ?? new FileSystem()); return ret; } - private static int OnParse(GlobalOptions globals, CliOptions opts) + private static int OnParse(GlobalOptions globals, IFileSystem fileSystem, CliOptions opts) { var bootstrapper = new MicroserviceHostBootstrapper(() => new DicomAnonymiserHost(globals)); int ret = bootstrapper.Main(); diff --git a/src/SmiServices/Microservices/DicomAnonymiser/DicomAnonymiserHost.cs b/src/SmiServices/Microservices/DicomAnonymiser/DicomAnonymiserHost.cs index 08867db04..a08ef8e9d 100644 --- a/src/SmiServices/Microservices/DicomAnonymiser/DicomAnonymiserHost.cs +++ b/src/SmiServices/Microservices/DicomAnonymiser/DicomAnonymiserHost.cs @@ -13,10 +13,10 @@ public class DicomAnonymiserHost : MicroserviceHost public DicomAnonymiserHost( GlobalOptions options, - IDicomAnonymiser? anonymiser = null, - IFileSystem? fileSystem = null + IFileSystem? fileSystem = null, + IDicomAnonymiser? anonymiser = null ) - : base(options) + : base(options, fileSystem ?? new FileSystem()) { _anonymiser = anonymiser ?? AnonymiserFactory.CreateAnonymiser(options!); @@ -28,7 +28,7 @@ public DicomAnonymiserHost( Globals.FileSystemOptions.ExtractRoot!, _anonymiser, producerModel, - fileSystem + FileSystem ); } diff --git a/src/SmiServices/Microservices/DicomRelationalMapper/DicomRelationalMapper.cs b/src/SmiServices/Microservices/DicomRelationalMapper/DicomRelationalMapper.cs index 9f8aa7322..347ae7c15 100644 --- a/src/SmiServices/Microservices/DicomRelationalMapper/DicomRelationalMapper.cs +++ b/src/SmiServices/Microservices/DicomRelationalMapper/DicomRelationalMapper.cs @@ -1,18 +1,19 @@ using SmiServices.Common.Execution; using SmiServices.Common.Options; using System.Collections.Generic; +using System.IO.Abstractions; namespace SmiServices.Microservices.DicomRelationalMapper { public static class DicomRelationalMapper { - public static int Main(IEnumerable args) + public static int Main(IEnumerable args, IFileSystem? fileSystem = null) { - int ret = SmiCliInit.ParseAndRun(args, typeof(DicomRelationalMapper), OnParse); + int ret = SmiCliInit.ParseAndRun(args, typeof(DicomRelationalMapper), OnParse, fileSystem ?? new FileSystem()); return ret; } - private static int OnParse(GlobalOptions globals, CliOptions opts) + private static int OnParse(GlobalOptions globals, IFileSystem fileSystem, CliOptions opts) { var bootstrapper = new MicroserviceHostBootstrapper(() => new DicomRelationalMapperHost(globals)); int ret = bootstrapper.Main(); diff --git a/src/SmiServices/Microservices/DicomRelationalMapper/DicomRelationalMapperHost.cs b/src/SmiServices/Microservices/DicomRelationalMapper/DicomRelationalMapperHost.cs index 2922e9d26..0c77d30a2 100644 --- a/src/SmiServices/Microservices/DicomRelationalMapper/DicomRelationalMapperHost.cs +++ b/src/SmiServices/Microservices/DicomRelationalMapper/DicomRelationalMapperHost.cs @@ -11,6 +11,7 @@ using SmiServices.Common.Execution; using SmiServices.Common.Options; using System; +using System.IO.Abstractions; namespace SmiServices.Microservices.DicomRelationalMapper @@ -19,8 +20,8 @@ public class DicomRelationalMapperHost : MicroserviceHost, IDisposable { public DicomRelationalMapperQueueConsumer? Consumer { get; private set; } - public DicomRelationalMapperHost(GlobalOptions globals) - : base(globals) + public DicomRelationalMapperHost(GlobalOptions globals, IFileSystem? fileSystem = null) + : base(globals, fileSystem ?? new FileSystem()) { FansiImplementations.Load(); } diff --git a/src/SmiServices/Microservices/DicomRelationalMapper/ExplicitListDicomProcessListProvider.cs b/src/SmiServices/Microservices/DicomRelationalMapper/ExplicitListDicomProcessListProvider.cs index 27735d607..d0bd8c567 100644 --- a/src/SmiServices/Microservices/DicomRelationalMapper/ExplicitListDicomProcessListProvider.cs +++ b/src/SmiServices/Microservices/DicomRelationalMapper/ExplicitListDicomProcessListProvider.cs @@ -25,19 +25,25 @@ public bool GetNextFileOrDirectoryToProcess(out DirectoryInfo? directory, out Am return false; } +#pragma warning disable IO0002 // Replace File class with IFileSystem.File for improved testability if (File.Exists(_filesAndOrDirectories[index])) { file = new AmbiguousFilePath(_filesAndOrDirectories[index]); index++; return true; } +#pragma warning restore IO0002 // Replace File class with IFileSystem.File for improved testability +#pragma warning disable IO0003 // Replace Directory class with IFileSystem.Directory for improved testability if (Directory.Exists(_filesAndOrDirectories[index])) { +#pragma warning disable IO0007 // Replace DirectoryInfo class with IFileSystem.DirectoryInfo for improved testability directory = new DirectoryInfo(_filesAndOrDirectories[index]); +#pragma warning restore IO0007 // Replace DirectoryInfo class with IFileSystem.DirectoryInfo for improved testability index++; return true; } +#pragma warning restore IO0003 // Replace Directory class with IFileSystem.Directory for improved testability throw new Exception("Array element " + index + " of filesAndOrDirectories was not a File or Directory (or the referenced file did not exist). Array element is '" + _filesAndOrDirectories[index] + "'"); } diff --git a/src/SmiServices/Microservices/DicomReprocessor/DicomReprocessor.cs b/src/SmiServices/Microservices/DicomReprocessor/DicomReprocessor.cs index 11b96dad7..0a06458c9 100644 --- a/src/SmiServices/Microservices/DicomReprocessor/DicomReprocessor.cs +++ b/src/SmiServices/Microservices/DicomReprocessor/DicomReprocessor.cs @@ -1,18 +1,19 @@ using SmiServices.Common.Execution; using SmiServices.Common.Options; using System.Collections.Generic; +using System.IO.Abstractions; namespace SmiServices.Microservices.DicomReprocessor { public static class DicomReprocessor { - public static int Main(IEnumerable args) + public static int Main(IEnumerable args, IFileSystem? fileSystem = null) { - int ret = SmiCliInit.ParseAndRun(args, typeof(DicomReprocessor), OnParse); + int ret = SmiCliInit.ParseAndRun(args, typeof(DicomReprocessor), OnParse, fileSystem ?? new FileSystem()); return ret; } - private static int OnParse(GlobalOptions globals, DicomReprocessorCliOptions opts) + private static int OnParse(GlobalOptions globals, IFileSystem fileSystem, DicomReprocessorCliOptions opts) { var bootstrapper = new MicroserviceHostBootstrapper(() => new DicomReprocessorHost(globals, opts)); int ret = bootstrapper.Main(); diff --git a/src/SmiServices/Microservices/DicomReprocessor/DicomReprocessorHost.cs b/src/SmiServices/Microservices/DicomReprocessor/DicomReprocessorHost.cs index 657734438..f9f1f076a 100644 --- a/src/SmiServices/Microservices/DicomReprocessor/DicomReprocessorHost.cs +++ b/src/SmiServices/Microservices/DicomReprocessor/DicomReprocessorHost.cs @@ -2,7 +2,7 @@ using SmiServices.Common.Messaging; using SmiServices.Common.Options; using System; -using System.IO; +using System.IO.Abstractions; using System.Threading.Tasks; namespace SmiServices.Microservices.DicomReprocessor @@ -15,8 +15,8 @@ public class DicomReprocessorHost : MicroserviceHost private readonly string? _queryString; - public DicomReprocessorHost(GlobalOptions options, DicomReprocessorCliOptions cliOptions) - : base(options) + public DicomReprocessorHost(GlobalOptions options, DicomReprocessorCliOptions cliOptions, IFileSystem? fileSystem = null) + : base(options, fileSystem ?? new FileSystem()) { string? key = cliOptions.ReprocessingRoutingKey; @@ -33,7 +33,7 @@ public DicomReprocessorHost(GlobalOptions options, DicomReprocessorCliOptions cl options.RabbitOptions!.RabbitMqVirtualHost + " with routing key \"" + key + "\""); if (!string.IsNullOrWhiteSpace(cliOptions.QueryFile)) - _queryString = File.ReadAllText(cliOptions.QueryFile); + _queryString = FileSystem.File.ReadAllText(cliOptions.QueryFile); //TODO Make this into a CreateInstance<> call _processor = options.DicomReprocessorOptions.ProcessingMode switch diff --git a/src/SmiServices/Microservices/DicomTagReader/DicomTagReader.cs b/src/SmiServices/Microservices/DicomTagReader/DicomTagReader.cs index 6bb0cfb2a..b4f71daf4 100644 --- a/src/SmiServices/Microservices/DicomTagReader/DicomTagReader.cs +++ b/src/SmiServices/Microservices/DicomTagReader/DicomTagReader.cs @@ -4,6 +4,7 @@ using SmiServices.Microservices.DicomTagReader.Execution; using System; using System.Collections.Generic; +using System.IO.Abstractions; namespace SmiServices.Microservices.DicomTagReader { @@ -13,20 +14,21 @@ public static class DicomTagReader /// Program entry point when run from the command line /// /// - public static int Main(IEnumerable args) + /// + public static int Main(IEnumerable args, IFileSystem? fileSystem = null) { - int ret = SmiCliInit.ParseAndRun(args, typeof(DicomTagReader), OnParse); + int ret = SmiCliInit.ParseAndRun(args, typeof(DicomTagReader), OnParse, fileSystem ?? new FileSystem()); return ret; } - private static int OnParse(GlobalOptions globals, DicomTagReaderCliOptions opts) + private static int OnParse(GlobalOptions globals, IFileSystem fileSystem, DicomTagReaderCliOptions opts) { if (opts.File != null) { try { var host = new DicomTagReaderHost(globals); - host.AccessionDirectoryMessageConsumer.RunSingleFile(opts.File); + host.AccessionDirectoryMessageConsumer.RunSingleFile(fileSystem.FileInfo.New(opts.File)); return 0; } catch (Exception ex) diff --git a/src/SmiServices/Microservices/DicomTagReader/DicomTagReaderCliOptions.cs b/src/SmiServices/Microservices/DicomTagReader/DicomTagReaderCliOptions.cs index 75551f5ec..5ecd16cac 100644 --- a/src/SmiServices/Microservices/DicomTagReader/DicomTagReaderCliOptions.cs +++ b/src/SmiServices/Microservices/DicomTagReader/DicomTagReaderCliOptions.cs @@ -15,6 +15,6 @@ public class DicomTagReaderCliOptions : CliOptions Required = false, HelpText = "[Optional] Name of a specific dicom or zip file to process instead of subscribing to rabbit" )] - public FileInfo? File { get; set; } + public string? File { get; set; } } } diff --git a/src/SmiServices/Microservices/DicomTagReader/Execution/DicomTagReaderHost.cs b/src/SmiServices/Microservices/DicomTagReader/Execution/DicomTagReaderHost.cs index bda3f364c..4d6602126 100644 --- a/src/SmiServices/Microservices/DicomTagReader/Execution/DicomTagReaderHost.cs +++ b/src/SmiServices/Microservices/DicomTagReader/Execution/DicomTagReaderHost.cs @@ -15,10 +15,10 @@ public class DicomTagReaderHost : MicroserviceHost private readonly TagReaderBase _tagReader; - public DicomTagReaderHost(GlobalOptions options) - : base(options) + public DicomTagReaderHost(GlobalOptions options, IFileSystem? fileSystem = null) + : base(options, fileSystem ?? new FileSystem()) { - if (!Directory.Exists(options.FileSystemOptions!.FileSystemRoot)) + if (!FileSystem.Directory.Exists(options.FileSystemOptions!.FileSystemRoot)) throw new ArgumentException( $"Cannot find the FileSystemRoot specified in the given MicroservicesOptions ({options.FileSystemOptions.FileSystemRoot})"); diff --git a/src/SmiServices/Microservices/DicomTagReader/Execution/ParallelTagReader.cs b/src/SmiServices/Microservices/DicomTagReader/Execution/ParallelTagReader.cs index 5c3762863..6b105b2a4 100644 --- a/src/SmiServices/Microservices/DicomTagReader/Execution/ParallelTagReader.cs +++ b/src/SmiServices/Microservices/DicomTagReader/Execution/ParallelTagReader.cs @@ -28,7 +28,7 @@ public ParallelTagReader(DicomTagReaderOptions options, FileSystemOptions fileSy Logger.Info($"Using MaxDegreeOfParallelism={_parallelOptions.MaxDegreeOfParallelism} for parallel IO operations"); } - protected override List ReadTagsImpl(IEnumerable dicomFilePaths, + protected override List ReadTagsImpl(IEnumerable dicomFilePaths, AccessionDirectoryMessage accMessage) { var fileMessages = new List(); diff --git a/src/SmiServices/Microservices/DicomTagReader/Execution/SerialTagReader.cs b/src/SmiServices/Microservices/DicomTagReader/Execution/SerialTagReader.cs index 513c6a4ea..237bf130f 100644 --- a/src/SmiServices/Microservices/DicomTagReader/Execution/SerialTagReader.cs +++ b/src/SmiServices/Microservices/DicomTagReader/Execution/SerialTagReader.cs @@ -16,11 +16,11 @@ public SerialTagReader(DicomTagReaderOptions options, FileSystemOptions fileSyst IProducerModel seriesMessageProducerModel, IProducerModel fileMessageProducerModel, IFileSystem fs) : base(options, fileSystemOptions, seriesMessageProducerModel, fileMessageProducerModel, fs) { } - protected override List ReadTagsImpl(IEnumerable dicomFilePaths, AccessionDirectoryMessage accMessage) + protected override List ReadTagsImpl(IEnumerable dicomFilePaths, AccessionDirectoryMessage accMessage) { var fileMessages = new List(); - foreach (FileInfo dicomFilePath in dicomFilePaths) + foreach (var dicomFilePath in dicomFilePaths) { Logger.Trace("TagReader: Processing " + dicomFilePath); diff --git a/src/SmiServices/Microservices/DicomTagReader/Execution/TagReaderBase.cs b/src/SmiServices/Microservices/DicomTagReader/Execution/TagReaderBase.cs index b3c0b60a9..065eca696 100644 --- a/src/SmiServices/Microservices/DicomTagReader/Execution/TagReaderBase.cs +++ b/src/SmiServices/Microservices/DicomTagReader/Execution/TagReaderBase.cs @@ -20,7 +20,7 @@ namespace SmiServices.Microservices.DicomTagReader.Execution public abstract class TagReaderBase { private readonly string _filesystemRoot; - private readonly IFileSystem _fs; + private readonly IFileSystem _fileSystem; private readonly IProducerModel _seriesMessageProducerModel; private readonly IProducerModel _fileMessageProducerModel; @@ -68,7 +68,7 @@ public TagReaderBase(DicomTagReaderOptions options, FileSystemOptions fileSystem _seriesMessageProducerModel = seriesMessageProducerModel; _fileMessageProducerModel = fileMessageProducerModel; - _fs = fs; + _fileSystem = fs; Logger.Info($"Stopwatch implementation - IsHighResolution: {Stopwatch.IsHighResolution}. Frequency: {Stopwatch.Frequency} ticks/s"); } @@ -82,18 +82,18 @@ public void ReadTags(IMessageHeader? header, AccessionDirectoryMessage message) { _stopwatch.Restart(); - string dirPath = message.GetAbsolutePath(_filesystemRoot); + string dirPath = _fileSystem.Path.Combine(_filesystemRoot, message.DirectoryPath); Logger.Debug("TagReader: About to process files in " + dirPath); - if (!_fs.Directory.Exists(dirPath)) + if (!_fileSystem.Directory.Exists(dirPath)) throw new ApplicationException("Directory not found: " + dirPath); if (!dirPath.StartsWith(_filesystemRoot, StringComparison.CurrentCultureIgnoreCase)) throw new ApplicationException("Directory " + dirPath + " is not below the given FileSystemRoot (" + _filesystemRoot + ")"); long beginEnumerate = _stopwatch.ElapsedTicks; - string[] dicomFilePaths = _fs.Directory.EnumerateFiles(dirPath, _searchPattern).Where(Include).ToArray(); - string[] zipFilePaths = _fs.Directory.EnumerateFiles(dirPath).Where(ZipHelper.IsZip).Where(Include).ToArray(); + string[] dicomFilePaths = _fileSystem.Directory.EnumerateFiles(dirPath, _searchPattern).Where(Include).ToArray(); + string[] zipFilePaths = _fileSystem.Directory.EnumerateFiles(dirPath).Where(x => ZipHelper.IsZip(_fileSystem.FileInfo.New(x))).Where(Include).ToArray(); _swTotals[0] += _stopwatch.ElapsedTicks - beginEnumerate; Logger.Debug("TagReader: Found " + dicomFilePaths.Length + " dicom files to process"); @@ -108,8 +108,8 @@ public void ReadTags(IMessageHeader? header, AccessionDirectoryMessage message) long beginRead = _stopwatch.ElapsedTicks; - List fileMessages = ReadTagsImpl(dicomFilePaths.Select(p => new FileInfo(p)), message); - fileMessages.AddRange(ReadZipFilesImpl(zipFilePaths.Select(p => new FileInfo(p)), message)); + List fileMessages = ReadTagsImpl(dicomFilePaths.Select(_fileSystem.FileInfo.New), message); + fileMessages.AddRange(ReadZipFilesImpl(zipFilePaths.Select(_fileSystem.FileInfo.New), message)); _swTotals[1] += (_stopwatch.ElapsedTicks - beginRead) / toProcess; @@ -192,9 +192,9 @@ public bool Include(string filePath) /// All the zip files that must be explored for dcm files /// The upstream message that suggested we look for dicom files in a given directory /// - protected virtual IEnumerable ReadZipFilesImpl(IEnumerable zipFilePaths, AccessionDirectoryMessage accMessage) + protected virtual IEnumerable ReadZipFilesImpl(IEnumerable zipFilePaths, AccessionDirectoryMessage accMessage) { - foreach (FileInfo zipFilePath in zipFilePaths) + foreach (var zipFilePath in zipFilePaths) { using var archive = ZipFile.Open(zipFilePath.FullName, ZipArchiveMode.Read); foreach (var entry in archive.Entries) @@ -285,7 +285,7 @@ private static byte[] ReadFully(Stream stream) ms.Write(buffer, 0, read); } } - protected abstract List ReadTagsImpl(IEnumerable dicomFilePaths, + protected abstract List ReadTagsImpl(IEnumerable dicomFilePaths, AccessionDirectoryMessage accMessage); /// @@ -293,7 +293,7 @@ protected abstract List ReadTagsImpl(IEnumerable dic /// /// /// - protected DicomFileMessage ReadTagsFromFile(FileInfo dicomFilePath) + protected DicomFileMessage ReadTagsFromFile(IFileInfo dicomFilePath) { try { diff --git a/src/SmiServices/Microservices/DicomTagReader/Messaging/DicomTagReaderConsumer.cs b/src/SmiServices/Microservices/DicomTagReader/Messaging/DicomTagReaderConsumer.cs index 5ec1ebc88..0eb8fbbc0 100644 --- a/src/SmiServices/Microservices/DicomTagReader/Messaging/DicomTagReaderConsumer.cs +++ b/src/SmiServices/Microservices/DicomTagReader/Messaging/DicomTagReaderConsumer.cs @@ -5,6 +5,7 @@ using SmiServices.Microservices.DicomTagReader.Execution; using System; using System.IO; +using System.IO.Abstractions; namespace SmiServices.Microservices.DicomTagReader.Messaging { @@ -61,10 +62,10 @@ protected override void ProcessMessageImpl(IMessageHeader header, AccessionDirec /// Runs a single file (dicom or zip) through tag reading process /// /// - public void RunSingleFile(FileInfo file) + public void RunSingleFile(IFileInfo file) { // tell reader only to consider our specific file - _reader.IncludeFile = f => new FileInfo(f).FullName.Equals(file.FullName, StringComparison.CurrentCultureIgnoreCase); + _reader.IncludeFile = f => file.FileSystem.FileInfo.New(f).FullName.Equals(file.FullName, StringComparison.CurrentCultureIgnoreCase); _reader.ReadTags(null, new AccessionDirectoryMessage(_opts.FileSystemOptions!.FileSystemRoot!, file.Directory!)); // good practice to clear this afterwards diff --git a/src/SmiServices/Microservices/FileCopier/FileCopier.cs b/src/SmiServices/Microservices/FileCopier/FileCopier.cs index 59c0a84ec..59f457069 100644 --- a/src/SmiServices/Microservices/FileCopier/FileCopier.cs +++ b/src/SmiServices/Microservices/FileCopier/FileCopier.cs @@ -1,6 +1,7 @@ using SmiServices.Common.Execution; using SmiServices.Common.Options; using System.Collections.Generic; +using System.IO.Abstractions; namespace SmiServices.Microservices.FileCopier { @@ -10,13 +11,14 @@ public static class FileCopier /// Program entry point when run from the command line /// /// - public static int Main(IEnumerable args) + /// + public static int Main(IEnumerable args, IFileSystem? fileSystem = null) { - int ret = SmiCliInit.ParseAndRun(args, typeof(FileCopier), OnParse); + int ret = SmiCliInit.ParseAndRun(args, typeof(FileCopier), OnParse, fileSystem ?? new FileSystem()); return ret; } - private static int OnParse(GlobalOptions globals, CliOptions opts) + private static int OnParse(GlobalOptions globals, IFileSystem fileSystem, CliOptions opts) { var bootstrapper = new MicroserviceHostBootstrapper(() => new FileCopierHost(globals)); int ret = bootstrapper.Main(); diff --git a/src/SmiServices/Microservices/FileCopier/FileCopierHost.cs b/src/SmiServices/Microservices/FileCopier/FileCopierHost.cs index c8f2fd3d0..a01697c10 100644 --- a/src/SmiServices/Microservices/FileCopier/FileCopierHost.cs +++ b/src/SmiServices/Microservices/FileCopier/FileCopierHost.cs @@ -15,7 +15,8 @@ public FileCopierHost( IFileSystem? fileSystem = null ) : base( - options + options, + fileSystem ?? new FileSystem() ) { Logger.Debug("Creating FileCopierHost with FileSystemRoot: " + Globals.FileSystemOptions!.FileSystemRoot); @@ -27,7 +28,7 @@ public FileCopierHost( copyStatusProducerModel, Globals.FileSystemOptions.FileSystemRoot!, Globals.FileSystemOptions.ExtractRoot!, - fileSystem + FileSystem ); _consumer = new FileCopyQueueConsumer(fileCopier); } diff --git a/src/SmiServices/Microservices/IdentifierMapper/IdentifierMapper.cs b/src/SmiServices/Microservices/IdentifierMapper/IdentifierMapper.cs index 022b01c1b..4372146af 100644 --- a/src/SmiServices/Microservices/IdentifierMapper/IdentifierMapper.cs +++ b/src/SmiServices/Microservices/IdentifierMapper/IdentifierMapper.cs @@ -1,18 +1,19 @@ using SmiServices.Common.Execution; using SmiServices.Common.Options; using System.Collections.Generic; +using System.IO.Abstractions; namespace SmiServices.Microservices.IdentifierMapper { public static class IdentifierMapper { - public static int Main(IEnumerable args) + public static int Main(IEnumerable args, IFileSystem? fileSystem = null) { - int ret = SmiCliInit.ParseAndRun(args, typeof(IdentifierMapper), OnParse); + int ret = SmiCliInit.ParseAndRun(args, typeof(IdentifierMapper), OnParse, fileSystem ?? new FileSystem()); return ret; } - private static int OnParse(GlobalOptions globals, CliOptions opts) + private static int OnParse(GlobalOptions globals, IFileSystem fileSystem, CliOptions opts) { var bootstrapper = new MicroserviceHostBootstrapper(() => new IdentifierMapperHost(globals)); int ret = bootstrapper.Main(); diff --git a/src/SmiServices/Microservices/IdentifierMapper/IdentifierMapperHost.cs b/src/SmiServices/Microservices/IdentifierMapper/IdentifierMapperHost.cs index 0530ca9d9..58f025898 100644 --- a/src/SmiServices/Microservices/IdentifierMapper/IdentifierMapperHost.cs +++ b/src/SmiServices/Microservices/IdentifierMapper/IdentifierMapperHost.cs @@ -7,6 +7,7 @@ using SmiServices.Microservices.IdentifierMapper.Swappers; using StackExchange.Redis; using System; +using System.IO.Abstractions; namespace SmiServices.Microservices.IdentifierMapper @@ -24,8 +25,8 @@ public class IdentifierMapperHost : MicroserviceHost - public IdentifierMapperHost(GlobalOptions options, ISwapIdentifiers? swapper = null) - : base(options) + public IdentifierMapperHost(GlobalOptions options, ISwapIdentifiers? swapper = null, IFileSystem? fileSystem = null) + : base(options, fileSystem ?? new FileSystem()) { _consumerOptions = options.IdentifierMapperOptions!; diff --git a/src/SmiServices/Microservices/IsIdentifiable/Classifier.cs b/src/SmiServices/Microservices/IsIdentifiable/Classifier.cs index e7a1a81c5..83b7a2c48 100644 --- a/src/SmiServices/Microservices/IsIdentifiable/Classifier.cs +++ b/src/SmiServices/Microservices/IsIdentifiable/Classifier.cs @@ -9,10 +9,10 @@ namespace SmiServices.Microservices.IsIdentifiable { public abstract class Classifier : IClassifier { - public DirectoryInfo? DataDirectory { get; set; } + public IDirectoryInfo? DataDirectory { get; set; } - protected Classifier(DirectoryInfo dataDirectory) + protected Classifier(IDirectoryInfo dataDirectory) { DataDirectory = dataDirectory; @@ -27,7 +27,7 @@ protected Classifier(DirectoryInfo dataDirectory) /// /// /// - protected DirectoryInfo GetSubdirectory(string toFind) + protected IDirectoryInfo GetSubdirectory(string toFind) { var stanfordNerDir = DataDirectory!.GetDirectories(toFind).SingleOrDefault() ?? throw new DirectoryNotFoundException($"Expected sub-directory called '{toFind}' to exist in '{DataDirectory}'"); return stanfordNerDir; diff --git a/src/SmiServices/Microservices/IsIdentifiable/IClassifier.cs b/src/SmiServices/Microservices/IsIdentifiable/IClassifier.cs index df3ee7ec8..de2c1c8eb 100644 --- a/src/SmiServices/Microservices/IsIdentifiable/IClassifier.cs +++ b/src/SmiServices/Microservices/IsIdentifiable/IClassifier.cs @@ -1,6 +1,5 @@ using IsIdentifiable.Failures; using System.Collections.Generic; -using System.IO; using System.IO.Abstractions; namespace SmiServices.Microservices.IsIdentifiable @@ -10,7 +9,7 @@ public interface IClassifier /// /// The location in which you can get your required data files /// - DirectoryInfo? DataDirectory { get; set; } + IDirectoryInfo? DataDirectory { get; set; } IEnumerable Classify(IFileInfo dcm); } diff --git a/src/SmiServices/Microservices/IsIdentifiable/IsIdentifiable.cs b/src/SmiServices/Microservices/IsIdentifiable/IsIdentifiable.cs index 07db67bfd..4bb29ea23 100644 --- a/src/SmiServices/Microservices/IsIdentifiable/IsIdentifiable.cs +++ b/src/SmiServices/Microservices/IsIdentifiable/IsIdentifiable.cs @@ -1,18 +1,19 @@ using SmiServices.Common.Execution; using SmiServices.Common.Options; using System.Collections.Generic; +using System.IO.Abstractions; namespace SmiServices.Microservices.IsIdentifiable { public static class IsIdentifiable { - public static int Main(IEnumerable args) + public static int Main(IEnumerable args, IFileSystem? fileSystem = null) { - int ret = SmiCliInit.ParseAndRun(args, typeof(IsIdentifiable), OnParse); + int ret = SmiCliInit.ParseAndRun(args, typeof(IsIdentifiable), OnParse, fileSystem ?? new FileSystem()); return ret; } - private static int OnParse(GlobalOptions globals, CliOptions opts) + private static int OnParse(GlobalOptions globals, IFileSystem fileSystem, CliOptions opts) { var bootstrapper = new MicroserviceHostBootstrapper( () => new IsIdentifiableHost(globals)); diff --git a/src/SmiServices/Microservices/IsIdentifiable/IsIdentifiableHost.cs b/src/SmiServices/Microservices/IsIdentifiable/IsIdentifiableHost.cs index 4b5ce59c3..5393093ba 100644 --- a/src/SmiServices/Microservices/IsIdentifiable/IsIdentifiableHost.cs +++ b/src/SmiServices/Microservices/IsIdentifiable/IsIdentifiableHost.cs @@ -3,7 +3,7 @@ using SmiServices.Common.Messaging; using SmiServices.Common.Options; using System; -using System.IO; +using System.IO.Abstractions; namespace SmiServices.Microservices.IsIdentifiable { @@ -15,9 +15,10 @@ public class IsIdentifiableHost : MicroserviceHost private readonly IProducerModel _producerModel; public IsIdentifiableHost( - GlobalOptions globals + GlobalOptions globals, + IFileSystem? fileSystem = null ) - : base(globals) + : base(globals, fileSystem ?? new FileSystem()) { _consumerOptions = globals.IsIdentifiableServiceOptions ?? throw new ArgumentNullException(nameof(globals)); @@ -30,7 +31,7 @@ GlobalOptions globals throw new ArgumentException("A DataDirectory must be set", nameof(globals)); var objectFactory = new MicroserviceObjectFactory(); - var classifier = objectFactory.CreateInstance(classifierTypename, typeof(IClassifier).Assembly, new DirectoryInfo(dataDirectory), globals.IsIdentifiableOptions!) + var classifier = objectFactory.CreateInstance(classifierTypename, typeof(IClassifier).Assembly, FileSystem.DirectoryInfo.New(dataDirectory), globals.IsIdentifiableOptions!) ?? throw new TypeLoadException($"Could not find IClassifier Type {classifierTypename}"); _producerModel = MessageBroker.SetupProducer(globals.IsIdentifiableServiceOptions.IsIdentifiableProducerOptions!, isBatch: false); diff --git a/src/SmiServices/Microservices/IsIdentifiable/RejectAllClassifier.cs b/src/SmiServices/Microservices/IsIdentifiable/RejectAllClassifier.cs index e2869e019..df7d3bd96 100644 --- a/src/SmiServices/Microservices/IsIdentifiable/RejectAllClassifier.cs +++ b/src/SmiServices/Microservices/IsIdentifiable/RejectAllClassifier.cs @@ -1,14 +1,13 @@ using IsIdentifiable.Failures; using IsIdentifiable.Options; using System.Collections.Generic; -using System.IO; using System.IO.Abstractions; namespace SmiServices.Microservices.IsIdentifiable { public class RejectAllClassifier : Classifier { - public RejectAllClassifier(DirectoryInfo dataDirectory, IsIdentifiableDicomFileOptions _) : base(dataDirectory) + public RejectAllClassifier(IDirectoryInfo dataDirectory, IsIdentifiableDicomFileOptions _) : base(dataDirectory) { } diff --git a/src/SmiServices/Microservices/IsIdentifiable/TesseractStanfordDicomFileClassifier.cs b/src/SmiServices/Microservices/IsIdentifiable/TesseractStanfordDicomFileClassifier.cs index 35ebebe35..2242fcb17 100644 --- a/src/SmiServices/Microservices/IsIdentifiable/TesseractStanfordDicomFileClassifier.cs +++ b/src/SmiServices/Microservices/IsIdentifiable/TesseractStanfordDicomFileClassifier.cs @@ -4,7 +4,6 @@ using IsIdentifiable.Runners; using System; using System.Collections.Generic; -using System.IO; using System.IO.Abstractions; namespace SmiServices.Microservices.IsIdentifiable @@ -14,15 +13,15 @@ public class TesseractStanfordDicomFileClassifier : Classifier, IDisposable private readonly DicomFileRunner _runner; //public TesseractStanfordDicomFileClassifier(DirectoryInfo dataDirectory) : base(dataDirectory) - public TesseractStanfordDicomFileClassifier(DirectoryInfo dataDirectory, IsIdentifiableDicomFileOptions fileOptions) : base(dataDirectory) + public TesseractStanfordDicomFileClassifier(IDirectoryInfo dataDirectory, IsIdentifiableDicomFileOptions fileOptions) : base(dataDirectory) { //need to pass this so that the runner doesn't get unhappy about there being no reports (even though we clear it below) fileOptions.ColumnReport = true; fileOptions.TessDirectory = dataDirectory.FullName; // The Rules directory is always called "IsIdentifiableRules" - DirectoryInfo[] subDirs = dataDirectory.GetDirectories("IsIdentifiableRules"); - foreach (DirectoryInfo subDir in subDirs) + var subDirs = dataDirectory.GetDirectories("IsIdentifiableRules"); + foreach (var subDir in subDirs) fileOptions.RulesDirectory = subDir.FullName; _runner = new DicomFileRunner(fileOptions, new FileSystem()); diff --git a/src/SmiServices/Microservices/MongoDBPopulator/MongoDBPopulator.cs b/src/SmiServices/Microservices/MongoDBPopulator/MongoDBPopulator.cs index 9c7f36f32..d76d4ad69 100644 --- a/src/SmiServices/Microservices/MongoDBPopulator/MongoDBPopulator.cs +++ b/src/SmiServices/Microservices/MongoDBPopulator/MongoDBPopulator.cs @@ -1,6 +1,7 @@ using SmiServices.Common.Execution; using SmiServices.Common.Options; using System.Collections.Generic; +using System.IO.Abstractions; namespace SmiServices.Microservices.MongoDBPopulator { @@ -9,13 +10,13 @@ public static class MongoDBPopulator /// /// Program entry point when run from command line /// - public static int Main(IEnumerable args) + public static int Main(IEnumerable args, IFileSystem? fileSystem = null) { - int ret = SmiCliInit.ParseAndRun(args, typeof(MongoDBPopulator), OnParse); + int ret = SmiCliInit.ParseAndRun(args, typeof(MongoDBPopulator), OnParse, fileSystem ?? new FileSystem()); return ret; } - private static int OnParse(GlobalOptions globals, CliOptions _) + private static int OnParse(GlobalOptions globals, IFileSystem fileSystem, CliOptions _) { var bootstrapper = new MicroserviceHostBootstrapper(() => new MongoDbPopulatorHost(globals)); int ret = bootstrapper.Main(); diff --git a/src/SmiServices/Microservices/MongoDBPopulator/MongoDbPopulatorHost.cs b/src/SmiServices/Microservices/MongoDBPopulator/MongoDbPopulatorHost.cs index ec9d1df58..a1e7e7fbb 100644 --- a/src/SmiServices/Microservices/MongoDBPopulator/MongoDbPopulatorHost.cs +++ b/src/SmiServices/Microservices/MongoDBPopulator/MongoDbPopulatorHost.cs @@ -3,6 +3,7 @@ using SmiServices.Common.Options; using System; using System.Collections.Generic; +using System.IO.Abstractions; using System.Linq; namespace SmiServices.Microservices.MongoDBPopulator @@ -15,8 +16,8 @@ public class MongoDbPopulatorHost : MicroserviceHost public readonly List Consumers = []; - public MongoDbPopulatorHost(GlobalOptions options) - : base(options) + public MongoDbPopulatorHost(GlobalOptions options, IFileSystem? fileSystem = null) + : base(options, fileSystem ?? new FileSystem()) { Consumers.Add(new MongoDbPopulatorMessageConsumer(options.MongoDatabases!.DicomStoreOptions!, options.MongoDbPopulatorOptions!, options.MongoDbPopulatorOptions!.SeriesQueueConsumerOptions!)); Consumers.Add(new MongoDbPopulatorMessageConsumer(options.MongoDatabases.DicomStoreOptions!, options.MongoDbPopulatorOptions, options.MongoDbPopulatorOptions.ImageQueueConsumerOptions!)); diff --git a/src/SmiServices/Microservices/UpdateValues/UpdateValues.cs b/src/SmiServices/Microservices/UpdateValues/UpdateValues.cs index b57746e49..107c069da 100644 --- a/src/SmiServices/Microservices/UpdateValues/UpdateValues.cs +++ b/src/SmiServices/Microservices/UpdateValues/UpdateValues.cs @@ -1,23 +1,25 @@ using SmiServices.Common.Execution; using SmiServices.Common.Options; using System.Collections.Generic; +using System.IO.Abstractions; namespace SmiServices.Microservices.UpdateValues { public static class UpdateValues { - public static int Main(IEnumerable args) + public static int Main(IEnumerable args, IFileSystem? fileSystem = null) { return SmiCliInit .ParseAndRun( args, typeof(UpdateValues), - OnParse + OnParse, + fileSystem ?? new FileSystem() ); } - private static int OnParse(GlobalOptions globals, UpdateValuesCliOptions opts) + private static int OnParse(GlobalOptions globals, IFileSystem fileSystem, UpdateValuesCliOptions opts) { var bootstrapper = new MicroserviceHostBootstrapper(() => new UpdateValuesHost(globals)); int ret = bootstrapper.Main(); diff --git a/src/SmiServices/Microservices/UpdateValues/UpdateValuesHost.cs b/src/SmiServices/Microservices/UpdateValues/UpdateValuesHost.cs index 559765927..3183dca0a 100644 --- a/src/SmiServices/Microservices/UpdateValues/UpdateValuesHost.cs +++ b/src/SmiServices/Microservices/UpdateValues/UpdateValuesHost.cs @@ -2,6 +2,7 @@ using SmiServices.Common; using SmiServices.Common.Execution; using SmiServices.Common.Options; +using System.IO.Abstractions; namespace SmiServices.Microservices.UpdateValues { @@ -9,8 +10,8 @@ public class UpdateValuesHost : MicroserviceHost { public UpdateValuesQueueConsumer? Consumer { get; set; } - public UpdateValuesHost(GlobalOptions globals, IMessageBroker? messageBroker = null, bool threaded = false) - : base(globals, messageBroker, threaded) + public UpdateValuesHost(GlobalOptions globals, IFileSystem? fileSystem = null, IMessageBroker? messageBroker = null, bool threaded = false) + : base(globals, fileSystem ?? new FileSystem(), messageBroker, threaded) { FansiImplementations.Load(); }