refactor: Use IConsole instead of Console

pull/108/head
Robert Dailey 3 years ago
parent 956f370350
commit 3a5e1f0c5e

@ -1,6 +1,7 @@
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using System.Text; using System.Text;
using CliFx.Infrastructure;
namespace Common; namespace Common;
@ -9,6 +10,7 @@ namespace Common;
/// </summary> /// </summary>
public sealed class ProgressBar //: IProgress<double> public sealed class ProgressBar //: IProgress<double>
{ {
private readonly IConsole _console;
private readonly TimeSpan _animationInterval = TimeSpan.FromSeconds(1.0 / 8); private readonly TimeSpan _animationInterval = TimeSpan.FromSeconds(1.0 / 8);
private const string Animation = @"|/-\"; private const string Animation = @"|/-\";
private int _animationIndex; private int _animationIndex;
@ -17,12 +19,14 @@ public sealed class ProgressBar //: IProgress<double>
public IObserver<float> ReportProgress => _reportProgress; public IObserver<float> ReportProgress => _reportProgress;
public string Description { get; set; } = ""; 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. // A progress bar is only for temporary display in a console window.
// If the console output is redirected to a file, draw nothing. // 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. // Otherwise, we'll end up with a lot of garbage in the target file.
if (!Console.IsOutputRedirected) if (!_console.IsOutputRedirected)
{ {
_reportProgress.Sample(_animationInterval) _reportProgress.Sample(_animationInterval)
.Select(CalculateText) .Select(CalculateText)
@ -43,7 +47,7 @@ public sealed class ProgressBar //: IProgress<double>
return $"[{progressBlocks}{progressBlocksUnfilled}] {percent,3}% {currentAnimationFrame} {Description}"; 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(); var outputBuilder = new StringBuilder();
outputBuilder.Append('\r'); outputBuilder.Append('\r');
@ -56,6 +60,6 @@ public sealed class ProgressBar //: IProgress<double>
outputBuilder.Append(' ', lengthDifference); outputBuilder.Append(' ', lengthDifference);
} }
Console.Write(outputBuilder); _console.Output.Write(outputBuilder);
} }
} }

@ -50,6 +50,7 @@ internal class CompositionRoot : ICompositionRoot
builder.RegisterModule<CacheAutofacModule>(); builder.RegisterModule<CacheAutofacModule>();
builder.RegisterType<CacheStoragePath>().As<ICacheStoragePath>(); builder.RegisterType<CacheStoragePath>().As<ICacheStoragePath>();
builder.RegisterType<RepoUpdater>().As<IRepoUpdater>(); builder.RegisterType<RepoUpdater>().As<IRepoUpdater>();
builder.RegisterType<ProgressBar>();
ConfigurationRegistrations(builder); ConfigurationRegistrations(builder);
CommandRegistrations(builder); CommandRegistrations(builder);

