using System.Diagnostics; using System.Text.Json; using View_by_Distance.Property.Models; namespace View_by_Distance.Map.Models; public class MapLogic { protected readonly List<(int, string[])> _AllCollection; protected readonly Dictionary _KeyValuePairs; protected readonly Dictionary _IndicesFromNew; protected readonly string _DeterministicHashCodeRootDirectory; protected readonly Dictionary _SixCharacterNamedFaceInfo; protected readonly Dictionary _NamedFaceInfoDeterministicHashCodeKeyValuePairs; protected readonly Dictionary _NamedDeterministicHashCodeKeyValuePairs; protected readonly Dictionary _IncorrectDeterministicHashCodeKeyValuePairs; public Dictionary KeyValuePairs => _KeyValuePairs; public Dictionary IndicesFromNew => _IndicesFromNew; public string DeterministicHashCodeRootDirectory => _DeterministicHashCodeRootDirectory; public Dictionary NamedDeterministicHashCodeKeyValuePairs => _NamedDeterministicHashCodeKeyValuePairs; public Dictionary IncorrectDeterministicHashCodeKeyValuePairs => _IncorrectDeterministicHashCodeKeyValuePairs; public Dictionary NamedFaceInfoDeterministicHashCodeKeyValuePairs => _NamedFaceInfoDeterministicHashCodeKeyValuePairs; private readonly Serilog.ILogger? _Log; private readonly Configuration _Configuration; public MapLogic(int maxDegreeOfParallelism, Configuration configuration) { _AllCollection = new(); _Configuration = configuration; _Log = Serilog.Log.ForContext(); Dictionary? namedFaceInfoDeterministicHashCode; if (configuration.VerifyToSeason is null || !configuration.VerifyToSeason.Any()) throw new Exception(); string json; string[] files; string fullPath; Dictionary? keyValuePairs; string deterministicHashCodeRootDirectory; List>? collection; Dictionary indicesFromNew = new(); Dictionary? sixCharacterNamedFaceInfo; Dictionary namedDeterministicHashCode = new(); Dictionary incorrectDeterministicHashCode = new(); string? rootDirectoryParent = Path.GetDirectoryName(configuration.RootDirectory); if (string.IsNullOrEmpty(rootDirectoryParent)) throw new NullReferenceException(nameof(rootDirectoryParent)); files = Directory.GetFiles(rootDirectoryParent, "*DeterministicHashCode*.json", SearchOption.TopDirectoryOnly); if (files.Length != 1) namedFaceInfoDeterministicHashCode = new(); else { json = File.ReadAllText(files[0]); namedFaceInfoDeterministicHashCode = JsonSerializer.Deserialize>(json); if (namedFaceInfoDeterministicHashCode is null) throw new NullReferenceException(nameof(namedFaceInfoDeterministicHashCode)); } string[] directories = Directory.GetDirectories(rootDirectoryParent, "*DeterministicHashCode*", SearchOption.TopDirectoryOnly); if (!directories.Any()) deterministicHashCodeRootDirectory = string.Empty; else { Dictionary> faces = new(); deterministicHashCodeRootDirectory = directories[0]; SetKeyValuePairs(deterministicHashCodeRootDirectory, namedDeterministicHashCode, incorrectDeterministicHashCode, faces); } if (!namedFaceInfoDeterministicHashCode.Any()) sixCharacterNamedFaceInfo = new(); else { files = Directory.GetFiles(rootDirectoryParent, "*SixCharacter*.json", SearchOption.TopDirectoryOnly); if (files.Length != 1) sixCharacterNamedFaceInfo = new(); else { json = File.ReadAllText(files[0]); sixCharacterNamedFaceInfo = JsonSerializer.Deserialize>(json); if (sixCharacterNamedFaceInfo is null) throw new NullReferenceException(nameof(sixCharacterNamedFaceInfo)); } } files = Directory.GetFiles(rootDirectoryParent, "*keyValuePairs*.json", SearchOption.TopDirectoryOnly); if (files.Length != 1) keyValuePairs = new(); else { json = File.ReadAllText(files[0]); keyValuePairs = JsonSerializer.Deserialize>(json); if (keyValuePairs is null) throw new NullReferenceException(nameof(keyValuePairs)); } foreach (string propertyContentCollectionFile in configuration.PropertyContentCollectionFiles) { fullPath = Path.GetFullPath(string.Concat(rootDirectoryParent, propertyContentCollectionFile)); if (fullPath.Contains(configuration.RootDirectory)) continue; if (!File.Exists(fullPath)) continue; json = File.ReadAllText(fullPath); collection = JsonSerializer.Deserialize>>(json); if (collection is null) throw new NullReferenceException(nameof(collection)); foreach (KeyValuePair keyValuePair in collection) { if (indicesFromNew.ContainsKey(keyValuePair.Key)) continue; indicesFromNew.Add(keyValuePair.Key, keyValuePair.Value); } } _KeyValuePairs = keyValuePairs; _IndicesFromNew = indicesFromNew; _SixCharacterNamedFaceInfo = sixCharacterNamedFaceInfo; _NamedDeterministicHashCodeKeyValuePairs = namedDeterministicHashCode; _DeterministicHashCodeRootDirectory = deterministicHashCodeRootDirectory; _IncorrectDeterministicHashCodeKeyValuePairs = incorrectDeterministicHashCode; _NamedFaceInfoDeterministicHashCodeKeyValuePairs = namedFaceInfoDeterministicHashCode; } private static void SetKeyValuePairs(string deterministicHashCodeRootDirectory, List<(string, double)> named, List<(string, double)> incorrect, Dictionary> keyValuePairs) { string[] files; string fileName; string personKey; string? checkFile; string[] yearDirectories; string[] personKeyDirectories; string[] personNameDirectories; double? idAndNormalizedPixelPercentage; string[] ticksDirectories = Directory.GetDirectories(deterministicHashCodeRootDirectory, "*", SearchOption.TopDirectoryOnly); foreach (string ticksDirectory in ticksDirectories) { if (!ticksDirectory.EndsWith(')')) continue; personKeyDirectories = Directory.GetDirectories(ticksDirectory, "*", SearchOption.TopDirectoryOnly); foreach (string personKeyDirectory in personKeyDirectories) { personKey = Path.GetFileName(personKeyDirectory); 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) { if (file.EndsWith(".lnk")) continue; fileName = Path.GetFileName(file); idAndNormalizedPixelPercentage = Shared.Models.Stateless.Methods.INamed.GetReversedDeterministicHashCode(fileName); if (idAndNormalizedPixelPercentage is null) { (checkFile, idAndNormalizedPixelPercentage) = Shared.Models.Stateless.Methods.INamed.GetReversedDeterministicHashCode(keyValuePairs, file); if (idAndNormalizedPixelPercentage is null) break; if (!string.IsNullOrEmpty(checkFile)) File.Move(file, checkFile); } incorrect.Add(new(personKey, idAndNormalizedPixelPercentage.Value)); } foreach (string personNameDirectory in personNameDirectories) { files = Directory.GetFiles(personNameDirectory, "*", SearchOption.TopDirectoryOnly); foreach (string file in files) { if (file.EndsWith(".lnk")) continue; fileName = Path.GetFileName(file); idAndNormalizedPixelPercentage = Shared.Models.Stateless.Methods.INamed.GetReversedDeterministicHashCode(fileName); if (idAndNormalizedPixelPercentage is null) { (checkFile, idAndNormalizedPixelPercentage) = Shared.Models.Stateless.Methods.INamed.GetReversedDeterministicHashCode(keyValuePairs, file); if (idAndNormalizedPixelPercentage is null) break; if (!string.IsNullOrEmpty(checkFile)) File.Move(file, checkFile); } named.Add(new(personKey, idAndNormalizedPixelPercentage.Value)); } } } } } } private static void SetKeyValuePairs(string deterministicHashCodeRootDirectory, Dictionary namedDeterministicHashCode, Dictionary incorrectDeterministicHashCode, Dictionary> keyValuePairs) { 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 (!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) namedDeterministicHashCode.Add(keyValuePair.Key, keyValuePair.Value.Distinct().ToArray()); foreach (KeyValuePair> keyValuePair in incorrectKeyValuePairs) incorrectDeterministicHashCode.Add(keyValuePair.Key, keyValuePair.Value.Distinct().ToArray()); } public void UpdateKeyValuePairs(List containers) { Dictionary> keyValuePairs = new(); Dictionary namedDeterministicHashCode = new(); Dictionary incorrectDeterministicHashCode = new(); foreach (Shared.Models.Container container in containers) { foreach (Shared.Models.Item item in container.Items) { if (item.ImageFileHolder is null || item.Property?.Id is null || !item.Faces.Any()) continue; if (keyValuePairs.ContainsKey(item.Property.Id.Value)) { if (keyValuePairs[item.Property.Id.Value].Count != item.Faces.Count) throw new Exception(); continue; } keyValuePairs.Add(item.Property.Id.Value, item.Faces); } } SetKeyValuePairs(_DeterministicHashCodeRootDirectory, namedDeterministicHashCode, incorrectDeterministicHashCode, keyValuePairs); foreach (KeyValuePair keyValuePair in namedDeterministicHashCode) _NamedDeterministicHashCodeKeyValuePairs.Add(keyValuePair.Key, keyValuePair.Value); foreach (KeyValuePair keyValuePair in incorrectDeterministicHashCode) _IncorrectDeterministicHashCodeKeyValuePairs.Add(keyValuePair.Key, keyValuePair.Value); } public override string ToString() { string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); return result; } private long LogDelta(long ticks, string? methodName) { long result; if (_Log is null) throw new NullReferenceException(nameof(_Log)); double delta = new TimeSpan(DateTime.Now.Ticks - ticks).TotalMilliseconds; _Log.Debug($"{methodName} took {Math.Floor(delta)} millisecond(s)"); result = DateTime.Now.Ticks; return result; } public void AddToMapLogicAllCollection(Shared.Models.Item[] filteredItems) { if (_SixCharacterNamedFaceInfo.Any()) { string[] keys; Shared.Models.Item item; for (int i = 0; i < filteredItems.Length; i++) { item = filteredItems[i]; if (item.Property?.Id is null) continue; foreach (int sixCharacterIndex in item.Property.Indices) { if (!_SixCharacterNamedFaceInfo.ContainsKey(sixCharacterIndex)) continue; keys = _SixCharacterNamedFaceInfo[sixCharacterIndex]; _AllCollection.Add(new(item.Property.Id.Value, keys)); } } } } public void SaveAllCollection() { if (_AllCollection.Any()) { string[] keys; long ticks = DateTime.Now.Ticks; string? rootDirectoryParent = Path.GetDirectoryName(_Configuration.RootDirectory); if (string.IsNullOrEmpty(rootDirectoryParent)) throw new NullReferenceException(nameof(rootDirectoryParent)); Dictionary namedFaceInfoDeterministicHashCodeIndices = new(); List<(int, string[])> allCollection = _AllCollection.OrderBy(l => l.Item1).ToList(); foreach ((int deterministicHashCode, string[] values) in allCollection) { if (namedFaceInfoDeterministicHashCodeIndices.ContainsKey(deterministicHashCode)) { keys = namedFaceInfoDeterministicHashCodeIndices[deterministicHashCode]; if (JsonSerializer.Serialize(values) == JsonSerializer.Serialize(keys)) continue; throw new Exception(); } namedFaceInfoDeterministicHashCodeIndices.Add(deterministicHashCode, values); } string json = JsonSerializer.Serialize(namedFaceInfoDeterministicHashCodeIndices, new JsonSerializerOptions { WriteIndented = true }); string checkFile = Path.Combine(rootDirectoryParent, "NamedFaceInfoDeterministicHashCodeIndices.json"); _ = Shared.Models.Stateless.Methods.IPath.WriteAllText(checkFile, json, updateDateWhenMatches: true, compareBeforeWrite: true); _ = LogDelta(ticks, new StackFrame().GetMethod()?.Name); } } public static void AddToNamed(MapLogic mapLogic, List items) { bool? isWrongYear; DateTime minimumDateTime; double deterministicHashCodeKey; List personKeys = new(); Shared.Models.PersonBirthday? personBirthday; foreach (Shared.Models.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) { personKeys.Clear(); if (face.LocationIndex is null) continue; deterministicHashCodeKey = Shared.Models.Stateless.Methods.INamed.GetDeterministicHashCodeKey(item, face); if (!mapLogic.NamedDeterministicHashCodeKeyValuePairs.ContainsKey(deterministicHashCodeKey)) continue; minimumDateTime = Shared.Models.Stateless.Methods.IProperty.GetMinimumDateTime(item.Property); personKeys.AddRange(mapLogic.NamedDeterministicHashCodeKeyValuePairs[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 (personBirthday is null) continue; if (face.Location is null) continue; item.Named.Add(new(isWrongYear, minimumDateTime, face.Location.NormalizedPixelPercentage, personBirthday)); } } if (!personKeys.Any()) { if (!mapLogic.NamedFaceInfoDeterministicHashCodeKeyValuePairs.ContainsKey(item.Property.Id.Value)) continue; minimumDateTime = Shared.Models.Stateless.Methods.IProperty.GetMinimumDateTime(item.Property); personKeys.AddRange(mapLogic.NamedFaceInfoDeterministicHashCodeKeyValuePairs[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 (personBirthday is null) continue; item.Named.Add(new(isWrongYear, minimumDateTime, personBirthday)); } } } } public static List<(Shared.Models.Item, (string, Shared.Models.Face?, (string, string, string, string))[])> GetCollection(MapLogic mapLogic, List items, string dFacesContentDirectory) { List<(Shared.Models.Item, (string, Shared.Models.Face?, (string, string, string, string))[])> results = new(); string[] keys; string directory; string personKey; bool? isWrongYear; const int zero = 0; TimeSpan? timeSpan; string copyFileName; 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; for (int i = 0; i < items.Count; i++) { indices.Clear(); copyFileName = string.Empty; copyDirectory = string.Empty; item = items[i]; if (item.ImageFileHolder is null) continue; relativePath = Path.GetDirectoryName($"C:{item.RelativePath}"); if (string.IsNullOrEmpty(relativePath) || relativePath.Length < 3) continue; if (item.Property?.Id is null || item.ResizedFileHolder is null) continue; collection = new(); if (!mapLogic.NamedFaceInfoDeterministicHashCodeKeyValuePairs.ContainsKey(item.Property.Id.Value)) { faceCollection = new(); personKey = string.Empty; directory = Path.Combine(dFacesContentDirectory, $"Unnamed{relativePath[2..]}"); } else { faceCollection = item.Faces; keys = mapLogic.NamedFaceInfoDeterministicHashCodeKeyValuePairs[item.Property.Id.Value]; minimumDateTime = Shared.Models.Stateless.Methods.IProperty.GetMinimumDateTime(item.Property); if (minimumDateTime is null) continue; (isWrongYear, _) = item.Property.IsWrongYear(item.ImageFileHolder.FullName, minimumDateTime.Value); isWrongYearFlag = Shared.Models.Stateless.Methods.IItem.GetWrongYearFlag(isWrongYear); subDirectoryName = $"{isWrongYearFlag}{minimumDateTime.Value:yyyy}"; if (!faceCollection.Any()) { personKey = string.Empty; directory = Path.Combine(dFacesContentDirectory, $"None{relativePath[2..]}", subDirectoryName); } else if (keys.Length != 1) { personKey = string.Empty; directory = Path.Combine(dFacesContentDirectory, $"Not Supported{relativePath[2..]}", subDirectoryName); } else if (faceCollection.Count != 1) { personKey = string.Empty; directory = Path.Combine(dFacesContentDirectory, $"Many{relativePath[2..]}", subDirectoryName); } else { indices.Add(zero); personKey = keys[zero]; personBirthday = Shared.Models.Stateless.Methods.IPersonBirthday.GetPersonBirthday(personKey); if (personBirthday is null) continue; timeSpan = Shared.Models.Stateless.Methods.IPersonBirthday.GetTimeSpan(minimumDateTime.Value, isWrongYear, personBirthday); if (timeSpan.HasValue) { if (timeSpan.Value.Ticks < 0) subDirectoryName = "!---"; else subDirectoryName = $"^{Math.Floor(timeSpan.Value.TotalDays / 365):000}"; } face = faceCollection[zero]; directory = Path.Combine(dFacesContentDirectory, "Shortcuts", personKey, subDirectoryName); if (face.FaceEncoding is not null && face.Location?.NormalizedPixelPercentage is not null) copyDirectory = Path.Combine(dFacesContentDirectory, "Images", personKey, subDirectoryName); else copyDirectory = Path.Combine(dFacesContentDirectory, "ImagesBut", personKey, subDirectoryName); copyFileName = Path.Combine(copyDirectory, $"{item.Property.Id.Value}{item.ResizedFileHolder.ExtensionLowered}"); } } shortcutFileName = Path.Combine(directory, $"{item.Property.Id.Value}.lnk"); if (string.IsNullOrEmpty(personKey) || !indices.Any()) collection.Add(new(personKey, null, (directory, copyDirectory, copyFileName, shortcutFileName))); else { foreach (int index in indices) collection.Add(new(personKey, faceCollection[index], (directory, copyDirectory, copyFileName, shortcutFileName))); } results.Add(new(item, collection.ToArray())); } return results; } public static string GetKey(DateTime minimumDateTime, bool? isWrongYear, Shared.Models.PersonBirthday personBirthday) { 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) { if (!container.Items.Any()) continue; if (!container.SourceDirectory.StartsWith(argZero)) continue; foreach (Shared.Models.Item item in container.Items) { if (item.ImageFileHolder is null || item.Property is null || !item.Named.Any()) continue; foreach (Shared.Models.Named named in item.Named) { if (named.NormalizedPixelPercentage is null && (item.Named.Count != 1 || item.Faces.Count != 1)) continue; foreach (Shared.Models.Face face in item.Faces) { if (face.FaceEncoding is null || face.Location?.NormalizedPixelPercentage is null) continue; if (named.PersonBirthday is null) 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) break; } } } } return results; } public static (bool?, string[]) IsWrongYear(Shared.Models.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; } }