diff --git a/src/Common/ProgressBar.cs b/src/Common/ProgressBar.cs index 1dcc6175..6d2cc972 100644 --- a/src/Common/ProgressBar.cs +++ b/src/Common/ProgressBar.cs @@ -1,6 +1,7 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using System.Text; +using CliFx.Infrastructure; namespace Common; @@ -9,6 +10,7 @@ namespace Common; /// public sealed class ProgressBar //: IProgress { + private readonly IConsole _console; private readonly TimeSpan _animationInterval = TimeSpan.FromSeconds(1.0 / 8); private const string Animation = @"|/-\"; private int _animationIndex; @@ -17,12 +19,14 @@ public sealed class ProgressBar //: IProgress public IObserver ReportProgress => _reportProgress; public string Description { get; set; } = ""; - public ProgressBar() + public ProgressBar(IConsole console) { + _console = console; + // A progress bar is only for temporary display in a console window. // If the console output is redirected to a file, draw nothing. // Otherwise, we'll end up with a lot of garbage in the target file. - if (!Console.IsOutputRedirected) + if (!_console.IsOutputRedirected) { _reportProgress.Sample(_animationInterval) .Select(CalculateText) @@ -43,7 +47,7 @@ public sealed class ProgressBar //: IProgress return $"[{progressBlocks}{progressBlocksUnfilled}] {percent,3}% {currentAnimationFrame} {Description}"; } - private static void UpdateText(int previousTextLength, string text) + private void UpdateText(int previousTextLength, string text) { var outputBuilder = new StringBuilder(); outputBuilder.Append('\r'); @@ -56,6 +60,6 @@ public sealed class ProgressBar //: IProgress outputBuilder.Append(' ', lengthDifference); } - Console.Write(outputBuilder); + _console.Output.Write(outputBuilder); } } diff --git a/src/Recyclarr/CompositionRoot.cs b/src/Recyclarr/CompositionRoot.cs index c96ce649..0e474be4 100644 --- a/src/Recyclarr/CompositionRoot.cs +++ b/src/Recyclarr/CompositionRoot.cs @@ -50,6 +50,7 @@ internal class CompositionRoot : ICompositionRoot builder.RegisterModule(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType(); ConfigurationRegistrations(builder); CommandRegistrations(builder); diff --git a/src/Recyclarr/IServiceLocatorProxy.cs b/src/Recyclarr/IServiceLocatorProxy.cs index 2974af36..c1abc26a 100644 --- a/src/Recyclarr/IServiceLocatorProxy.cs +++ b/src/Recyclarr/IServiceLocatorProxy.cs @@ -2,6 +2,12 @@ using Autofac; namespace Recyclarr; +/// +/// This class exists to make unit testing easier. Many methods for ILifetimeScope are extension +/// methods and make unit testing more difficult. +/// This class wraps Autofac to make it more +/// "mockable". +/// public interface IServiceLocatorProxy : IDisposable { ILifetimeScope Container { get; } diff --git a/src/TrashLib/Radarr/CustomFormat/CustomFormatUpdater.cs b/src/TrashLib/Radarr/CustomFormat/CustomFormatUpdater.cs index 0834933a..0b9a8c75 100644 --- a/src/TrashLib/Radarr/CustomFormat/CustomFormatUpdater.cs +++ b/src/TrashLib/Radarr/CustomFormat/CustomFormatUpdater.cs @@ -1,3 +1,4 @@ +using CliFx.Infrastructure; using Common.Extensions; using Serilog; using TrashLib.Extensions; @@ -12,17 +13,20 @@ internal class CustomFormatUpdater : ICustomFormatUpdater private readonly ICachePersister _cache; private readonly IGuideProcessor _guideProcessor; private readonly IPersistenceProcessor _persistenceProcessor; + private readonly IConsole _console; public CustomFormatUpdater( ILogger log, ICachePersister cache, IGuideProcessor guideProcessor, - IPersistenceProcessor persistenceProcessor) + IPersistenceProcessor persistenceProcessor, + IConsole console) { Log = log; _cache = cache; _guideProcessor = guideProcessor; _persistenceProcessor = persistenceProcessor; + _console = console; } private ILogger Log { get; } @@ -133,7 +137,7 @@ internal class CustomFormatUpdater : ICustomFormatUpdater private bool ValidateGuideDataAndCheckShouldProceed(RadarrConfiguration config) { - Console.WriteLine(""); + _console.Output.WriteLine(""); if (_guideProcessor.DuplicatedCustomFormats.Count > 0) { @@ -151,7 +155,7 @@ internal class CustomFormatUpdater : ICustomFormatUpdater } } - Console.WriteLine(""); + _console.Output.WriteLine(""); } if (_guideProcessor.CustomFormatsNotInGuide.Count > 0) @@ -162,7 +166,7 @@ internal class CustomFormatUpdater : ICustomFormatUpdater "warning"); Log.Warning("{CfList}", _guideProcessor.CustomFormatsNotInGuide); - Console.WriteLine(""); + _console.Output.WriteLine(""); } var cfsWithoutQualityProfiles = _guideProcessor.ConfigData @@ -175,7 +179,7 @@ internal class CustomFormatUpdater : ICustomFormatUpdater Log.Debug("These custom formats will be uploaded but are not associated to a quality profile in the " + "config file: {UnassociatedCfs}", cfsWithoutQualityProfiles); - Console.WriteLine(""); + _console.Output.WriteLine(""); } // No CFs are defined in this item, or they are all invalid. Skip this whole instance. @@ -195,7 +199,7 @@ internal class CustomFormatUpdater : ICustomFormatUpdater Log.Information("{CfList}", tuple); } - Console.WriteLine(""); + _console.Output.WriteLine(""); } if (_guideProcessor.CustomFormatsWithOutdatedNames.Count > 0) @@ -209,7 +213,7 @@ internal class CustomFormatUpdater : ICustomFormatUpdater Log.Warning(" - '{OldName}' -> '{NewName}'", oldName, newName); } - Console.WriteLine(""); + _console.Output.WriteLine(""); } return true; @@ -217,34 +221,34 @@ internal class CustomFormatUpdater : ICustomFormatUpdater private void PreviewCustomFormats() { - Console.WriteLine(""); - Console.WriteLine("========================================================="); - Console.WriteLine(" >>> Custom Formats From Guide <<< "); - Console.WriteLine("========================================================="); - Console.WriteLine(""); + _console.Output.WriteLine(""); + _console.Output.WriteLine("========================================================="); + _console.Output.WriteLine(" >>> Custom Formats From Guide <<< "); + _console.Output.WriteLine("========================================================="); + _console.Output.WriteLine(""); const string format = "{0,-30} {1,-35}"; - Console.WriteLine(format, "Custom Format", "Trash ID"); - Console.WriteLine(string.Concat(Enumerable.Repeat('-', 1 + 30 + 35))); + _console.Output.WriteLine(format, "Custom Format", "Trash ID"); + _console.Output.WriteLine(string.Concat(Enumerable.Repeat('-', 1 + 30 + 35))); foreach (var cf in _guideProcessor.ProcessedCustomFormats) { - Console.WriteLine(format, cf.Name, cf.TrashId); + _console.Output.WriteLine(format, cf.Name, cf.TrashId); } - Console.WriteLine(""); - Console.WriteLine("========================================================="); - Console.WriteLine(" >>> Quality Profile Assignments & Scores <<< "); - Console.WriteLine("========================================================="); - Console.WriteLine(""); + _console.Output.WriteLine(""); + _console.Output.WriteLine("========================================================="); + _console.Output.WriteLine(" >>> Quality Profile Assignments & Scores <<< "); + _console.Output.WriteLine("========================================================="); + _console.Output.WriteLine(""); const string profileFormat = "{0,-18} {1,-20} {2,-8}"; - Console.WriteLine(profileFormat, "Profile", "Custom Format", "Score"); - Console.WriteLine(string.Concat(Enumerable.Repeat('-', 2 + 18 + 20 + 8))); + _console.Output.WriteLine(profileFormat, "Profile", "Custom Format", "Score"); + _console.Output.WriteLine(string.Concat(Enumerable.Repeat('-', 2 + 18 + 20 + 8))); foreach (var (profileName, scoreMap) in _guideProcessor.ProfileScores) { - Console.WriteLine(profileFormat, profileName, "", ""); + _console.Output.WriteLine(profileFormat, profileName, "", ""); foreach (var (customFormat, score) in scoreMap.Mapping) { @@ -258,10 +262,10 @@ internal class CustomFormatUpdater : ICustomFormatUpdater continue; } - Console.WriteLine(profileFormat, "", matchingCf.Name, score); + _console.Output.WriteLine(profileFormat, "", matchingCf.Name, score); } } - Console.WriteLine(""); + _console.Output.WriteLine(""); } } diff --git a/src/TrashLib/Radarr/QualityDefinition/RadarrQualityDefinitionUpdater.cs b/src/TrashLib/Radarr/QualityDefinition/RadarrQualityDefinitionUpdater.cs index f116e7a1..67a28f53 100644 --- a/src/TrashLib/Radarr/QualityDefinition/RadarrQualityDefinitionUpdater.cs +++ b/src/TrashLib/Radarr/QualityDefinition/RadarrQualityDefinitionUpdater.cs @@ -1,3 +1,4 @@ +using CliFx.Infrastructure; using Serilog; using TrashLib.Radarr.Config; using TrashLib.Radarr.QualityDefinition.Api; @@ -8,14 +9,19 @@ namespace TrashLib.Radarr.QualityDefinition; internal class RadarrQualityDefinitionUpdater : IRadarrQualityDefinitionUpdater { private readonly IQualityDefinitionService _api; + private readonly IConsole _console; private readonly IRadarrQualityDefinitionGuideParser _parser; - public RadarrQualityDefinitionUpdater(ILogger logger, IRadarrQualityDefinitionGuideParser parser, - IQualityDefinitionService api) + public RadarrQualityDefinitionUpdater( + ILogger logger, + IRadarrQualityDefinitionGuideParser parser, + IQualityDefinitionService api, + IConsole console) { Log = logger; _parser = parser; _api = api; + _console = console; } private ILogger Log { get; } @@ -53,19 +59,19 @@ internal class RadarrQualityDefinitionUpdater : IRadarrQualityDefinitionUpdater await ProcessQualityDefinition(selectedQuality); } - private static void PrintQualityPreview(IEnumerable quality) + private void PrintQualityPreview(IEnumerable quality) { - Console.WriteLine(""); + _console.Output.WriteLine(""); const string format = "{0,-20} {1,-10} {2,-15} {3,-15}"; - Console.WriteLine(format, "Quality", "Min", "Max", "Preferred"); - Console.WriteLine(format, "-------", "---", "---", "---------"); + _console.Output.WriteLine(format, "Quality", "Min", "Max", "Preferred"); + _console.Output.WriteLine(format, "-------", "---", "---", "---------"); foreach (var q in quality) { - Console.WriteLine(format, q.Name, q.AnnotatedMin, q.AnnotatedMax, q.AnnotatedPreferred); + _console.Output.WriteLine(format, q.Name, q.AnnotatedMin, q.AnnotatedMax, q.AnnotatedPreferred); } - Console.WriteLine(""); + _console.Output.WriteLine(""); } private async Task ProcessQualityDefinition(IEnumerable guideQuality) @@ -99,7 +105,7 @@ internal class RadarrQualityDefinitionUpdater : IRadarrQualityDefinitionUpdater continue; } - // Not using the original list again, so it's OK to modify the definition reftype objects in-place. + // Not using the original list again, so it's OK to modify the definition ref type objects in-place. entry.MinSize = qualityData.MinForApi; entry.MaxSize = qualityData.MaxForApi; entry.PreferredSize = qualityData.PreferredForApi; diff --git a/src/TrashLib/Sonarr/QualityDefinition/SonarrQualityDefinitionUpdater.cs b/src/TrashLib/Sonarr/QualityDefinition/SonarrQualityDefinitionUpdater.cs index f629e648..6cd8b505 100644 --- a/src/TrashLib/Sonarr/QualityDefinition/SonarrQualityDefinitionUpdater.cs +++ b/src/TrashLib/Sonarr/QualityDefinition/SonarrQualityDefinitionUpdater.cs @@ -1,4 +1,5 @@ using System.Text.RegularExpressions; +using CliFx.Infrastructure; using Serilog; using TrashLib.Sonarr.Api; using TrashLib.Sonarr.Api.Objects; @@ -9,15 +10,20 @@ namespace TrashLib.Sonarr.QualityDefinition; internal class SonarrQualityDefinitionUpdater : ISonarrQualityDefinitionUpdater { private readonly ISonarrApi _api; + private readonly IConsole _console; private readonly ISonarrQualityDefinitionGuideParser _parser; private readonly Regex _regexHybrid = new(@"720|1080", RegexOptions.Compiled); - public SonarrQualityDefinitionUpdater(ILogger logger, ISonarrQualityDefinitionGuideParser parser, - ISonarrApi api) + public SonarrQualityDefinitionUpdater( + ILogger logger, + ISonarrQualityDefinitionGuideParser parser, + ISonarrApi api, + IConsole console) { Log = logger; _parser = parser; _api = api; + _console = console; } private ILogger Log { get; } @@ -87,19 +93,19 @@ internal class SonarrQualityDefinitionUpdater : ISonarrQualityDefinitionUpdater return hybrid; } - private static void PrintQualityPreview(IEnumerable quality) + private void PrintQualityPreview(IEnumerable quality) { - Console.WriteLine(""); + _console.Output.WriteLine(""); const string format = "{0,-20} {1,-10} {2,-15}"; - Console.WriteLine(format, "Quality", "Min", "Max"); - Console.WriteLine(format, "-------", "---", "---"); + _console.Output.WriteLine(format, "Quality", "Min", "Max"); + _console.Output.WriteLine(format, "-------", "---", "---"); foreach (var q in quality) { - Console.WriteLine(format, q.Name, q.AnnotatedMin, q.AnnotatedMax); + _console.Output.WriteLine(format, q.Name, q.AnnotatedMin, q.AnnotatedMax); } - Console.WriteLine(""); + _console.Output.WriteLine(""); } private async Task ProcessQualityDefinition(IEnumerable guideQuality) @@ -132,7 +138,7 @@ internal class SonarrQualityDefinitionUpdater : ISonarrQualityDefinitionUpdater continue; } - // Not using the original list again, so it's OK to modify the definition reftype objects in-place. + // Not using the original list again, so it's OK to modify the definition ref type objects in-place. entry.MinSize = qualityData.MinForApi; entry.MaxSize = qualityData.MaxForApi; newQuality.Add(entry); diff --git a/src/TrashLib/Sonarr/ReleaseProfile/ReleaseProfileUpdater.cs b/src/TrashLib/Sonarr/ReleaseProfile/ReleaseProfileUpdater.cs index c5ca298e..52b2d135 100644 --- a/src/TrashLib/Sonarr/ReleaseProfile/ReleaseProfileUpdater.cs +++ b/src/TrashLib/Sonarr/ReleaseProfile/ReleaseProfileUpdater.cs @@ -1,4 +1,5 @@ using System.Reactive.Linq; +using CliFx.Infrastructure; using Common.Extensions; using Serilog; using TrashLib.ExceptionTypes; @@ -15,6 +16,7 @@ public class ReleaseProfileUpdater : IReleaseProfileUpdater private readonly ISonarrApi _api; private readonly ISonarrCompatibility _compatibility; private readonly IReleaseProfileFilterPipeline _pipeline; + private readonly IConsole _console; private readonly ISonarrGuideService _guide; private readonly ILogger _log; @@ -23,13 +25,15 @@ public class ReleaseProfileUpdater : IReleaseProfileUpdater ISonarrGuideService guide, ISonarrApi api, ISonarrCompatibility compatibility, - IReleaseProfileFilterPipeline pipeline) + IReleaseProfileFilterPipeline pipeline, + IConsole console) { _log = logger; _guide = guide; _api = api; _compatibility = compatibility; _pipeline = pipeline; + _console = console; } public async Task Process(bool isPreview, SonarrConfiguration config) @@ -56,7 +60,7 @@ public class ReleaseProfileUpdater : IReleaseProfileUpdater if (isPreview) { - Utils.PrintTermsAndScores(selectedProfile); + PrintTermsAndScores(selectedProfile); continue; } @@ -66,6 +70,59 @@ public class ReleaseProfileUpdater : IReleaseProfileUpdater await ProcessReleaseProfiles(filteredProfiles); } + private void PrintTermsAndScores(ReleaseProfileData profile) + { + void PrintPreferredTerms(string title, IReadOnlyCollection preferredTerms) + { + if (preferredTerms.Count <= 0) + { + return; + } + + _console.Output.WriteLine($" {title}:"); + foreach (var (score, terms) in preferredTerms) + { + foreach (var term in terms) + { + _console.Output.WriteLine($" {score,-10} {term}"); + } + } + + _console.Output.WriteLine(""); + } + + void PrintTerms(string title, IReadOnlyCollection terms) + { + if (terms.Count == 0) + { + return; + } + + _console.Output.WriteLine($" {title}:"); + foreach (var term in terms) + { + _console.Output.WriteLine($" {term}"); + } + + _console.Output.WriteLine(""); + } + + _console.Output.WriteLine(""); + + _console.Output.WriteLine(profile.Name); + + _console.Output.WriteLine(" Include Preferred when Renaming?"); + _console.Output.WriteLine(" " + + (profile.IncludePreferredWhenRenaming ? "YES" : "NO")); + _console.Output.WriteLine(""); + + PrintTerms("Must Contain", profile.Required); + PrintTerms("Must Not Contain", profile.Ignored); + PrintPreferredTerms("Preferred", profile.Preferred); + + _console.Output.WriteLine(""); + } + private async Task ProcessReleaseProfiles( List<(ReleaseProfileData Profile, IReadOnlyCollection Tags)> profilesAndTags) { diff --git a/src/TrashLib/Sonarr/ReleaseProfile/Utils.cs b/src/TrashLib/Sonarr/ReleaseProfile/Utils.cs deleted file mode 100644 index 7a38b3ca..00000000 --- a/src/TrashLib/Sonarr/ReleaseProfile/Utils.cs +++ /dev/null @@ -1,57 +0,0 @@ -namespace TrashLib.Sonarr.ReleaseProfile; - -public static class Utils -{ - public static void PrintTermsAndScores(ReleaseProfileData profile) - { - static void PrintPreferredTerms(string title, IReadOnlyCollection preferredTerms) - { - if (preferredTerms.Count <= 0) - { - return; - } - - Console.WriteLine($" {title}:"); - foreach (var (score, terms) in preferredTerms) - { - foreach (var term in terms) - { - Console.WriteLine($" {score,-10} {term}"); - } - } - - Console.WriteLine(""); - } - - static void PrintTerms(string title, IReadOnlyCollection terms) - { - if (terms.Count == 0) - { - return; - } - - Console.WriteLine($" {title}:"); - foreach (var term in terms) - { - Console.WriteLine($" {term}"); - } - - Console.WriteLine(""); - } - - Console.WriteLine(""); - - Console.WriteLine(profile.Name); - - Console.WriteLine(" Include Preferred when Renaming?"); - Console.WriteLine(" " + - (profile.IncludePreferredWhenRenaming ? "YES" : "NO")); - Console.WriteLine(""); - - PrintTerms("Must Contain", profile.Required); - PrintTerms("Must Not Contain", profile.Ignored); - PrintPreferredTerms("Preferred", profile.Preferred); - - Console.WriteLine(""); - } -} diff --git a/src/VersionControl/GitRepositoryFactory.cs b/src/VersionControl/GitRepositoryFactory.cs index b7464e48..0886c58a 100644 --- a/src/VersionControl/GitRepositoryFactory.cs +++ b/src/VersionControl/GitRepositoryFactory.cs @@ -9,15 +9,18 @@ public class GitRepositoryFactory : IGitRepositoryFactory private readonly IFileUtilities _fileUtils; private readonly IRepositoryStaticWrapper _staticWrapper; private readonly Func _repoFactory; + private readonly Func _progressBarFactory; public GitRepositoryFactory( IFileUtilities fileUtils, IRepositoryStaticWrapper staticWrapper, - Func repoFactory) + Func repoFactory, + Func progressBarFactory) { _fileUtils = fileUtils; _staticWrapper = staticWrapper; _repoFactory = repoFactory; + _progressBarFactory = progressBarFactory; } public IGitRepository CreateAndCloneIfNeeded(string repoUrl, string repoPath, string branch) @@ -36,10 +39,8 @@ public class GitRepositoryFactory : IGitRepositoryFactory { _fileUtils.DeleteReadOnlyDirectory(repoPath); - var progress = new ProgressBar - { - Description = "Fetching guide data\n" - }; + var progress = _progressBarFactory(); + progress.Description = "Fetching guide data\n"; _staticWrapper.Clone(repoUrl, repoPath, new CloneOptions {