@ -2,6 +2,12 @@ using Autofac;
namespace Recyclarr; namespace Recyclarr;
/// <remarks>
/// 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".
/// </remarks>
public interface IServiceLocatorProxy : IDisposable public interface IServiceLocatorProxy : IDisposable
{ {
ILifetimeScope Container { get; } ILifetimeScope Container { get; }

@ -1,3 +1,4 @@
using CliFx.Infrastructure;
using Common.Extensions; using Common.Extensions;
using Serilog; using Serilog;
using TrashLib.Extensions; using TrashLib.Extensions;
@ -12,17 +13,20 @@ internal class CustomFormatUpdater : ICustomFormatUpdater
private readonly ICachePersister _cache; private readonly ICachePersister _cache;
private readonly IGuideProcessor _guideProcessor; private readonly IGuideProcessor _guideProcessor;
private readonly IPersistenceProcessor _persistenceProcessor; private readonly IPersistenceProcessor _persistenceProcessor;
private readonly IConsole _console;
public CustomFormatUpdater( public CustomFormatUpdater(
ILogger log, ILogger log,
ICachePersister cache, ICachePersister cache,
IGuideProcessor guideProcessor, IGuideProcessor guideProcessor,
IPersistenceProcessor persistenceProcessor) IPersistenceProcessor persistenceProcessor,
IConsole console)
{ {
Log = log; Log = log;
_cache = cache; _cache = cache;
_guideProcessor = guideProcessor; _guideProcessor = guideProcessor;
_persistenceProcessor = persistenceProcessor; _persistenceProcessor = persistenceProcessor;
_console = console;
} }
private ILogger Log { get; } private ILogger Log { get; }
@ -133,7 +137,7 @@ internal class CustomFormatUpdater : ICustomFormatUpdater
private bool ValidateGuideDataAndCheckShouldProceed(RadarrConfiguration config) private bool ValidateGuideDataAndCheckShouldProceed(RadarrConfiguration config)
{ {
Console.WriteLine(""); _console.Output.WriteLine("");
if (_guideProcessor.DuplicatedCustomFormats.Count > 0) if (_guideProcessor.DuplicatedCustomFormats.Count > 0)
{ {
@ -151,7 +155,7 @@ internal class CustomFormatUpdater : ICustomFormatUpdater
} }
} }
Console.WriteLine(""); _console.Output.WriteLine("");
} }
if (_guideProcessor.CustomFormatsNotInGuide.Count > 0) if (_guideProcessor.CustomFormatsNotInGuide.Count > 0)
@ -162,7 +166,7 @@ internal class CustomFormatUpdater : ICustomFormatUpdater
"warning"); "warning");
Log.Warning("{CfList}", _guideProcessor.CustomFormatsNotInGuide); Log.Warning("{CfList}", _guideProcessor.CustomFormatsNotInGuide);
Console.WriteLine(""); _console.Output.WriteLine("");
} }
var cfsWithoutQualityProfiles = _guideProcessor.ConfigData 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 " + Log.Debug("These custom formats will be uploaded but are not associated to a quality profile in the " +
"config file: {UnassociatedCfs}", cfsWithoutQualityProfiles); "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. // 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); Log.Information("{CfList}", tuple);
} }
Console.WriteLine(""); _console.Output.WriteLine("");
} }
if (_guideProcessor.CustomFormatsWithOutdatedNames.Count > 0) if (_guideProcessor.CustomFormatsWithOutdatedNames.Count > 0)
@ -209,7 +213,7 @@ internal class CustomFormatUpdater : ICustomFormatUpdater
Log.Warning(" - '{OldName}' -> '{NewName}'", oldName, newName); Log.Warning(" - '{OldName}' -> '{NewName}'", oldName, newName);
} }
Console.WriteLine(""); _console.Output.WriteLine("");
} }
return true; return true;
@ -217,34 +221,34 @@ internal class CustomFormatUpdater : ICustomFormatUpdater
private void PreviewCustomFormats() private void PreviewCustomFormats()
{ {
Console.WriteLine(""); _console.Output.WriteLine("");
Console.WriteLine("========================================================="); _console.Output.WriteLine("=========================================================");
Console.WriteLine(" >>> Custom Formats From Guide <<< "); _console.Output.WriteLine(" >>> Custom Formats From Guide <<< ");
Console.WriteLine("========================================================="); _console.Output.WriteLine("=========================================================");
Console.WriteLine(""); _console.Output.WriteLine("");
const string format = "{0,-30} {1,-35}"; const string format = "{0,-30} {1,-35}";
Console.WriteLine(format, "Custom Format", "Trash ID"); _console.Output.WriteLine(format, "Custom Format", "Trash ID");
Console.WriteLine(string.Concat(Enumerable.Repeat('-', 1 + 30 + 35))); _console.Output.WriteLine(string.Concat(Enumerable.Repeat('-', 1 + 30 + 35)));
foreach (var cf in _guideProcessor.ProcessedCustomFormats) foreach (var cf in _guideProcessor.ProcessedCustomFormats)
{ {
Console.WriteLine(format, cf.Name, cf.TrashId); _console.Output.WriteLine(format, cf.Name, cf.TrashId);
} }
Console.WriteLine(""); _console.Output.WriteLine("");
Console.WriteLine("========================================================="); _console.Output.WriteLine("=========================================================");
Console.WriteLine(" >>> Quality Profile Assignments & Scores <<< "); _console.Output.WriteLine(" >>> Quality Profile Assignments & Scores <<< ");
Console.WriteLine("========================================================="); _console.Output.WriteLine("=========================================================");
Console.WriteLine(""); _console.Output.WriteLine("");
const string profileFormat = "{0,-18} {1,-20} {2,-8}"; const string profileFormat = "{0,-18} {1,-20} {2,-8}";
Console.WriteLine(profileFormat, "Profile", "Custom Format", "Score"); _console.Output.WriteLine(profileFormat, "Profile", "Custom Format", "Score");
Console.WriteLine(string.Concat(Enumerable.Repeat('-', 2 + 18 + 20 + 8))); _console.Output.WriteLine(string.Concat(Enumerable.Repeat('-', 2 + 18 + 20 + 8)));
foreach (var (profileName, scoreMap) in _guideProcessor.ProfileScores) foreach (var (profileName, scoreMap) in _guideProcessor.ProfileScores)
{ {
Console.WriteLine(profileFormat, profileName, "", ""); _console.Output.WriteLine(profileFormat, profileName, "", "");
foreach (var (customFormat, score) in scoreMap.Mapping) foreach (var (customFormat, score) in scoreMap.Mapping)
{ {
@ -258,10 +262,10 @@ internal class CustomFormatUpdater : ICustomFormatUpdater
continue; continue;
} }
Console.WriteLine(profileFormat, "", matchingCf.Name, score); _console.Output.WriteLine(profileFormat, "", matchingCf.Name, score);
} }
} }
Console.WriteLine(""); _console.Output.WriteLine("");
} }
} }

