diff --git a/Compare/Compare.cs b/Compare/Compare.cs index 8decec5..a9891d6 100644 --- a/Compare/Compare.cs +++ b/Compare/Compare.cs @@ -54,7 +54,8 @@ public class Compare bool reverse = false; string outputExtension = ".jpg"; PredictorModel? predictorModel = null; - Map.Models.MapLogic mapLogic = new(_AppSettings.MaxDegreeOfParallelism, propertyConfiguration); + Dictionary personKeyValuePairs = new(); + Map.Models.MapLogic mapLogic = new(_AppSettings.MaxDegreeOfParallelism, propertyConfiguration, outputExtension, personKeyValuePairs); A_Property propertyLogic = GetPropertyLogic(reverse, model, outputExtension, predictorModel, mapLogic); foreach (string spelling in configuration.Spelling) { diff --git a/Instance/DlibDotNet.cs b/Instance/DlibDotNet.cs index 770424b..7f5acef 100644 --- a/Instance/DlibDotNet.cs +++ b/Instance/DlibDotNet.cs @@ -234,6 +234,7 @@ public class DlibDotNet sourceDirectoryNames = Array.Empty(); else { + string? century; string argZero = Path.GetFullPath(args[0]); sourceDirectoryNames = argZero.Split(Path.DirectorySeparatorChar); if (!argZero.StartsWith(propertyConfiguration.RootDirectory)) @@ -243,7 +244,8 @@ public class DlibDotNet if (!configuration.MixedYearRelativePaths.Contains(sourceDirectoryNames[0])) { string[] segments = sourceDirectoryNames[0].Split(' '); - if (segments.Length < 2 || segments[^1].Length != 4 || (segments[^1][..2] != "19" && segments[^1][..2] != "20")) + century = segments[^1].Length == 4 ? segments[^1][..2] : null; + if (segments.Length < 2 || century is null || (century != "18" && century != "19" && century != "20")) throw new Exception("root subdirectory must have a year at the end or directory name needs to be added to the exclude list!"); } } @@ -362,7 +364,7 @@ public class DlibDotNet string message = $"{container.R:000}.{container.G} / {containersCount:000}) {filteredItems.Length:000} file(s) - {totalSeconds} total second(s) - {outputResolution} - {container.SourceDirectory}"; using (ProgressBar progressBar = new(filteredItems.Length, message, options)) { - _ = Parallel.For(0, filteredItems.Length, parallelOptions, i => + _ = Parallel.For(0, filteredItems.Length, parallelOptions, (i, state) => { try { @@ -640,6 +642,36 @@ public class DlibDotNet } } + private static List<(string, int, Mapping, DateTime, bool?, List<(FaceRecognitionDotNet.FaceEncoding, MappingContainer)>)> Convert(Dictionary> keyValuePairs) + { + List<(string, int, Mapping, DateTime, bool?, List<(FaceRecognitionDotNet.FaceEncoding, MappingContainer)>)> results = new(); + MappingContainer mc; + foreach (KeyValuePair> keyValuePair in keyValuePairs) + { + foreach ((FaceRecognitionDotNet.FaceEncoding _, MappingContainer mappingContainer) in keyValuePair.Value) + { + mc = mappingContainer; + results.Add(new(mc.Key, mc.Id, mc.Mapping, mc.MinimumDateTime, mc.IsWrongYear, keyValuePair.Value)); + } + } + return results; + } + + private static Dictionary> Strip(Dictionary> keyValuePairs) + { + Dictionary> results = new(); + foreach (KeyValuePair> keyValuePair in keyValuePairs) + { + foreach ((FaceRecognitionDotNet.FaceEncoding _, MappingContainer mappingContainer) in keyValuePair.Value) + { + if (!results.ContainsKey(mappingContainer.Id)) + results.Add(mappingContainer.Id, new()); + results[mappingContainer.Id].Add(mappingContainer); + } + } + return results; + } + private void Search(Property.Models.Configuration configuration, bool reverse, Model? model, PredictorModel? predictorModel, string argZero, Person[] people, bool isSilent) { if (_Log is null) @@ -653,8 +685,8 @@ public class DlibDotNet string eResultsFullGroupDirectory; string zResultsFullGroupDirectory; string d2ResultsFullGroupDirectory; - Dictionary> peopleCollection = A2_People.Convert(people); - Map.Models.MapLogic mapLogic = new(_AppSettings.MaxDegreeOfParallelism, _Configuration.PropertyConfiguration); + Dictionary personKeyValuePairs = A2_People.Convert(people); + Map.Models.MapLogic mapLogic = new(_AppSettings.MaxDegreeOfParallelism, _Configuration.PropertyConfiguration, _Resize.FilenameExtension, personKeyValuePairs); A_Property propertyLogic = new(_AppSettings.MaxDegreeOfParallelism, _Configuration.PropertyConfiguration, _Resize.FilenameExtension, reverse, model, predictorModel, mapLogic.IndicesFromNew, mapLogic.KeyValuePairs); if (string.IsNullOrEmpty(configuration.RootDirectory)) containers = A_Property.Get(configuration, propertyLogic); @@ -669,30 +701,35 @@ public class DlibDotNet mapLogic.UseKeyValuePairsSaveFaceEncoding(containers); foreach (Container container in containers) { - mapLogic.AddToNamed(container.Items); + mapLogic.AddToMapping(container.Items); if (_Configuration.SaveShortcutsForOutputResolutions.Contains(outputResolution)) - D_Face.SaveShortcuts(_Configuration.JuliePhares, dResultsFullGroupDirectory, ticks, peopleCollection, mapLogic, container.Items); + mapLogic.SaveShortcuts(_Configuration.JuliePhares, dResultsFullGroupDirectory, ticks, container.Items); } mapLogic.SaveAllCollection(); if (_Configuration.SaveResizedSubfiles) { string dFacesContentDirectory; - string eDistanceContentDirectory; - string eDistanceCollectionDirectory; + string zPropertyHolderContentDirectory; string zPropertyHolderSingletonDirectory; + string zPropertyHolderCollectionDirectory; dFacesContentDirectory = Path.Combine(dResultsFullGroupDirectory, "()"); - eDistanceContentDirectory = Path.Combine(eResultsFullGroupDirectory, $"({ticks})"); zPropertyHolderSingletonDirectory = Path.Combine(zResultsFullGroupDirectory, "{}"); - eDistanceCollectionDirectory = Path.Combine(eResultsFullGroupDirectory, $"[{ticks}]"); - List<(DateTime, bool?, PersonBirthday, FaceRecognitionDotNet.FaceEncoding[])> collection; - collection = E_Distance.ParallelWork(_AppSettings.MaxDegreeOfParallelism, argZero, mapLogic, containers); + zPropertyHolderContentDirectory = Path.Combine(zResultsFullGroupDirectory, $"({ticks})"); + zPropertyHolderCollectionDirectory = Path.Combine(zResultsFullGroupDirectory, $"[{ticks}]"); + Dictionary> keyValuePairs = _Distance.ParallelWork(_AppSettings.MaxDegreeOfParallelism, _Configuration.IgnoreRelativePaths, argZero, ticks, containers); _ = LogDeltaInSeconds(ticks, nameof(E_Distance.ParallelWork)); + Dictionary> strippedKeyValuePairs = Strip(keyValuePairs); + List<(string, int, Mapping, DateTime, bool?, List<(FaceRecognitionDotNet.FaceEncoding, MappingContainer)>)> collection = Convert(keyValuePairs); + mapLogic.SaveMapping(argZero, containers, dFacesContentDirectory, d2ResultsFullGroupDirectory, zPropertyHolderContentDirectory); + _ = LogDeltaInMinutes(ticks, nameof(mapLogic.SaveMapping)); + _Distance.AddToFaceDistance(_AppSettings.MaxDegreeOfParallelism, argZero, ticks, mapLogic, containers, outputResolution, collection); + _ = LogDeltaInSeconds(ticks, nameof(_Distance.AddToFaceDistance)); + mapLogic.AddToClosest(_AppSettings.MaxDegreeOfParallelism, argZero, containers); + _ = LogDeltaInMinutes(ticks, nameof(mapLogic.AddToClosest)); + mapLogic.SaveClosest(argZero, containers, dFacesContentDirectory, d2ResultsFullGroupDirectory, zPropertyHolderContentDirectory); + _ = LogDeltaInMinutes(ticks, nameof(mapLogic.SaveClosest)); E_Distance.SavePropertyHolders(argZero, containers, zPropertyHolderSingletonDirectory); _ = LogDeltaInSeconds(ticks, nameof(E_Distance.SavePropertyHolders)); - E_Distance.SaveThreeSigmaFaceEncodings(collection, peopleCollection, eDistanceCollectionDirectory); - _ = LogDeltaInSeconds(ticks, nameof(E_Distance.SaveThreeSigmaFaceEncodings)); - E_Distance.SaveClosest(argZero, containers, peopleCollection, dFacesContentDirectory, d2ResultsFullGroupDirectory, eDistanceContentDirectory); - _ = LogDeltaInMinutes(ticks, nameof(E_Distance.SaveClosest)); } if (!_Configuration.LoadOrCreateThenSaveImageFacesResultsForOutputResolutions.Any()) break; diff --git a/Instance/Models/_A2_People.cs b/Instance/Models/_A2_People.cs index 353d9b8..1464614 100644 --- a/Instance/Models/_A2_People.cs +++ b/Instance/Models/_A2_People.cs @@ -73,16 +73,16 @@ internal class A2_People return results.ToArray(); } - internal static Dictionary> Convert(Person[] people) + internal static Dictionary Convert(Person[] people) { - Dictionary> results = new(); + Dictionary results = new(); string personKey; foreach (Person person in people) { personKey = Shared.Models.Stateless.Methods.IPersonBirthday.GetFormatted(person.Birthday); - if (!results.ContainsKey(personKey)) - results.Add(personKey, new List()); - results[personKey].Add(person); + if (results.ContainsKey(personKey)) + break; + results.Add(personKey, person); } return results; } diff --git a/Instance/Models/_D2_FaceParts.cs b/Instance/Models/_D2_FaceParts.cs index 1d830db..bdeeccc 100644 --- a/Instance/Models/_D2_FaceParts.cs +++ b/Instance/Models/_D2_FaceParts.cs @@ -153,7 +153,7 @@ internal class D2_FaceParts collection.Add(new(face, string.Empty, string.Empty)); continue; } - deterministicHashCodeKey = Shared.Models.Stateless.Methods.INamed.GetDeterministicHashCodeKey(item, face); + deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(item, face); fileInfo = new FileInfo(Path.Combine(facesDirectory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}{_FilenameExtension}")); if (!fileInfo.Exists) { diff --git a/Instance/Models/_D_Face.cs b/Instance/Models/_D_Face.cs index 213fa51..091256f 100644 --- a/Instance/Models/_D_Face.cs +++ b/Instance/Models/_D_Face.cs @@ -2,14 +2,12 @@ using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Text.Json; -using System.Text.RegularExpressions; using View_by_Distance.FaceRecognitionDotNet; using View_by_Distance.Metadata.Models; using View_by_Distance.Property.Models; using View_by_Distance.Resize.Models; using View_by_Distance.Shared.Models; using View_by_Distance.Shared.Models.Stateless; -using WindowsShortcutFactory; namespace View_by_Distance.Instance.Models; @@ -346,7 +344,7 @@ public class D_Face collection.Add(new(face, string.Empty)); continue; } - deterministicHashCodeKey = Shared.Models.Stateless.Methods.INamed.GetDeterministicHashCodeKey(item, face); + deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(item, face); fileInfo = new FileInfo(Path.Combine(facesDirectory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}{_FilenameExtension}")); if (!fileInfo.Exists) { @@ -368,60 +366,6 @@ public class D_Face SaveFaces(item.ResizedFileHolder, collection); } - internal static void SaveShortcuts(string[] juliePhares, string dResultsFullGroupDirectory, long ticks, Dictionary> peopleCollection, Map.Models.MapLogic mapLogic, List items) - { - Person person; - string fileName; - string fullName; - DateTime? minimumDateTime; - WindowsShortcut windowsShortcut; - const string pattern = @"[\\,\/,\:,\*,\?,\"",\<,\>,\|]"; - string dFacesContentDirectory = Path.Combine(dResultsFullGroupDirectory, $"({ticks})"); - List<(Item, (string, Face?, (string, string, string, string))[])> collections = mapLogic.GetCollection(items, dFacesContentDirectory); - foreach ((Item item, (string personKey, Face? _, (string, string, string, string))[] collection) in collections) - { - if (collection.Length != 1) - continue; - foreach ((string personKey, Face? _, (string directory, string copyDirectory, string copyFileName, string shortcutFileName)) in collection) - { - if (string.IsNullOrEmpty(personKey)) - continue; - if (item.Property?.Id is null || item.ImageFileHolder is null || item.ResizedFileHolder is null) - continue; - minimumDateTime = Shared.Models.Stateless.Methods.IProperty.GetMinimumDateTime(item.Property); - if (minimumDateTime is null) - continue; - if (!Directory.Exists(directory)) - { - _ = Directory.CreateDirectory(directory); - if (!string.IsNullOrEmpty(personKey) && peopleCollection.ContainsKey(personKey)) - { - person = peopleCollection[personKey][0]; - fullName = string.Concat(Regex.Replace(Shared.Models.Stateless.Methods.IPersonName.GetFullName(person.Name), pattern, string.Empty), ".txt"); - File.WriteAllText(Path.Combine(directory, fullName), string.Empty); - } - } - if (juliePhares.Contains(personKey) && !string.IsNullOrEmpty(copyDirectory)) - { - if (!Directory.Exists(copyDirectory)) - _ = Directory.CreateDirectory(copyDirectory); - fileName = Path.Combine(copyDirectory, $"{item.Property.Id.Value}{item.ResizedFileHolder.ExtensionLowered}"); - if (!File.Exists(fileName)) - File.Copy(item.ResizedFileHolder.FullName, fileName); - } - fileName = Path.Combine(directory, $"{item.Property.Id.Value}.lnk"); - if (File.Exists(fileName)) - continue; - windowsShortcut = new() { Path = item.ImageFileHolder.FullName }; - windowsShortcut.Save(fileName); - windowsShortcut.Dispose(); - if (!File.Exists(fileName)) - continue; - File.SetLastWriteTime(fileName, minimumDateTime.Value); - } - } - } - private static bool HasLeftAndRight(Dictionary faceParts) { bool result = true; diff --git a/Instance/Models/_E_Distance.cs b/Instance/Models/_E_Distance.cs index 6dfb9fb..6d44663 100644 --- a/Instance/Models/_E_Distance.cs +++ b/Instance/Models/_E_Distance.cs @@ -1,13 +1,10 @@ using System.Text.Json; -using System.Text.RegularExpressions; using View_by_Distance.FaceRecognitionDotNet; using View_by_Distance.Metadata.Models; using View_by_Distance.Property.Models; using View_by_Distance.Resize.Models; using View_by_Distance.Shared.Models; -using View_by_Distance.Shared.Models.Properties; using View_by_Distance.Shared.Models.Stateless; -using WindowsShortcutFactory; namespace View_by_Distance.Instance.Models; @@ -424,6 +421,152 @@ internal class E_Distance return result; } + private static int GetSelectedIndex(int maxDegreeOfParallelism, Random random, List faceEncodings) + { + int? result; + int selectedIndex; + List<(int? Index, double? Sum)> faceDistanceCollections = new(); + if (maxDegreeOfParallelism == 1) + { + double sum; + List faceDistances; + for (int i = 0; i < faceEncodings.Count; i++) + { + faceDistances = FaceRecognition.FaceDistances(faceEncodings, faceEncodings[i]); + sum = faceDistances.Sum(); + faceDistanceCollections.Add(new(i, sum)); + } + } + else + { + for (int i = 0; i < faceEncodings.Count; i++) + faceDistanceCollections.Add(new(null, null)); + ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; + _ = Parallel.For(0, faceEncodings.Count, parallelOptions, (i, state) => + { + List faceDistances = FaceRecognition.FaceDistances(faceEncodings, faceEncodings[i]); + double sum = faceDistances.Sum(); + lock (faceDistanceCollections) + faceDistanceCollections[i] = new(i, sum); + }); + } + faceDistanceCollections = faceDistanceCollections.OrderBy(l => l.Sum).ToList(); + if (faceDistanceCollections.Count != faceEncodings.Count) + throw new Exception(); + if (faceDistanceCollections.Count > 1000) + selectedIndex = random.Next(0, 36); + else if (faceDistanceCollections.Count > 500) + selectedIndex = random.Next(0, 31); + else if (faceDistanceCollections.Count > 200) + selectedIndex = random.Next(0, 26); + else if (faceDistanceCollections.Count > 100) + selectedIndex = random.Next(0, 21); + else if (faceDistanceCollections.Count > 50) + selectedIndex = random.Next(0, 16); + else if (faceDistanceCollections.Count > 25) + selectedIndex = random.Next(0, 11); + else if (faceDistanceCollections.Count > 10) + selectedIndex = random.Next(0, 6); + else if (faceDistanceCollections.Count > 5) + selectedIndex = random.Next(0, 3); + else + selectedIndex = 0; + result = faceDistanceCollections[selectedIndex].Index; + if (result is null) + throw new NullReferenceException(nameof(result)); + return result.Value; + } + + private static void SetFiltered(List<(FaceRecognitionDotNet.FaceEncoding FaceEncoding, MappingContainer MappingContainer)> collection) + { + double ucl; + bool check; + double average; + double[] doubles; + double standardDeviation; + double?[] nullableDoubles; + for (int i = 0; i < int.MaxValue; i++) + { + check = true; + nullableDoubles = (from l in collection where l.MappingContainer.Mapping.Filtered is not null && !l.MappingContainer.Mapping.Filtered.Value select l.MappingContainer.Distance).ToArray(); + doubles = (from l in nullableDoubles where l.HasValue select l.Value).ToArray(); + if (doubles.Length < 4) + break; + average = doubles.Average(); + standardDeviation = GetStandardDeviation(doubles, average); + ucl = average + (standardDeviation * 3); + if (ucl > IFaceDistance.Tolerance) + ucl = IFaceDistance.Tolerance; + foreach ((FaceRecognitionDotNet.FaceEncoding _, MappingContainer mappingContainer) in collection) + { + if (mappingContainer.Mapping.Filtered is null || mappingContainer.Mapping.Filtered.Value || mappingContainer.Distance <= ucl) + continue; + if (check) + check = false; + mappingContainer.Mapping.SetFiltered(); + } + if (check) + break; + } + } + + private static FaceDistance GetFaceDistanceParallelFor(Face face, FaceRecognitionDotNet.FaceEncoding faceEncoding, Mapping mapping, DateTime minimumDateTime, bool? isWrongYear, string key, FaceRecognitionDotNet.FaceEncoding[] faceEncodings) + { + FaceDistance result; + if (face.Location?.NormalizedPixelPercentage is null) + throw new NullReferenceException(nameof(face.Location.NormalizedPixelPercentage)); + List faceDistances = FaceRecognition.FaceDistances(faceEncodings, faceEncoding); + result = new(faceDistances, isWrongYear, key, mapping, minimumDateTime); + return result; + } + + private static List GetFaceDistanceCollection(int maxDegreeOfParallelism, List<(string Key, int Id, Mapping Mapping, DateTime MinimumDateTime, bool? IsWrongYear, List<(FaceRecognitionDotNet.FaceEncoding, MappingContainer)>)> collection, Face face) + { + List results; + if (face.FaceEncoding is null) + throw new NullReferenceException(nameof(face.FaceEncoding)); + FaceRecognitionDotNet.FaceEncoding faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding); + if (maxDegreeOfParallelism == 1) + { + results = new(); + FaceDistance faceDistance; + List faceDistances; + FaceRecognitionDotNet.FaceEncoding[] faceEncodings; + if (face.Location?.NormalizedPixelPercentage is null) + throw new NullReferenceException(nameof(face.Location.NormalizedPixelPercentage)); + foreach ((string key, int id, Mapping mapping, DateTime minimumDateTime, bool? isWrongYear, List<(FaceRecognitionDotNet.FaceEncoding FaceEncoding, MappingContainer _)> faceEncodingContainers) in collection) + { + faceEncodings = (from l in faceEncodingContainers select l.FaceEncoding).ToArray(); + faceDistances = FaceRecognition.FaceDistances(faceEncodings, faceEncoding); + faceDistance = new(faceDistances, isWrongYear, key, mapping, minimumDateTime); + results.Add(faceDistance); + if (results.Count > IFaceDistance.MaximumPer) + break; + } + } + else + { + results = new(); + ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; + _ = Parallel.For(0, collection.Count, parallelOptions, (i, state) => + { + (string key, int id, Mapping mapping, DateTime minimumDateTime, bool? isWrongYear, List<(FaceRecognitionDotNet.FaceEncoding FaceEncoding, MappingContainer _)> faceEncodingContainers) = collection[i]; + FaceRecognitionDotNet.FaceEncoding[] faceEncodings = (from l in faceEncodingContainers select l.FaceEncoding).ToArray(); + FaceDistance? closest = GetFaceDistanceParallelFor(face, faceEncoding, mapping, minimumDateTime, isWrongYear, key, faceEncodings); + if (closest is not null) + { + lock (results) + { + results.Add(closest); + if (results.Count > IFaceDistance.MaximumPer) + state.Break(); + } + } + }); + } + return results; + } + private static FaceRecognitionDotNet.FaceEncoding? GetFaceEncoding(Face face) { FaceRecognitionDotNet.FaceEncoding? result; @@ -434,24 +577,18 @@ internal class E_Distance return result; } - private static (int index, double sum) GetIndexAndSum(int i, List results) - { - List faceDistances = FaceRecognition.FaceDistances(results, results[i]); - return new(i, faceDistances.Sum()); - } - - private static List GetFaceEncodings(int maxDegreeOfParallelism, List<(DateTime MinimumDateTime, bool? IsWrongYear, PersonBirthday PersonBirthday, Face Face)> collection) + private static List GetFaceEncodingsOnly(int maxDegreeOfParallelism, List collection) { List results; if (maxDegreeOfParallelism == 1) { results = new(); FaceRecognitionDotNet.FaceEncoding faceEncoding; - foreach ((DateTime _, bool? _, PersonBirthday _, Face face) in collection) + foreach (MappingContainer mappingContainer in collection) { - if (face.FaceEncoding is null || face.Location?.NormalizedPixelPercentage is null) + if (mappingContainer.Face?.FaceEncoding is null || mappingContainer.Face.Location?.NormalizedPixelPercentage is null) continue; - faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding); + faceEncoding = FaceRecognition.LoadFaceEncoding(mappingContainer.Face.FaceEncoding.RawEncoding); results.Add(faceEncoding); } } @@ -459,9 +596,12 @@ internal class E_Distance { results = new(); ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; - _ = Parallel.For(0, collection.Count, parallelOptions, i => + _ = Parallel.For(0, collection.Count, parallelOptions, (i, state) => { - FaceRecognitionDotNet.FaceEncoding? faceEncoding = GetFaceEncoding(collection[i].Face); + Face? face = collection[i].Face; + if (face is null) + throw new Exception(); + FaceRecognitionDotNet.FaceEncoding? faceEncoding = GetFaceEncoding(face); if (faceEncoding is not null) { lock (results) @@ -469,136 +609,68 @@ internal class E_Distance } }); } - if (collection.Count == results.Count && results.Count > 1) - { - double sum; - int lowestIndex; - double lowestSum; - List faceDistances; - if (maxDegreeOfParallelism == 1) - { - lowestIndex = 0; - lowestSum = double.MaxValue; - for (int i = 0; i < results.Count; i++) - { - faceDistances = FaceRecognition.FaceDistances(results, results[i]); - sum = faceDistances.Sum(); - if (sum >= lowestSum) - continue; - lowestIndex = i; - lowestSum = sum; - } - } - else - { - List<(int Index, double Sum)> indicesAndSums = new(); - ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; - _ = Parallel.For(0, results.Count, parallelOptions, i => - { - (int index, double sum) = GetIndexAndSum(i, results); - lock (indicesAndSums) - indicesAndSums.Add(new(index, sum)); - }); - (lowestIndex, lowestSum) = (from l in indicesAndSums orderby l.Sum select l).First(); - } - faceDistances = FaceRecognition.FaceDistances(results, results[lowestIndex]); - sum = faceDistances.Sum(); - if (sum == lowestSum) - { - double average = faceDistances.Average(); - double standardDeviation = GetStandardDeviation(faceDistances, average); - double lcl = average - (standardDeviation * 3); - double ucl = average + (standardDeviation * 3); - for (int i = results.Count - 1; i > -1; i--) - { - if (faceDistances[i] < lcl || faceDistances[i] > ucl) - results.RemoveAt(i); - } - } - } return results; } - private static List<(DateTime, bool?, PersonBirthday, FaceRecognitionDotNet.FaceEncoding[])> GetThreeSigmaFaceEncodings(int maxDegreeOfParallelism, Dictionary> keyValuePairs) + private Dictionary> GetThreeSigmaFaceEncodings(int maxDegreeOfParallelism, long ticks, Dictionary> keyValuePairs) { - List<(DateTime, bool?, PersonBirthday, FaceRecognitionDotNet.FaceEncoding[])> results = new(); - const int zero = 0; + if (_Log is null) + throw new NullReferenceException(nameof(_Log)); + Dictionary> results = new(); + int totalSeconds; + int selectedIndex; + Random random = new(); + List faceDistances; + MappingContainer mappingContainer; + int keyValuePairsCount = keyValuePairs.Count; + FaceRecognitionDotNet.FaceEncoding faceEncoding; List faceEncodings; - foreach (KeyValuePair> keyValuePair in keyValuePairs) + List<(FaceRecognitionDotNet.FaceEncoding FaceEncoding, MappingContainer MappingContainer)> collection; + foreach (KeyValuePair> keyValuePair in keyValuePairs) { - faceEncodings = GetFaceEncodings(maxDegreeOfParallelism, keyValuePair.Value); - results.Add(new(keyValuePair.Value[zero].MinimumDateTime, keyValuePair.Value[zero].IsWrongYear, keyValuePair.Value[zero].PersonBirthday, faceEncodings.ToArray())); - } - return results; - } - - private static Closest? GetClosestParallelFor(DateTime minimumDateTime, bool? isWrongYear, Face face, FaceRecognitionDotNet.FaceEncoding faceEncoding, (DateTime MinimumDateTime, bool? IsWrongYear, PersonBirthday PersonBirthday, FaceRecognitionDotNet.FaceEncoding[] FaceEncodings) tuple) - { - Closest? result; - if (isWrongYear.HasValue && !isWrongYear.Value && minimumDateTime < tuple.PersonBirthday.Value) - result = null; - else - { - List faceDistances = FaceRecognition.FaceDistances(tuple.FaceEncodings, faceEncoding); - result = new(face.Location?.NormalizedPixelPercentage, tuple.MinimumDateTime, tuple.IsWrongYear, tuple.PersonBirthday, faceDistances); - if (result.Minimum > Shared.Models.Stateless.IClosest.MaximumMinimum) - result = null; - } - return result; - } - - private static Closest[] GetClosestCollection(int maxDegreeOfParallelism, List<(DateTime, bool?, PersonBirthday, FaceRecognitionDotNet.FaceEncoding[])> collection, DateTime itemMinimumDateTime, bool? itemIsWrongYear, Face face) - { - Closest[] results; - List closestCollection; - if (face.FaceEncoding is null) - throw new NullReferenceException(nameof(face.FaceEncoding)); - FaceRecognitionDotNet.FaceEncoding faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding); - if (maxDegreeOfParallelism == 1) - { - closestCollection = new(); - Closest closest; - List faceDistances; - foreach ((DateTime minimumDateTime, bool? isWrongYear, PersonBirthday personBirthday, FaceRecognitionDotNet.FaceEncoding[] faceEncodings) in collection) + collection = new(); + faceEncodings = GetFaceEncodingsOnly(maxDegreeOfParallelism, keyValuePair.Value); + for (int i = 0; i < faceEncodings.Count; i++) { - if (itemIsWrongYear.HasValue && !itemIsWrongYear.Value && itemMinimumDateTime < personBirthday.Value) - continue; - faceDistances = FaceRecognition.FaceDistances(faceEncodings, faceEncoding); - closest = new(face.Location?.NormalizedPixelPercentage, minimumDateTime, isWrongYear, personBirthday, faceDistances); - if (closest.Minimum > Shared.Models.Stateless.IClosest.MaximumMinimum) - continue; - closestCollection.Add(closest); + faceEncoding = faceEncodings[i]; + mappingContainer = keyValuePair.Value[i]; + collection.Add(new(faceEncoding, mappingContainer)); } + results.Add(keyValuePair.Key, collection); + if (faceEncodings.Count == 1) + selectedIndex = 0; + else + selectedIndex = GetSelectedIndex(maxDegreeOfParallelism, random, faceEncodings); + faceDistances = FaceRecognition.FaceDistances(faceEncodings, faceEncodings[selectedIndex]); + for (int i = 0; i < faceEncodings.Count; i++) + collection[i].MappingContainer.SetDistance(faceDistances[i]); + if (collection.Count > 1) + SetFiltered(collection); + totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); + _Log.Information($"{keyValuePairsCount:0000}) {totalSeconds} total second(s) - {keyValuePair.Key} - {collection[selectedIndex].MappingContainer.Mapping.DisplayDirectoryName}"); } - else - { - closestCollection = new(); - ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; - _ = Parallel.For(0, collection.Count, parallelOptions, i => - { - Closest? closest = GetClosestParallelFor(itemMinimumDateTime, itemIsWrongYear, face, faceEncoding, collection[i]); - if (closest is not null) - { - lock (closestCollection) - closestCollection.Add(closest); - } - }); - } - results = Shared.Models.Stateless.Methods.IClosest.Get(closestCollection); return results; } - private static void AddClosest(int maxDegreeOfParallelism, string argZero, Map.Models.MapLogic mapLogic, List containers, List<(DateTime, bool?, PersonBirthday, FaceRecognitionDotNet.FaceEncoding[])> collection) + internal Dictionary> ParallelWork(int maxDegreeOfParallelism, string[] ignoreRelativePaths, string argZero, long ticks, List containers) { - string key; + Dictionary> results; + Dictionary> keyValuePairs = Map.Models.Stateless.IMapLogic.GetKeyValuePairs(ignoreRelativePaths, argZero, containers); + results = GetThreeSigmaFaceEncodings(maxDegreeOfParallelism, ticks, keyValuePairs); + return results; + } + + public void AddToFaceDistance(int maxDegreeOfParallelism, string argZero, long ticks, Map.Models.MapLogic mapLogic, List containers, string outputResolution, List<(string, int, Mapping, DateTime, bool?, List<(FaceRecognitionDotNet.FaceEncoding, MappingContainer)>)> collection) + { + if (_Log is null) + throw new NullReferenceException(nameof(_Log)); Face face; - Closest closest; - string personKey; - bool? itemIsWrongYear; - Closest[] closestCollection; - DateTime? itemMinimumDateTime; + string message; + int totalSeconds; double deterministicHashCodeKey; - Dictionary results = new(); + DateTime dateTime = DateTime.Now; + List faceDistances; + int containersCount = containers.Count; foreach (Container container in containers) { if (!container.Items.Any()) @@ -607,55 +679,29 @@ internal class E_Distance continue; foreach (Item item in container.Items) { - if (item.ImageFileHolder is null || item.Property is null || item.Named.Any()) + if (item.ImageFileHolder is null || item.Property?.Id is null) continue; - itemMinimumDateTime = Shared.Models.Stateless.Methods.IProperty.GetMinimumDateTime(item.Property); - if (itemMinimumDateTime is null) - continue; - (itemIsWrongYear, _) = Map.Models.MapLogic.IsWrongYear(item); - if (Shared.Models.Stateless.IClosest.SkipIsWrongYear && itemIsWrongYear.HasValue && itemIsWrongYear.Value) - continue; - item.Closest.Clear(); for (int i = 0; i < item.Faces.Count; i++) { face = item.Faces[i]; - closest = new(face.Location?.NormalizedPixelPercentage, itemMinimumDateTime.Value, itemIsWrongYear); - item.Closest.Add(closest); + face.FaceDistances.Clear(); if (face.FaceEncoding is null || face.Location?.NormalizedPixelPercentage is null) continue; - deterministicHashCodeKey = Shared.Models.Stateless.Methods.INamed.GetDeterministicHashCodeKey(item, face); - closestCollection = GetClosestCollection(maxDegreeOfParallelism, collection, itemMinimumDateTime.Value, itemIsWrongYear, face); - for (int j = 0; j < closestCollection.Length; j++) - { - closest = closestCollection[j]; - if (closest.PersonBirthday is null) - continue; - personKey = Shared.Models.Stateless.Methods.IPersonBirthday.GetFormatted(closest.PersonBirthday); - if (mapLogic.IsIncorrect(deterministicHashCodeKey, personKey)) - continue; - key = Map.Models.MapLogic.GetKey(closest.MinimumDateTime, closest.IsWrongYear, closest.PersonBirthday); - if (!results.ContainsKey(key)) - results.Add(key, 0); - else if (results[key] > Shared.Models.Stateless.IClosest.MaximumPer) - continue; - results[key] += 1; - item.Closest[0] = closest; - break; - } + if ((from l in item.Mapping where l.NormalizedPixelPercentage.HasValue && l.NormalizedPixelPercentage.Value == face.Location.NormalizedPixelPercentage.Value select true).Any()) + continue; + deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(item, face); + if (mapLogic.Skip(deterministicHashCodeKey)) + continue; + faceDistances = GetFaceDistanceCollection(maxDegreeOfParallelism, collection, face); + face.FaceDistances.AddRange(faceDistances); } } + totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); + message = $"{container.R:000}.{container.G} / {containersCount:000}) {container.Items.Count:000} file(s) - {totalSeconds} total second(s) - {outputResolution} - {container.SourceDirectory}"; + _Log.Information(message); } } - internal static List<(DateTime, bool?, PersonBirthday, FaceRecognitionDotNet.FaceEncoding[])> ParallelWork(int maxDegreeOfParallelism, string argZero, Map.Models.MapLogic mapLogic, List containers) - { - List<(DateTime, bool?, PersonBirthday, FaceRecognitionDotNet.FaceEncoding[])> results; - Dictionary> keyValuePairs = Map.Models.MapLogic.GetKeyValuePairs(argZero, containers); - results = GetThreeSigmaFaceEncodings(maxDegreeOfParallelism, keyValuePairs); - AddClosest(maxDegreeOfParallelism, argZero, mapLogic, containers, results); - return results; - } - public static void SavePropertyHolders(string argZero, List containers, string zPropertyHolderSingletonDirectory) { string json; @@ -672,8 +718,6 @@ internal class E_Distance { if (item.ImageFileHolder is null || item.Property is null || !item.Faces.Any() || !item.Closest.Any()) continue; - if (!(from l in item.Closest where l.Average.HasValue select true).Any()) - continue; json = JsonSerializer.Serialize(item, jsonSerializerOptions); fileInfo = new(string.Concat(zPropertyHolderSingletonDirectory, item.RelativePath, ".json")); if (fileInfo.Directory is null) @@ -685,140 +729,4 @@ internal class E_Distance } } - internal static void SaveThreeSigmaFaceEncodings(List<(DateTime, bool?, PersonBirthday, FaceRecognitionDotNet.FaceEncoding[])> collection, Dictionary> peopleCollection, string eDistanceCollectionDirectory) - { - string json; - string checkFile; - string personKey; - string directory; - List rawEncodings; - Person person; - const string facePopulatedKey = "ThreeSigma"; - const string pattern = @"[\\,\/,\:,\*,\?,\"",\<,\>,\|]"; - foreach ((DateTime minimumDateTime, bool? isWrongYear, PersonBirthday personBirthday, FaceRecognitionDotNet.FaceEncoding[] faceEncodings) in collection) - { - rawEncodings = new(); - checkFile = string.Empty; - personKey = Shared.Models.Stateless.Methods.IPersonBirthday.GetFormatted(personBirthday); - directory = Map.Models.MapLogic.GetDirectory(eDistanceCollectionDirectory, facePopulatedKey, minimumDateTime, isWrongYear, personBirthday, personKey); - if (!peopleCollection.ContainsKey(personKey)) - continue; - person = peopleCollection[personKey][0]; - checkFile = string.Concat(directory, " - ", Regex.Replace(Shared.Models.Stateless.Methods.IPersonName.GetFullName(person.Name), pattern, string.Empty), ".json"); - if (string.IsNullOrEmpty(checkFile)) - continue; - if (!Directory.Exists(directory)) - _ = Directory.CreateDirectory(directory); - for (int i = 0; i < faceEncodings.Length; i++) - rawEncodings.Add(faceEncodings[i].GetRawEncoding()); - json = JsonSerializer.Serialize(rawEncodings, new JsonSerializerOptions { WriteIndented = true }); - _ = Shared.Models.Stateless.Methods.IPath.WriteAllText(checkFile, json, updateDateWhenMatches: true, compareBeforeWrite: true); - } - } - - internal static List<(IFileHolder? resizedFileHolder, string directory, FileInfo? faceFileInfo, string checkFile, string shortcutFile)> GetClosest(string argZero, List containers, Dictionary> peopleCollection, string dFacesContentDirectory, string d2ResultsFullGroupDirectory, string eDistanceContentDirectory) - { - List<(IFileHolder?, string, FileInfo?, string, string)> results = new(); - Person person; - string checkFile; - string directory; - string personKey; - string personName; - string shortcutFile; - FileInfo faceFileInfo; - string? directoryName; - string facesDirectory; - string personDirectory; - FileInfo landmarkFileInfo; - string landmarksDirectory; - double deterministicHashCodeKey; - const string facePopulatedKey = nameof(Closest); - const string pattern = @"[\\,\/,\:,\*,\?,\"",\<,\>,\|]"; - foreach (Container container in containers) - { - if (!container.Items.Any()) - continue; - if (!container.SourceDirectory.StartsWith(argZero)) - continue; - foreach (Item item in container.Items) - { - if (item.ImageFileHolder is null || item.Property?.Id is null || item.ResizedFileHolder is null || item.Named.Any()) - continue; - if (!item.Closest.Any()) - continue; - directoryName = Path.GetDirectoryName(item.RelativePath); - if (directoryName is null) - throw new Exception(); - foreach (Closest closest in item.Closest) - { - if (closest.Average is null || closest.NormalizedPixelPercentage is null || closest.PersonBirthday is null) - continue; - personKey = Shared.Models.Stateless.Methods.IPersonBirthday.GetFormatted(closest.PersonBirthday); - directory = Map.Models.MapLogic.GetDirectory(eDistanceContentDirectory, facePopulatedKey, closest.MinimumDateTime, closest.IsWrongYear, closest.PersonBirthday, personKey); - if (!peopleCollection.ContainsKey(personKey)) - personDirectory = string.Empty; - else - { - person = peopleCollection[personKey][0]; - personName = Shared.Models.Stateless.Methods.IPersonName.GetFullName(person.Name); - personDirectory = Path.Combine(directory, Regex.Replace(personName, pattern, string.Empty), "lnk"); - results.Add(new(null, personDirectory, null, string.Empty, string.Empty)); - } - facesDirectory = string.Concat(dFacesContentDirectory, Path.Combine(directoryName, item.ImageFileHolder.NameWithoutExtension)); - landmarksDirectory = string.Concat(d2ResultsFullGroupDirectory, Path.Combine(directoryName, item.ImageFileHolder.NameWithoutExtension)); - deterministicHashCodeKey = Shared.Models.Stateless.Methods.INamed.GetDeterministicHashCodeKey(item, closest); - checkFile = Path.Combine(directory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}"); - faceFileInfo = new(Path.Combine(facesDirectory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}.png")); - landmarkFileInfo = new(Path.Combine(landmarksDirectory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}.gif")); - if (string.IsNullOrEmpty(personDirectory)) - shortcutFile = string.Empty; - else - shortcutFile = Path.Combine(personDirectory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}.lnk"); - results.Add(new(item.ResizedFileHolder, directory, faceFileInfo, checkFile, shortcutFile)); - } - } - } - return results; - } - - internal static void SaveClosest(string argZero, List containers, Dictionary> peopleCollection, string dFacesContentDirectory, string d2ResultsFullGroupDirectory, string eDistanceContentDirectory) - { - WindowsShortcut windowsShortcut; - List<(IFileHolder? resizedFileHolder, string directory, FileInfo? faceFileInfo, string checkFile, string shortcutFile)> collection = GetClosest(argZero, containers, peopleCollection, dFacesContentDirectory, d2ResultsFullGroupDirectory, eDistanceContentDirectory); - string[] directories = (from l in collection select l.directory).Distinct().ToArray(); - foreach (string directory in directories) - { - if (string.IsNullOrEmpty(directory)) - continue; - if (!Directory.Exists(directory)) - _ = Directory.CreateDirectory(directory); - } - foreach ((IFileHolder? resizedFileHolder, string directory, FileInfo? faceFileInfo, string checkFile, string shortcutFile) in collection) - { - if (string.IsNullOrEmpty(directory) || string.IsNullOrEmpty(checkFile) || resizedFileHolder is null || faceFileInfo is null) - continue; - if (File.Exists(checkFile)) - continue; - if (faceFileInfo.Directory is not null && faceFileInfo.Directory.Exists && faceFileInfo.Exists) - File.Copy(faceFileInfo.FullName, checkFile); - else - File.Copy(resizedFileHolder.FullName, checkFile); - } - foreach ((IFileHolder? resizedFileHolder, string directory, FileInfo? _, string checkFile, string shortcutFile) in collection) - { - if (string.IsNullOrEmpty(directory) || string.IsNullOrEmpty(checkFile) || resizedFileHolder is null) - continue; - if (string.IsNullOrEmpty(shortcutFile) || !resizedFileHolder.Exists) - continue; - try - { - windowsShortcut = new() { Path = resizedFileHolder.FullName }; - windowsShortcut.Save(shortcutFile); - windowsShortcut.Dispose(); - } - catch (Exception) - { } - } - } - } \ No newline at end of file diff --git a/Map/Map.csproj b/Map/Map.csproj index 080a285..a6274de 100644 --- a/Map/Map.csproj +++ b/Map/Map.csproj @@ -38,6 +38,7 @@ + diff --git a/Map/Models/MapLogic.cs b/Map/Models/MapLogic.cs index dadf32a..240f5de 100644 --- a/Map/Models/MapLogic.cs +++ b/Map/Models/MapLogic.cs @@ -1,28 +1,34 @@ using System.Diagnostics; using System.Text.Json; using View_by_Distance.Property.Models; +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Properties; +using WindowsShortcutFactory; namespace View_by_Distance.Map.Models; public class MapLogic { + protected readonly List _SkipCollection; protected readonly List<(int, string[])> _AllCollection; protected readonly Dictionary _KeyValuePairs; protected readonly Dictionary _IndicesFromNew; - protected readonly string _DeterministicHashCodeRootDirectory; + protected readonly string _DeterministicHashCodeContentDirectory; protected readonly Dictionary _SixCharacterNamedFaceInfo; - protected readonly Dictionary _DeterministicHashCodeUnknownFaceKeyValuePairs; protected readonly Dictionary _DeterministicHashCodeKeyValuePairs; + protected readonly Dictionary _DeterministicHashCodeUnknownFaceKeyValuePairs; protected readonly Dictionary _IncorrectDeterministicHashCodeKeyValuePairs; + protected readonly Dictionary _PeopleKeyValuePairs; public Dictionary KeyValuePairs => _KeyValuePairs; public Dictionary IndicesFromNew => _IndicesFromNew; private readonly Serilog.ILogger? _Log; + private readonly string _OutputExtension; private readonly Configuration _Configuration; - public MapLogic(int maxDegreeOfParallelism, Configuration configuration) + public MapLogic(int maxDegreeOfParallelism, Configuration configuration, string outputExtension, Dictionary personKeyValuePairs) { _AllCollection = new(); _Configuration = configuration; @@ -33,17 +39,20 @@ public class MapLogic string json; string[] files; string fullPath; + _OutputExtension = outputExtension; + List skipCollection = new(); Dictionary? keyValuePairs; - string deterministicHashCodeRootDirectory; List>? collection; + string deterministicHashCodeContentDirectory; Dictionary indicesFromNew = new(); Dictionary? sixCharacterNamedFaceInfo; Dictionary deterministicHashCodeKeyValuePairs = new(); Dictionary incorrectDeterministicHashCodeKeyValuePairs = new(); string? rootDirectoryParent = Path.GetDirectoryName(configuration.RootDirectory); + Dictionary peopleKeyValuePairs = new(); if (string.IsNullOrEmpty(rootDirectoryParent)) throw new NullReferenceException(nameof(rootDirectoryParent)); - files = Directory.GetFiles(rootDirectoryParent, "*DeterministicHashCode*.json", SearchOption.TopDirectoryOnly); + files = Directory.GetFiles(rootDirectoryParent, "DeterministicHashCode*.json", SearchOption.TopDirectoryOnly); if (files.Length != 1) deterministicHashCodeUnknownFaceKeyValuePairs = new(); else @@ -53,15 +62,11 @@ public class MapLogic if (deterministicHashCodeUnknownFaceKeyValuePairs is null) throw new NullReferenceException(nameof(deterministicHashCodeUnknownFaceKeyValuePairs)); } - string[] directories = Directory.GetDirectories(rootDirectoryParent, "*DeterministicHashCode*", SearchOption.TopDirectoryOnly); + string[] directories = Directory.GetDirectories(rootDirectoryParent, "DeterministicHashCode", SearchOption.TopDirectoryOnly); if (!directories.Any()) - deterministicHashCodeRootDirectory = string.Empty; + deterministicHashCodeContentDirectory = string.Empty; else - { - Dictionary> faces = new(); - deterministicHashCodeRootDirectory = directories[0]; - SetKeyValuePairs(deterministicHashCodeRootDirectory, deterministicHashCodeKeyValuePairs, incorrectDeterministicHashCodeKeyValuePairs, faces); - } + deterministicHashCodeContentDirectory = Stateless.ByDeterministicHashCode.SetByRef(_OutputExtension, personKeyValuePairs, skipCollection, peopleKeyValuePairs, deterministicHashCodeUnknownFaceKeyValuePairs, deterministicHashCodeKeyValuePairs, incorrectDeterministicHashCodeKeyValuePairs, directories[0]); if (!deterministicHashCodeUnknownFaceKeyValuePairs.Any()) sixCharacterNamedFaceInfo = new(); else @@ -77,7 +82,7 @@ public class MapLogic throw new NullReferenceException(nameof(sixCharacterNamedFaceInfo)); } } - files = Directory.GetFiles(rootDirectoryParent, "*keyValuePairs*.json", SearchOption.TopDirectoryOnly); + files = Directory.GetFiles(rootDirectoryParent, "*keyValuePairs-6*.json", SearchOption.TopDirectoryOnly); if (files.Length != 1) keyValuePairs = new(); else @@ -107,116 +112,80 @@ public class MapLogic } _KeyValuePairs = keyValuePairs; _IndicesFromNew = indicesFromNew; + _SkipCollection = skipCollection; + _PeopleKeyValuePairs = peopleKeyValuePairs; _SixCharacterNamedFaceInfo = sixCharacterNamedFaceInfo; - _DeterministicHashCodeRootDirectory = deterministicHashCodeRootDirectory; _DeterministicHashCodeKeyValuePairs = deterministicHashCodeKeyValuePairs; + _DeterministicHashCodeContentDirectory = deterministicHashCodeContentDirectory; _IncorrectDeterministicHashCodeKeyValuePairs = incorrectDeterministicHashCodeKeyValuePairs; _DeterministicHashCodeUnknownFaceKeyValuePairs = deterministicHashCodeUnknownFaceKeyValuePairs; } - public bool IsIncorrect(double deterministicHashCodeKey, string personKey) => _DeterministicHashCodeKeyValuePairs.ContainsKey(deterministicHashCodeKey) && _IncorrectDeterministicHashCodeKeyValuePairs[deterministicHashCodeKey].Contains(personKey); + public bool Skip(double deterministicHashCodeKey) => _SkipCollection.Contains(deterministicHashCodeKey); - private void SetKeyValuePairs(string deterministicHashCodeRootDirectory, List<(string, double)> named, List<(string, double)> incorrect, Dictionary> keyValuePairs) + public void SaveShortcuts(string[] juliePhares, string dResultsFullGroupDirectory, long ticks, List items) { - string[] files; - string personKey; - string[] yearDirectories; - string ticksDirectoryName; - string[] personKeyDirectories; - string[] personNameDirectories; - string[] personNameLinkDirectories; - double? reversedDeterministicHashCodeKey; - bool keyValuePairsAny = keyValuePairs.Any(); - string[] ticksDirectories = Directory.GetDirectories(deterministicHashCodeRootDirectory, "*", SearchOption.TopDirectoryOnly); - foreach (string ticksDirectory in ticksDirectories) + string fileName; + string fullName; + DateTime? minimumDateTime; + WindowsShortcut windowsShortcut; + (string DisplayDirectoryName, int? ApproximateYears, string Key, PersonBirthday[] _) person; + string dFacesContentDirectory = Path.Combine(dResultsFullGroupDirectory, $"({ticks})"); + List<(Item, (string, Face?, (string, string, string, string))[])> collections = GetCollection(items, dFacesContentDirectory); + foreach ((Item item, (string personKey, Face? _, (string, string, string, string))[] collection) in collections) { - ticksDirectoryName = Path.GetFileName(ticksDirectory); - if (ticksDirectoryName.Length < 3 || ticksDirectoryName[0] != '(' || ticksDirectoryName[^1] != ')') + if (collection.Length != 1) continue; - personKeyDirectories = Directory.GetDirectories(ticksDirectory, "*", SearchOption.TopDirectoryOnly); - foreach (string personKeyDirectory in personKeyDirectories) + foreach ((string personKey, Face? _, (string directory, string copyDirectory, string copyFileName, string shortcutFileName)) in collection) { - personKey = Path.GetFileName(personKeyDirectory); - if (personKey == nameof(Shared.Models.Closest)) - throw new Exception($"Move personKey directories up one from {nameof(Shared.Models.Closest)} and delete {nameof(Shared.Models.Closest)} directory!"); - yearDirectories = Directory.GetDirectories(personKeyDirectory, "*", SearchOption.TopDirectoryOnly); - foreach (string yearDirectory in yearDirectories) + if (string.IsNullOrEmpty(personKey)) + continue; + if (item.Property?.Id is null || item.ImageFileHolder is null || item.ResizedFileHolder is null) + continue; + minimumDateTime = Shared.Models.Stateless.Methods.IProperty.GetMinimumDateTime(item.Property); + if (minimumDateTime is null) + continue; + if (!Directory.Exists(directory)) { - files = Directory.GetFiles(yearDirectory, "*", SearchOption.TopDirectoryOnly); - personNameDirectories = Directory.GetDirectories(yearDirectory, "*", SearchOption.TopDirectoryOnly); - foreach (string file in files) - File.Delete(file); - foreach (string personNameDirectory in personNameDirectories) + _ = Directory.CreateDirectory(directory); + if (!string.IsNullOrEmpty(personKey) && _PeopleKeyValuePairs.ContainsKey(personKey)) { - files = Directory.GetFiles(personNameDirectory, "*", SearchOption.TopDirectoryOnly); - personNameLinkDirectories = Directory.GetDirectories(personNameDirectory, "*", SearchOption.TopDirectoryOnly); - foreach (string file in files) - { - if (file.EndsWith(".lnk") || file.EndsWith(".json")) - continue; - reversedDeterministicHashCodeKey = Shared.Models.Stateless.Methods.INamed.GetReversedDeterministicHashCodeKey(keyValuePairsAny, keyValuePairs, file); - if (reversedDeterministicHashCodeKey is null) - continue; - named.Add(new(personKey, reversedDeterministicHashCodeKey.Value)); - } - foreach (string personNameLinkDirectory in personNameLinkDirectories) - { - files = Directory.GetFiles(personNameLinkDirectory, "*", SearchOption.TopDirectoryOnly); - foreach (string file in files) - { - if (!file.EndsWith(".lnk")) - continue; - File.Delete(file); - } - _ = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(personNameLinkDirectory); - } - _ = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(personNameDirectory); + person = _PeopleKeyValuePairs[personKey]; + fullName = string.Concat(person.DisplayDirectoryName, ".txt"); + File.WriteAllText(Path.Combine(directory, fullName), string.Empty); } - _ = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(yearDirectory); } - _ = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(personKeyDirectory); + if (juliePhares.Contains(personKey) && !string.IsNullOrEmpty(copyDirectory)) + { + if (!Directory.Exists(copyDirectory)) + _ = Directory.CreateDirectory(copyDirectory); + fileName = Path.Combine(copyDirectory, $"{item.Property.Id.Value}{item.ResizedFileHolder.ExtensionLowered}"); + if (!File.Exists(fileName)) + File.Copy(item.ResizedFileHolder.FullName, fileName); + } + fileName = Path.Combine(directory, $"{item.Property.Id.Value}.lnk"); + if (File.Exists(fileName)) + continue; + windowsShortcut = new() { Path = item.ImageFileHolder.FullName }; + windowsShortcut.Save(fileName); + windowsShortcut.Dispose(); + if (!File.Exists(fileName)) + continue; + File.SetLastWriteTime(fileName, minimumDateTime.Value); } - _ = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(ticksDirectory); } } - private void SetKeyValuePairs(string deterministicHashCodeRootDirectory, Dictionary deterministicHashCodeKeyValuePairs, Dictionary incorrectDeterministicHashCodeKeyValuePairs, Dictionary> keyValuePairs) + public void UseKeyValuePairsSaveFaceEncoding(List containers) { - Dictionary> namedKeyValuePairs = new(); - Dictionary> incorrectKeyValuePairs = new(); - List<(string PersonKey, double IdAndNormalizedPixelPercentage)> named = new(); - List<(string PersonKey, double IdAndNormalizedPixelPercentage)> incorrect = new(); - SetKeyValuePairs(deterministicHashCodeRootDirectory, named, incorrect, keyValuePairs); - named = (from l in named orderby l.IdAndNormalizedPixelPercentage select l).ToList(); - incorrect = (from l in incorrect orderby l.IdAndNormalizedPixelPercentage select l).ToList(); - foreach ((string personKey, double idAndNormalizedPixelPercentage) in named) + if (!string.IsNullOrEmpty(_DeterministicHashCodeContentDirectory)) { - if (!namedKeyValuePairs.ContainsKey(idAndNormalizedPixelPercentage)) - namedKeyValuePairs.Add(idAndNormalizedPixelPercentage, new()); - namedKeyValuePairs[idAndNormalizedPixelPercentage].Add(personKey); - } - foreach ((string personKey, double idAndNormalizedPixelPercentage) in incorrect) - { - if (!incorrectKeyValuePairs.ContainsKey(idAndNormalizedPixelPercentage)) - incorrectKeyValuePairs.Add(idAndNormalizedPixelPercentage, new()); - incorrectKeyValuePairs[idAndNormalizedPixelPercentage].Add(personKey); - } - foreach (KeyValuePair> keyValuePair in namedKeyValuePairs) - deterministicHashCodeKeyValuePairs.Add(keyValuePair.Key, keyValuePair.Value.Distinct().ToArray()); - foreach (KeyValuePair> keyValuePair in incorrectKeyValuePairs) - incorrectDeterministicHashCodeKeyValuePairs.Add(keyValuePair.Key, keyValuePair.Value.Distinct().ToArray()); - } - - public void UseKeyValuePairsSaveFaceEncoding(List containers) - { - if (!string.IsNullOrEmpty(_DeterministicHashCodeRootDirectory)) - { - Dictionary> keyValuePairs = new(); - List<(string PersonKey, double IdAndNormalizedPixelPercentage)> named = new(); - List<(string PersonKey, double IdAndNormalizedPixelPercentage)> incorrect = new(); - foreach (Shared.Models.Container container in containers) + Dictionary> keyValuePairs = new(); + List<(string PersonKey, double IdAndNormalizedPixelPercentage)> deterministicHashCodeCollection = new(); + List<(string PersonKey, double IdAndNormalizedPixelPercentage)> incorrectDeterministicHashCodeCollection = new(); + foreach (Container container in containers) { - foreach (Shared.Models.Item item in container.Items) + foreach (Item item in container.Items) { if (item.ImageFileHolder is null || item.Property?.Id is null || !item.Faces.Any()) continue; @@ -229,7 +198,7 @@ public class MapLogic keyValuePairs.Add(item.Property.Id.Value, item.Faces); } } - SetKeyValuePairs(_DeterministicHashCodeRootDirectory, named, incorrect, keyValuePairs); + Stateless.ByDeterministicHashCode.SetKeyValuePairs(_DeterministicHashCodeContentDirectory, deterministicHashCodeCollection, incorrectDeterministicHashCodeCollection, keyValuePairs); } } @@ -250,12 +219,12 @@ public class MapLogic return result; } - public void AddToMapLogicAllCollection(Shared.Models.Item[] filteredItems) + public void AddToMapLogicAllCollection(Item[] filteredItems) { if (_SixCharacterNamedFaceInfo.Any()) { string[] keys; - Shared.Models.Item item; + Item item; for (int i = 0; i < filteredItems.Length; i++) { item = filteredItems[i]; @@ -301,61 +270,91 @@ public class MapLogic } } - public void AddToNamed(List items) + public void AddToMapping(List items) { - bool? isWrongYear; - DateTime minimumDateTime; + Mapping mapping; + string personKey; + int? approximateYears; + string displayDirectoryName; + PersonBirthday? personBirthday; double deterministicHashCodeKey; List personKeys = new(); - Shared.Models.PersonBirthday? personBirthday; - foreach (Shared.Models.Item item in items) + (string DisplayDirectoryName, int? ApproximateYears, string Key, PersonBirthday[] PersonBirthdays) person; + foreach (Item item in items) { if (item.ImageFileHolder is null) continue; if (item.Property?.Id is null || item.ResizedFileHolder is null) continue; - foreach (Shared.Models.Face face in item.Faces) + foreach (Face face in item.Faces) { personKeys.Clear(); - if (face.LocationIndex is null) + if (face.FaceEncoding is null || face.Location?.NormalizedPixelPercentage is null) continue; - deterministicHashCodeKey = Shared.Models.Stateless.Methods.INamed.GetDeterministicHashCodeKey(item, face); + deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(item, face); if (!_DeterministicHashCodeKeyValuePairs.ContainsKey(deterministicHashCodeKey)) continue; - minimumDateTime = Shared.Models.Stateless.Methods.IProperty.GetMinimumDateTime(item.Property); personKeys.AddRange(_DeterministicHashCodeKeyValuePairs[deterministicHashCodeKey]); - (isWrongYear, _) = item.Property.IsWrongYear(item.ImageFileHolder.FullName, minimumDateTime); for (int i = 0; i < personKeys.Count; i++) { - personBirthday = Shared.Models.Stateless.Methods.IPersonBirthday.GetPersonBirthday(personKeys[i]); + if (_PeopleKeyValuePairs.ContainsKey(personKeys[i])) + { + person = _PeopleKeyValuePairs[personKeys[i]]; + personBirthday = person.PersonBirthdays[0]; + approximateYears = person.ApproximateYears; + displayDirectoryName = person.DisplayDirectoryName; + personKey = Shared.Models.Stateless.Methods.IPersonBirthday.GetFormatted(personBirthday); + } + else + { + approximateYears = null; + personKey = personKeys[i]; + displayDirectoryName = "_ _ _"; + personBirthday = Shared.Models.Stateless.Methods.IPersonBirthday.GetPersonBirthday(personKey); + } if (personBirthday is null) continue; - if (face.Location is null) - continue; - item.Named.Add(new(isWrongYear, minimumDateTime, face.Location.NormalizedPixelPercentage, personBirthday)); + mapping = new(approximateYears, displayDirectoryName, face.Location.NormalizedPixelPercentage, personBirthday, personKey); + item.Mapping.Add(mapping); } } - if (!personKeys.Any()) + if (Shared.Models.Stateless.IMapping.UseDeterministicHashCodeUnknownFaceKeyValuePairsForAddToMapping && !personKeys.Any()) { if (!_DeterministicHashCodeUnknownFaceKeyValuePairs.ContainsKey(item.Property.Id.Value)) continue; - minimumDateTime = Shared.Models.Stateless.Methods.IProperty.GetMinimumDateTime(item.Property); personKeys.AddRange(_DeterministicHashCodeUnknownFaceKeyValuePairs[item.Property.Id.Value]); - (isWrongYear, _) = item.Property.IsWrongYear(item.ImageFileHolder.FullName, minimumDateTime); for (int i = 0; i < personKeys.Count; i++) { - personBirthday = Shared.Models.Stateless.Methods.IPersonBirthday.GetPersonBirthday(personKeys[i]); + if (_PeopleKeyValuePairs.ContainsKey(personKeys[i])) + { + person = _PeopleKeyValuePairs[personKeys[i]]; + personBirthday = person.PersonBirthdays[0]; + approximateYears = person.ApproximateYears; + displayDirectoryName = person.DisplayDirectoryName; + personKey = Shared.Models.Stateless.Methods.IPersonBirthday.GetFormatted(personBirthday); + } + else + { + approximateYears = null; + personKey = personKeys[i]; + displayDirectoryName = "_ _ _"; + personBirthday = Shared.Models.Stateless.Methods.IPersonBirthday.GetPersonBirthday(personKey); + } if (personBirthday is null) continue; - item.Named.Add(new(isWrongYear, minimumDateTime, personBirthday)); + mapping = new(approximateYears, displayDirectoryName, personBirthday, personKey); + item.Mapping.Add(mapping); } } } } - public List<(Shared.Models.Item, (string, Shared.Models.Face?, (string, string, string, string))[])> GetCollection(List items, string dFacesContentDirectory) + public List<(Item, (string, Face?, (string, string, string, string))[])> GetCollection(List items, string dFacesContentDirectory) { - List<(Shared.Models.Item, (string, Shared.Models.Face?, (string, string, string, string))[])> results = new(); + List<(Item, (string, Face?, (string, string, string, string))[])> results = new(); + int years; + Face face; + Item item; string[] keys; string directory; string personKey; @@ -366,15 +365,14 @@ public class MapLogic string copyDirectory; string? relativePath; string isWrongYearFlag; - Shared.Models.Face face; string shortcutFileName; - Shared.Models.Item item; string subDirectoryName; List indices = new(); DateTime? minimumDateTime; - List faceCollection; - Shared.Models.PersonBirthday? personBirthday; - List<(string, Shared.Models.Face?, (string, string, string, string))> collection; + DateTime dateTime = DateTime.Now; + List faceCollection; + PersonBirthday? personBirthday; + List<(string, Face?, (string, string, string, string))> collection; for (int i = 0; i < items.Count; i++) { indices.Clear(); @@ -433,7 +431,10 @@ public class MapLogic if (timeSpan.Value.Ticks < 0) subDirectoryName = "!---"; else - subDirectoryName = $"^{Math.Floor(timeSpan.Value.TotalDays / 365):000}"; + { + (years, _) = Shared.Models.Stateless.Methods.IPersonBirthday.GetAge(dateTime, personBirthday); + subDirectoryName = $"^{years:000}"; + } } face = faceCollection[zero]; directory = Path.Combine(dFacesContentDirectory, "Shortcuts", personKey, subDirectoryName); @@ -457,71 +458,304 @@ public class MapLogic return results; } - public static string GetKey(DateTime minimumDateTime, bool? isWrongYear, Shared.Models.PersonBirthday personBirthday) + public void AddToClosest(int maxDegreeOfParallelism, string argZero, List containers) { - string result; - string personKey = Shared.Models.Stateless.Methods.IPersonBirthday.GetFormatted(personBirthday); - TimeSpan? timeSpan = Shared.Models.Stateless.Methods.IPersonBirthday.GetTimeSpan(minimumDateTime, isWrongYear, personBirthday); - if (timeSpan.HasValue && timeSpan.Value.Ticks < 0) - result = string.Concat(personKey, "!---"); - else if (timeSpan.HasValue) - result = string.Concat(personKey, $"^{Math.Floor(timeSpan.Value.TotalDays / 365):000}"); - else - { - string isWrongYearFlag = Shared.Models.Stateless.Methods.IItem.GetWrongYearFlag(isWrongYear); - result = string.Concat(personKey, $"{isWrongYearFlag}{minimumDateTime:yyyy}"); - } - return result; - } - - public static string GetDirectory(string directory, string subDirectory, DateTime minimumDateTime, bool? isWrongYear, Shared.Models.PersonBirthday personBirthday, string personKey) - { - string result; - TimeSpan? timeSpan = Shared.Models.Stateless.Methods.IPersonBirthday.GetTimeSpan(minimumDateTime, isWrongYear, personBirthday); - if (timeSpan.HasValue && timeSpan.Value.Ticks < 0) - result = Path.Combine(directory, subDirectory, personKey, "!---"); - else if (timeSpan.HasValue) - result = Path.Combine(directory, subDirectory, personKey, $"^{Math.Floor(timeSpan.Value.TotalDays / 365):000}"); - else - { - string isWrongYearFlag = Shared.Models.Stateless.Methods.IItem.GetWrongYearFlag(isWrongYear); - result = Path.Combine(directory, subDirectory, personKey, $"{isWrongYearFlag}{minimumDateTime:yyyy}"); - } - return result; - } - - public static Dictionary> GetKeyValuePairs(string argZero, List containers) - { - Dictionary> results = new(); string key; - foreach (Shared.Models.Container container in containers) + string dateKey; + Closest closest; + DateTime minimumDateTime; + Closest[] closestCollection; + double deterministicHashCodeKey; + DateTime dateTime = DateTime.Now; + Dictionary keyValuePairs = new(); + foreach (Container container in containers) { if (!container.Items.Any()) continue; if (!container.SourceDirectory.StartsWith(argZero)) continue; - foreach (Shared.Models.Item item in container.Items) + foreach (Item item in container.Items) { - if (item.ImageFileHolder is null || item.Property is null || !item.Named.Any()) + if (item.ImageFileHolder is null || item.Property is null) continue; - foreach (Shared.Models.Named named in item.Named) + foreach (Face face in item.Faces) { - if (named.NormalizedPixelPercentage is null && (item.Named.Count != 1 || item.Faces.Count != 1)) + if (face.FaceEncoding is null || face.Location?.NormalizedPixelPercentage is null) continue; - foreach (Shared.Models.Face face in item.Faces) + deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(item, face); + if (_DeterministicHashCodeKeyValuePairs.ContainsKey(deterministicHashCodeKey)) + continue; + minimumDateTime = Shared.Models.Stateless.Methods.IProperty.GetMinimumDateTime(item.Property); + closestCollection = Shared.Models.Stateless.Methods.IClosest.GetCollection(face, minimumDateTime, face.FaceDistances); + face.FaceDistances.Clear(); + for (int j = 0; j < closestCollection.Length; j++) { + closest = closestCollection[j]; + if (_IncorrectDeterministicHashCodeKeyValuePairs.ContainsKey(deterministicHashCodeKey) && _IncorrectDeterministicHashCodeKeyValuePairs[deterministicHashCodeKey].Contains(closest.Mapping.PersonKey)) + continue; + dateKey = Stateless.MapLogic.GetDateKey(dateTime, closest.Mapping, closest.MinimumDateTime, closest.IsWrongYear); + key = string.Concat(closest.Mapping.PersonKey, dateKey); + if (!keyValuePairs.ContainsKey(key)) + keyValuePairs.Add(key, 0); + else if (keyValuePairs[key] > Shared.Models.Stateless.IClosest.MaximumPer) + continue; + keyValuePairs[key] += 1; + item.Closest.Add(closest); + break; + } + } + } + } + } + + private List<(IFileHolder? resizedFileHolder, string directory, FileInfo? faceFileInfo, string checkFile, string shortcutFile, string json)> GetClosest(string argZero, List containers, string dFacesContentDirectory, string d2ResultsFullGroupDirectory, string zPropertyHolderContentDirectory) + { + List<(IFileHolder?, string, FileInfo?, string, string, string)> results = new(); + Closest? match; + string dateKey; + string checkFile; + string directory; + string shortcutFile; + FileInfo faceFileInfo; + string? directoryName; + string facesDirectory; + string personDirectory; + List used = new(); + FileInfo landmarkFileInfo; + string landmarksDirectory; + double deterministicHashCodeKey; + DateTime dateTime = DateTime.Now; + const string facePopulatedKey = nameof(Closest); + foreach (Container container in containers) + { + if (!container.Items.Any()) + continue; + if (!container.SourceDirectory.StartsWith(argZero)) + continue; + foreach (Item item in container.Items) + { + used.Clear(); + if (item.ImageFileHolder is null || item.Property?.Id is null || item.ResizedFileHolder is null) + continue; + if (!item.Closest.Any()) + continue; + directoryName = Path.GetDirectoryName(item.RelativePath); + if (directoryName is null) + throw new Exception(); + foreach (Face face in item.Faces) + { + match = null; + if (face.FaceEncoding is null || face.Location?.NormalizedPixelPercentage is null) + continue; + foreach (Closest closest in item.Closest) + { + if (closest.NormalizedPixelPercentage != face.Location.NormalizedPixelPercentage.Value) + continue; + match = closest; + break; + } + if (match is null) + continue; + foreach (Mapping mapping in item.Mapping) + { + if (mapping.NormalizedPixelPercentage is null || mapping.NormalizedPixelPercentage.Value != face.Location.NormalizedPixelPercentage.Value) + continue; + throw new Exception(); + } + dateKey = Stateless.MapLogic.GetDateKey(dateTime, match.Mapping, match.MinimumDateTime, match.IsWrongYear); + directory = Path.Combine(zPropertyHolderContentDirectory, facePopulatedKey, match.Mapping.PersonKey, dateKey); + personDirectory = Path.Combine(directory, match.Mapping.DisplayDirectoryName[..1], "lnk"); + results.Add(new(null, personDirectory, null, string.Empty, string.Empty, string.Empty)); + facesDirectory = string.Concat(dFacesContentDirectory, Path.Combine(directoryName, item.ImageFileHolder.NameWithoutExtension)); + landmarksDirectory = string.Concat(d2ResultsFullGroupDirectory, Path.Combine(directoryName, item.ImageFileHolder.NameWithoutExtension)); + deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(item, face); + checkFile = Path.Combine(directory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}"); + faceFileInfo = new(Path.Combine(facesDirectory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}.png")); + landmarkFileInfo = new(Path.Combine(landmarksDirectory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}.gif")); + if (string.IsNullOrEmpty(personDirectory)) + shortcutFile = string.Empty; + else + shortcutFile = Path.Combine(personDirectory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}.lnk"); + results.Add(new(item.ResizedFileHolder, directory, faceFileInfo, checkFile, shortcutFile, string.Empty)); + personDirectory = Path.Combine(directory, match.Mapping.DisplayDirectoryName[..1], "lnk", match.Mapping.DisplayDirectoryName); + results.Add(new(null, personDirectory, null, string.Empty, string.Empty, string.Empty)); + used.Add(face.Location.NormalizedPixelPercentage.Value); + } + foreach (Closest closest in item.Closest) + { + if (used.Contains(closest.NormalizedPixelPercentage)) + continue; + dateKey = Stateless.MapLogic.GetDateKey(dateTime, closest.Mapping, closest.MinimumDateTime, closest.IsWrongYear); + directory = Path.Combine(zPropertyHolderContentDirectory, facePopulatedKey, closest.Mapping.PersonKey, dateKey); + personDirectory = Path.Combine(directory, closest.Mapping.DisplayDirectoryName, "lnk"); + results.Add(new(null, personDirectory, null, string.Empty, string.Empty, string.Empty)); + facesDirectory = string.Concat(dFacesContentDirectory, Path.Combine(directoryName, item.ImageFileHolder.NameWithoutExtension)); + landmarksDirectory = string.Concat(d2ResultsFullGroupDirectory, Path.Combine(directoryName, item.ImageFileHolder.NameWithoutExtension)); + deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(item, closest); + checkFile = Path.Combine(directory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}"); + faceFileInfo = new(Path.Combine(facesDirectory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}.png")); + landmarkFileInfo = new(Path.Combine(landmarksDirectory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}.gif")); + if (string.IsNullOrEmpty(personDirectory)) + shortcutFile = string.Empty; + else + shortcutFile = Path.Combine(personDirectory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}.lnk"); + results.Add(new(item.ResizedFileHolder, directory, faceFileInfo, checkFile, shortcutFile, string.Empty)); + personDirectory = Path.Combine(directory, closest.Mapping.DisplayDirectoryName[..1], "lnk", closest.Mapping.DisplayDirectoryName); + results.Add(new(null, personDirectory, null, string.Empty, string.Empty, string.Empty)); + used.Add(closest.NormalizedPixelPercentage); + } + } + } + return results; + } + + private List<(IFileHolder? resizedFileHolder, string directory, FileInfo? faceFileInfo, string checkFile, string shortcutFile, string json)> GetMapping(string argZero, List containers, string dFacesContentDirectory, string d2ResultsFullGroupDirectory, string zPropertyHolderContentDirectory) + { + List<(IFileHolder?, string, FileInfo?, string, string, string)> results = new(); + string key; + string json; + string dateKey; + Mapping? match; + string checkFile; + string directory; + bool? isWrongYear; + string shortcutFile; + FileInfo faceFileInfo; + string? directoryName; + string facesDirectory; + string personDirectory; + List used = new(); + DateTime minimumDateTime; + FileInfo landmarkFileInfo; + string landmarksDirectory; + double deterministicHashCodeKey; + DateTime dateTime = DateTime.Now; + const string facePopulatedKey = nameof(Mapping); + bool deterministicHashCodeKeyValuePairsAny = _DeterministicHashCodeKeyValuePairs.Any(); + foreach (Container container in containers) + { + if (!container.Items.Any()) + continue; + if (!container.SourceDirectory.StartsWith(argZero)) + continue; + foreach (Item item in container.Items) + { + used.Clear(); + if (item.ImageFileHolder is null || item.Property?.Id is null || item.ResizedFileHolder is null) + continue; + directoryName = Path.GetDirectoryName(item.RelativePath); + if (directoryName is null) + throw new Exception(); + foreach (Face face in item.Faces) + { + match = null; + if (face.FaceEncoding is null || face.Location?.NormalizedPixelPercentage is null) + continue; + foreach (Mapping mapping in item.Mapping) + { + if (mapping.NormalizedPixelPercentage is null || mapping.NormalizedPixelPercentage.Value != face.Location.NormalizedPixelPercentage.Value) + continue; + match = mapping; + break; + } + if (match is null) + continue; + foreach (Closest closest in item.Closest) + { + if (closest.NormalizedPixelPercentage != face.Location.NormalizedPixelPercentage.Value) + continue; + throw new Exception(); + } + minimumDateTime = Shared.Models.Stateless.Methods.IProperty.GetMinimumDateTime(item.Property); + (isWrongYear, _) = item.Property.IsWrongYear(item.ImageFileHolder.FullName, minimumDateTime); + dateKey = Stateless.MapLogic.GetDateKey(dateTime, match, minimumDateTime, isWrongYear); + key = string.Concat(match.PersonKey, dateKey); + if (match.Filtered is null) + directory = Path.Combine(zPropertyHolderContentDirectory, $"{facePopulatedKey}Null", match.PersonKey, dateKey); + else if (!match.Filtered.Value) + directory = Path.Combine(zPropertyHolderContentDirectory, $"{facePopulatedKey}Okay", match.PersonKey, dateKey); + else + directory = Path.Combine(zPropertyHolderContentDirectory, $"{facePopulatedKey}OutOfControl", match.PersonKey, dateKey); + personDirectory = Path.Combine(directory, match.DisplayDirectoryName[..1], "lnk"); + results.Add(new(null, personDirectory, null, string.Empty, string.Empty, string.Empty)); + facesDirectory = string.Concat(dFacesContentDirectory, Path.Combine(directoryName, item.ImageFileHolder.NameWithoutExtension)); + landmarksDirectory = string.Concat(d2ResultsFullGroupDirectory, Path.Combine(directoryName, item.ImageFileHolder.NameWithoutExtension)); + deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(item, face); + checkFile = Path.Combine(directory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}"); + faceFileInfo = new(Path.Combine(facesDirectory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}.png")); + landmarkFileInfo = new(Path.Combine(landmarksDirectory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}.gif")); + if (string.IsNullOrEmpty(personDirectory)) + shortcutFile = string.Empty; + else + shortcutFile = Path.Combine(personDirectory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}.lnk"); + results.Add(new(item.ResizedFileHolder, directory, faceFileInfo, checkFile, shortcutFile, string.Empty)); + if (!string.IsNullOrEmpty(checkFile) && Shared.Models.Stateless.IMapping.SaveFaceEncoding) + { + checkFile = Path.Combine(directory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}.json"); + json = JsonSerializer.Serialize(face.FaceEncoding); + results.Add(new(null, directory, null, checkFile, string.Empty, json)); + } + personDirectory = Path.Combine(directory, match.DisplayDirectoryName[..1], "lnk", match.DisplayDirectoryName); + results.Add(new(null, personDirectory, null, string.Empty, string.Empty, string.Empty)); + used.Add(face.Location.NormalizedPixelPercentage.Value); + } + if (deterministicHashCodeKeyValuePairsAny && Shared.Models.Stateless.IMapping.UseDeterministicHashCodeUnknownFaceKeyValuePairsForSaveMapping) + { + foreach (Face face in item.Faces) + { + match = null; if (face.FaceEncoding is null || face.Location?.NormalizedPixelPercentage is null) continue; - if (named.PersonBirthday is null) + if (used.Contains(face.Location.NormalizedPixelPercentage.Value)) continue; - if (named.NormalizedPixelPercentage.HasValue && named.NormalizedPixelPercentage.Value != face.Location?.NormalizedPixelPercentage) - continue; - key = GetKey(named.MinimumDateTime, named.IsWrongYear, named.PersonBirthday); - if (!results.ContainsKey(key)) - results.Add(key, new()); - results[key].Add(new(named.MinimumDateTime, named.IsWrongYear, named.PersonBirthday, face)); - if (named.NormalizedPixelPercentage is null) + // if (item.Faces.Count != 1 || item.Mapping.Count != 1) + // continue; + foreach (Mapping mapping in item.Mapping) + { + if (mapping.NormalizedPixelPercentage is not null) + continue; + match = mapping; break; + } + if (match is null) + continue; + foreach (Closest closest in item.Closest) + { + if (closest.NormalizedPixelPercentage != face.Location.NormalizedPixelPercentage.Value) + continue; + throw new Exception(); + } + minimumDateTime = Shared.Models.Stateless.Methods.IProperty.GetMinimumDateTime(item.Property); + (isWrongYear, _) = item.Property.IsWrongYear(item.ImageFileHolder.FullName, minimumDateTime); + dateKey = Stateless.MapLogic.GetDateKey(dateTime, match, minimumDateTime, isWrongYear); + if (match.Filtered is null) + directory = Path.Combine(zPropertyHolderContentDirectory, $"{facePopulatedKey}WithButNull", match.PersonKey, dateKey); + else if (!match.Filtered.Value) + directory = Path.Combine(zPropertyHolderContentDirectory, $"{facePopulatedKey}WithAndOkay", match.PersonKey, dateKey); + else + directory = Path.Combine(zPropertyHolderContentDirectory, $"{facePopulatedKey}WithButOutOfControl", match.PersonKey, dateKey); + personDirectory = Path.Combine(directory, match.DisplayDirectoryName[..1], "lnk"); + results.Add(new(null, personDirectory, null, string.Empty, string.Empty, string.Empty)); + facesDirectory = string.Concat(dFacesContentDirectory, Path.Combine(directoryName, item.ImageFileHolder.NameWithoutExtension)); + landmarksDirectory = string.Concat(d2ResultsFullGroupDirectory, Path.Combine(directoryName, item.ImageFileHolder.NameWithoutExtension)); + deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(item, face); + checkFile = Path.Combine(directory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}"); + faceFileInfo = new(Path.Combine(facesDirectory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}.png")); + landmarkFileInfo = new(Path.Combine(landmarksDirectory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}.gif")); + if (string.IsNullOrEmpty(personDirectory)) + shortcutFile = string.Empty; + else + shortcutFile = Path.Combine(personDirectory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}.lnk"); + results.Add(new(item.ResizedFileHolder, directory, faceFileInfo, checkFile, shortcutFile, string.Empty)); + if (!string.IsNullOrEmpty(checkFile) && Shared.Models.Stateless.IMapping.SaveFaceEncoding) + { + checkFile = Path.Combine(directory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}.json"); + json = JsonSerializer.Serialize(face.FaceEncoding); + results.Add(new(null, directory, null, checkFile, string.Empty, json)); + } + personDirectory = Path.Combine(directory, match.DisplayDirectoryName[..1], "lnk", match.DisplayDirectoryName); + results.Add(new(null, personDirectory, null, string.Empty, string.Empty, string.Empty)); + used.Add(face.Location.NormalizedPixelPercentage.Value); } } } @@ -529,14 +763,63 @@ public class MapLogic return results; } - public static (bool?, string[]) IsWrongYear(Shared.Models.Item item) + private static void Save(List<(IFileHolder? resizedFileHolder, string directory, FileInfo? faceFileInfo, string checkFile, string shortcutFile, string json)> collection) { - (bool?, string[]) result; - if (item.Property is null || item.ImageFileHolder is null) - throw new NullReferenceException(); - DateTime? minimumDateTime = Shared.Models.Stateless.Methods.IProperty.GetMinimumDateTime(item.Property); - result = item.Property.IsWrongYear(item.ImageFileHolder.FullName, minimumDateTime); - return result; + WindowsShortcut windowsShortcut; + string[] directories = (from l in collection select l.directory).Distinct().ToArray(); + foreach (string directory in directories) + { + if (string.IsNullOrEmpty(directory)) + continue; + if (!Directory.Exists(directory)) + _ = Directory.CreateDirectory(directory); + } + foreach ((IFileHolder? resizedFileHolder, string directory, FileInfo? faceFileInfo, string checkFile, string shortcutFile, string json) in collection) + { + if (string.IsNullOrEmpty(directory) || string.IsNullOrEmpty(checkFile) || resizedFileHolder is null || faceFileInfo is null || !string.IsNullOrEmpty(json)) + continue; + if (File.Exists(checkFile)) + continue; + if (faceFileInfo.Directory is not null && faceFileInfo.Directory.Exists && faceFileInfo.Exists) + File.Copy(faceFileInfo.FullName, checkFile); + else + File.Copy(resizedFileHolder.FullName, checkFile); + } + foreach ((IFileHolder? resizedFileHolder, string directory, FileInfo? faceFileInfo, string checkFile, string shortcutFile, string json) in collection) + { + if (string.IsNullOrEmpty(directory) || string.IsNullOrEmpty(checkFile) || resizedFileHolder is not null || faceFileInfo is not null || string.IsNullOrEmpty(json)) + continue; + if (File.Exists(checkFile)) + continue; + _ = Shared.Models.Stateless.Methods.IPath.WriteAllText(checkFile, json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null); + } + foreach ((IFileHolder? resizedFileHolder, string directory, FileInfo? _, string checkFile, string shortcutFile, string json) in collection) + { + if (string.IsNullOrEmpty(directory) || string.IsNullOrEmpty(checkFile) || resizedFileHolder is null) + continue; + if (string.IsNullOrEmpty(shortcutFile) || !resizedFileHolder.Exists) + continue; + try + { + windowsShortcut = new() { Path = resizedFileHolder.FullName }; + windowsShortcut.Save(shortcutFile); + windowsShortcut.Dispose(); + } + catch (Exception) + { } + } + } + + public void SaveClosest(string argZero, List containers, string dFacesContentDirectory, string d2ResultsFullGroupDirectory, string zPropertyHolderContentDirectory) + { + List<(IFileHolder? resizedFileHolder, string directory, FileInfo? faceFileInfo, string checkFile, string shortcutFile, string json)> collection = GetClosest(argZero, containers, dFacesContentDirectory, d2ResultsFullGroupDirectory, zPropertyHolderContentDirectory); + Save(collection); + } + + public void SaveMapping(string argZero, List containers, string dFacesContentDirectory, string d2ResultsFullGroupDirectory, string zPropertyHolderContentDirectory) + { + List<(IFileHolder? resizedFileHolder, string directory, FileInfo? faceFileInfo, string checkFile, string shortcutFile, string json)> collection = GetMapping(argZero, containers, dFacesContentDirectory, d2ResultsFullGroupDirectory, zPropertyHolderContentDirectory); + Save(collection); } } \ No newline at end of file diff --git a/Map/Models/Stateless/IMapLogic.cs b/Map/Models/Stateless/IMapLogic.cs new file mode 100644 index 0000000..12d8676 --- /dev/null +++ b/Map/Models/Stateless/IMapLogic.cs @@ -0,0 +1,18 @@ +namespace View_by_Distance.Map.Models.Stateless; + +public interface IMapLogic +{ // ... + + (bool?, string[]) TestStatic_IsWrongYear(Shared.Models.Item item); + static (bool?, string[]) IsWrongYear(Shared.Models.Item item) => + MapLogic.IsWrongYear(item); + + string TestStatic_GetDateKey(DateTime dateTime, Shared.Models.Mapping mapping, DateTime minimumDateTime, bool? isWrongYear); + static string GetDateKey(DateTime dateTime, Shared.Models.Mapping mapping, DateTime minimumDateTime, bool? isWrongYear) => + MapLogic.GetDateKey(dateTime, mapping, minimumDateTime, isWrongYear); + + Dictionary> TestStatic_GetKeyValuePairs(string[] ignoreRelativePaths, string argZero, List containers); + static Dictionary> GetKeyValuePairs(string[] ignoreRelativePaths, string argZero, List containers) => + MapLogic.GetKeyValuePairs(ignoreRelativePaths, argZero, containers); + +} \ No newline at end of file diff --git a/Map/Models/Stateless/MapLogic.cs b/Map/Models/Stateless/MapLogic.cs new file mode 100644 index 0000000..5825ae9 --- /dev/null +++ b/Map/Models/Stateless/MapLogic.cs @@ -0,0 +1,93 @@ +using View_by_Distance.Shared.Models; + +namespace View_by_Distance.Map.Models.Stateless; + +internal abstract class MapLogic +{ + + internal static string GetDateKey(DateTime dateTime, Mapping mapping, DateTime minimumDateTime, bool? isWrongYear) + { + int years; + string result; + TimeSpan? timeSpan = Shared.Models.Stateless.Methods.IPersonBirthday.GetTimeSpan(minimumDateTime, isWrongYear, mapping.PersonBirthday); + if (timeSpan.HasValue && timeSpan.Value.Ticks < 0) + result = "!---"; + else if (timeSpan.HasValue) + { + (years, _) = Shared.Models.Stateless.Methods.IPersonBirthday.GetAge(minimumDateTime, mapping.PersonBirthday); + result = $"^{years:000}"; + } + else if (mapping.ApproximateYears.HasValue) + { + (years, _) = Shared.Models.Stateless.Methods.IAge.GetAge(minimumDateTime, dateTime.AddYears(-mapping.ApproximateYears.Value)); + result = $"~{years:000}"; + } + else + { + string isWrongYearFlag = Shared.Models.Stateless.Methods.IItem.GetWrongYearFlag(isWrongYear); + result = $"{isWrongYearFlag}{minimumDateTime:yyyy}"; + } + return result; + } + + internal static Dictionary> GetKeyValuePairs(string[] ignoreRelativePaths, string argZero, List containers) + { + Dictionary> results = new(); + string key; + string dateKey; + bool? isWrongYear; + DateTime minimumDateTime; + DateTime dateTime = DateTime.Now; + MappingContainer mappingContainer; + foreach (Container container in containers) + { + if (!container.Items.Any()) + continue; + if (!container.SourceDirectory.StartsWith(argZero)) + continue; + if (ignoreRelativePaths.Contains(Path.GetFileName(container.SourceDirectory))) + continue; + foreach (Item item in container.Items) + { + if (item.ImageFileHolder is null || item.Property?.Id is null || !item.Mapping.Any()) + continue; + foreach (Face face in item.Faces) + { + if (face.FaceEncoding is null || face.Location?.NormalizedPixelPercentage is null) + continue; + foreach (Mapping mapping in item.Mapping) + { + if (mapping.PersonBirthday is null) + continue; + if (mapping.NormalizedPixelPercentage.HasValue && mapping.NormalizedPixelPercentage.Value != face.Location.NormalizedPixelPercentage.Value) + continue; + // if (named.NormalizedPixelPercentage is null && (Shared.Models.Stateless.INamed.OnlyUseNamedWithNormalizedPixelPercentagePopulatedForGetKeyValuePairs || item.Named.Count != 1 || item.Faces.Count != 1)) + // continue; + minimumDateTime = Shared.Models.Stateless.Methods.IProperty.GetMinimumDateTime(item.Property); + (isWrongYear, _) = item.Property.IsWrongYear(item.ImageFileHolder.FullName, minimumDateTime); + dateKey = GetDateKey(dateTime, mapping, minimumDateTime, isWrongYear); + key = string.Concat(mapping.PersonKey, dateKey); + if (!results.ContainsKey(key)) + results.Add(key, new()); + mappingContainer = new(face, item.Property.Id.Value, isWrongYear, key, mapping, minimumDateTime); + results[key].Add(mappingContainer); + // if (named.NormalizedPixelPercentage is null) + // break; + } + } + } + } + return results; + } + + internal static (bool?, string[]) IsWrongYear(Item item) + { + (bool?, string[]) result; + if (item.Property is null || item.ImageFileHolder is null) + throw new NullReferenceException(); + DateTime? minimumDateTime = Shared.Models.Stateless.Methods.IProperty.GetMinimumDateTime(item.Property); + result = item.Property.IsWrongYear(item.ImageFileHolder.FullName, minimumDateTime); + return result; + } + +} \ No newline at end of file diff --git a/Map/Models/Stateless/SetByDeterministicHashCode.cs b/Map/Models/Stateless/SetByDeterministicHashCode.cs new file mode 100644 index 0000000..e3def39 --- /dev/null +++ b/Map/Models/Stateless/SetByDeterministicHashCode.cs @@ -0,0 +1,220 @@ +using System.Globalization; +using System.Text.Json; +using View_by_Distance.Shared.Models; + +namespace View_by_Distance.Map.Models.Stateless; + +public class ByDeterministicHashCode +{ + + private static void SetOther(string outputExtension, Dictionary personKeyValuePairs, string deterministicHashCodePeopleDirectory, List skipCollection, List<(string, int?, string, PersonBirthday[])> peopleCollection) + { + string json; + string personKey; + string[] segments; + int? approximateYears; + string groupDirectoryName; + string personKeyJsonFileName; + string[] personKeyDirectories; + string personKeyJsonDirectory; + PersonBirthday? personBirthday; + string[] personDisplayDirectories; + string convertedPersonKeyDirectory; + string? personDisplayDirectoryName; + List personBirthdays; + string[] groupDirectories = Directory.GetDirectories(deterministicHashCodePeopleDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string groupDirectory in groupDirectories) + { + groupDirectoryName = Path.GetFileName(groupDirectory); + if (groupDirectoryName[0] == '!') + { + skipCollection.AddRange(from l in Directory.GetFiles(groupDirectory, $"*{outputExtension}", SearchOption.AllDirectories) select double.Parse(Path.GetFileNameWithoutExtension(l))); + continue; + } + else if (groupDirectoryName[0] is not '_' and not '~' and not '^') + continue; + skipCollection.AddRange(from l in Directory.GetFiles(groupDirectory, $"*{outputExtension}", SearchOption.AllDirectories) select double.Parse(Path.GetFileNameWithoutExtension(l))); + personDisplayDirectories = Directory.GetDirectories(groupDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string personDisplayDirectory in personDisplayDirectories) + { + personBirthdays = new(); + personDisplayDirectoryName = Path.GetFileName(personDisplayDirectory); + if (string.IsNullOrEmpty(personDisplayDirectoryName)) + continue; + if (groupDirectoryName[0] != '~') + approximateYears = null; + else + { + segments = personDisplayDirectoryName.Split('~'); + if (segments.Length == 1 || !int.TryParse(segments[1].Split('-')[0], out int years)) + approximateYears = null; + else + approximateYears = years; + } + personKeyDirectories = Directory.GetDirectories(personDisplayDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string personKeyDirectory in personKeyDirectories) + { + personKey = Path.GetFileName(personKeyDirectory); + if (!DateTime.TryParseExact(personKey, "MM.dd.yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime birthday)) + personBirthday = Shared.Models.Stateless.Methods.IPersonBirthday.GetPersonBirthday(personKey); + else + { + personBirthday = new PersonBirthday(birthday); + personKey = Shared.Models.Stateless.Methods.IPersonBirthday.GetFormatted(personBirthday); + convertedPersonKeyDirectory = Path.Combine(personDisplayDirectory, personKey); + if (!Directory.Exists(convertedPersonKeyDirectory)) + Directory.Move(personKeyDirectory, convertedPersonKeyDirectory); + } + if (personBirthday is null) + continue; + personBirthdays.Add(personBirthday); + } + foreach (string personKeyDirectory in personKeyDirectories) + { + personKey = Path.GetFileName(personKeyDirectory); + personBirthday = Shared.Models.Stateless.Methods.IPersonBirthday.GetPersonBirthday(personKey); + if (personBirthday is null) + continue; + if (personKeyValuePairs.ContainsKey(personKey)) + { + personKeyJsonDirectory = Path.Combine(personDisplayDirectory, personKey); + if (!Directory.Exists(personKeyJsonDirectory)) + Directory.Move(personKeyDirectory, personKeyJsonDirectory); + personKeyJsonFileName = Path.Combine(personKeyJsonDirectory, $"{personKey}.json"); + json = JsonSerializer.Serialize(personKeyValuePairs[personKey], new JsonSerializerOptions() { WriteIndented = true }); + _ = Shared.Models.Stateless.Methods.IPath.WriteAllText(personKeyJsonFileName, json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null); + } + peopleCollection.Add(new(personDisplayDirectoryName, approximateYears, personKey, personBirthdays.OrderByDescending(l => l.Value).ToArray())); + } + } + } + } + + internal static void SetKeyValuePairs(string deterministicHashCodeContentDirectory, List<(string, double)> deterministicHashCodeCollection, List<(string, double)> incorrectDeterministicHashCodeCollection, Dictionary> keyValuePairs) + { + string[] files; + string personKey; + string[] yearDirectories; + string ticksDirectoryName; + string? personFirstInitial; + string[] personKeyDirectories; + string[] personNameDirectories; + string[] personNameLinkDirectories; + string? personFirstInitialDirectory; + double? reversedDeterministicHashCodeKey; + bool keyValuePairsAny = keyValuePairs.Any(); + string[] ticksDirectories = Directory.GetDirectories(deterministicHashCodeContentDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string ticksDirectory in ticksDirectories) + { + ticksDirectoryName = Path.GetFileName(ticksDirectory); + if (ticksDirectoryName.Length < 3 || ticksDirectoryName[0] != '(' || ticksDirectoryName[^1] != ')') + continue; + personKeyDirectories = Directory.GetDirectories(ticksDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string personKeyDirectory in personKeyDirectories) + { + personKey = Path.GetFileName(personKeyDirectory); + if (personKey == nameof(Closest)) + throw new Exception($"Move personKey directories up one from {nameof(Closest)} and delete {nameof(Closest)} directory!"); + yearDirectories = Directory.GetDirectories(personKeyDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string yearDirectory in yearDirectories) + { + files = Directory.GetFiles(yearDirectory, "*", SearchOption.TopDirectoryOnly); + personNameDirectories = Directory.GetDirectories(yearDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string file in files) + File.Delete(file); + foreach (string personNameDirectory in personNameDirectories) + { + personFirstInitial = Path.GetFileName(personNameDirectory)[..1]; + if (personFirstInitial is null) + continue; + personFirstInitialDirectory = Path.Combine(yearDirectory, personFirstInitial); + files = Directory.GetFiles(personNameDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string file in files) + { + if (file.EndsWith(".lnk") || file.EndsWith(".json")) + continue; + reversedDeterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetReversedDeterministicHashCodeKey(keyValuePairsAny, keyValuePairs, file); + if (reversedDeterministicHashCodeKey is null) + continue; + deterministicHashCodeCollection.Add(new(personKey, reversedDeterministicHashCodeKey.Value)); + } + if (personNameDirectory == personFirstInitialDirectory) + continue; + personNameLinkDirectories = Directory.GetDirectories(personNameDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string personNameLinkDirectory in personNameLinkDirectories) + { + files = Directory.GetFiles(personNameLinkDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string file in files) + { + if (!file.EndsWith(".lnk")) + continue; + File.Delete(file); + } + _ = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(personNameLinkDirectory); + } + Directory.Move(personNameDirectory, personFirstInitialDirectory); + _ = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(personNameDirectory); + } + _ = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(yearDirectory); + } + _ = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(personKeyDirectory); + } + _ = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(ticksDirectory); + } + } + + internal static string SetByRef(string outputExtension, Dictionary personKeyValuePairs, List skipCollection, Dictionary peopleKeyValuePairs, Dictionary deterministicHashCodeUnknownFaceKeyValuePairs, Dictionary deterministicHashCodeKeyValuePairs, Dictionary incorrectDeterministicHashCodeKeyValuePairs, string deterministicHashCodeRootDirectory) + { + string result; + List deterministicHashCodePersonKeys = new(); + List deterministicHashCodeUnknownFacePersonKeys = new(); + foreach (KeyValuePair keyValuePair in deterministicHashCodeUnknownFaceKeyValuePairs) + deterministicHashCodeUnknownFacePersonKeys.AddRange(keyValuePair.Value); + deterministicHashCodeUnknownFacePersonKeys = deterministicHashCodeUnknownFacePersonKeys.Distinct().ToList(); + List<(string, int?, string, PersonBirthday[])> peopleCollection = new(); + string deterministicHashCodePeopleDirectory = Path.Combine(deterministicHashCodeRootDirectory, "People"); + if (Directory.Exists(deterministicHashCodePeopleDirectory)) + SetOther(outputExtension, personKeyValuePairs, deterministicHashCodePeopleDirectory, skipCollection, peopleCollection); + result = Path.Combine(deterministicHashCodeRootDirectory, "()"); + if (!Directory.Exists(result)) + result = string.Empty; + else + { + Dictionary> keyValuePairs = new(); + Dictionary> deterministicHashCodeScope = new(); + Dictionary> incorrectDeterministicHashCodeScope = new(); + List<(string PersonKey, double IdAndNormalizedPixelPercentage)> deterministicHashCodeCollection = new(); + List<(string PersonKey, double IdAndNormalizedPixelPercentage)> incorrectDeterministicHashCodeCollection = new(); + SetKeyValuePairs(result, deterministicHashCodeCollection, incorrectDeterministicHashCodeCollection, keyValuePairs); + deterministicHashCodeCollection = (from l in deterministicHashCodeCollection orderby l.IdAndNormalizedPixelPercentage select l).ToList(); + incorrectDeterministicHashCodeCollection = (from l in incorrectDeterministicHashCodeCollection orderby l.IdAndNormalizedPixelPercentage select l).ToList(); + foreach ((string personKey, double idAndNormalizedPixelPercentage) in deterministicHashCodeCollection) + { + if (!deterministicHashCodeScope.ContainsKey(idAndNormalizedPixelPercentage)) + deterministicHashCodeScope.Add(idAndNormalizedPixelPercentage, new()); + deterministicHashCodeScope[idAndNormalizedPixelPercentage].Add(personKey); + deterministicHashCodePersonKeys.Add(personKey); + } + deterministicHashCodePersonKeys = deterministicHashCodePersonKeys.Distinct().ToList(); + foreach ((string personKey, double idAndNormalizedPixelPercentage) in incorrectDeterministicHashCodeCollection) + { + if (!incorrectDeterministicHashCodeScope.ContainsKey(idAndNormalizedPixelPercentage)) + incorrectDeterministicHashCodeScope.Add(idAndNormalizedPixelPercentage, new()); + incorrectDeterministicHashCodeScope[idAndNormalizedPixelPercentage].Add(personKey); + } + foreach (KeyValuePair> keyValuePair in deterministicHashCodeScope) + deterministicHashCodeKeyValuePairs.Add(keyValuePair.Key, keyValuePair.Value.Distinct().ToArray()); + foreach (KeyValuePair> keyValuePair in incorrectDeterministicHashCodeScope) + incorrectDeterministicHashCodeKeyValuePairs.Add(keyValuePair.Key, keyValuePair.Value.Distinct().ToArray()); + } + foreach ((string personDisplayDirectoryName, int? approximateYears, string personKey, PersonBirthday[] personBirthdays) in peopleCollection) + { + if (peopleKeyValuePairs.ContainsKey(personKey) && peopleKeyValuePairs[personKey].Item1 != personDisplayDirectoryName) + throw new NotImplementedException(); + if (deterministicHashCodeUnknownFacePersonKeys.Contains(personKey) || deterministicHashCodePersonKeys.Contains(personKey)) + peopleKeyValuePairs.Add(personKey, new(personDisplayDirectoryName, approximateYears, personKey, personBirthdays)); + } + return result; + } + +} \ No newline at end of file diff --git a/Property-Compare/Models/PropertyCompareLogic.cs b/Property-Compare/Models/PropertyCompareLogic.cs index b82e538..3ed5a76 100644 --- a/Property-Compare/Models/PropertyCompareLogic.cs +++ b/Property-Compare/Models/PropertyCompareLogic.cs @@ -295,7 +295,7 @@ public class PropertyCompareLogic continue; totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); using ProgressBar progressBar = new(filteredSourceDirectoryFiles.Length, $"{r + 1:000}.{g} / {groupCollection.Count:000}) {filteredSourceDirectoryFiles.Length:000} file(s) - {totalSeconds} total second(s) - {sourceDirectory}", options); - _ = Parallel.For(0, filteredSourceDirectoryFiles.Length, parallelOptions, i => + _ = Parallel.For(0, filteredSourceDirectoryFiles.Length, parallelOptions, (i, state) => { try { diff --git a/Property/Models/A_Property.cs b/Property/Models/A_Property.cs index a2c9dbd..2901bb2 100644 --- a/Property/Models/A_Property.cs +++ b/Property/Models/A_Property.cs @@ -571,7 +571,7 @@ public class A_Property ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; string message = $"{container.R:000}.{container.G} / {containersCount:000}) {filteredItems.Length:000} file(s) - {totalSeconds} total second(s) - {container.SourceDirectory}"; using ProgressBar progressBar = new(filteredItems.Length, message, options); - _ = Parallel.For(0, filteredItems.Length, parallelOptions, i => + _ = Parallel.For(0, filteredItems.Length, parallelOptions, (i, state) => { try { diff --git a/Property/Models/Stateless/Container.cs b/Property/Models/Stateless/Container.cs index 78d3565..c3e81f4 100644 --- a/Property/Models/Stateless/Container.cs +++ b/Property/Models/Stateless/Container.cs @@ -140,7 +140,7 @@ public class Container List<(int, string, List<(string, Shared.Models.Property?)>, int)> results = new(); int length = rootDirectory.Length; ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = Environment.ProcessorCount }; - _ = Parallel.For(0, jsonCollection.Count, parallelOptions, i => ParallelFor(jsonCollection, i, length, results)); + _ = Parallel.For(0, jsonCollection.Count, parallelOptions, (i, state) => ParallelFor(jsonCollection, i, length, results)); return results; } diff --git a/Shared/Models/Closest.cs b/Shared/Models/Closest.cs index a4d603f..0d64e2f 100644 --- a/Shared/Models/Closest.cs +++ b/Shared/Models/Closest.cs @@ -6,38 +6,33 @@ namespace View_by_Distance.Shared.Models; public class Closest : Properties.IClosest { - protected readonly double? _Average; - protected readonly int? _NormalizedPixelPercentage; + protected readonly int _Average; protected readonly bool? _IsWrongYear; - protected readonly double? _Minimum; + protected Mapping _Mapping; + protected readonly double _Minimum; protected readonly DateTime _MinimumDateTime; - protected readonly PersonBirthday? _PersonBirthday; - public double? Average => _Average; - public int? NormalizedPixelPercentage => _NormalizedPixelPercentage; + protected readonly int _NormalizedPixelPercentage; + protected readonly long? _TicksDelta; + public double Average => _Average; public bool? IsWrongYear => _IsWrongYear; - public double? Minimum => _Minimum; + public Mapping Mapping => _Mapping; + public double Minimum => _Minimum; public DateTime MinimumDateTime => _MinimumDateTime; - public PersonBirthday? PersonBirthday => _PersonBirthday; + public int NormalizedPixelPercentage => _NormalizedPixelPercentage; + public long? TicksDelta => _TicksDelta; [JsonConstructor] - public Closest(double? average, int? normalizedPixelPercentage, bool? isWrongYear, double? minimum, DateTime minimumDateTime, PersonBirthday? personBirthday) + public Closest(int average, int normalizedPixelPercentage, bool? isWrongYear, Mapping mapping, double minimum, DateTime minimumDateTime, long? ticksDelta) { _Average = average; _NormalizedPixelPercentage = normalizedPixelPercentage; _IsWrongYear = isWrongYear; + _Mapping = mapping; _Minimum = minimum; _MinimumDateTime = minimumDateTime; - _PersonBirthday = personBirthday; + _TicksDelta = ticksDelta; } - public Closest(int? normalizedPixelPercentage, DateTime minimumDateTime, bool? isWrongYear) : - this(null, normalizedPixelPercentage, isWrongYear, null, minimumDateTime, null) - { } - - public Closest(int? normalizedPixelPercentage, DateTime minimumDateTime, bool? isWrongYear, PersonBirthday? personBirthday, List faceDistances) : - this(faceDistances.Average(), normalizedPixelPercentage, isWrongYear, faceDistances.Min(), minimumDateTime, personBirthday) - { } - public override string ToString() { string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); diff --git a/Shared/Models/Face.cs b/Shared/Models/Face.cs index 30faf75..e81da60 100644 --- a/Shared/Models/Face.cs +++ b/Shared/Models/Face.cs @@ -7,6 +7,7 @@ public class Face : Properties.IFace { protected DateTime _DateTime; + protected List _FaceDistances; protected FaceEncoding? _FaceEncoding; protected Dictionary? _FaceParts; protected readonly OutputResolution? _OutputResolution; @@ -14,6 +15,7 @@ public class Face : Properties.IFace protected readonly int? _LocationIndex; protected readonly string _RelativePath; public DateTime DateTime => _DateTime; + public List FaceDistances => _FaceDistances; public FaceEncoding? FaceEncoding => _FaceEncoding; public Dictionary? FaceParts => _FaceParts; public Location? Location => _Location; @@ -22,9 +24,12 @@ public class Face : Properties.IFace public string RelativePath => _RelativePath; [JsonConstructor] - public Face(DateTime dateTime, FaceEncoding? faceEncoding, Dictionary? faceParts, Location? location, int? locationIndex, OutputResolution? outputResolution, string relativePath) + public Face(DateTime dateTime, List faceDistances, FaceEncoding? faceEncoding, Dictionary? faceParts, Location? location, int? locationIndex, OutputResolution? outputResolution, string relativePath) { + if (faceDistances is null) + faceDistances = new(); _DateTime = dateTime; + _FaceDistances = faceDistances; _FaceEncoding = faceEncoding; _FaceParts = faceParts; _Location = location; @@ -34,29 +39,29 @@ public class Face : Properties.IFace } public Face() : - this(DateTime.MinValue, null, null, null, null, null, string.Empty) + this(DateTime.MinValue, new(), null, null, null, null, null, string.Empty) { } public Face(Location location) : - this(DateTime.MinValue, null, null, location, null, null, string.Empty) + this(DateTime.MinValue, new(), null, null, location, null, null, string.Empty) { } public Face(int facesCount, Face face) : - this(face.DateTime, face.FaceEncoding, face.FaceParts, face.Location, face.LocationIndex, face.OutputResolution, face.RelativePath) + this(face.DateTime, new(), face.FaceEncoding, face.FaceParts, face.Location, face.LocationIndex, face.OutputResolution, face.RelativePath) { if (face.Location?.Confidence is not null && face.OutputResolution is not null) _Location = new(face.Location.Confidence, face.OutputResolution.Height, face.Location, face.OutputResolution.Width, facesCount); } public Face(int outputResolutionWidth, int outputResolutionHeight, int outputResolutionOrientation, Face face) : - this(face.DateTime, face.FaceEncoding, face.FaceParts, face.Location, face.LocationIndex, null, face.RelativePath) + this(face.DateTime, new(), face.FaceEncoding, face.FaceParts, face.Location, face.LocationIndex, null, face.RelativePath) { if (outputResolutionHeight > 0) _OutputResolution = new(outputResolutionHeight, outputResolutionOrientation, outputResolutionWidth); } public Face(Property property, int outputResolutionWidth, int outputResolutionHeight, int outputResolutionOrientation, string relativePath, int? i, Location? location) : - this(DateTime.MinValue, null, null, location, i, null, relativePath) + this(DateTime.MinValue, new(), null, null, location, i, null, relativePath) { DateTime?[] dateTimes; _OutputResolution = new(outputResolutionHeight, outputResolutionOrientation, outputResolutionWidth); diff --git a/Shared/Models/FaceDistance.cs b/Shared/Models/FaceDistance.cs new file mode 100644 index 0000000..9350229 --- /dev/null +++ b/Shared/Models/FaceDistance.cs @@ -0,0 +1,36 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public class FaceDistance : Properties.IFaceDistance +{ + + protected readonly List _Distances; + protected readonly bool? _IsWrongYear; + protected readonly string _Key; + protected readonly Mapping _Mapping; + protected readonly DateTime _MinimumDateTime; + public List Distances => _Distances; + public bool? IsWrongYear => _IsWrongYear; + public string Key => _Key; + public Mapping Mapping => _Mapping; + public DateTime MinimumDateTime => _MinimumDateTime; + + [JsonConstructor] + public FaceDistance(List distances, bool? isWrongYear, string key, Mapping mapping, DateTime minimumDateTime) + { + _Distances = distances; + _IsWrongYear = isWrongYear; + _Key = key; + _Mapping = mapping; + _MinimumDateTime = minimumDateTime; + } + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); + return result; + } + +} \ No newline at end of file diff --git a/Shared/Models/Item.cs b/Shared/Models/Item.cs index 7fedaf7..a45cb18 100644 --- a/Shared/Models/Item.cs +++ b/Shared/Models/Item.cs @@ -11,8 +11,8 @@ public class Item : Properties.IItem protected List _Closest; protected List _Faces; protected readonly FileHolder? _ImageFileHolder; + protected List _Mapping; protected bool? _Moved; - protected List _Named; protected readonly bool? _NoJson; protected Property? _Property; protected readonly string _RelativePath; @@ -24,9 +24,9 @@ public class Item : Properties.IItem public List Closest => _Closest; public List Faces => _Faces; public FileHolder? ImageFileHolder => _ImageFileHolder; + public List Mapping => _Mapping; public bool? Moved => _Moved; public bool? NoJson => _NoJson; - public List Named => _Named; public Property? Property => _Property; public string RelativePath => _RelativePath; public FileHolder? ResizedFileHolder => _ResizedFileHolder; @@ -34,15 +34,15 @@ public class Item : Properties.IItem public bool ValidImageFormatExtension => _ValidImageFormatExtension; [JsonConstructor] - public Item(bool? abandoned, bool? changed, List closest, List faces, FileHolder? imageFileHolder, bool? moved, List named, bool? noJson, Property? property, string relativePath, FileHolder? resizedFileHolder, string sourceDirectoryFile, bool validImageFormatExtension) + public Item(bool? abandoned, bool? changed, List closest, List faces, FileHolder? imageFileHolder, List mapping, bool? moved, bool? noJson, Property? property, string relativePath, FileHolder? resizedFileHolder, string sourceDirectoryFile, bool validImageFormatExtension) { _Abandoned = abandoned; _Changed = changed; _Closest = closest; _Faces = faces; _ImageFileHolder = imageFileHolder; + _Mapping = mapping; _Moved = moved; - _Named = named; _NoJson = noJson; _Property = property; _RelativePath = relativePath; @@ -54,7 +54,7 @@ public class Item : Properties.IItem public Item(string sourceDirectoryFile, string relativePath, FileHolder? imageFileInfo, bool isValidImageFormatExtension, Property? property, bool? abandoned, bool? changed) { _Faces = new(); - _Named = new(); + _Mapping = new(); _Closest = new(); _Changed = changed; _Property = property; diff --git a/Shared/Models/Location.cs b/Shared/Models/Location.cs index 02e779f..ed42432 100644 --- a/Shared/Models/Location.cs +++ b/Shared/Models/Location.cs @@ -116,7 +116,7 @@ public class Location : Properties.ILocation, IEquatable value = at / total; if (value < 0) value = 3; - result = (int)(Math.Round(value, Stateless.ILocation.Decimals) * Stateless.ILocation.Factor); + result = (int)(Math.Round(value, Stateless.ILocation.Digits) * Stateless.ILocation.Factor); return result; } diff --git a/Shared/Models/Mapping.cs b/Shared/Models/Mapping.cs new file mode 100644 index 0000000..6abdeaf --- /dev/null +++ b/Shared/Models/Mapping.cs @@ -0,0 +1,49 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public class Mapping : Properties.IMapping +{ + + protected readonly int? _ApproximateYears; + protected readonly string _DisplayDirectoryName; + protected bool? _Filtered; + protected readonly int? _NormalizedPixelPercentage; + protected readonly PersonBirthday _PersonBirthday; + protected readonly string _PersonKey; + public int? ApproximateYears => _ApproximateYears; + public string DisplayDirectoryName => _DisplayDirectoryName; + public bool? Filtered => _Filtered; + public int? NormalizedPixelPercentage => _NormalizedPixelPercentage; + public PersonBirthday PersonBirthday => _PersonBirthday; + public string PersonKey => _PersonKey; + + [JsonConstructor] + public Mapping(int? approximateYears, string displayDirectoryName, bool? filtered, int? normalizedPixelPercentage, PersonBirthday personBirthday, string personKey) + { + _ApproximateYears = approximateYears; + _DisplayDirectoryName = displayDirectoryName; + _Filtered = filtered; + _NormalizedPixelPercentage = normalizedPixelPercentage; + _PersonBirthday = personBirthday; + _PersonKey = personKey; + } + + public Mapping(int? approximateYears, string displayDirectoryName, int? normalizedPixelPercentage, PersonBirthday personBirthday, string personKey) : + this(approximateYears, displayDirectoryName, null, normalizedPixelPercentage, personBirthday, personKey) + { } + + public Mapping(int? approximateYears, string displayDirectoryName, PersonBirthday personBirthday, string personKey) : + this(approximateYears, displayDirectoryName, null, null, personBirthday, personKey) + { } + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); + return result; + } + + public void SetFiltered() => _Filtered = true; + +} \ No newline at end of file diff --git a/Shared/Models/MappingContainer.cs b/Shared/Models/MappingContainer.cs new file mode 100644 index 0000000..afbf6cc --- /dev/null +++ b/Shared/Models/MappingContainer.cs @@ -0,0 +1,48 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public class MappingContainer : Properties.IMappingContainer +{ + + protected double? _Distance; + protected readonly Face? _Face; + protected readonly int _Id; + protected readonly bool? _IsWrongYear; + protected readonly string _Key; + protected readonly Mapping _Mapping; + protected readonly DateTime _MinimumDateTime; + public double? Distance => _Distance; + public Face? Face => _Face; + public int Id => _Id; + public bool? IsWrongYear => _IsWrongYear; + public string Key => _Key; + public Mapping Mapping => _Mapping; + public DateTime MinimumDateTime => _MinimumDateTime; + + [JsonConstructor] + public MappingContainer(double? distance, Face? face, int id, bool? isWrongYear, string key, Mapping mapping, DateTime minimumDateTime) + { + _Distance = distance; + _Face = face; + _Id = id; + _IsWrongYear = isWrongYear; + _Key = key; + _Mapping = mapping; + _MinimumDateTime = minimumDateTime; + } + + public MappingContainer(Face face, int id, bool? isWrongYear, string key, Mapping mapping, DateTime minimumDateTime) : + this(null, face, id, isWrongYear, key, mapping, minimumDateTime) + { } + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); + return result; + } + + public void SetDistance(double v) => _Distance = v; + +} \ No newline at end of file diff --git a/Shared/Models/Methods/INamed.cs b/Shared/Models/Methods/INamed.cs index 8958a57..5086173 100644 --- a/Shared/Models/Methods/INamed.cs +++ b/Shared/Models/Methods/INamed.cs @@ -1,6 +1,6 @@ namespace View_by_Distance.Shared.Models.Methods; -public interface INamed : Stateless.Methods.INamed +public interface INamed : Stateless.Methods.IMapping { // ... // diff --git a/Shared/Models/Named.cs b/Shared/Models/Named.cs deleted file mode 100644 index d1f8f61..0000000 --- a/Shared/Models/Named.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace View_by_Distance.Shared.Models; - -public class Named : Properties.INamed -{ - - protected readonly bool? _IsWrongYear; - protected readonly DateTime _MinimumDateTime; - protected readonly int? _NormalizedPixelPercentage; - protected readonly PersonBirthday? _PersonBirthday; - public bool? IsWrongYear => _IsWrongYear; - public DateTime MinimumDateTime => _MinimumDateTime; - public int? NormalizedPixelPercentage => _NormalizedPixelPercentage; - public PersonBirthday? PersonBirthday => _PersonBirthday; - - [JsonConstructor] - public Named(bool? isWrongYear, DateTime minimumDateTime, int? normalizedPixelPercentage, PersonBirthday? personBirthday) - { - _IsWrongYear = isWrongYear; - _MinimumDateTime = minimumDateTime; - _NormalizedPixelPercentage = normalizedPixelPercentage; - _PersonBirthday = personBirthday; - } - - public Named(bool? isWrongYear, DateTime minimumDateTime, PersonBirthday? personBirthday) : - this(isWrongYear, minimumDateTime, null, personBirthday) - { } - - public override string ToString() - { - string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); - return result; - } - -} \ No newline at end of file diff --git a/Shared/Models/Person.cs b/Shared/Models/Person.cs index 5de3fb1..28c78f3 100644 --- a/Shared/Models/Person.cs +++ b/Shared/Models/Person.cs @@ -1,3 +1,4 @@ +using System.Text; using System.Text.Json; using System.Text.Json.Serialization; @@ -53,4 +54,18 @@ public class Person : Properties.IPerson return result; } // ... + public string GetFullName() + { + StringBuilder result = new(); + if (_Name?.First is not null && !string.IsNullOrEmpty(_Name.First.Value)) + _ = result.Append(_Name.First.Value); + if (_Name?.Middle is not null && !string.IsNullOrEmpty(_Name.Middle.Value)) + _ = result.Append(' ').Append(_Name.Middle.Value); + if (_Name?.Last is not null && !string.IsNullOrEmpty(_Name.Last.Value)) + _ = result.Append(' ').Append(_Name.Last.Value); + if (_Name?.Alias is not null && !string.IsNullOrEmpty(_Name.Alias.Value)) + _ = result.Append(" (").Append(_Name.Alias.Value).Append(')'); + return result.ToString(); + } + } \ No newline at end of file diff --git a/Shared/Models/Properties/IClosest.cs b/Shared/Models/Properties/IClosest.cs index eb52923..ed29d80 100644 --- a/Shared/Models/Properties/IClosest.cs +++ b/Shared/Models/Properties/IClosest.cs @@ -3,11 +3,12 @@ namespace View_by_Distance.Shared.Models.Properties; public interface IClosest { - public double? Average { get; } - public int? NormalizedPixelPercentage { get; } + public double Average { get; } public bool? IsWrongYear { get; } - public double? Minimum { get; } + public Mapping Mapping { get; } + public double Minimum { get; } public DateTime MinimumDateTime { get; } - public PersonBirthday? PersonBirthday { get; } + public int NormalizedPixelPercentage { get; } + public long? TicksDelta { get; } } \ No newline at end of file diff --git a/Shared/Models/Properties/IFace.cs b/Shared/Models/Properties/IFace.cs index 665fe7a..c320cb2 100644 --- a/Shared/Models/Properties/IFace.cs +++ b/Shared/Models/Properties/IFace.cs @@ -4,6 +4,7 @@ public interface IFace { public DateTime DateTime { get; } + public List FaceDistances { get; } public FaceEncoding? FaceEncoding { get; } public Dictionary? FaceParts { get; } public Location? Location { get; } diff --git a/Shared/Models/Properties/INamed.cs b/Shared/Models/Properties/IFaceDistance.cs similarity index 50% rename from Shared/Models/Properties/INamed.cs rename to Shared/Models/Properties/IFaceDistance.cs index 8494c63..b0a0f09 100644 --- a/Shared/Models/Properties/INamed.cs +++ b/Shared/Models/Properties/IFaceDistance.cs @@ -1,11 +1,12 @@ namespace View_by_Distance.Shared.Models.Properties; -public interface INamed +public interface IFaceDistance { + public List Distances { get; } public bool? IsWrongYear { get; } + public string Key { get; } + public Mapping Mapping { get; } public DateTime MinimumDateTime { get; } - public int? NormalizedPixelPercentage { get; } - public PersonBirthday? PersonBirthday { get; } } \ No newline at end of file diff --git a/Shared/Models/Properties/IItem.cs b/Shared/Models/Properties/IItem.cs index ac6e519..422e8f5 100644 --- a/Shared/Models/Properties/IItem.cs +++ b/Shared/Models/Properties/IItem.cs @@ -8,9 +8,9 @@ public interface IItem public List Closest { get; } public List Faces { get; } public FileHolder? ImageFileHolder { get; } + public List Mapping { get; } public bool? Moved { get; } public bool? NoJson { get; } - public List Named { get; } public Property? Property { get; } public string RelativePath { get; } public FileHolder? ResizedFileHolder { get; } diff --git a/Shared/Models/Properties/IMapping.cs b/Shared/Models/Properties/IMapping.cs new file mode 100644 index 0000000..58fdbd5 --- /dev/null +++ b/Shared/Models/Properties/IMapping.cs @@ -0,0 +1,13 @@ +namespace View_by_Distance.Shared.Models.Properties; + +public interface IMapping +{ + + public int? ApproximateYears { get; } + public string DisplayDirectoryName { get; } + public bool? Filtered { get; } + public int? NormalizedPixelPercentage { get; } + public PersonBirthday PersonBirthday { get; } + public string PersonKey { get; } + +} \ No newline at end of file diff --git a/Shared/Models/Properties/IMappingContainer.cs b/Shared/Models/Properties/IMappingContainer.cs new file mode 100644 index 0000000..570681a --- /dev/null +++ b/Shared/Models/Properties/IMappingContainer.cs @@ -0,0 +1,14 @@ +namespace View_by_Distance.Shared.Models.Properties; + +public interface IMappingContainer +{ + + public double? Distance { get; } + public Face? Face { get; } + public int Id { get; } + public bool? IsWrongYear { get; } + public string Key { get; } + public Mapping Mapping { get; } + public DateTime MinimumDateTime { get; } + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/IClosest.cs b/Shared/Models/Stateless/IClosest.cs index 70bd684..80f0bc2 100644 --- a/Shared/Models/Stateless/IClosest.cs +++ b/Shared/Models/Stateless/IClosest.cs @@ -3,9 +3,14 @@ public interface IClosest { + // 637972153144596958 + // const int Digits = 3; + // const int Factor = 1000; + // const int MaximumPer = 50; + // const bool SkipIsWrongYear = false; + + const int Digits = 3; + const int Factor = 1000; const int MaximumPer = 50; - const float MaximumMinimum = 0.50f; - const bool SkipIsWrongYear = true; - const float MinimumMinimum = 0.05f; } \ No newline at end of file diff --git a/Shared/Models/Stateless/IFaceDistance.cs b/Shared/Models/Stateless/IFaceDistance.cs new file mode 100644 index 0000000..e47a66c --- /dev/null +++ b/Shared/Models/Stateless/IFaceDistance.cs @@ -0,0 +1,11 @@ +namespace View_by_Distance.Shared.Models.Stateless; + +public interface IFaceDistance +{ + // 637972153144596958 + // const int MaximumPer = 999; + + const int MaximumPer = 9999; + const double Tolerance = 0.6d; + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/ILocation.cs b/Shared/Models/Stateless/ILocation.cs index 089a958..8051d28 100644 --- a/Shared/Models/Stateless/ILocation.cs +++ b/Shared/Models/Stateless/ILocation.cs @@ -3,7 +3,7 @@ public interface ILocation { - const int Decimals = 6; + const int Digits = 6; const int Factor = 1000000; } \ No newline at end of file diff --git a/Shared/Models/Stateless/IMapping.cs b/Shared/Models/Stateless/IMapping.cs new file mode 100644 index 0000000..56d156f --- /dev/null +++ b/Shared/Models/Stateless/IMapping.cs @@ -0,0 +1,14 @@ +namespace View_by_Distance.Shared.Models.Stateless; + +public interface IMapping +{ + + // 637972153144596958 + // const bool UseDeterministicHashCodeUnknownFaceKeyValuePairsForAddToNamed = true; + // const bool OnlyUseNamedWithNormalizedPixelPercentagePopulatedForGetKeyValuePairs = false; + + const bool UseDeterministicHashCodeUnknownFaceKeyValuePairsForAddToMapping = false; + const bool UseDeterministicHashCodeUnknownFaceKeyValuePairsForSaveMapping = false; + const bool SaveFaceEncoding = false; + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/IPersonBirthday.cs b/Shared/Models/Stateless/IPersonBirthday.cs new file mode 100644 index 0000000..d3a376b --- /dev/null +++ b/Shared/Models/Stateless/IPersonBirthday.cs @@ -0,0 +1,8 @@ +namespace View_by_Distance.Shared.Models.Stateless; + +public interface IPersonBirthday +{ + + const string Format = "yyyy-MM-dd_HH"; + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/Age.cs b/Shared/Models/Stateless/Methods/Age.cs new file mode 100644 index 0000000..08e17a3 --- /dev/null +++ b/Shared/Models/Stateless/Methods/Age.cs @@ -0,0 +1,22 @@ +namespace View_by_Distance.Shared.Models.Stateless.Methods; + +internal abstract class Age +{ + + internal static (int, TimeSpan) GetAge(DateTime minuend, DateTime subtrahend) + { + TimeSpan result; + int years = 0; + DateTime check = new(subtrahend.Ticks); + for (int i = 0; i < int.MaxValue; i++) + { + check = check.AddYears(1); + if (check > minuend) + break; + years += 1; + } + result = new(minuend.Ticks - check.AddYears(-1).Ticks); + return (years, result); + } + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/Closest.cs b/Shared/Models/Stateless/Methods/Closest.cs index 6e9a496..3568195 100644 --- a/Shared/Models/Stateless/Methods/Closest.cs +++ b/Shared/Models/Stateless/Methods/Closest.cs @@ -3,6 +3,33 @@ namespace View_by_Distance.Shared.Models.Stateless.Methods; internal abstract class Closest { - internal static Models.Closest[] Get(List collection) => (from l in collection orderby l.Minimum < Stateless.IClosest.MinimumMinimum, l.Average select l).ToArray(); + private static int Get(List faceDistances) => (int)(Math.Round(faceDistances.Average(), Stateless.IClosest.Digits) * Stateless.ILocation.Factor); + private static Models.Closest Get(Models.Face face, DateTime minimumDateTime, FaceDistance faceDistance) + { + Models.Closest result; + int average = Get(faceDistance.Distances); + double minimum = faceDistance.Distances.Min(); + long? ticksDelta; + if (faceDistance.IsWrongYear is null || faceDistance.IsWrongYear.Value) + ticksDelta = null; + else + { + ticksDelta = Math.Abs(faceDistance.MinimumDateTime.Ticks - minimumDateTime.Ticks); + if (faceDistance.MinimumDateTime < faceDistance.Mapping.PersonBirthday.Value) + ticksDelta *= 2; + } + if (face.Location?.NormalizedPixelPercentage is null) + throw new NullReferenceException(nameof(face.Location.NormalizedPixelPercentage)); + result = new(average, face.Location.NormalizedPixelPercentage.Value, faceDistance.IsWrongYear, faceDistance.Mapping, minimum, faceDistance.MinimumDateTime, ticksDelta); + return result; + } + + internal static Models.Closest[] GetCollection(Models.Face face, DateTime minimumDateTime, List faceDistances) + { + Models.Closest[] results; + Models.Closest[] closestCollection = (from l in faceDistances select Get(face, minimumDateTime, l)).ToArray(); + results = (from l in closestCollection orderby l.Average, l.TicksDelta.HasValue, l.TicksDelta select l).ToArray(); + return results; + } } \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IAge.cs b/Shared/Models/Stateless/Methods/IAge.cs new file mode 100644 index 0000000..c74d6c2 --- /dev/null +++ b/Shared/Models/Stateless/Methods/IAge.cs @@ -0,0 +1,9 @@ +namespace View_by_Distance.Shared.Models.Stateless.Methods; + +public interface IAge +{ // ... + + (int, TimeSpan) TestStatic_GetAge(DateTime minuend, DateTime subtrahend); + static (int, TimeSpan) GetAge(DateTime minuend, DateTime subtrahend) => Age.GetAge(minuend, subtrahend); + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IClosest.cs b/Shared/Models/Stateless/Methods/IClosest.cs index 12c8722..027faad 100644 --- a/Shared/Models/Stateless/Methods/IClosest.cs +++ b/Shared/Models/Stateless/Methods/IClosest.cs @@ -3,8 +3,8 @@ namespace View_by_Distance.Shared.Models.Stateless.Methods; public interface IClosest { // ... - Models.Closest[] TestStatic_Get(List collection); + Models.Closest[] TestStatic_Get(List faceDistances); - static Models.Closest[] Get(List collection) => Closest.Get(collection); + static Models.Closest[] GetCollection(Models.Face face, DateTime minimumDateTime, List faceDistances) => Closest.GetCollection(face, minimumDateTime, faceDistances); } \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/INamed.cs b/Shared/Models/Stateless/Methods/IIMapping.cs similarity index 73% rename from Shared/Models/Stateless/Methods/INamed.cs rename to Shared/Models/Stateless/Methods/IIMapping.cs index abbd564..2ee2df5 100644 --- a/Shared/Models/Stateless/Methods/INamed.cs +++ b/Shared/Models/Stateless/Methods/IIMapping.cs @@ -1,18 +1,18 @@ namespace View_by_Distance.Shared.Models.Stateless.Methods; -public interface INamed +public interface IMapping { // ... double? TestStatic_GetReversedDeterministicHashCodeKey(string file); static double? GetReversedDeterministicHashCodeKey(bool keyValuePairsAny, Dictionary> keyValuePairs, string file) => - Named.GetReversedDeterministicHashCodeKey(keyValuePairsAny, keyValuePairs, file); + Mapping.GetReversedDeterministicHashCodeKey(keyValuePairsAny, keyValuePairs, file); double TestStatic_GetDeterministicHashCodeKey(Models.Item item, Models.Face face); static double GetDeterministicHashCodeKey(Models.Item item, Models.Face face) => - Named.GetDeterministicHashCodeKey(item, face); + Mapping.GetDeterministicHashCodeKey(item, face); double TestStatic_GetDeterministicHashCodeKey(Models.Item item, Models.Closest closest); static double GetDeterministicHashCodeKey(Models.Item item, Models.Closest closest) => - Named.GetDeterministicHashCodeKey(item, closest); + Mapping.GetDeterministicHashCodeKey(item, closest); } \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IPersonBirthday.cs b/Shared/Models/Stateless/Methods/IPersonBirthday.cs index a7b51bf..e221ed5 100644 --- a/Shared/Models/Stateless/Methods/IPersonBirthday.cs +++ b/Shared/Models/Stateless/Methods/IPersonBirthday.cs @@ -3,37 +3,65 @@ namespace View_by_Distance.Shared.Models.Stateless.Methods; public interface IPersonBirthday { - DateTime TestStatic_GetDefaultValue() => PersonBirthday.GetDefaultValue(); // {{1}}SingletonValue + DateTime TestStatic_GetDefaultValue() => + PersonBirthday.GetDefaultValue(); // {{1}}SingletonValue - static DateTime GetDefaultValue() => PersonBirthday.GetDefaultValue(); // {{1}}SingletonValue + static DateTime GetDefaultValue() => + PersonBirthday.GetDefaultValue(); // {{1}}SingletonValue // ... - string TestStatic_GetFormat() => PersonBirthday.GetFormat(); - static string GetFormat() => PersonBirthday.GetFormat(); + double? TestStatic_GetAge(Models.PersonBirthday birthday); + static double? GetAge(Models.PersonBirthday birthday) => + PersonBirthday.GetAge(birthday); - DateTime? TestStatic_GetDateTime(string personKey) => PersonBirthday.GetDateTime(personKey); - static DateTime? GetDateTime(string personKey) => PersonBirthday.GetDateTime(personKey); + DateTime? TestStatic_GetDateTime(string personKey) => + PersonBirthday.GetDateTime(personKey); + static DateTime? GetDateTime(string personKey) => + PersonBirthday.GetDateTime(personKey); - string TestStatic_GetFileName(Models.PersonBirthday personBirthday) => PersonBirthday.GetFileName(personBirthday); - static string GetFileName(Models.PersonBirthday personBirthday) => PersonBirthday.GetFileName(personBirthday); + string TestStatic_GetFileName(Models.PersonBirthday personBirthday) => + PersonBirthday.GetFileName(personBirthday); + static string GetFileName(Models.PersonBirthday personBirthday) => + PersonBirthday.GetFileName(personBirthday); - Models.PersonBirthday? TestStatic_GetPersonBirthday(string personKey) => PersonBirthday.GetPersonBirthday(personKey); - static Models.PersonBirthday? GetPersonBirthday(string personKey) => PersonBirthday.GetPersonBirthday(personKey); + (int, TimeSpan) TestStatic_GetAge(DateTime dateTime, Models.PersonBirthday birthday); + static (int, TimeSpan) GetAge(DateTime dateTime, Models.PersonBirthday birthday) => + PersonBirthday.GetAge(dateTime, birthday); - string TestStatic_GetFormatted(Models.PersonBirthday personBirthday) => PersonBirthday.GetFormatted(personBirthday); - static string GetFormatted(Models.PersonBirthday personBirthday) => PersonBirthday.GetFormatted(personBirthday); + string TestStatic_GetFormatted(Models.PersonBirthday personBirthday) => + PersonBirthday.GetFormatted(personBirthday); + static string GetFormatted(Models.PersonBirthday personBirthday) => + PersonBirthday.GetFormatted(personBirthday); - Models.PersonBirthday TestStatic_GetNextBirthDate(Properties.IStorage storage) => PersonBirthday.GetNextBirthDate(storage); - static Models.PersonBirthday GetNextBirthDate(Properties.IStorage storage) => PersonBirthday.GetNextBirthDate(storage); + Models.PersonBirthday? TestStatic_GetPersonBirthday(string personKey) => + PersonBirthday.GetPersonBirthday(personKey); + static Models.PersonBirthday? GetPersonBirthday(string personKey) => + PersonBirthday.GetPersonBirthday(personKey); - bool TestStatic_DoesBirthDateExits(Properties.IStorage storage, Models.PersonBirthday personBirthday) => DoesBirthDateExits(storage, personBirthday); - static bool DoesBirthDateExits(Properties.IStorage storage, Models.PersonBirthday personBirthday) => DoesBirthDateExits(storage, personBirthday); + Models.PersonBirthday TestStatic_GetNextBirthDate(Properties.IStorage storage) => + PersonBirthday.GetNextBirthDate(storage); + static Models.PersonBirthday GetNextBirthDate(Properties.IStorage storage) => + PersonBirthday.GetNextBirthDate(storage); - string TestStatic_GetFileFullName(Properties.IStorage storage, Models.PersonBirthday personBirthday) => PersonBirthday.GetFileFullName(storage, personBirthday); - static string GetFileFullName(Properties.IStorage storage, Models.PersonBirthday personBirthday) => PersonBirthday.GetFileFullName(storage, personBirthday); + TimeSpan? TestStatic_Get(DateTime now, Models.PersonBirthday personBirthday) => + PersonBirthday.GetTimeSpan(now, isWrongYear: false, personBirthday); + static TimeSpan? GetTimeSpan(DateTime minimumDateTime, Models.PersonBirthday personBirthday) => + PersonBirthday.GetTimeSpan(minimumDateTime, isWrongYear: false, personBirthday); - TimeSpan? TestStatic_Get(DateTime minimumDateTime, bool? isWrongYear, Models.PersonBirthday personBirthday) => PersonBirthday.GetTimeSpan(minimumDateTime, isWrongYear, personBirthday); - static TimeSpan? GetTimeSpan(DateTime minimumDateTime, bool? isWrongYear, Models.PersonBirthday personBirthday) => PersonBirthday.GetTimeSpan(minimumDateTime, isWrongYear, personBirthday); + string TestStatic_GetFileFullName(Properties.IStorage storage, Models.PersonBirthday personBirthday) => + PersonBirthday.GetFileFullName(storage, personBirthday); + static string GetFileFullName(Properties.IStorage storage, Models.PersonBirthday personBirthday) => + PersonBirthday.GetFileFullName(storage, personBirthday); + + bool TestStatic_DoesBirthDateExits(Properties.IStorage storage, Models.PersonBirthday personBirthday) => + DoesBirthDateExits(storage, personBirthday); + static bool DoesBirthDateExits(Properties.IStorage storage, Models.PersonBirthday personBirthday) => + DoesBirthDateExits(storage, personBirthday); + + TimeSpan? TestStatic_Get(DateTime minimumDateTime, bool? isWrongYear, Models.PersonBirthday personBirthday) => + PersonBirthday.GetTimeSpan(minimumDateTime, isWrongYear, personBirthday); + static TimeSpan? GetTimeSpan(DateTime minimumDateTime, bool? isWrongYear, Models.PersonBirthday personBirthday) => + PersonBirthday.GetTimeSpan(minimumDateTime, isWrongYear, personBirthday); } \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/Named.cs b/Shared/Models/Stateless/Methods/Mapping.cs similarity index 95% rename from Shared/Models/Stateless/Methods/Named.cs rename to Shared/Models/Stateless/Methods/Mapping.cs index 7c482c5..906ce5f 100644 --- a/Shared/Models/Stateless/Methods/Named.cs +++ b/Shared/Models/Stateless/Methods/Mapping.cs @@ -2,18 +2,18 @@ using System.Text.Json; namespace View_by_Distance.Shared.Models.Stateless.Methods; -internal abstract class Named +internal abstract class Mapping { private static double GetDeterministicHashCodeKey(int id, int normalizedPixelPercentage) - => Math.Round(double.Parse($"{id}.{normalizedPixelPercentage}"), Stateless.ILocation.Decimals); + => Math.Round(double.Parse($"{id}.{normalizedPixelPercentage}"), Stateless.ILocation.Digits); internal static double GetDeterministicHashCodeKey(Models.Item item, Models.Closest closest) { double result; - if (item.Property?.Id is null || item.ImageFileHolder is null || closest.NormalizedPixelPercentage is null) + if (item.Property?.Id is null || item.ImageFileHolder is null) throw new NullReferenceException(); - result = GetDeterministicHashCodeKey(item.Property.Id.Value, closest.NormalizedPixelPercentage.Value); + result = GetDeterministicHashCodeKey(item.Property.Id.Value, closest.NormalizedPixelPercentage); return result; } diff --git a/Shared/Models/Stateless/Methods/Person.cs b/Shared/Models/Stateless/Methods/Person.cs index a535f1a..c402618 100644 --- a/Shared/Models/Stateless/Methods/Person.cs +++ b/Shared/Models/Stateless/Methods/Person.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using System.Text.RegularExpressions; namespace View_by_Distance.Shared.Models.Stateless.Methods; @@ -166,9 +167,9 @@ internal abstract class Person Models.PersonName name; List urls; Models.PersonBirthday birthday; + List comments; List emails = new(); List numbers = new(); - List comments = new(); List addresses = new(); Dictionary keyValuePairs = GetPersonCollection(localKnownPeopleFile); foreach (KeyValuePair keyValuePair in keyValuePairs) @@ -176,6 +177,7 @@ internal abstract class Person if (string.IsNullOrEmpty(keyValuePair.Value.Name)) continue; urls = new(); + comments = new(); birthday = new(keyValuePair.Key); name = PersonName.Create(keyValuePair.Value.Name); if (name.First is null || string.IsNullOrEmpty(name.First.Value)) @@ -242,7 +244,54 @@ internal abstract class Person results = GetPeopleFromText(storage, localKnownPeopleFile); } } + SaveToDirectory(storage, results); return results.ToArray(); } + private static void SaveToDirectory(Properties.IStorage storage, List people) + { + int years; + TimeSpan? timeSpan; + string personDirectory; + string? personFullName; + DateTime createdDateTime; + string birthdayDirectory; + string personJsonFileName; + string personDirectoryName; + string? peopleDirectory = null; + DateTime dateTime = DateTime.Now; + string? personJsonFileNameWithoutExtension; + const string pattern = @"[\\,\/,\:,\*,\?,\"",\<,\>,\|]"; + foreach (Models.Person person in people) + { + personJsonFileName = IPerson.GetFileFullName(storage, person); + if (string.IsNullOrEmpty(peopleDirectory)) + peopleDirectory = Path.GetDirectoryName(personJsonFileName); + if (string.IsNullOrEmpty(peopleDirectory)) + break; + personJsonFileNameWithoutExtension = Path.GetFileNameWithoutExtension(personJsonFileName); + if (string.IsNullOrEmpty(personJsonFileNameWithoutExtension)) + break; + personFullName = Regex.Replace(person.GetFullName(), pattern, string.Empty); + timeSpan = IPersonBirthday.GetTimeSpan(dateTime, person.Birthday); + if (timeSpan is null || timeSpan.Value.Ticks < 0) + personDirectoryName = $"{personFullName}~"; + else + { + createdDateTime = new FileInfo(personJsonFileName).CreationTime; + (years, timeSpan) = IPersonBirthday.GetAge(createdDateTime, person.Birthday); + personDirectoryName = $"{personFullName}^{years}-{Math.Floor(timeSpan.Value.TotalDays):000}"; + } + personDirectory = Path.Combine(peopleDirectory, personDirectoryName); + if (!Directory.Exists(personDirectory)) + _ = Directory.CreateDirectory(personDirectory); + birthdayDirectory = Path.Combine(personDirectory, personJsonFileNameWithoutExtension); + if (!Directory.Exists(birthdayDirectory)) + { + _ = Directory.CreateDirectory(birthdayDirectory); + File.Copy(personJsonFileName, Path.Combine(birthdayDirectory, $"{personJsonFileNameWithoutExtension}.json")); + } + } + } + } \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/PersonBirthday.cs b/Shared/Models/Stateless/Methods/PersonBirthday.cs index 077df35..8abae10 100644 --- a/Shared/Models/Stateless/Methods/PersonBirthday.cs +++ b/Shared/Models/Stateless/Methods/PersonBirthday.cs @@ -8,14 +8,12 @@ internal abstract class PersonBirthday internal static DateTime GetDefaultValue() => DateTime.MinValue; // {{1}}SingletonValue // ... - - internal static string GetFormat() => "yyyy-MM-dd_HH"; - internal static string GetFormatted(Models.PersonBirthday personBirthday) => personBirthday.Value.ToString(GetFormat()); - internal static string GetFileName(Models.PersonBirthday personBirthday) => $"{personBirthday.Value.ToString(GetFormat())}.json"; + internal static string GetFormatted(Models.PersonBirthday personBirthday) => personBirthday.Value.ToString(Stateless.IPersonBirthday.Format); + internal static string GetFileName(Models.PersonBirthday personBirthday) => $"{personBirthday.Value.ToString(Stateless.IPersonBirthday.Format)}.json"; internal static bool DoesBirthDateExits(Properties.IStorage storage, Models.PersonBirthday personBirthday) => File.Exists(GetFileFullName(storage, personBirthday)); internal static Models.PersonBirthday GetNextBirthDate(Properties.IStorage storage) => throw new Exception(storage.ToString()); // Person.GetNextBirthDate(storage); internal static string GetFileFullName(Properties.IStorage storage, Models.PersonBirthday personBirthday) => Path.Combine(storage.PeopleRootDirectory, "{}", GetFileName(personBirthday)); - internal static DateTime? GetDateTime(string personKey) => DateTime.TryParseExact(personKey, GetFormat(), CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateTime) ? dateTime : null; + internal static DateTime? GetDateTime(string personKey) => DateTime.TryParseExact(personKey, Stateless.IPersonBirthday.Format, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateTime) ? dateTime : null; internal static Models.PersonBirthday? GetPersonBirthday(string personKey) { @@ -37,4 +35,41 @@ internal abstract class PersonBirthday timeSpan = new(minimumDateTime.Ticks - personBirthday.Value.Ticks); return timeSpan; } + + internal static (int, TimeSpan) GetAge(DateTime dateTime, Models.PersonBirthday birthday) + { + TimeSpan result; + int years; + if (birthday?.Value is null) + throw new NullReferenceException(nameof(birthday.Value)); + (years, result) = Age.GetAge(dateTime, birthday.Value); + return (years, result); + } + + internal static (int, double) GetAge(DateTime dateTime, DateTime dayBeforeLeapDate, Models.PersonBirthday birthday) + { + double result; + (int years, TimeSpan timeSpan) = GetAge(dateTime, birthday); + if (!DateTime.IsLeapYear(dateTime.Year) || dateTime < dayBeforeLeapDate.AddDays(1)) + result = timeSpan.TotalDays / 365; + else + result = timeSpan.TotalDays / 366; + return (years, result); + } + + internal static double? GetAge(Models.PersonBirthday birthday) + { + double? result; + if (birthday is null) + result = null; + else + { + DateTime dateTime = DateTime.Now; + DateTime dayBeforeLeapDate = new(dateTime.Year, 2, 28); + (int years, double r) = GetAge(dateTime, dayBeforeLeapDate, birthday); + result = years + r; + } + return result; + } + } \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/Property.cs b/Shared/Models/Stateless/Methods/Property.cs index 771888c..eaa5f43 100644 --- a/Shared/Models/Stateless/Methods/Property.cs +++ b/Shared/Models/Stateless/Methods/Property.cs @@ -62,10 +62,10 @@ internal abstract class Property in segments where l?.Length > 2 && ( - l[..2] is "19" or "20" - || (l.Length == 5 && l.Substring(1, 2) is "19" or "20" && (l[0] is '~' or '=' or '-' or '^' or '#')) - || (l.Length == 6 && l[..2] is "19" or "20" && l[4] == '.') - || (l.Length == 7 && l.Substring(1, 2) is "19" or "20" && l[5] == '.') + l[..2] is "18" or "19" or "20" + || (l.Length == 5 && l.Substring(1, 2) is "18" or "19" or "20" && (l[0] is '~' or '=' or '-' or '^' or '#')) + || (l.Length == 6 && l[..2] is "18" or "19" or "20" && l[4] == '.') + || (l.Length == 7 && l.Substring(1, 2) is "18" or "19" or "20" && l[5] == '.') ) select l ).ToArray(); diff --git a/Tests/UnitTestCalculations.cs b/Tests/UnitTestCalculations.cs new file mode 100644 index 0000000..ab91055 --- /dev/null +++ b/Tests/UnitTestCalculations.cs @@ -0,0 +1,80 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Phares.Shared; +using Serilog; +using System.Diagnostics; +using System.Reflection; +using View_by_Distance.Shared.Models.Stateless.Methods; +using View_by_Distance.Tests.Models; + +namespace View_by_Distance.Tests; + +[TestClass] +public class UnitTestCalculations +{ + + private readonly ILogger _Logger; + private readonly AppSettings _AppSettings; + private readonly string _WorkingDirectory; + private readonly Configuration _Configuration; + private readonly IsEnvironment _IsEnvironment; + private readonly IConfigurationRoot _ConfigurationRoot; + private readonly Property.Models.Configuration _PropertyConfiguration; + + public UnitTestCalculations() + { + ILogger logger; + AppSettings appSettings; + string workingDirectory; + Configuration configuration; + IsEnvironment isEnvironment; + IConfigurationRoot configurationRoot; + LoggerConfiguration loggerConfiguration = new(); + Property.Models.Configuration propertyConfiguration; + Assembly assembly = Assembly.GetExecutingAssembly(); + bool debuggerWasAttachedAtLineZero = Debugger.IsAttached || assembly.Location.Contains(@"\bin\Debug"); + isEnvironment = new(processesCount: null, nullASPNetCoreEnvironmentIsDevelopment: debuggerWasAttachedAtLineZero, nullASPNetCoreEnvironmentIsProduction: !debuggerWasAttachedAtLineZero); + IConfigurationBuilder configurationBuilder = new ConfigurationBuilder() + .AddEnvironmentVariables() + .AddJsonFile(isEnvironment.AppSettingsFileName); + configurationRoot = configurationBuilder.Build(); + appSettings = Models.Binder.AppSettings.Get(configurationRoot); + workingDirectory = IWorkingDirectory.GetWorkingDirectory(assembly.GetName().Name, appSettings.WorkingDirectoryName); + Environment.SetEnvironmentVariable(nameof(workingDirectory), workingDirectory); + _ = ConfigurationLoggerConfigurationExtensions.Configuration(loggerConfiguration.ReadFrom, configurationRoot); + Log.Logger = loggerConfiguration.CreateLogger(); + logger = Log.ForContext(); + propertyConfiguration = Property.Models.Binder.Configuration.Get(isEnvironment, configurationRoot); + configuration = Models.Binder.Configuration.Get(isEnvironment, configurationRoot, propertyConfiguration); + logger.Information("Complete"); + _Logger = logger; + _AppSettings = appSettings; + _Configuration = configuration; + _IsEnvironment = isEnvironment; + _WorkingDirectory = workingDirectory; + _ConfigurationRoot = configurationRoot; + _PropertyConfiguration = propertyConfiguration; + } + + [TestMethod] + public void TestMethodNull() + { + Assert.IsFalse(_Logger is null); + Assert.IsFalse(_AppSettings is null); + Assert.IsFalse(_Configuration is null); + Assert.IsFalse(_IsEnvironment is null); + Assert.IsFalse(_WorkingDirectory is null); + Assert.IsFalse(_ConfigurationRoot is null); + Assert.IsFalse(_PropertyConfiguration is null); + } + + [TestMethod] + public void TestMethodGetAge() + { + Shared.Models.PersonBirthday personBirthday = new(new(1980, 1, 17)); + double? age = IPersonBirthday.GetAge(personBirthday); + Assert.IsNotNull(age); + Assert.IsTrue(age.Value > 42.6092); + } + +} \ No newline at end of file