Move image save logic from TaskExecutor to IOutputSink

This commit is contained in:
Denis-Cosmin Nutiu 2022-02-12 23:25:34 +02:00
parent a0f719b8df
commit 13552f1f96
13 changed files with 135 additions and 64 deletions

View file

@ -31,7 +31,6 @@ namespace ConsoleInterface
private static void RunOptions(ProgramOptions options) private static void RunOptions(ProgramOptions options)
{ {
SetupLogging(options.LogLevel); SetupLogging(options.LogLevel);
CreateDestinationDirectory(options.DestinationDirectory);
var outputFormatter = SimpleOutputSink.Create(options.DestinationDirectory); var outputFormatter = SimpleOutputSink.Create(options.DestinationDirectory);
var executor = TaskExecutor.Create(new TaskExecutorOptions var executor = TaskExecutor.Create(new TaskExecutorOptions
{ {
@ -44,15 +43,6 @@ namespace ConsoleInterface
executor.ParallelCleanImages(filesRetriever.GetFilenamesFromPath(options.SourceDirectory)); executor.ParallelCleanImages(filesRetriever.GetFilenamesFromPath(options.SourceDirectory));
} }
/// <summary>
/// Creates the directory if it doesn't exist.
/// </summary>
/// <param name="destinationDirectory">The destination directory.</param>
private static void CreateDestinationDirectory(string destinationDirectory)
{
FileSystemHelpers.CreateDestinationDirectory(destinationDirectory);
}
public static void SetupLogging(string logLevel) public static void SetupLogging(string logLevel)
{ {
_loggerFactory = LoggerFactory.Create(b => _loggerFactory = LoggerFactory.Create(b =>

View file

@ -1,28 +0,0 @@
using System;
using Image.Files;
using Xunit;
namespace ImageCore.Tests
{
public class TestSimpleOutputSink
{
[Theory]
[InlineData("", "./", @".\.jpg")]
[InlineData("", @".\", @".\.jpg")]
[InlineData("", "asd", @".\asd.jpg")]
[InlineData("dir", "asd", @"dir\asd.jpg")]
public void TestGetOutputPath(string directory, string file, string expectedPath)
{
var outputPathFormatter = SimpleOutputSink.Create(directory);
Assert.Equal(expectedPath, outputPathFormatter.GetOutputPath(file));
}
[Fact]
public void TestGetOutputPathNull()
{
var outputPathFormatter = SimpleOutputSink.Create("directory");
Assert.Throws<ArgumentException>(() => outputPathFormatter.GetOutputPath(""));
}
}
}

View file

@ -32,7 +32,7 @@ namespace ImageCore.Tests
var filePathsList = filePaths.ToList(); var filePathsList = filePaths.ToList();
var expectedFileNames = new List<string> var expectedFileNames = new List<string>
{ {
"IMG_0138.HEIC", "IMG_0140.HEIC", "IMG_0138.HEIC", "IMG_0138.jpg", "IMG_0140.HEIC",
}; };
Assert.NotEmpty(filePathsList); Assert.NotEmpty(filePathsList);

View file

@ -8,7 +8,7 @@ namespace ImageCore.Tests
public class TestMetadataRemover public class TestMetadataRemover
{ {
[Fact] [Fact]
public void TestCleanImage() public void TestExifRemoverAndCompressorCleanImage()
{ {
// Setup // Setup
var magicImageMock = new Mock<IMagickImage>(); var magicImageMock = new Mock<IMagickImage>();
@ -23,5 +23,22 @@ namespace ImageCore.Tests
magicImageMock.Verify( i => i.Write("path")); magicImageMock.Verify( i => i.Write("path"));
compressorMock.Verify( i => i.Compress("path")); compressorMock.Verify( i => i.Compress("path"));
} }
[Fact]
public void TestExifRemoverAndCompressorGetImagePath()
{
// Setup
var magicImageMock = new Mock<IMagickImage>();
magicImageMock.Setup(i => i.FileName).Returns("P4th");
var compressorMock = new Mock<ICompressor>();
var metadataRemover = new ExifRemoverAndCompressor(magicImageMock.Object, compressorMock.Object);
// Test
var result = metadataRemover.GetImagePath();
// Assert
Assert.Equal("P4th", result);
}
} }
} }

View file

@ -0,0 +1,68 @@
using System;
using System.IO;
using Image.Core;
using Image.Files;
using Moq;
using Xunit;
namespace ImageCore.Tests
{
public class TestSimpleOutputSink
{
private readonly string _testsProjectDirectory;
public TestSimpleOutputSink()
{
_testsProjectDirectory = Environment.GetEnvironmentVariable("IMAGE_CORE_TESTS");
}
[Theory]
[InlineData("", "./", @".\.jpg")]
[InlineData("", @".\", @".\.jpg")]
[InlineData("", "asd", @".\asd.jpg")]
[InlineData("dir", "asd", @"dir\asd.jpg")]
public void TestGetOutputPath(string directory, string file, string expectedPath)
{
var sink = SimpleOutputSink.Create(directory);
Assert.Equal(expectedPath, sink.GetOutputPath(file));
}
[Fact]
public void TestGetOutputPathNull()
{
var sink = SimpleOutputSink.Create("directory");
Assert.Throws<ArgumentException>(() => sink.GetOutputPath(""));
}
[Fact]
public void TestSave()
{
// Setup
var sink = SimpleOutputSink.Create("directory");
var metadataRemoverMock = new Mock<IMetadataRemover>();
metadataRemoverMock.Setup(i => i.GetImagePath()).Returns("alo.wtf");
// Test
sink.Save(metadataRemoverMock.Object);
// Assert
metadataRemoverMock.Verify(i => i.CleanImage("directory\\alo.jpg"));
}
[Fact]
public void TestSaveFileExists()
{
// Setup
var sink = SimpleOutputSink.Create(Path.Join(_testsProjectDirectory, "test_pictures"));
var metadataRemoverMock = new Mock<IMetadataRemover>();
var sourceFileName = Path.Join(_testsProjectDirectory, "test_pictures\\IMG_0138.HEIC");
metadataRemoverMock.Setup(i => i.GetImagePath()).Returns(sourceFileName);
// Test
sink.Save(metadataRemoverMock.Object);
// Assert
metadataRemoverMock.Verify(i => i.CleanImage(It.IsAny<string>()), Times.Never);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

View file

@ -31,5 +31,11 @@ namespace Image.Core
_magickImage.Write(newFilePath); _magickImage.Write(newFilePath);
_compressor.Compress(newFilePath); _compressor.Compress(newFilePath);
} }
/// <inheritdoc />
public string GetImagePath()
{
return _magickImage.FileName;
}
} }
} }

View file

@ -6,9 +6,15 @@
public interface IMetadataRemover public interface IMetadataRemover
{ {
/// <summary> /// <summary>
/// CleanImage cleans an image and saves it.. /// Cleans an image and saves it under a new path.
/// </summary> /// </summary>
/// <param name="newFilePath">The file path to save the clean image.</param> /// <param name="newFilePath">The file path to save the clean image.</param>
void CleanImage(string newFilePath); void CleanImage(string newFilePath);
/// <summary>
/// GetImagePath gets the current image path on the filesystem.
/// </summary>
/// <returns>A string representing the absolute path.</returns>
string GetImagePath();
} }
} }

View file

@ -1,4 +1,6 @@
namespace Image.Files using Image.Core;
namespace Image.Files
{ {
/// <summary> /// <summary>
/// IOutputSink is an interface for generating saving the generated files.. /// IOutputSink is an interface for generating saving the generated files..
@ -6,10 +8,10 @@
public interface IOutputSink public interface IOutputSink
{ {
/// <summary> /// <summary>
/// Generates an absolute output path given the initial absolute file path. /// Saves the image.
/// </summary> /// </summary>
/// <param name="initialFilePath">The initial file path.</param> /// <param name="metadataRemover">Metadata remover instance.</param>
/// <returns>The formatted absolute output path.</returns> /// <returns>True if the image was saved successfully, false otherwise.</returns>
string GetOutputPath(string initialFilePath); bool Save(IMetadataRemover metadataRemover);
} }
} }

View file

@ -1,5 +1,6 @@
using System.IO; using System.IO;
using Ardalis.GuardClauses; using Ardalis.GuardClauses;
using Image.Core;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
@ -25,6 +26,10 @@ namespace Image.Files
{ {
outputDirectory = "."; outputDirectory = ".";
} }
else
{
FileSystemHelpers.CreateDestinationDirectory(outputDirectory);
}
_outputDirectory = outputDirectory; _outputDirectory = outputDirectory;
} }
@ -39,10 +44,23 @@ namespace Image.Files
Guard.Against.NullOrEmpty(initialFilePath, nameof(initialFilePath)); Guard.Against.NullOrEmpty(initialFilePath, nameof(initialFilePath));
var fileName = Path.GetFileName(initialFilePath).Split('.')[0]; var fileName = Path.GetFileName(initialFilePath).Split('.')[0];
var path = Path.Combine(_outputDirectory, $"{fileName}.jpg"); var path = Path.Combine(_outputDirectory, $"{fileName}.jpg");
return path; return path;
} }
public bool Save(IMetadataRemover metadataRemover)
{
var newFilePath = GetOutputPath(metadataRemover.GetImagePath());
var fileExists = FileSystemHelpers.CheckIfFileExists(newFilePath);
if (fileExists)
{
Logger.LogWarning($"File {newFilePath} exists, skipping");
return false;
}
// Save the image under the same name in the new directory.
metadataRemover.CleanImage(newFilePath);
return true;
}
/// <summary> /// <summary>
/// Creates an instance of OriginalFilenameFileOutputPathFormatter. /// Creates an instance of OriginalFilenameFileOutputPathFormatter.
/// </summary> /// </summary>

View file

@ -41,27 +41,19 @@ namespace Image.Tasks
/// Cleans an image. Errors are silenced by default. /// Cleans an image. Errors are silenced by default.
/// </summary> /// </summary>
/// <param name="filePath">The file path of the image to be cleaned.</param> /// <param name="filePath">The file path of the image to be cleaned.</param>
/// <param name="newFilePath">The new file path of the cleaned image.</param> /// <param name="outputSink">The output sink for the image..</param>
/// <returns>True of the image was cleaned, false otherwise.</returns> /// <returns>True of the image was cleaned, false otherwise.</returns>
public bool CleanImage(string filePath, string newFilePath) public bool CleanImage(string filePath, IOutputSink outputSink)
{ {
try try
{ {
var fileExists = FileSystemHelpers.CheckIfFileExists(newFilePath); Logger.LogDebug($"Cleaning {filePath}, compression {_options.EnableCompression}, outputFormatter {nameof(_options.OutputSink)}.");
if (fileExists)
{
Logger.LogWarning($"File {newFilePath} exists, skipping");
return false;
}
ICompressor compressor = NullCompressor.Instance; ICompressor compressor = NullCompressor.Instance;
var imageMagick = new MagickImage(filePath);
if (_options.EnableCompression) compressor = LosslessCompressor.Instance; if (_options.EnableCompression) compressor = LosslessCompressor.Instance;
var imageMagick = new MagickImage(filePath);
Logger.LogDebug(
$"Cleaning {filePath}, compression {_options.EnableCompression}, outputFormatter {nameof(_options.OutputSink)}.");
IMetadataRemover metadataRemover = new ExifRemoverAndCompressor(imageMagick, compressor); IMetadataRemover metadataRemover = new ExifRemoverAndCompressor(imageMagick, compressor);
metadataRemover.CleanImage(newFilePath); return outputSink.Save(metadataRemover);
return true;
} }
catch (Exception e) catch (Exception e)
{ {
@ -87,8 +79,7 @@ namespace Image.Tasks
var tasks = new List<Task<bool>>(); var tasks = new List<Task<bool>>();
foreach (var fileName in filenamesArray) foreach (var fileName in filenamesArray)
{ {
var task = new Task<bool>(() => var task = new Task<bool>(() => CleanImage(fileName, _options.OutputSink));
CleanImage(fileName, _options.OutputSink.GetOutputPath(fileName)));
tasks.Add(task); tasks.Add(task);
task.Start(); task.Start();
} }

View file

@ -17,7 +17,7 @@ namespace Image.Tasks
public IOutputSink OutputSink public IOutputSink OutputSink
{ {
get => _outputSink; get => _outputSink;
set => _outputSink = value ?? throw new ArgumentException("Output formatter cannot be null!"); set => _outputSink = value ?? throw new ArgumentException("OutputSink cannot be null!");
} }
/// <summary> /// <summary>

View file

@ -4,6 +4,7 @@
&lt;Assembly Path="C:\Users\nutiu\.nuget\packages\magick.net.core\8.6.1\lib\netstandard21\Magick.NET.Core.dll" /&gt;&#xD; &lt;Assembly Path="C:\Users\nutiu\.nuget\packages\magick.net.core\8.6.1\lib\netstandard21\Magick.NET.Core.dll" /&gt;&#xD;
&lt;Assembly Path="C:\Users\nutiu\.nuget\packages\microsoft.extensions.logging.abstractions\6.0.0\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll" /&gt;&#xD; &lt;Assembly Path="C:\Users\nutiu\.nuget\packages\microsoft.extensions.logging.abstractions\6.0.0\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll" /&gt;&#xD;
&lt;/AssemblyExplorer&gt;</s:String> &lt;/AssemblyExplorer&gt;</s:String>
<s:String x:Key="/Default/Environment/Highlighting/HighlightingSourceSnapshotLocation/@EntryValue">C:\Users\nutiu\AppData\Local\JetBrains\Rider2021.3\resharper-host\temp\Rider\vAny\CoverageData\_ImgMetadataRemover.61395890\Snapshot\snapshot.utdcvr</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=761234b5_002D46ba_002D4312_002Dab60_002Dd15d5d83a61a/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="TestGetFilenamesFromPath" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD; <s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=761234b5_002D46ba_002D4312_002Dab60_002Dd15d5d83a61a/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="TestGetFilenamesFromPath" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;Project Location="C:\Users\nutiu\RiderProjects\ImgMetadataRemover\ImageCore.Tests" Presentation="&amp;lt;ImageCore.Tests&amp;gt;" /&gt;&#xD; &lt;Project Location="C:\Users\nutiu\RiderProjects\ImgMetadataRemover\ImageCore.Tests" Presentation="&amp;lt;ImageCore.Tests&amp;gt;" /&gt;&#xD;
&lt;/SessionState&gt;</s:String></wpf:ResourceDictionary> &lt;/SessionState&gt;</s:String></wpf:ResourceDictionary>