Introduce IImageCreator interface for image creation, and update ImageCreatorSharp to implement it. Add ImageCreatorAlternate (adapter) and ImageCreatorMapper (runtime selector) classes. Extend PicSettings with ImageCreatorProvider to control backend selection. Update DI registrations and refactor ImageCreationStuff to depend on IImageCreator, enabling backend switching via configuration.
211 lines
No EOL
7.9 KiB
C#
211 lines
No EOL
7.9 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Drawing;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Dasync.Collections;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace MaddoShared
|
|
{
|
|
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
|
|
public class ImageCreationStuff(
|
|
ILogger<ImageCreationStuff> logger,
|
|
PicSettings picSettings,
|
|
IImageCreator imageCreatorService)
|
|
{
|
|
public class Options
|
|
{
|
|
public bool AggiornaSottodirectory { get; set; }
|
|
public bool CreaSottocartelle { get; set; }
|
|
|
|
public int FilePerCartella { get; set; }
|
|
public string SuffissoCartelle { get; set; }
|
|
public int CifreContatore { get; set; }
|
|
public NumerazioneType NumerazioneType { get; set; }
|
|
public string SourcePath { get; set; }
|
|
public string DestinationPath { get; set; }
|
|
|
|
public int MaxThreads { get; set; }
|
|
public int ChunksSize { get; set; }
|
|
public bool LinearExecution { get; set; }
|
|
}
|
|
|
|
public async Task<string> CreaCatalogoParallel(Options options, ConcurrentBag<string> results,
|
|
EventHandler<Tuple<string, int>> updateEvent,
|
|
CancellationToken cancellationToken = default(CancellationToken))
|
|
{
|
|
var stopwatch = new Stopwatch();
|
|
stopwatch.Start();
|
|
|
|
await ProcessImagesParallel(options, results, updateEvent, cancellationToken);
|
|
|
|
stopwatch.Stop();
|
|
|
|
return
|
|
$"{stopwatch.Elapsed.Hours}h {stopwatch.Elapsed.Minutes}m {stopwatch.Elapsed.Seconds}s ({stopwatch.Elapsed.TotalSeconds}s)";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the list of files that will be processed based on the provided options.
|
|
/// Useful for benchmarking and testing to understand the scope of work.
|
|
/// </summary>
|
|
public List<FileData> GetFilesToProcessPublic(Options options)
|
|
{
|
|
return GetFilesToProcess(options);
|
|
}
|
|
|
|
public async Task ProcessImagesParallel(
|
|
Options options,
|
|
ConcurrentBag<string> results,
|
|
EventHandler<Tuple<string, int>> updateEvent,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var dataToProcess = GetFilesToProcess(options);
|
|
|
|
// int threads = options.MaxThreads == 0 ? Environment.ProcessorCount * 2 : options.MaxThreads;
|
|
int threads = options.MaxThreads;
|
|
|
|
Bitmap logoBmp = null;
|
|
// Load Logo (short-circuit)
|
|
if (picSettings.LogoAggiungi && File.Exists(picSettings.LogoNomeFile))
|
|
{
|
|
logoBmp = new Bitmap(picSettings.LogoNomeFile);
|
|
}
|
|
|
|
Func<FileData, Task> processFile = async fileData =>
|
|
{
|
|
Bitmap logoCopy = logoBmp is null
|
|
? null
|
|
: logoBmp.Clone(new Rectangle(0, 0, logoBmp.Width, logoBmp.Height), logoBmp.PixelFormat);
|
|
|
|
var imgState = new ImageState
|
|
{
|
|
WorkFile = fileData.File,
|
|
DestDir = fileData.Directory,
|
|
};
|
|
|
|
try
|
|
{
|
|
// Ensure CreateImageAsync can accept a null logoCopy value.
|
|
await imageCreatorService.CreateImageAsync(imgState, logoCopy);
|
|
|
|
results.Add(fileData.File.Name);
|
|
|
|
try
|
|
{
|
|
updateEvent?.Invoke(this, new Tuple<string, int>(fileData.File.Name, dataToProcess.Count));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
logger.LogError(e, "Error in reporting update");
|
|
// Don't rethrow - continue processing other images
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
// Dispose the clone if it was created
|
|
logoCopy?.Dispose();
|
|
}
|
|
};
|
|
|
|
if (options.LinearExecution)
|
|
{
|
|
foreach (var fileData in dataToProcess)
|
|
await processFile(fileData);
|
|
}
|
|
else
|
|
{
|
|
var chunks = options.ChunksSize > 0
|
|
? SplitList(dataToProcess, options.ChunksSize)
|
|
: new List<List<FileData>> { dataToProcess };
|
|
|
|
foreach (var chunk in chunks)
|
|
{
|
|
await chunk.ParallelForEachAsync(
|
|
processFile,
|
|
maxDegreeOfParallelism: threads,
|
|
false,
|
|
cancellationToken);
|
|
|
|
chunk.Clear();
|
|
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, blocking: false, compacting: false);
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
logoBmp?.Dispose();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
logger.LogError(e, "Error in disposing the logo");
|
|
}
|
|
}
|
|
|
|
private List<FileData> GetFilesToProcess(Options options)
|
|
{
|
|
// Support multiple common JPEG patterns so files named .jpeg, .jpe, etc. are included
|
|
var jpgPatterns = new[] { "*.jpg", "*.jpeg", "*.jpe", "*.jfif", "*.pjpeg", "*.pjp" };
|
|
|
|
if (options.AggiornaSottodirectory && options.CreaSottocartelle)
|
|
{
|
|
var helper = new FileHelperSharp();
|
|
// Pass patterns joined by ';' - FileHelperSharp will split and handle multiple patterns
|
|
return helper.GetFilesRecursive(
|
|
new DirectoryInfo(options.SourcePath),
|
|
new DirectoryInfo(options.DestinationPath),
|
|
string.Join(";", jpgPatterns),
|
|
new FileHelperOptions
|
|
{
|
|
FilesPerFolder = options.FilePerCartella,
|
|
Suffix = options.SuffissoCartelle,
|
|
CounterSize = options.CifreContatore,
|
|
NumerationType = options.NumerazioneType
|
|
});
|
|
}
|
|
|
|
// For non-recursive or recursive enumeration without using the helper, enumerate for each pattern
|
|
var searchOption = options.AggiornaSottodirectory ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
|
|
|
var files = jpgPatterns
|
|
.SelectMany(p => Directory.EnumerateFiles(options.SourcePath, p, searchOption))
|
|
.Distinct()
|
|
.ToList();
|
|
|
|
return files.Select(x =>
|
|
{
|
|
var fInfo = new FileInfo(x);
|
|
var filePath = fInfo.DirectoryName;
|
|
var trimmedSourcePath = options.SourcePath.TrimEnd('\\');
|
|
var newFilePath = fInfo.FullName.Replace(trimmedSourcePath, "").TrimStart('\\');
|
|
newFilePath = Path.Combine(options.DestinationPath, newFilePath);
|
|
|
|
var destFolderPath = new FileInfo(newFilePath).DirectoryName;
|
|
var destFolderInfo = new DirectoryInfo(destFolderPath);
|
|
destFolderInfo.EnsureDirectoryExists();
|
|
|
|
return new FileData(fInfo, new DirectoryInfo(new FileInfo(newFilePath).DirectoryName));
|
|
|
|
// var destDir = new FileInfo(newFilePath).Directory!;
|
|
// destDir.Create(); // Ensure exists
|
|
//
|
|
// return new FileData(fInfo, destDir);
|
|
}).ToList();
|
|
}
|
|
|
|
private static IEnumerable<List<T>> SplitList<T>(List<T> bigList, int nSize = 3)
|
|
{
|
|
for (int i = 0; i < bigList.Count; i += nSize)
|
|
{
|
|
yield return bigList.GetRange(i, Math.Min(nSize, bigList.Count - i));
|
|
}
|
|
}
|
|
}
|
|
} |