Move image save logic from TaskExecutor to IOutputSink
This commit is contained in:
parent
a0f719b8df
commit
13552f1f96
13 changed files with 135 additions and 64 deletions
|
@ -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 =>
|
||||||
|
|
|
@ -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(""));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
68
ImageCore.Tests/TestSimpleOutputSink.cs
Normal file
68
ImageCore.Tests/TestSimpleOutputSink.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
ImageCore.Tests/test_pictures/IMG_0138.jpg
Normal file
BIN
ImageCore.Tests/test_pictures/IMG_0138.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 MiB |
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
|
||||||
var imageMagick = new MagickImage(filePath);
|
|
||||||
if (_options.EnableCompression) compressor = LosslessCompressor.Instance;
|
|
||||||
|
|
||||||
Logger.LogDebug(
|
ICompressor compressor = NullCompressor.Instance;
|
||||||
$"Cleaning {filePath}, compression {_options.EnableCompression}, outputFormatter {nameof(_options.OutputSink)}.");
|
if (_options.EnableCompression) compressor = LosslessCompressor.Instance;
|
||||||
|
var imageMagick = new MagickImage(filePath);
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<Assembly Path="C:\Users\nutiu\.nuget\packages\magick.net.core\8.6.1\lib\netstandard21\Magick.NET.Core.dll" />
|
<Assembly Path="C:\Users\nutiu\.nuget\packages\magick.net.core\8.6.1\lib\netstandard21\Magick.NET.Core.dll" />
|
||||||
<Assembly Path="C:\Users\nutiu\.nuget\packages\microsoft.extensions.logging.abstractions\6.0.0\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll" />
|
<Assembly Path="C:\Users\nutiu\.nuget\packages\microsoft.extensions.logging.abstractions\6.0.0\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll" />
|
||||||
</AssemblyExplorer></s:String>
|
</AssemblyExplorer></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"><SessionState ContinuousTestingMode="0" IsActive="True" Name="TestGetFilenamesFromPath" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=761234b5_002D46ba_002D4312_002Dab60_002Dd15d5d83a61a/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" Name="TestGetFilenamesFromPath" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||||
<Project Location="C:\Users\nutiu\RiderProjects\ImgMetadataRemover\ImageCore.Tests" Presentation="&lt;ImageCore.Tests&gt;" />
|
<Project Location="C:\Users\nutiu\RiderProjects\ImgMetadataRemover\ImageCore.Tests" Presentation="&lt;ImageCore.Tests&gt;" />
|
||||||
</SessionState></s:String></wpf:ResourceDictionary>
|
</SessionState></s:String></wpf:ResourceDictionary>
|
Loading…
Reference in a new issue