@ -1,3 +1,4 @@
using CliFx.Infrastructure;
using Serilog; using Serilog;
using TrashLib.Radarr.Config; using TrashLib.Radarr.Config;
using TrashLib.Radarr.QualityDefinition.Api; using TrashLib.Radarr.QualityDefinition.Api;
@ -8,14 +9,19 @@ namespace TrashLib.Radarr.QualityDefinition;
internal class RadarrQualityDefinitionUpdater : IRadarrQualityDefinitionUpdater internal class RadarrQualityDefinitionUpdater : IRadarrQualityDefinitionUpdater
{ {
private readonly IQualityDefinitionService _api; private readonly IQualityDefinitionService _api;
private readonly IConsole _console;
private readonly IRadarrQualityDefinitionGuideParser _parser; private readonly IRadarrQualityDefinitionGuideParser _parser;
public RadarrQualityDefinitionUpdater(ILogger logger, IRadarrQualityDefinitionGuideParser parser, public RadarrQualityDefinitionUpdater(
IQualityDefinitionService api) ILogger logger,
IRadarrQualityDefinitionGuideParser parser,
IQualityDefinitionService api,
IConsole console)
{ {
Log = logger; Log = logger;
_parser = parser; _parser = parser;
_api = api; _api = api;
_console = console;
} }
private ILogger Log { get; } private ILogger Log { get; }
@ -53,19 +59,19 @@ internal class RadarrQualityDefinitionUpdater : IRadarrQualityDefinitionUpdater
await ProcessQualityDefinition(selectedQuality); await ProcessQualityDefinition(selectedQuality);
} }
private static void PrintQualityPreview(IEnumerable<RadarrQualityData> quality) private void PrintQualityPreview(IEnumerable<RadarrQualityData> quality)
{ {
Console.WriteLine(""); _console.Output.WriteLine("");
const string format = "{0,-20} {1,-10} {2,-15} {3,-15}"; const string format = "{0,-20} {1,-10} {2,-15} {3,-15}";
Console.WriteLine(format, "Quality", "Min", "Max", "Preferred"); _console.Output.WriteLine(format, "Quality", "Min", "Max", "Preferred");
Console.WriteLine(format, "-------", "---", "---", "---------"); _console.Output.WriteLine(format, "-------", "---", "---", "---------");
foreach (var q in quality) 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<RadarrQualityData> guideQuality) private async Task ProcessQualityDefinition(IEnumerable<RadarrQualityData> guideQuality)
@ -99,7 +105,7 @@ internal class RadarrQualityDefinitionUpdater : IRadarrQualityDefinitionUpdater
continue; 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.MinSize = qualityData.MinForApi;
entry.MaxSize = qualityData.MaxForApi; entry.MaxSize = qualityData.MaxForApi;
entry.PreferredSize = qualityData.PreferredForApi; entry.PreferredSize = qualityData.PreferredForApi;

@ -1,4 +1,5 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using CliFx.Infrastructure;
using Serilog; using Serilog;
using TrashLib.Sonarr.Api; using TrashLib.Sonarr.Api;
using TrashLib.Sonarr.Api.Objects; using TrashLib.Sonarr.Api.Objects;
@ -9,15 +10,20 @@ namespace TrashLib.Sonarr.QualityDefinition;
internal class SonarrQualityDefinitionUpdater : ISonarrQualityDefinitionUpdater internal class SonarrQualityDefinitionUpdater : ISonarrQualityDefinitionUpdater
{ {
private readonly ISonarrApi _api; private readonly ISonarrApi _api;
private readonly IConsole _console;
private readonly ISonarrQualityDefinitionGuideParser _parser; private readonly ISonarrQualityDefinitionGuideParser _parser;
private readonly Regex _regexHybrid = new(@"720|1080", RegexOptions.Compiled); private readonly Regex _regexHybrid = new(@"720|1080", RegexOptions.Compiled);
public SonarrQualityDefinitionUpdater(ILogger logger, ISonarrQualityDefinitionGuideParser parser, public SonarrQualityDefinitionUpdater(
ISonarrApi api) ILogger logger,
ISonarrQualityDefinitionGuideParser parser,
ISonarrApi api,
IConsole console)
{ {
Log = logger; Log = logger;
_parser = parser; _parser = parser;
_api = api; _api = api;
_console = console;
} }
private ILogger Log { get; } private ILogger Log { get; }
@ -87,19 +93,19 @@ internal class SonarrQualityDefinitionUpdater : ISonarrQualityDefinitionUpdater
return hybrid; return hybrid;
} }
private static void PrintQualityPreview(IEnumerable<SonarrQualityData> quality) private void PrintQualityPreview(IEnumerable<SonarrQualityData> quality)
{ {
Console.WriteLine(""); _console.Output.WriteLine("");
const string format = "{0,-20} {1,-10} {2,-15}"; const string format = "{0,-20} {1,-10} {2,-15}";
Console.WriteLine(format, "Quality", "Min", "Max"); _console.Output.WriteLine(format, "Quality", "Min", "Max");
Console.WriteLine(format, "-------", "---", "---"); _console.Output.WriteLine(format, "-------", "---", "---");
foreach (var q in quality) 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<SonarrQualityData> guideQuality) private async Task ProcessQualityDefinition(IEnumerable<SonarrQualityData> guideQuality)
@ -132,7 +138,7 @@ internal class SonarrQualityDefinitionUpdater : ISonarrQualityDefinitionUpdater
continue; 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.MinSize = qualityData.MinForApi;
entry.MaxSize = qualityData.MaxForApi; entry.MaxSize = qualityData.MaxForApi;
newQuality.Add(entry); newQuality.Add(entry);

@ -1,4 +1,5 @@
using System.Reactive.Linq; using System.Reactive.Linq;
using CliFx.Infrastructure;
using Common.Extensions; using Common.Extensions;
using Serilog; using Serilog;
using TrashLib.ExceptionTypes; using TrashLib.ExceptionTypes;
@ -15,6 +16,7 @@ public class ReleaseProfileUpdater : IReleaseProfileUpdater
private readonly ISonarrApi _api; private readonly ISonarrApi _api;
private readonly ISonarrCompatibility _compatibility; private readonly ISonarrCompatibility _compatibility;
private readonly IReleaseProfileFilterPipeline _pipeline; private readonly IReleaseProfileFilterPipeline _pipeline;
private readonly IConsole _console;
private readonly ISonarrGuideService _guide; private readonly ISonarrGuideService _guide;
private readonly ILogger _log; private readonly ILogger _log;
@ -23,13 +25,15 @@ public class ReleaseProfileUpdater : IReleaseProfileUpdater
ISonarrGuideService guide, ISonarrGuideService guide,
ISonarrApi api, ISonarrApi api,
ISonarrCompatibility compatibility, ISonarrCompatibility compatibility,
IReleaseProfileFilterPipeline pipeline) IReleaseProfileFilterPipeline pipeline,
IConsole console)
{ {
_log = logger; _log = logger;
_guide = guide; _guide = guide;
_api = api; _api = api;
_compatibility = compatibility; _compatibility = compatibility;
_pipeline = pipeline; _pipeline = pipeline;
_console = console;
} }
public async Task Process(bool isPreview, SonarrConfiguration config) public async Task Process(bool isPreview, SonarrConfiguration config)
@ -56,7 +60,7 @@ public class ReleaseProfileUpdater : IReleaseProfileUpdater
if (isPreview) if (isPreview)
{ {
Utils.PrintTermsAndScores(selectedProfile); PrintTermsAndScores(selectedProfile);
continue; continue;
} }
@ -66,6 +70,59 @@ public class ReleaseProfileUpdater : IReleaseProfileUpdater
await ProcessReleaseProfiles(filteredProfiles); await ProcessReleaseProfiles(filteredProfiles);
} }
private void PrintTermsAndScores(ReleaseProfileData profile)
{
void PrintPreferredTerms(string title, IReadOnlyCollection<PreferredTermData> 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<TermData> 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( private async Task ProcessReleaseProfiles(
List<(ReleaseProfileData Profile, IReadOnlyCollection<string> Tags)> profilesAndTags) List<(ReleaseProfileData Profile, IReadOnlyCollection<string> Tags)> profilesAndTags)
{ {

@ -1,57 +0,0 @@
namespace TrashLib.Sonarr.ReleaseProfile;
public static class Utils
{
public static void PrintTermsAndScores(ReleaseProfileData profile)
{
static void PrintPreferredTerms(string title, IReadOnlyCollection<PreferredTermData> 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<TermData> 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("");
}
}

@ -9,15 +9,18 @@ public class GitRepositoryFactory : IGitRepositoryFactory
private readonly IFileUtilities _fileUtils; private readonly IFileUtilities _fileUtils;
private readonly IRepositoryStaticWrapper _staticWrapper; private readonly IRepositoryStaticWrapper _staticWrapper;
private readonly Func<string, IGitRepository> _repoFactory; private readonly Func<string, IGitRepository> _repoFactory;
private readonly Func<ProgressBar> _progressBarFactory;
public GitRepositoryFactory( public GitRepositoryFactory(
IFileUtilities fileUtils, IFileUtilities fileUtils,
IRepositoryStaticWrapper staticWrapper, IRepositoryStaticWrapper staticWrapper,
Func<string, IGitRepository> repoFactory) Func<string, IGitRepository> repoFactory,
Func<ProgressBar> progressBarFactory)
{ {
_fileUtils = fileUtils; _fileUtils = fileUtils;
_staticWrapper = staticWrapper; _staticWrapper = staticWrapper;
_repoFactory = repoFactory; _repoFactory = repoFactory;
_progressBarFactory = progressBarFactory;
} }
public IGitRepository CreateAndCloneIfNeeded(string repoUrl, string repoPath, string branch) public IGitRepository CreateAndCloneIfNeeded(string repoUrl, string repoPath, string branch)
@ -36,10 +39,8 @@ public class GitRepositoryFactory : IGitRepositoryFactory
{ {
_fileUtils.DeleteReadOnlyDirectory(repoPath); _fileUtils.DeleteReadOnlyDirectory(repoPath);
var progress = new ProgressBar var progress = _progressBarFactory();
{ progress.Description = "Fetching guide data\n";
Description = "Fetching guide data\n"
};
_staticWrapper.Clone(repoUrl, repoPath, new CloneOptions _staticWrapper.Clone(repoUrl, repoPath, new CloneOptions
{ {

Loading…
Cancel
Save