using Microsoft.Extensions.Logging; using ShellProgressBar; using System.Collections.ObjectModel; using System.Drawing; using View_by_Distance.Compare.Models; using View_by_Distance.Distance.Models.Stateless; using View_by_Distance.Face.Models.Stateless; using View_by_Distance.Metadata.Models; using View_by_Distance.People.Models.Stateless; using View_by_Distance.Shared.Models; using View_by_Distance.Shared.Models.Stateless; namespace View_by_Distance.Compare; public partial class Compare : ICompare, IDisposable { public long Ticks { get; init; } public int? CurrentTick => _ProgressBar?.CurrentTick; private ProgressBar? _ProgressBar; private readonly ProgressBarOptions _ProgressBarOptions; void ICompare.Tick() => _ProgressBar?.Tick(); void IDisposable.Dispose() { _ProgressBar?.Dispose(); GC.SuppressFinalize(this); } void ICompare.ConstructProgressBar(int maxTicks, string message) { _ProgressBar?.Dispose(); _ProgressBar = new(maxTicks, message, _ProgressBarOptions); } public Compare(List args, ILogger? logger, AppSettings appSettings, bool isSilent, IConsole console) { if (isSilent) { } if (args is null) throw new NullReferenceException(nameof(args)); if (console is null) throw new NullReferenceException(nameof(console)); ICompare compare = this; Ticks = DateTime.Now.Ticks; _ProgressBarOptions = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; CompareWork(logger, appSettings, compare); } private void CompareWork(ILogger? logger, AppSettings appSettings, ICompare compare) { const int updated = 0; DistanceLimits? distanceLimits; ReadOnlyCollection matrix; logger?.LogInformation("{Ticks}", compare.Ticks); ReadOnlyCollection saveContainers; ReadOnlyCollection exifDirectories; ReadOnlyCollection preFiltered; ReadOnlyCollection postFiltered; ReadOnlyDictionary onlyOne; bool runToDoCollectionFirst = GetRunToDoCollectionFirst(appSettings, compare); ReadOnlyCollections readOnlyCollections = GetReadOnlyCollections(appSettings); ReadOnlyDictionary> mappedIdsThenWholePercentagesToLocationContainer = GetMappedIdsThenWholePercentagesToLocationContainer(appSettings, compare, readOnlyCollections); if (appSettings.CompareSettings.SaveExtractedFaces || appSettings.CompareSettings.SaveExtractedJavaScriptObjectNotation) SaveExtracted(appSettings, mappedIdsThenWholePercentagesToLocationContainer); foreach (string outputResolution in appSettings.CompareSettings.OutputResolutions) { if (runToDoCollectionFirst || outputResolution.Any(char.IsNumber)) continue; logger?.LogInformation("{outputResolution}", outputResolution); exifDirectories = IFace.GetExifDirectories(appSettings.ResultSettings, appSettings.MetadataSettings, appSettings.DistanceSettings, appSettings.CompareSettings, compare, outputResolution); preFiltered = IDistance.GetPreFilterLocationContainer(appSettings.DistanceSettings, appSettings.CompareSettings, compare, readOnlyCollections, mappedIdsThenWholePercentagesToLocationContainer, exifDirectories); if (preFiltered.Count == 0) continue; if (appSettings.CompareSettings.SaveExtractedFaces || appSettings.CompareSettings.SaveExtractedJavaScriptObjectNotation) SaveExtracted(appSettings, preFiltered); distanceLimits = new(appSettings.DistanceSettings); postFiltered = IDistance.GetPostFilterLocationContainer(preFiltered, distanceLimits); if (postFiltered.Count == 0) continue; matrix = IDistance.GetMatrixLocationContainers(appSettings.DistanceSettings, compare, mappedIdsThenWholePercentagesToLocationContainer, distanceLimits, postFiltered); if (matrix.Count == 0) continue; onlyOne = IDistance.GetOnlyOne(appSettings.DistanceSettings, matrix); if (onlyOne.Count == 0) continue; saveContainers = IDistance.GetSaveContainers(appSettings.ResultSettings, appSettings.DistanceSettings, appSettings.CompareSettings, compare, outputResolution, onlyOne); if (saveContainers.Count == 0) continue; IDistance.SaveContainers(appSettings.DistanceSettings, appSettings.CompareSettings, compare, updated, saveContainers); } } private static bool GetRunToDoCollectionFirst(AppSettings appSettings, ICompare compare) { bool result = appSettings.DistanceSettings.SaveSortingWithoutPerson; if (!result) result = !IId.IsOffsetDeterministicHashCode(appSettings.MetadataSettings); if (!result) { string[] directories; directories = Directory.GetDirectories(appSettings.ResultSettings.RootDirectory, "*", SearchOption.TopDirectoryOnly); if (directories.Length == 0) result = true; else { string seasonDirectory; DirectoryInfo directoryInfo; DateTime dateTime = new(compare.Ticks); string rootDirectory = appSettings.ResultSettings.RootDirectory; (int season, string seasonName) = IDate.GetSeason(dateTime.DayOfYear); string eDistanceContentDirectory = IResult.GetResultsDateGroupDirectory(appSettings.ResultSettings, nameof(E_Distance), appSettings.ResultSettings.ResultContent); FileSystemInfo fileSystemInfo = new DirectoryInfo(eDistanceContentDirectory); string[] checkDirectories = [ Path.Combine(rootDirectory, "Ancestry"), Path.Combine(rootDirectory, "Facebook"), Path.Combine(rootDirectory, "LinkedIn") ]; foreach (string checkDirectory in checkDirectories) { if (checkDirectory == rootDirectory) seasonDirectory = Path.Combine(checkDirectory, $"{dateTime.Year}.{season} {seasonName}"); else seasonDirectory = Path.Combine(checkDirectory, $"{dateTime.Year}.{season} {seasonName} {Path.GetFileName(checkDirectory)}"); if (!Directory.Exists(seasonDirectory)) _ = Directory.CreateDirectory(seasonDirectory); if (result) continue; directories = Directory.GetDirectories(checkDirectory, "*", SearchOption.TopDirectoryOnly); foreach (string directory in directories) { directoryInfo = new(directory); if (directoryInfo.LastWriteTime > fileSystemInfo.LastWriteTime) { result = true; break; } } } } } if (result) result = true; if (!result) result = false; return result; } private static ReadOnlyCollections GetReadOnlyCollections(AppSettings appSettings) { ReadOnlyCollections result; ReadOnlyCollection personContainers = IPeople.GetPersonContainers(appSettings.ResultSettings, appSettings.MetadataSettings, appSettings.PeopleSettings, appSettings.CompareSettings); ReadOnlyCollection personKeys = IPeople.GetPersonKeys(personContainers); result = IPeople.GetReadOnlyCollections(appSettings.ResultSettings, appSettings.PeopleSettings, appSettings.DistanceSettings, appSettings.CompareSettings, personContainers, personKeys); return result; } private static ReadOnlyDictionary> GetMappedIdsThenWholePercentagesToLocationContainer(AppSettings appSettings, ICompare compare, ReadOnlyCollections readOnlyCollections) { ReadOnlyDictionary> results; results = IDistance.GetMappedIdsThenWholePercentagesToLocationContainer(appSettings.ResultSettings, appSettings.MetadataSettings, appSettings.PeopleSettings, appSettings.DistanceSettings, appSettings.CompareSettings, compare, readOnlyCollections); if (results.Count == 0 && !appSettings.DistanceSettings.SaveSortingWithoutPerson) throw new NotSupportedException($"Switch {nameof(appSettings.DistanceSettings.SaveSortingWithoutPerson)}!"); return results; } private void SaveExtracted(AppSettings appSettings, ReadOnlyDictionary> mappedIdsThenWholePercentagesToLocationContainer) { ReadOnlyCollection? paths; ReadOnlyDictionary> rootDirectoryFileNameToPaths = GetRootDirectoryFileNameToPaths(appSettings.ResultSettings, appSettings.CompareSettings); foreach (KeyValuePair> keyValuePair in mappedIdsThenWholePercentagesToLocationContainer) { foreach (KeyValuePair keyValue in keyValuePair.Value) { if (keyValue.Value.ExifDirectory is null || keyValue.Value.FaceFile?.Location is null) continue; if (rootDirectoryFileNameToPaths.TryGetValue(keyValue.Value.FilePath.FileNameFirstSegment, out paths)) { if (appSettings.CompareSettings.SaveExtractedJavaScriptObjectNotation && keyValue.Value.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName is not null) SaveExtractedJavaScriptObjectNotation(keyValue.Key, keyValue.Value, paths); if (appSettings.CompareSettings.SaveExtractedFaces) SaveExtractedFace(keyValuePair.Key, keyValue.Key, keyValue.Value.ExifDirectory, keyValue.Value.FaceFile.Location, keyValue.Value.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName, paths); } } } } private static void SaveExtracted(AppSettings appSettings, ReadOnlyCollection preFiltered) { ReadOnlyCollection? paths; ReadOnlyDictionary> rootDirectoryFileNameToPaths = GetRootDirectoryFileNameToPaths(appSettings.ResultSettings, appSettings.CompareSettings); foreach (LocationContainer locationContainer in preFiltered) { if (locationContainer?.FilePath?.Id is null || locationContainer?.WholePercentages is null || locationContainer?.ExifDirectory is null || locationContainer?.FaceFile?.Location is null) continue; if (rootDirectoryFileNameToPaths.TryGetValue(locationContainer.FilePath.FileNameFirstSegment, out paths)) { if (appSettings.CompareSettings.SaveExtractedJavaScriptObjectNotation && locationContainer.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName is not null) SaveExtractedJavaScriptObjectNotation(locationContainer.WholePercentages.Value, locationContainer, paths); if (appSettings.CompareSettings.SaveExtractedFaces) SaveExtractedFace(locationContainer.FilePath.Id.Value, locationContainer.WholePercentages.Value, locationContainer.ExifDirectory, locationContainer.FaceFile.Location, locationContainer.PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName, paths); } } } private static ReadOnlyDictionary> GetRootDirectoryFileNameToPaths(ResultSettings resultSettings, CompareSettings compareSettings) { Dictionary> results = []; string key; string extension; List? collection; Dictionary> keyValuePairs = []; string[] files = !compareSettings.SaveExtractedFaces && !compareSettings.SaveExtractedJavaScriptObjectNotation ? [] : Directory.GetFiles(resultSettings.RootDirectory, "*", SearchOption.AllDirectories); foreach (string file in files) { extension = Path.GetExtension(file); if (resultSettings.IgnoreExtensions.Contains(extension)) continue; key = Path.GetFileNameWithoutExtension(file); if (!keyValuePairs.TryGetValue(key, out collection)) { keyValuePairs.Add(key, []); if (!keyValuePairs.TryGetValue(key, out collection)) throw new Exception(); } collection.Add(file); } foreach (KeyValuePair> keyValuePair in keyValuePairs) results.Add(keyValuePair.Key, keyValuePair.Value.AsReadOnly()); return results.AsReadOnly(); } private static void SaveExtractedJavaScriptObjectNotation(int wholePercentages, LocationContainer locationContainer, ReadOnlyCollection paths) { string file; string json; foreach (string path in paths) { json = locationContainer.GetWithoutEncoding(); file = $"{path}-{wholePercentages}.json"; _ = IPath.WriteAllText(file, json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null); } } private static void SaveExtractedFace(int id, int wholePercentages, ExifDirectory _, Location location, PersonKeyFormattedAndKeyTicksAndDisplayDirectoryName? personKeyFormattedAndKeyTicksAndDisplayDirectoryName, ReadOnlyCollection paths) { int width; int height; string person; foreach (string path in paths) { width = location.Right - location.Left; height = location.Bottom - location.Top; person = personKeyFormattedAndKeyTicksAndDisplayDirectoryName is null ? string.Empty : $"-{personKeyFormattedAndKeyTicksAndDisplayDirectoryName.DisplayDirectoryName}"; ExtractFace(file: path, width: width, height: height, left: location.Left, top: location.Top, suffix: $"-{id}-{wholePercentages}{person}-face.jpg"); } } private static void ExtractFace(string file, float width, float height, double left, double top, string suffix) { RectangleF rectangle = new((float)left, (float)top, width, height); using Bitmap source = new(file); using Bitmap bitmap = new((int)width, (int)height); using (Graphics graphics = Graphics.FromImage(bitmap)) graphics.DrawImage(source, new RectangleF(0, 0, width, height), rectangle, GraphicsUnit.Pixel); bitmap.Save($"{file}{suffix}"); } }