diff --git a/.kanbn/tasks/family-tree-as-markdown-files.md b/.kanbn/tasks/family-tree-as-markdown-files.md index ad46ed2..81bbac3 100644 --- a/.kanbn/tasks/family-tree-as-markdown-files.md +++ b/.kanbn/tasks/family-tree-as-markdown-files.md @@ -9,4 +9,4 @@ tags: [] # Family Tree as Markdown Files - [ ] Look at how I can export from ged files - +- [x] In addition to writing the *.pged maybe write *.md also diff --git a/Instance/DlibDotNet.cs b/Instance/DlibDotNet.cs index cf4b4f0..646a2d0 100644 --- a/Instance/DlibDotNet.cs +++ b/Instance/DlibDotNet.cs @@ -41,8 +41,6 @@ public partial class DlibDotNet private readonly List _PersonContainers; private readonly bool _ArgZeroIsConfigurationRootDirectory; private readonly Map.Models.Configuration _MapConfiguration; - private readonly string[]? _GenealogicalDataCommunicationFooterLines; - private readonly string[]? _GenealogicalDataCommunicationHeaderLines; public DlibDotNet( List args, @@ -111,11 +109,7 @@ public partial class DlibDotNet _MapConfiguration = Get(configuration, _Faces.FileNameExtension, _Faces.HiddenFileNameExtension, _FaceParts.FileNameExtension); _Distance = new(configuration.DistanceMoveUnableToMatch, configuration.DistanceRenameToMatch, _Configuration.FaceConfidencePercent, configuration.RangeDistanceTolerance, configuration.RangeFaceConfidence, configuration.RectangleIntersectMinimums); if (_PropertyRootExistedBefore || !_ArgZeroIsConfigurationRootDirectory) - { - _GenealogicalDataCommunicationFooterLines = null; - _GenealogicalDataCommunicationHeaderLines = null; _PersonContainers = new(); - } else { int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); @@ -128,9 +122,20 @@ public partial class DlibDotNet string? rootResultsDirectory = Path.GetDirectoryName(Path.GetDirectoryName(peopleRootDirectory)) ?? throw new Exception(); Storage storage = new(rootDirectory, rootResultsDirectory, peopleRootDirectory); _ = Shared.Models.Stateless.Methods.IPath.DeleteEmptyDirectories(Path.Combine(peopleRootDirectory, _Configuration.PropertyConfiguration.ResultSingleton)); - (_GenealogicalDataCommunicationHeaderLines, Dictionary> individuals, _GenealogicalDataCommunicationFooterLines) = IGenealogicalDataCommunication.GetIndividuals(configuration.GenealogicalDataCommunicationFile, requireNickName: true); + (string[] headerLines, ReadOnlyDictionary individuals, List familyGroupLines, string[] footerLines, List genealogicalDataCommunicationRelations) = IGenealogicalDataCommunication.GetIndividuals(configuration.GenealogicalDataCommunicationFile, requireNickName: true); _PersonContainers = IPersonContainer.GetPersonContainers(storage, configuration.MappingDefaultName, configuration.PersonBirthdayFormat, configuration.PersonCharacters.ToArray(), _Faces.FileNameExtension, individuals); VerifyPersonContainersDisplayDirectoryAllFiles(); + if (!string.IsNullOrEmpty(_Configuration.GenealogicalDataCommunicationFile) && headerLines.Any() && familyGroupLines.Any() && genealogicalDataCommunicationRelations.Any() && footerLines.Any()) + { + string a2PeopleContentDirectory = Property.Models.Stateless.IResult.GetResultsDateGroupDirectory(_Configuration.PropertyConfiguration, nameof(A2_People), "([])"); + TimeSpan a2LastWriteTimeTimeSpan = new(ticks - new DirectoryInfo(a2PeopleContentDirectory).LastWriteTime.Ticks); + if (a2LastWriteTimeTimeSpan.TotalDays > 1) + { + IPersonContainer.MaybeWriteFiles(configuration.MappingDefaultName, configuration.PersonBirthdayFormat, genealogicalDataCommunicationRelations, _PersonContainers); + if (IGenealogicalDataCommunication.CleanDisplayDirectoryAllFilesAndWriteTicksGed(_Configuration.MappingDefaultName, _Configuration.PersonBirthdayFormat, _PersonContainers, headerLines, familyGroupLines, footerLines, ticks, a2PeopleContentDirectory)) + _PersonContainers = IPersonContainer.GetPersonContainers(storage, configuration.MappingDefaultName, configuration.PersonBirthdayFormat, configuration.PersonCharacters.ToArray(), _Faces.FileNameExtension, individuals); + } + } } { (ImageCodecInfo imageCodecInfo, EncoderParameters encoderParameters, string filenameExtension) = C_Resize.GetTuple( @@ -1150,6 +1155,7 @@ public partial class DlibDotNet int t; int count; string message; + MapLogic? mapLogic; Container[] containers; A_Property propertyLogic; string eDistanceContentDirectory; @@ -1167,17 +1173,23 @@ public partial class DlibDotNet List? filesCollection = null; const string directorySearchFilter = "*"; Dictionary> personKeyToIds; - bool eLastWriteTimeTimeSpanIsMoreThen = false; + bool configurationOutputResolutionsHas = false; Dictionary> fileNameToCollection; (aResultsFullGroupDirectory, bResultsFullGroupDirectory) = GetResultsFullGroupDirectories(); ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; + a2PeopleContentDirectory = Property.Models.Stateless.IResult.GetResultsDateGroupDirectory(_Configuration.PropertyConfiguration, nameof(A2_People), "([])"); eDistanceContentDirectory = Property.Models.Stateless.IResult.GetResultsDateGroupDirectory(_Configuration.PropertyConfiguration, nameof(E_Distance), _Configuration.PropertyConfiguration.ResultContent); + string a2PeopleSingletonDirectory = Property.Models.Stateless.IResult.GetResultsDateGroupDirectory(_Configuration.PropertyConfiguration, nameof(A2_People), _Configuration.PropertyConfiguration.ResultSingleton); TimeSpan eLastWriteTimeTimeSpan = new(ticks - new DirectoryInfo(eDistanceContentDirectory).LastWriteTime.Ticks); + if (eLastWriteTimeTimeSpan.TotalDays < 1) + mapLogic = null; + else + mapLogic = new(_AppSettings.MaxDegreeOfParallelism, _Configuration.PropertyConfiguration, _MapConfiguration, new(_PersonContainers), ticks, a2PeopleSingletonDirectory, eDistanceContentDirectory); foreach (string outputResolution in _Configuration.OutputResolutions) { if (outputResolution.Any(l => char.IsNumber(l))) continue; - eLastWriteTimeTimeSpanIsMoreThen = true; + configurationOutputResolutionsHas = true; if (eLastWriteTimeTimeSpan.TotalDays < 1) break; ProgressBar progressBar; @@ -1196,10 +1208,8 @@ public partial class DlibDotNet progressBar.Dispose(); break; } - a2PeopleContentDirectory = Property.Models.Stateless.IResult.GetResultsDateGroupDirectory(_Configuration.PropertyConfiguration, nameof(A2_People), "([])"); fPhotoPrismContentDirectory = Property.Models.Stateless.IResult.GetResultsDateGroupDirectory(_Configuration.PropertyConfiguration, nameof(F_PhotoPrism), _Configuration.PropertyConfiguration.ResultContent); fPhotoPrismSingletonDirectory = Property.Models.Stateless.IResult.GetResultsDateGroupDirectory(_Configuration.PropertyConfiguration, nameof(F_PhotoPrism), _Configuration.PropertyConfiguration.ResultSingleton); - string a2PeopleSingletonDirectory = Property.Models.Stateless.IResult.GetResultsDateGroupDirectory(_Configuration.PropertyConfiguration, nameof(A2_People), _Configuration.PropertyConfiguration.ResultSingleton); propertyLogic = new(_AppSettings.MaxDegreeOfParallelism, _Configuration.PropertyConfiguration, _Resize.FileNameExtension, _Configuration.Reverse, aResultsFullGroupDirectory); if (filesCollectionCountIsOne) { @@ -1221,7 +1231,7 @@ public partial class DlibDotNet propertyRoot = Property.Models.Stateless.IResult.GetResultsGroupDirectory(_Configuration.PropertyConfiguration, nameof(A_Property), create: false); propertyLogic = new(_AppSettings.MaxDegreeOfParallelism, _Configuration.PropertyConfiguration, _Resize.FileNameExtension, _Configuration.Reverse, aResultsFullGroupDirectory); } - if (eLastWriteTimeTimeSpanIsMoreThen) + if (configurationOutputResolutionsHas) { foreach (string outputResolution in _Configuration.OutputResolutions) { @@ -1247,19 +1257,14 @@ public partial class DlibDotNet } fileNameToCollection = !Directory.Exists(fPhotoPrismSingletonDirectory) ? fileNameToCollection = new() : F_PhotoPrism.GetFileNameToCollection(fPhotoPrismSingletonDirectory); B_Metadata metadata = new(_Configuration.PropertyConfiguration, _Configuration.ForceMetadataLastWriteTimeToCreationTime, _Configuration.PropertiesChangedForMetadata, bResultsFullGroupDirectory); - MapLogic mapLogic = new(_AppSettings.MaxDegreeOfParallelism, _Configuration.PropertyConfiguration, _MapConfiguration, new(_PersonContainers), ticks, a2PeopleSingletonDirectory, eDistanceContentDirectory); + mapLogic ??= new(_AppSettings.MaxDegreeOfParallelism, _Configuration.PropertyConfiguration, _MapConfiguration, new(_PersonContainers), ticks, a2PeopleSingletonDirectory, eDistanceContentDirectory); _PersonContainers.AddRange(IPersonContainer.GetNonSpecificPeopleCollection(_Configuration.MappingDefaultName, _Configuration.PersonBirthdayFirstYear, _Configuration.PersonCharacters.ToArray(), _PersonContainers, ticks)); containers = Shared.Models.Stateless.Methods.IContainer.SortContainers(_Configuration.PropertyConfiguration, _Configuration.IgnoreRelativePaths, _ArgZeroIsConfigurationRootDirectory, argZero, containers); - personKeyToIds = mapLogic.GetPersonKeyToIds(); - if (!string.IsNullOrEmpty(_Configuration.GenealogicalDataCommunicationFile) && !string.IsNullOrEmpty(a2PeopleContentDirectory) && _GenealogicalDataCommunicationHeaderLines is not null && _GenealogicalDataCommunicationFooterLines is not null && _GenealogicalDataCommunicationHeaderLines.Any() && _GenealogicalDataCommunicationFooterLines.Any()) - IGenealogicalDataCommunication.CreateTree(_Configuration.MappingDefaultName, _Configuration.PersonBirthdayFormat, _Configuration.PropertyConfiguration.ResultAllInOne, _PersonContainers, _GenealogicalDataCommunicationHeaderLines, _GenealogicalDataCommunicationFooterLines, ticks, a2PeopleContentDirectory, personKeyToIds); ReadOnlyDictionary>> idToLocationContainers = mapLogic.GetIdToLocationContainers(); FullDoWork(argZero, propertyRoot, ticks, aResultsFullGroupDirectory, bResultsFullGroupDirectory, t, containers, propertyLogic, metadata, fileNameToCollection, idToLocationContainers, mapLogic); if (_Configuration.LookForAbandoned) LookForAbandoned(bResultsFullGroupDirectory, containers, idToLocationContainers); _Distance.Clear(); - if (!personKeyToIds.Any()) - personKeyToIds = mapLogic.GetPersonKeyToIds(); List distinctFilteredItems = Shared.Models.Stateless.Methods.IContainer.GetItems(_Configuration.PropertyConfiguration, containers, distinctItems: true, filterItems: true); Verify(eDistanceContentDirectory, distinctFilteredItems); List distinctFilteredFaces = Map.Models.Stateless.Methods.IMapLogic.GetFaces(distinctFilteredItems); @@ -1273,6 +1278,7 @@ public partial class DlibDotNet { if (_PropertyRootExistedBefore) break; + personKeyToIds = mapLogic.GetPersonKeyToIds(); if (!string.IsNullOrEmpty(a2PeopleContentDirectory) && _Configuration.SaveShortcutsForOutputResolutions.Contains(outputResolution)) mapLogic.SaveShortcutsForOutputResolutionsPreMapLogic(eDistanceContentDirectory, personKeyToIds, distinctFilteredMappingCollection); if (!string.IsNullOrEmpty(a2PeopleContentDirectory) && _Configuration.SaveFilteredOriginalImagesFromJLinksForOutputResolutions.Contains(outputResolution)) diff --git a/Map/Models/Stateless/MapLogic.cs b/Map/Models/Stateless/MapLogic.cs index 0f362eb..21f8768 100644 --- a/Map/Models/Stateless/MapLogic.cs +++ b/Map/Models/Stateless/MapLogic.cs @@ -13,6 +13,10 @@ namespace View_by_Distance.Map.Models.Stateless; internal abstract class MapLogic { + private record Record(string PersonKeyFormatted, + string[] PersonDisplayDirectoryNames, + string MappedFaceFile); + private static void SetPersonCollections(Configuration configuration, ReadOnlyCollection personContainers, string? a2PeopleSingletonDirectory, Dictionary personKeyFormattedToNewestPersonKeyFormatted, List personKeyFormattedCollection, Dictionary> skipCollection, Dictionary> skipNotSkipCollection) { int? id; @@ -150,12 +154,45 @@ internal abstract class MapLogic } } - internal static List<(string, string[], string)> DeleteEmptyDirectoriesAndGetCollection(Configuration configuration, List personKeyFormattedCollection, string[] ticksDirectories, string message) + private static void MoveFiles(string personKeyFormatted, string personKeyDirectory, string newestPersonKeyFormatted, string newestPersonKeyDirectory) { - List<(string, string[], string)> results = new(); + string[] files; + string checkFile; + string? checkDirectory; + string[] directories = Directory.GetDirectories(personKeyDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string directory in directories) + { + checkDirectory = Path.Combine(newestPersonKeyDirectory, Path.GetFileName(directory)); + if (!Directory.Exists(checkDirectory)) + Directory.Move(directory, checkDirectory); + else + { + files = Directory.GetFiles(directory, "*", SearchOption.AllDirectories); + foreach (string file in files) + { + if (file.Split(personKeyFormatted).Length != 2 || file.Contains(newestPersonKeyFormatted)) + continue; + checkFile = file.Replace(personKeyFormatted, newestPersonKeyFormatted); + checkDirectory = Path.GetDirectoryName(checkFile); + if (checkDirectory is null) + continue; + if (File.Exists(checkFile)) + continue; + if (!Directory.Exists(checkDirectory)) + _ = Directory.CreateDirectory(checkDirectory); + File.Move(file, checkFile); + } + } + } + } + + private static List DeleteEmptyDirectoriesAndGetCollection(Configuration configuration, Dictionary personKeyFormattedToNewestPersonKeyFormatted, List personKeyFormattedCollection, string[] ticksDirectories, string message) + { + List results = new(); int? id; string[] files; string fileName; + bool check = false; const int zero = 0; int? wholePercentages; string[] yearDirectories; @@ -166,6 +203,8 @@ internal abstract class MapLogic List distinct = new(); string[] personKeyDirectories; string[] personNameDirectories; + string newestPersonKeyDirectory; + string? newestPersonKeyFormatted; string[] personNameLinkDirectories; string? personFirstInitialDirectory; string[] personDisplayDirectoryNames; @@ -189,6 +228,17 @@ internal abstract class MapLogic Individually(configuration, ticksDirectory, personKeyDirectory); throw new Exception($"B) Move personKey directories up one from {nameof(Shared.Models.Stateless.IMapLogic.Sorting)} and delete {nameof(Shared.Models.Stateless.IMapLogic.Sorting)} directory!"); } + if (personKeyFormattedToNewestPersonKeyFormatted.TryGetValue(personKeyFormatted, out newestPersonKeyFormatted) && personKeyFormatted != newestPersonKeyFormatted) + { + if (!check) + check = true; + newestPersonKeyDirectory = Path.Combine(ticksDirectory, newestPersonKeyFormatted); + if (Directory.Exists(newestPersonKeyDirectory)) + MoveFiles(personKeyFormatted, personKeyDirectory, newestPersonKeyFormatted, newestPersonKeyDirectory); + else + Directory.Move(personKeyDirectory, newestPersonKeyDirectory); + continue; + } yearDirectories = Directory.GetDirectories(personKeyDirectory, "*", SearchOption.TopDirectoryOnly); foreach (string yearDirectory in yearDirectories) { @@ -201,6 +251,11 @@ internal abstract class MapLogic personDisplayDirectoryNames = IPath.GetDirectoryNames(personNameDirectory); if (!personDisplayDirectoryNames.Any()) continue; + if (newestPersonKeyFormatted is null) + { + if (!IPerson.IsDefaultName(configuration.MappingDefaultName, personDisplayDirectoryNames[^1])) + throw new NotImplementedException("Should this happen?"); + } files = Directory.GetFiles(personNameDirectory, "*", SearchOption.TopDirectoryOnly); if (isReservedDirectoryName && files.Any()) throw new Exception($"Move personKey directories up one from {nameof(Shared.Models.Stateless.IMapLogic.Sorting)} and delete {nameof(Shared.Models.Stateless.IMapLogic.Sorting)} directory!"); @@ -231,22 +286,22 @@ internal abstract class MapLogic Directory.Move(personNameDirectory, personFirstInitialDirectory); files = Directory.GetFiles(personFirstInitialDirectory, "*", SearchOption.TopDirectoryOnly); } - foreach (string file in files) + foreach (string mappedFaceFile in files) { - if (file.EndsWith(".lnk")) + if (mappedFaceFile.EndsWith(".lnk")) continue; - (id, wholePercentages) = IMapping.GetConverted(configuration.FacesFileNameExtension, file); + (id, wholePercentages) = IMapping.GetConverted(configuration.FacesFileNameExtension, mappedFaceFile); if (id is null || wholePercentages is null) continue; - fileName = Path.GetFileName(file); + fileName = Path.GetFileName(mappedFaceFile); if (distinct.Contains(fileName)) { - if (!File.Exists($"{file}.dup")) - File.Move(file, $"{file}.dup"); + if (!File.Exists($"{mappedFaceFile}.dup")) + File.Move(mappedFaceFile, $"{mappedFaceFile}.dup"); continue; } distinct.Add(fileName); - results.Add(new(personKeyFormatted, personDisplayDirectoryNames, file)); + results.Add(new(personKeyFormatted, personDisplayDirectoryNames, mappedFaceFile)); } personNameLinkDirectories = Directory.GetDirectories(personFirstInitialDirectory, "*", SearchOption.TopDirectoryOnly); foreach (string personNameLinkDirectory in personNameLinkDirectories) @@ -269,6 +324,8 @@ internal abstract class MapLogic _ = IPath.DeleteEmptyDirectories(ticksDirectory); _ = IPath.DeleteEmptyDirectories(ticksDirectory); } + if (check) + throw new Exception("PersonKey director(ies) where renamed. Please restart!"); return results; } @@ -363,12 +420,11 @@ internal abstract class MapLogic return results; } - private static int SetCollectionsAndGetUnableToConvertCount(Configuration configuration, long ticks, Dictionary personKeyFormattedToNewestPersonKeyFormatted, List<(string, string[], int, int)> personKeyFormattedIdThenWholePercentagesCollection, List<(string, string[], string)> collection) + private static int SetCollectionsAndGetUnableToConvertCount(Configuration configuration, long ticks, List<(string, string[], int, int)> personKeyFormattedIdThenWholePercentagesCollection, List collection) { int result = 0; int? id; int? wholePercentages; - string? newestPersonKeyFormatted; string personDisplayDirectoryName; List wholePercentagesCollection; Dictionary> idToWholePercentagesCollection = new(); @@ -379,12 +435,6 @@ internal abstract class MapLogic foreach ((string personKeyFormatted, string[] personDisplayDirectoryNames, string mappedFaceFile) in collection) { progressBar.Tick(); - if (!personKeyFormattedToNewestPersonKeyFormatted.TryGetValue(personKeyFormatted, out newestPersonKeyFormatted)) - { - if (personDisplayDirectoryNames.Any() && !IPerson.IsDefaultName(configuration.MappingDefaultName, personDisplayDirectoryNames[^1])) - continue; - newestPersonKeyFormatted = personKeyFormatted; - } (id, wholePercentages) = IMapping.GetConverted(configuration.FacesFileNameExtension, mappedFaceFile); if (id is null || wholePercentages is null) { @@ -399,7 +449,7 @@ internal abstract class MapLogic personDisplayDirectoryName = personDisplayDirectoryNames[^1]; if (string.IsNullOrEmpty(personDisplayDirectoryName)) continue; - personKeyFormattedIdThenWholePercentagesCollection.Add(new(newestPersonKeyFormatted, personDisplayDirectoryNames, id.Value, wholePercentages.Value)); + personKeyFormattedIdThenWholePercentagesCollection.Add(new(personKeyFormatted, personDisplayDirectoryNames, id.Value, wholePercentages.Value)); } return result; } @@ -543,7 +593,7 @@ internal abstract class MapLogic collection.Add((ticksDirectory, new TimeSpan(directoryTicks - lastDirectoryTicks.Value))); lastDirectoryTicks = directoryTicks; } - string[] compare = (from l in collection where l.TimeSpan.TotalDays < 4 select l.Directory).ToArray(); + string[] compare = (from l in collection where l.TimeSpan.TotalDays < 3.95 select l.Directory).ToArray(); if (compare.Any()) throw new Exception($"Please Consolidate <{string.Join(Environment.NewLine, compare)}>"); return results; @@ -552,6 +602,7 @@ internal abstract class MapLogic private static List<(long, string)> GetDisplayDirectoryAllFiles(string fileNameExtension, ReadOnlyCollection personContainers) { List<(long, string)> results = new(); + string fileName; List distinct = new(); foreach (PersonContainer personContainer in personContainers) { @@ -559,35 +610,38 @@ internal abstract class MapLogic continue; foreach (string displayDirectoryAllFile in personContainer.DisplayDirectoryAllFiles) { - if (!displayDirectoryAllFile.EndsWith(fileNameExtension)) + fileName = Path.GetFileName(displayDirectoryAllFile); + if (!fileName.EndsWith(fileNameExtension)) continue; - if (distinct.Contains(displayDirectoryAllFile)) + if (distinct.Contains(fileName)) continue; - distinct.Add(displayDirectoryAllFile); + distinct.Add(fileName); results.Add(new(personContainer.Key.Value, displayDirectoryAllFile)); } } return results; } - private static List<(long PersonKey, string File)> GetCollection(Configuration configuration, ReadOnlyCollection personContainers, List<(string, string[], string)> collection) + private static List<(long PersonKey, string File)> GetCollection(Configuration configuration, ReadOnlyCollection personContainers, List records) { List<(long PersonKey, string File)> results = new(); string file; long personKey; + string fileName; List distinct = new(); PersonBirthday? personBirthday; results.AddRange(GetDisplayDirectoryAllFiles(configuration.FacesFileNameExtension, personContainers)); - foreach ((string personKeyFormatted, _, string mappedFaceFile) in collection) + foreach (Record record in records) { - personBirthday = IPersonBirthday.GetPersonBirthday(configuration.PersonBirthdayFormat, personKeyFormatted); + personBirthday = IPersonBirthday.GetPersonBirthday(configuration.PersonBirthdayFormat, record.PersonKeyFormatted); if (personBirthday is null) continue; - if (distinct.Contains(mappedFaceFile)) + fileName = Path.GetFileName(record.MappedFaceFile); + if (distinct.Contains(fileName)) continue; - distinct.Add(mappedFaceFile); + distinct.Add(fileName); personKey = personBirthday.Value.Ticks; - results.Add(new(personKey, mappedFaceFile)); + results.Add(new(personKey, record.MappedFaceFile)); } for (int i = results.Count - 1; i > -1; i--) { @@ -700,10 +754,10 @@ internal abstract class MapLogic } } - private static List> GetLocationContainers(int maxDegreeOfParallelism, Configuration configuration, long ticks, ReadOnlyCollection personContainers, string eDistanceContentDirectory, List<(string, string[], string)> sourceCollection) + private static List> GetLocationContainers(int maxDegreeOfParallelism, Configuration configuration, long ticks, ReadOnlyCollection personContainers, string eDistanceContentDirectory, List records) { List> results = new(); - List<(long PersonKey, string File)> collection = GetCollection(configuration, personContainers, sourceCollection); + List<(long PersonKey, string File)> collection = GetCollection(configuration, personContainers, records); if (collection.Any() && (configuration.DistanceMoveUnableToMatch || configuration.DistanceRenameToMatch)) { int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); @@ -734,17 +788,16 @@ internal abstract class MapLogic List<(string, string[], int, int)> personKeyFormattedIdThenWholePercentagesCollection = new(); List<(string[], PersonContainer)> possiblyNewPersonDisplayDirectoryNamesAndPersonContainer = new(); SetPersonCollections(configuration, personContainers, a2PeopleSingletonDirectory, personKeyFormattedToNewestPersonKeyFormatted, personKeyFormattedCollection, skipCollection, skipNotSkipCollection); - // personContainers.AddRange(GetNonSpecificPeopleCollection(configuration, ticks, personKeys)); totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); message = $") {ticksDirectories.Length:000} compile from and clean ticks Director(ies) - B - {totalSeconds} total second(s)"; - List<(string, string[], string)> collection = DeleteEmptyDirectoriesAndGetCollection(configuration, personKeyFormattedCollection, ticksDirectories, message); - locationContainers.AddRange(GetLocationContainers(maxDegreeOfParallelism, configuration, ticks, personContainers, eDistanceContentDirectory, collection)); - int unableToMatchCount = SetCollectionsAndGetUnableToConvertCount(configuration, ticks, personKeyFormattedToNewestPersonKeyFormatted, personKeyFormattedIdThenWholePercentagesCollection, collection); + List records = DeleteEmptyDirectoriesAndGetCollection(configuration, personKeyFormattedToNewestPersonKeyFormatted, personKeyFormattedCollection, ticksDirectories, message); + locationContainers.AddRange(GetLocationContainers(maxDegreeOfParallelism, configuration, ticks, personContainers, eDistanceContentDirectory, records)); + int unableToMatchCount = SetCollectionsAndGetUnableToConvertCount(configuration, ticks, personKeyFormattedIdThenWholePercentagesCollection, records); SetKeyValuePairs(configuration, personContainers, personKeyToPersonContainerCollection, personKeyFormattedToPersonContainer, personKeyFormattedIdThenWholePercentagesCollection, personKeyToPersonContainer, idThenWholePercentagesToPersonContainers, possiblyNewPersonDisplayDirectoryNamesAndPersonContainer); totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); - message = $") {collection.Count:000} message from ticks Director(ies) - D - {unableToMatchCount} Unable To Match Count / {collection.Count} Collection - {totalSeconds} total second(s)"; + message = $") {records.Count:000} message from ticks Director(ies) - D - {unableToMatchCount} Unable To Match Count / {records.Count} Collection - {totalSeconds} total second(s)"; ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; - using (ProgressBar progressBar = new(collection.Count, message, options)) + using (ProgressBar progressBar = new(records.Count, message, options)) { foreach (KeyValuePair> keyValuePair in idThenWholePercentagesToPersonContainers) { diff --git a/Shared/Models/GenealogicalDataCommunication.cs b/Shared/Models/GenealogicalDataCommunication.cs index f71aaf8..e4190b9 100644 --- a/Shared/Models/GenealogicalDataCommunication.cs +++ b/Shared/Models/GenealogicalDataCommunication.cs @@ -2,7 +2,17 @@ using System.Text.Json; namespace View_by_Distance.Shared.Models; -public record GenealogicalDataCommunication(string? Id, string? UId, string? Name, string? GivenName, string? SurName, string? Suffix, string? NickName, char Sex, DateTime? Birth, DateTime? Death, DateTime? Changed) +public record GenealogicalDataCommunication(string? Id, + string? UId, + string? Name, + string? GivenName, + string? SurName, + string? Suffix, + string? NickName, + char Sex, + DateTime? Birth, + DateTime? Death, + DateTime? Changed) { public override string ToString() diff --git a/Shared/Models/GenealogicalDataCommunicationLines.cs b/Shared/Models/GenealogicalDataCommunicationLines.cs index 37aac4a..f70e62a 100644 --- a/Shared/Models/GenealogicalDataCommunicationLines.cs +++ b/Shared/Models/GenealogicalDataCommunicationLines.cs @@ -2,7 +2,17 @@ using System.Text.Json; namespace View_by_Distance.Shared.Models; -public record GenealogicalDataCommunicationLines(string? Id, string? UId, string? Name, string? GivenName, string? SurName, string? Suffix, string? NickName, string? Sex, List Birth, List Death, List Changed) +public record GenealogicalDataCommunicationLines(string? Id, + string? UId, + string? Name, + string? GivenName, + string? SurName, + string? Suffix, + string? NickName, + string? Sex, + List Birth, + List Death, + List Changed) { public override string ToString() diff --git a/Shared/Models/GenealogicalDataCommunicationRelation.cs b/Shared/Models/GenealogicalDataCommunicationRelation.cs new file mode 100644 index 0000000..9878898 --- /dev/null +++ b/Shared/Models/GenealogicalDataCommunicationRelation.cs @@ -0,0 +1,18 @@ +using System.Text.Json; + +namespace View_by_Distance.Shared.Models; + +public record GenealogicalDataCommunicationRelation(int FamilyIndex, + string Relation, + string Id, + string NickName, + string? LineTwo) +{ + + 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/PersonContainer.cs b/Shared/Models/PersonContainer.cs index 94da408..045b6b5 100644 --- a/Shared/Models/PersonContainer.cs +++ b/Shared/Models/PersonContainer.cs @@ -10,18 +10,20 @@ public class PersonContainer : Properties.IPersonContainer public PersonBirthday[]? Birthdays { init; get; } public string[] DisplayDirectoryAllFiles { init; get; } public string DisplayDirectoryName { init; get; } + public string[]? GenealogicalDataCommunicationRelationIndividualsLines { init; get; } public long? Key { init; get; } public bool? KeyIsMaxBirthday { init; get; } public Person? Person { init; get; } public PersonDirectory? PersonDirectory { init; get; } [JsonConstructor] - public PersonContainer(int? approximateYears, PersonBirthday[]? birthdays, string[] displayDirectoryAllFiles, string displayDirectoryName, long? key, Person? person, PersonDirectory? personDirectory) + public PersonContainer(int? approximateYears, PersonBirthday[]? birthdays, string[] displayDirectoryAllFiles, string displayDirectoryName, string[]? genealogicalDataCommunicationRelationIndividualsLines, long? key, Person? person, PersonDirectory? personDirectory) { ApproximateYears = approximateYears; Birthdays = birthdays; DisplayDirectoryAllFiles = displayDirectoryAllFiles; DisplayDirectoryName = displayDirectoryName; + GenealogicalDataCommunicationRelationIndividualsLines = genealogicalDataCommunicationRelationIndividualsLines; Key = key; Person = person; PersonDirectory = personDirectory; @@ -29,23 +31,23 @@ public class PersonContainer : Properties.IPersonContainer } public PersonContainer(string mappingDefaultName, char[] personCharacters, PersonBirthday birthday, string displayDirectoryName) : - this(Stateless.Methods.IAge.GetApproximateYears(personCharacters, displayDirectoryName), new PersonBirthday[] { birthday }, Array.Empty(), displayDirectoryName, birthday.Value.Ticks, Stateless.Methods.IPerson.GetPerson(mappingDefaultName, personCharacters, displayDirectoryName, birthday.Value.Ticks, birthday), null) + this(Stateless.Methods.IAge.GetApproximateYears(personCharacters, displayDirectoryName), new PersonBirthday[] { birthday }, Array.Empty(), displayDirectoryName, null, birthday.Value.Ticks, Stateless.Methods.IPerson.GetPerson(mappingDefaultName, personCharacters, displayDirectoryName, birthday.Value.Ticks, birthday), null) { } public PersonContainer(int? approximateYears, PersonBirthday birthdays, string displayDirectoryName, long key) : - this(approximateYears, new PersonBirthday[] { birthdays }, Array.Empty(), displayDirectoryName, key, null, null) + this(approximateYears, new PersonBirthday[] { birthdays }, Array.Empty(), displayDirectoryName, null, key, null, null) { } public PersonContainer(int? approximateYears, PersonBirthday birthdays, PersonDirectory? personDirectory, string displayDirectoryName, long key) : - this(approximateYears, new PersonBirthday[] { birthdays }, Array.Empty(), displayDirectoryName, key, null, personDirectory) + this(approximateYears, new PersonBirthday[] { birthdays }, Array.Empty(), displayDirectoryName, null, key, null, personDirectory) { } public PersonContainer(int? approximateYears, string[] displayDirectoryAllFiles, string displayDirectoryName, PersonDirectory? personDirectory) : - this(approximateYears, null, displayDirectoryAllFiles, displayDirectoryName, null, null, personDirectory) + this(approximateYears, null, displayDirectoryAllFiles, displayDirectoryName, null, null, null, personDirectory) { } public PersonContainer(int? approximateYears, PersonBirthday[]? birthdays, string[] displayDirectoryAllFiles, string displayDirectoryName, long? key, Person? person) : - this(approximateYears, birthdays, displayDirectoryAllFiles, displayDirectoryName, key, person, null) + this(approximateYears, birthdays, displayDirectoryAllFiles, displayDirectoryName, null, key, person, null) { } public override string ToString() diff --git a/Shared/Models/Properties/IPersonContainer.cs b/Shared/Models/Properties/IPersonContainer.cs index b90183d..916a168 100644 --- a/Shared/Models/Properties/IPersonContainer.cs +++ b/Shared/Models/Properties/IPersonContainer.cs @@ -7,6 +7,7 @@ public interface IPersonContainer public PersonBirthday[]? Birthdays { init; get; } public string[] DisplayDirectoryAllFiles { init; get; } public string DisplayDirectoryName { init; get; } + public string[]? GenealogicalDataCommunicationRelationIndividualsLines { init; get; } public long? Key { init; get; } public bool? KeyIsMaxBirthday { init; get; } public Person? Person { init; get; } diff --git a/Shared/Models/Stateless/Methods/GenealogicalDataCommunication.cs b/Shared/Models/Stateless/Methods/GenealogicalDataCommunication.cs index 7e11da9..0c4bc30 100644 --- a/Shared/Models/Stateless/Methods/GenealogicalDataCommunication.cs +++ b/Shared/Models/Stateless/Methods/GenealogicalDataCommunication.cs @@ -1,3 +1,5 @@ +using System.Collections.ObjectModel; + namespace View_by_Distance.Shared.Models.Stateless.Methods; internal abstract class GenealogicalDataCommunication @@ -17,13 +19,52 @@ internal abstract class GenealogicalDataCommunication return results.ToArray(); } - internal static (string[] headerLines, Dictionary> individuals, string[] footerLines) GetIndividuals(string genealogicalDataCommunicationFile, bool requireNickName) + private static List GetRelations(Dictionary idToNick, List familyGroupLines) { - Dictionary> results = new(); + List results = new(); string? nick; + string relation; + string[] segments; + string[] familyLines; + for (int i = 0; i < familyGroupLines.Count; i++) + { + familyLines = familyGroupLines[i]; + for (int j = 0; j < familyLines.Length; j++) + { + segments = familyLines[j].Split('@'); + if (segments.First().Length < 3 || segments.Length != 3) + continue; + if (!idToNick.TryGetValue(segments[1], out nick)) + continue; + relation = segments.First()[2..].Trim(); + if (j + 1 >= familyLines.Length || familyLines[j + 1].Length < 3 || familyLines[j + 1][..3] != "2 _") + results.Add(new(i, relation, segments[1], nick, null)); + else + results.Add(new(i, relation, segments[1], nick, familyLines[j + 1][2..])); + } + } + return results; + } + + private static ReadOnlyDictionary Convert(Dictionary> keyValuePairs) + { + Dictionary results = new(); + foreach (KeyValuePair> keyValuePair in keyValuePairs) + results.Add(keyValuePair.Key, keyValuePair.Value.ToArray()); + return new(results); + } + + internal static (string[], ReadOnlyDictionary, List, string[], List genealogicalDataCommunicationRelations) GetIndividuals(string genealogicalDataCommunicationFile, bool requireNickName) + { + ReadOnlyDictionary results; + string? nick; + string[] segments; List lines = new(); const string startsWith = "0 @"; List footerLines = new(); + List familyGroupLines = new(); + Dictionary idToNick = new(); + Dictionary> keyValuePairs = new(); string[] sourceLines = string.IsNullOrEmpty(genealogicalDataCommunicationFile) || !File.Exists(genealogicalDataCommunicationFile) ? Array.Empty() : File.ReadAllLines(genealogicalDataCommunicationFile); string[] headerLines = GetHeaderLines(startsWith, sourceLines); for (int i = headerLines.Length; i < sourceLines.Length; i++) @@ -36,13 +77,29 @@ internal abstract class GenealogicalDataCommunication continue; else if (sourceLines[i].EndsWith("@ FAM")) { - for (int j = i + 1; j < sourceLines.Length; j++) + lines.Clear(); + for (int j = sourceLines.Length - 1; j >= i; j--) + { lines.Add(sourceLines[j]); - footerLines.AddRange(lines); + if (sourceLines[j].First() == '0') + { + if (!sourceLines[j].EndsWith("@ FAM")) + footerLines.AddRange(lines); + else + { + lines.Reverse(); + familyGroupLines.Add(lines.ToArray()); + } + lines.Clear(); + } + } + familyGroupLines.Reverse(); + footerLines.Reverse(); break; } else if (sourceLines[i].EndsWith("@ INDI")) { + segments = sourceLines[i].Split('@'); for (int j = i + 1; j < sourceLines.Length; j++) { if (sourceLines[j].StartsWith(startsWith)) @@ -51,26 +108,30 @@ internal abstract class GenealogicalDataCommunication if (!sourceLines[j].StartsWith("2 NICK ")) continue; nick = sourceLines[j][7..]; + if (segments.Length == 3) + idToNick.Add(segments[1], nick); } if (requireNickName && string.IsNullOrEmpty(nick)) throw new Exception(string.Join(Environment.NewLine, lines)); nick ??= Guid.NewGuid().ToString(); - results.Add(nick, new()); + keyValuePairs.Add(nick, new()); if (!lines.Any()) continue; - results[nick].AddRange(lines); + keyValuePairs[nick].AddRange(lines); lines.Clear(); } else throw new NotSupportedException(); } - return (headerLines, results, footerLines.ToArray()); + results = Convert(keyValuePairs); + List genealogicalDataCommunicationRelations = GetRelations(idToNick, familyGroupLines); + return (headerLines, results, familyGroupLines, footerLines.ToArray(), genealogicalDataCommunicationRelations); } - private static string[] GetFilteredOutMapped(List individualsLines) + private static string[] GetFilteredOutMapped(string[] individualsLines) { List results = new(); - for (int i = 0; i < individualsLines.Count; i++) + for (int i = 0; i < individualsLines.Length; i++) { if (individualsLines[i].StartsWith("0 @I")) continue; @@ -90,19 +151,19 @@ internal abstract class GenealogicalDataCommunication continue; else if (individualsLines[i] == "1 BIRT") { - if (individualsLines.Count > i + 1 && individualsLines[i + 1].StartsWith("2 DATE")) + if (individualsLines.Length > i + 1 && individualsLines[i + 1].StartsWith("2 DATE")) i += 1; continue; } else if (individualsLines[i].StartsWith("1 DEAT")) { - if (individualsLines.Count > i + 1 && individualsLines[i + 1].StartsWith("2 DATE")) + if (individualsLines.Length > i + 1 && individualsLines[i + 1].StartsWith("2 DATE")) i += 1; continue; } else if (individualsLines[i] == "1 CHAN") { - if (individualsLines.Count > i + 1 && individualsLines[i + 1].StartsWith("2 DATE")) + if (individualsLines.Length > i + 1 && individualsLines[i + 1].StartsWith("2 DATE")) i += 1; continue; } @@ -116,9 +177,9 @@ internal abstract class GenealogicalDataCommunication List results = new(); Models.PersonBirthday personBirthday = new(DateTime.Now); GenealogicalDataCommunicationLines genealogicalDataCommunicationLines; - (string[] headerLines, Dictionary> individuals, string[] footerLines) = GetIndividuals(genealogicalDataCommunicationFile, requireNickName); + (string[] headerLines, ReadOnlyDictionary individuals, List familyGroupLines, string[] footerLines, _) = GetIndividuals(genealogicalDataCommunicationFile, requireNickName); results.AddRange(headerLines); - foreach (KeyValuePair> keyValuePair in individuals) + foreach (KeyValuePair keyValuePair in individuals) { genealogicalDataCommunicationLines = GetGenealogicalDataCommunicationLines(personBirthday, keyValuePair.Value); if (!string.IsNullOrEmpty(genealogicalDataCommunicationLines.Id)) @@ -139,11 +200,13 @@ internal abstract class GenealogicalDataCommunication results.AddRange(genealogicalDataCommunicationLines.Death); results.AddRange(genealogicalDataCommunicationLines.Changed); } + for (int i = 0; i < familyGroupLines.Count; i++) + results.AddRange(familyGroupLines[i]); results.AddRange(footerLines); return results; } - internal static GenealogicalDataCommunicationLines GetGenealogicalDataCommunicationLines(Models.PersonBirthday personBirthday, List individualsLines) + internal static GenealogicalDataCommunicationLines GetGenealogicalDataCommunicationLines(Models.PersonBirthday personBirthday, string[] individualsLines) { GenealogicalDataCommunicationLines result; string? idLine = null; @@ -157,7 +220,7 @@ internal abstract class GenealogicalDataCommunication List birthLines = new(); List deathLines = new(); List changedLines = new(); - for (int i = 0; i < individualsLines.Count; i++) + for (int i = 0; i < individualsLines.Length; i++) { if (idLine is null && individualsLines[i].EndsWith("@ INDI")) idLine = individualsLines[i]; @@ -178,7 +241,7 @@ internal abstract class GenealogicalDataCommunication else if (!birthLines.Any() && individualsLines[i] == "1 BIRT") { birthLines.Add(individualsLines[i]); - if (individualsLines.Count > i + 1 && individualsLines[i + 1].StartsWith("2 DATE")) + if (individualsLines.Length > i + 1 && individualsLines[i + 1].StartsWith("2 DATE")) { i += 1; birthLines.Add(individualsLines[i]); @@ -187,7 +250,7 @@ internal abstract class GenealogicalDataCommunication else if (!deathLines.Any() && individualsLines[i].StartsWith("1 DEAT")) { deathLines.Add(individualsLines[i]); - if (individualsLines.Count > i + 1 && individualsLines[i + 1].StartsWith("2 DATE")) + if (individualsLines.Length > i + 1 && individualsLines[i + 1].StartsWith("2 DATE")) { i += 1; deathLines.Add(individualsLines[i]); @@ -196,7 +259,7 @@ internal abstract class GenealogicalDataCommunication else if (!changedLines.Any() && individualsLines[i] == "1 CHAN") { changedLines.Add(individualsLines[i]); - if (individualsLines.Count > i + 1 && individualsLines[i + 1].StartsWith("2 DATE")) + if (individualsLines.Length > i + 1 && individualsLines[i + 1].StartsWith("2 DATE")) { i += 1; changedLines.Add(individualsLines[i]); @@ -259,7 +322,7 @@ internal abstract class GenealogicalDataCommunication return result; } - internal static void WriteFile(string personKeyFormatted, Models.PersonName personName, List? individualsLines, bool isDefaultName, string directory, Models.GenealogicalDataCommunication genealogicalDataCommunication, bool verify, bool first) + internal static void WriteFile(string personKeyFormatted, Models.PersonName personName, string[]? individualsLines, bool isDefaultName, string directory, Models.GenealogicalDataCommunication genealogicalDataCommunication, bool verify, bool first) { if (verify) { @@ -287,64 +350,65 @@ internal abstract class GenealogicalDataCommunication _ = Directory.CreateDirectory(directory); if (first && !personKeyFormatted.EndsWith(code)) personKeyFormatted = $"{personKeyFormatted[..^2]}{code}"; - List pGedLines = new(); + List lines = new(); if (individualsLines is null || !individualsLines.Any()) - pGedLines.Add($"0 @I{personKeyFormatted}@ INDI"); + lines.Add($"0 @I{personKeyFormatted}@ INDI"); else { if (!individualsLines[0].StartsWith("0 @I")) throw new NotSupportedException(); - pGedLines.Add(individualsLines[0]); + lines.Add(individualsLines[0]); } - pGedLines.Add($"1 NAME {personName.First.Value} /{personName.Last.Value}/{jrOrSr}"); + lines.Add($"1 NAME {personName.First.Value} /{personName.Last.Value}/{jrOrSr}"); if (!string.IsNullOrEmpty(personName.First.Value)) - pGedLines.Add($"2 GIVN {personName.First.Value}"); + lines.Add($"2 GIVN {personName.First.Value}"); if (!string.IsNullOrEmpty(personName.Last.Value)) - pGedLines.Add($"2 SURN {personName.Last.Value}"); + lines.Add($"2 SURN {personName.Last.Value}"); if (!string.IsNullOrEmpty(jrOrSr)) - pGedLines.Add($"2 NSFX {jrOrSr.Trim()}"); - pGedLines.Add($"2 NICK {personKeyFormatted}"); - pGedLines.Add($"1 SEX {genealogicalDataCommunication.Sex}"); + lines.Add($"2 NSFX {jrOrSr.Trim()}"); + lines.Add($"2 NICK {personKeyFormatted}"); + lines.Add($"1 SEX {genealogicalDataCommunication.Sex}"); if (genealogicalDataCommunication.Birth is not null) { Models.PersonBirthday personBirthday = new(genealogicalDataCommunication.Birth.Value); if (!IPersonBirthday.IsCounterPersonBirthday(personBirthday)) { - pGedLines.Add("1 BIRT"); - pGedLines.Add($"2 DATE {genealogicalDataCommunication.Birth.Value:dd MMM yyyy}"); + lines.Add("1 BIRT"); + lines.Add($"2 DATE {genealogicalDataCommunication.Birth.Value:dd MMM yyyy}"); } } if (genealogicalDataCommunication.Death is not null) { if (genealogicalDataCommunication?.Death is null || genealogicalDataCommunication.Death == genealogicalDataCommunication.Birth || IPersonBirthday.IsCounterPersonBirthday(new(genealogicalDataCommunication.Death.Value))) - pGedLines.Add("1 DEAT Y"); + lines.Add("1 DEAT Y"); else { - pGedLines.Add("1 DEAT"); - pGedLines.Add($"2 DATE {genealogicalDataCommunication.Death.Value:dd MMM yyyy}"); + lines.Add("1 DEAT"); + lines.Add($"2 DATE {genealogicalDataCommunication.Death.Value:dd MMM yyyy}"); } } if (isDefaultName) - pGedLines.Add("9 NOTE"); + lines.Add("9 NOTE"); if (individualsLines is not null) - pGedLines.AddRange(GetFilteredOutMapped(individualsLines)); - string text = string.Join(Environment.NewLine, pGedLines); + lines.AddRange(GetFilteredOutMapped(individualsLines)); + string text = string.Join(Environment.NewLine, lines); _ = IPath.WriteAllText(Path.Combine(directory, $"{personKeyFormatted}.pged"), text, updateDateWhenMatches: false, compareBeforeWrite: true); } - internal static void CreateTree(string mappingDefaultName, string personBirthdayFormat, string resultAllInOne, List personContainers, string[] genealogicalDataCommunicationHeaderLines, string[] genealogicalDataCommunicationFooterLines, long ticks, string a2PeopleContentDirectory, Dictionary> personKeyToIds) + internal static bool CleanDisplayDirectoryAllFilesAndWriteTicksGed(string mappingDefaultName, string personBirthdayFormat, List personContainers, string[] headerLines, List familyGroupLines, string[] footerLines, long ticks, string a2PeopleContentDirectory) { - string by; - string[] matches; + bool result = false; + string[] mdFiles; + string[] txtFiles; const int zero = 0; + string[] jsonFiles; string[] pGedFiles; - string[] pGedLines; string personKeyFormatted; List lines = new(); List distinct = new(); - lines.AddRange(genealogicalDataCommunicationHeaderLines); + List individualsLines; Models.PersonBirthday personBirthday; - string rootDirectory = Path.Combine(a2PeopleContentDirectory, $"{ticks}-Tree"); + lines.AddRange(headerLines); foreach (Models.PersonContainer personContainer in personContainers) { if (personContainer.Key is null || personContainer.Birthdays is null || personContainer.Person is null || !personContainer.Birthdays.Any()) @@ -356,28 +420,56 @@ internal abstract class GenealogicalDataCommunication distinct.Add(personContainer.Key.Value); personBirthday = personContainer.Birthdays[zero]; personKeyFormatted = IPersonBirthday.GetFormatted(personBirthdayFormat, personBirthday); - by = IPersonBirthday.IsCounterPersonBirthday(personBirthday) ? resultAllInOne : "People"; - matches = (from l in personContainer.DisplayDirectoryAllFiles where !string.IsNullOrEmpty(personKeyFormatted) && l.Contains(personKeyFormatted) select l).ToArray(); - if (!matches.Any()) - continue; - pGedFiles = (from l in matches where l.EndsWith(".pged") select l).ToArray(); - if (!pGedFiles.Any()) - continue; - pGedLines = File.ReadAllLines(pGedFiles[0]); - lines.AddRange(pGedLines); - if (!personKeyToIds.ContainsKey(personContainer.Key.Value)) - lines.Add("1 NOTE"); - // segments = personContainer.DisplayDirectoryName.Split(_Configuration.PersonCharacters.ToArray()); - // if (segments.Length < 2) - // directory = Path.Combine(rootDirectory, $"000 {personKeyFormatted} {personContainer.DisplayDirectoryName}"); - // else - // directory = Path.Combine(rootDirectory, $"{segments[1].PadLeft(3, '0')} {personKeyFormatted} {personContainer.DisplayDirectoryName}"); - // if (Directory.Exists(directory)) - // continue; - // _ = Directory.CreateDirectory(directory); + mdFiles = (from l in personContainer.DisplayDirectoryAllFiles where l.EndsWith(".md") select l).ToArray(); + txtFiles = (from l in personContainer.DisplayDirectoryAllFiles where l.EndsWith(".txt") select l).ToArray(); + jsonFiles = (from l in personContainer.DisplayDirectoryAllFiles where l.EndsWith(".json") select l).ToArray(); + pGedFiles = (from l in personContainer.DisplayDirectoryAllFiles where l.EndsWith(".pged") select l).ToArray(); + foreach (string mdFile in mdFiles) + { + if (string.IsNullOrEmpty(personKeyFormatted)) + continue; + if (!mdFile.Contains(personKeyFormatted)) + { + if (!result) + result = true; + File.Delete(mdFile); + } + } + foreach (string pGedFile in pGedFiles) + { + if (string.IsNullOrEmpty(personKeyFormatted)) + continue; + if (!pGedFile.Contains(personKeyFormatted)) + { + if (!result) + result = true; + File.Delete(pGedFile); + continue; + } + individualsLines = File.ReadAllLines(pGedFile).ToList(); + foreach (string jsonFile in jsonFiles) + { + if (!result) + result = true; + File.Delete(jsonFile); + } + foreach (string txtFile in txtFiles) + { + if (new FileInfo(txtFile).Length == 0) + { + if (!result) + result = true; + File.Delete(txtFile); + } + } + lines.AddRange(individualsLines); + } } - lines.AddRange(genealogicalDataCommunicationFooterLines); + for (int i = 0; i < familyGroupLines.Count; i++) + lines.AddRange(familyGroupLines[i]); + lines.AddRange(footerLines); File.WriteAllLines(Path.Combine(a2PeopleContentDirectory, $"{ticks}.ged"), lines); + return result; } } \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IGenealogicalDataCommunication.cs b/Shared/Models/Stateless/Methods/IGenealogicalDataCommunication.cs index 856b029..2dd7396 100644 --- a/Shared/Models/Stateless/Methods/IGenealogicalDataCommunication.cs +++ b/Shared/Models/Stateless/Methods/IGenealogicalDataCommunication.cs @@ -1,3 +1,5 @@ +using System.Collections.ObjectModel; + namespace View_by_Distance.Shared.Models.Stateless.Methods; public interface IGenealogicalDataCommunication @@ -5,9 +7,9 @@ public interface IGenealogicalDataCommunication // ... - void TestStatic_WriteFile(string personKeyFormatted, Models.PersonName personName, List? individualsLines, bool isDefaultName, string directory, Models.GenealogicalDataCommunication genealogicalDataCommunication, bool verify, bool first) => + void TestStatic_WriteFile(string personKeyFormatted, Models.PersonName personName, string[]? individualsLines, bool isDefaultName, string directory, Models.GenealogicalDataCommunication genealogicalDataCommunication, bool verify, bool first) => WriteFile(personKeyFormatted, personName, individualsLines, isDefaultName, directory, genealogicalDataCommunication, verify, first); - static void WriteFile(string personKeyFormatted, Models.PersonName personName, List? individualsLines, bool isDefaultName, string directory, Models.GenealogicalDataCommunication genealogicalDataCommunication, bool verify, bool first) => + static void WriteFile(string personKeyFormatted, Models.PersonName personName, string[]? individualsLines, bool isDefaultName, string directory, Models.GenealogicalDataCommunication genealogicalDataCommunication, bool verify, bool first) => GenealogicalDataCommunication.WriteFile(personKeyFormatted, personName, individualsLines, isDefaultName, directory, genealogicalDataCommunication, verify, first); List TestStatic_GetMappedLines(string genealogicalDataCommunicationFile, bool requireNickName) => @@ -15,9 +17,9 @@ public interface IGenealogicalDataCommunication static List GetMappedLines(string genealogicalDataCommunicationFile, bool requireNickName) => GenealogicalDataCommunication.GetMappedLines(genealogicalDataCommunicationFile, requireNickName); - GenealogicalDataCommunicationLines TestStatic_GetGenealogicalDataCommunicationLines(Models.PersonBirthday personBirthday, List individualsLines) => + GenealogicalDataCommunicationLines TestStatic_GetGenealogicalDataCommunicationLines(Models.PersonBirthday personBirthday, string[] individualsLines) => GetGenealogicalDataCommunicationLines(individualsLines); - static GenealogicalDataCommunicationLines GetGenealogicalDataCommunicationLines(List individualsLines) => + static GenealogicalDataCommunicationLines GetGenealogicalDataCommunicationLines(string[] individualsLines) => GenealogicalDataCommunication.GetGenealogicalDataCommunicationLines(new(DateTime.Now), individualsLines); Models.GenealogicalDataCommunication TestStatic_GetGenealogicalDataCommunication(GenealogicalDataCommunicationLines genealogicalDataCommunicationLines) => @@ -25,14 +27,14 @@ public interface IGenealogicalDataCommunication static Models.GenealogicalDataCommunication GetGenealogicalDataCommunication(GenealogicalDataCommunicationLines genealogicalDataCommunicationLines) => GenealogicalDataCommunication.GetGenealogicalDataCommunication(genealogicalDataCommunicationLines); - (string[] headerLines, Dictionary> individuals, string[] footerLines) TestStatic_GetIndividuals(string genealogicalDataCommunicationFile, bool requireNickName) => + (string[], ReadOnlyDictionary, List, string[], List genealogicalDataCommunicationRelations) TestStatic_GetIndividuals(string genealogicalDataCommunicationFile, bool requireNickName) => GetIndividuals(genealogicalDataCommunicationFile, requireNickName); - static (string[] headerLines, Dictionary> individuals, string[] footerLines) GetIndividuals(string genealogicalDataCommunicationFile, bool requireNickName) => + static (string[], ReadOnlyDictionary, List, string[], List genealogicalDataCommunicationRelations) GetIndividuals(string genealogicalDataCommunicationFile, bool requireNickName) => GenealogicalDataCommunication.GetIndividuals(genealogicalDataCommunicationFile, requireNickName); - void TestStatic_CreateTree(string mappingDefaultName, string personBirthdayFormat, string resultAllInOne, List personContainers, string[] genealogicalDataCommunicationHeaderLines, string[] genealogicalDataCommunicationFooterLines, long ticks, string a2PeopleContentDirectory, Dictionary> personKeyToIds) => - CreateTree(mappingDefaultName, personBirthdayFormat, resultAllInOne, personContainers, genealogicalDataCommunicationHeaderLines, genealogicalDataCommunicationFooterLines, ticks, a2PeopleContentDirectory, personKeyToIds); - static void CreateTree(string mappingDefaultName, string personBirthdayFormat, string resultAllInOne, List personContainers, string[] genealogicalDataCommunicationHeaderLines, string[] genealogicalDataCommunicationFooterLines, long ticks, string a2PeopleContentDirectory, Dictionary> personKeyToIds) => - GenealogicalDataCommunication.CreateTree(mappingDefaultName, personBirthdayFormat, resultAllInOne, personContainers, genealogicalDataCommunicationHeaderLines, genealogicalDataCommunicationFooterLines, ticks, a2PeopleContentDirectory, personKeyToIds); + bool TestStatic_CleanDisplayDirectoryAllFilesAndWriteTicksGed(string mappingDefaultName, string personBirthdayFormat, List personContainers, string[] headerLines, List familyGroupLines, string[] footerLines, long ticks, string a2PeopleContentDirectory) => + CleanDisplayDirectoryAllFilesAndWriteTicksGed(mappingDefaultName, personBirthdayFormat, personContainers, headerLines, familyGroupLines, footerLines, ticks, a2PeopleContentDirectory); + static bool CleanDisplayDirectoryAllFilesAndWriteTicksGed(string mappingDefaultName, string personBirthdayFormat, List personContainers, string[] headerLines, List familyGroupLines, string[] footerLines, long ticks, string a2PeopleContentDirectory) => + GenealogicalDataCommunication.CleanDisplayDirectoryAllFilesAndWriteTicksGed(mappingDefaultName, personBirthdayFormat, personContainers, headerLines, familyGroupLines, footerLines, ticks, a2PeopleContentDirectory); } \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IPersonContainer.cs b/Shared/Models/Stateless/Methods/IPersonContainer.cs index be93209..90bc173 100644 --- a/Shared/Models/Stateless/Methods/IPersonContainer.cs +++ b/Shared/Models/Stateless/Methods/IPersonContainer.cs @@ -1,3 +1,5 @@ +using System.Collections.ObjectModel; + namespace View_by_Distance.Shared.Models.Stateless.Methods; public interface IPersonContainer @@ -15,11 +17,16 @@ public interface IPersonContainer static List GetNonSpecificPeopleCollection(string mappingDefaultName, int personBirthdayFirstYear, char[] personCharacters, List personContainers, long ticks) => PersonContainer.GetNonSpecificPeopleCollection(mappingDefaultName, personBirthdayFirstYear, personCharacters, personContainers, ticks); - List TestStatic_GetPersonContainers(Properties.IStorage storage, string mappingDefaultName, string personBirthdayFormat, char[] personCharacters, string facesFileNameExtension, Dictionary> individuals) => + List TestStatic_GetPersonContainers(Properties.IStorage storage, string mappingDefaultName, string personBirthdayFormat, char[] personCharacters, string facesFileNameExtension, ReadOnlyDictionary individuals) => GetPersonContainers(storage, mappingDefaultName, personBirthdayFormat, personCharacters, facesFileNameExtension, individuals); - static List GetPersonContainers(Properties.IStorage storage, string mappingDefaultName, string personBirthdayFormat, char[] personCharacters, string facesFileNameExtension, Dictionary> individuals) => + static List GetPersonContainers(Properties.IStorage storage, string mappingDefaultName, string personBirthdayFormat, char[] personCharacters, string facesFileNameExtension, ReadOnlyDictionary individuals) => PersonContainer.GetPersonContainers(storage, mappingDefaultName, personBirthdayFormat, personCharacters, facesFileNameExtension, individuals); + void TestStatic_MaybeWriteFiles(string mappingDefaultName, string personBirthdayFormat, List genealogicalDataCommunicationRelations, List personContainers) => + MaybeWriteFiles(mappingDefaultName, personBirthdayFormat, genealogicalDataCommunicationRelations, personContainers); + static void MaybeWriteFiles(string mappingDefaultName, string personBirthdayFormat, List genealogicalDataCommunicationRelations, List personContainers) => + PersonContainer.MaybeWriteFiles(mappingDefaultName, personBirthdayFormat, genealogicalDataCommunicationRelations, personContainers); + List<(long?, string)> TestStatic_GetDisplay(string personBirthdayFormat, Models.PersonContainer personContainer) => GetDisplay(personBirthdayFormat, personContainer); static List<(long?, string)> GetDisplay(string personBirthdayFormat, Models.PersonContainer personContainer) => diff --git a/Shared/Models/Stateless/Methods/MarkDown.cs b/Shared/Models/Stateless/Methods/MarkDown.cs new file mode 100644 index 0000000..2074470 --- /dev/null +++ b/Shared/Models/Stateless/Methods/MarkDown.cs @@ -0,0 +1,132 @@ +using System.Collections.ObjectModel; +using System.Text.RegularExpressions; + +namespace View_by_Distance.Shared.Models.Stateless.Methods; + +internal abstract class MarkDown +{ + + // ... + + internal static void WriteFile(string personKeyFormatted, Models.PersonName personName, List genealogicalDataCommunicationRelations, ReadOnlyDictionary personKeyFormattedToPersonFullName, ReadOnlyDictionary> familyIndexToCollection, bool isDefaultName, string directory, Models.GenealogicalDataCommunication genealogicalDataCommunication, bool first, string fullName, string lowerHyphenFullName) + { + string? personFullName; + bool hasRelation = false; + string lowerHyphenRelation; + List? relations; + string now = DateTime.Now.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); + string jrOrSr; + if (string.IsNullOrEmpty(personName.Alias.Value)) + jrOrSr = string.Empty; + else + { + if (personName.Alias.Value.Contains(" Jr")) + jrOrSr = " Jr"; + else if (personName.Alias.Value.Contains(" Sr")) + jrOrSr = " Sr"; + else + jrOrSr = string.Empty; + } + string code = IPersonBirthday.GetHour(genealogicalDataCommunication.Death is null, genealogicalDataCommunication.Sex).ToString("00"); + if (directory.EndsWith("00")) + directory = string.Concat(directory[..^2], code); + else if (directory.EndsWith("01")) + directory = string.Concat(directory[..^2], code); + if (!Directory.Exists(directory)) + _ = Directory.CreateDirectory(directory); + if (first && !personKeyFormatted.EndsWith(code)) + personKeyFormatted = $"{personKeyFormatted[..^2]}{code}"; + List lines = new() + { + "---", + "type: person", + $"created: {now}", + $"updated: {now}", + $"{nameof(fullName)}: {fullName}", + $"name: {personName.First.Value} /{personName.Last.Value}/{jrOrSr}", + }; + if (!string.IsNullOrEmpty(personName.First.Value)) + lines.Add($"given: {personName.First.Value}"); + if (!string.IsNullOrEmpty(personName.Last.Value)) + lines.Add($"surname: {personName.Last.Value}"); + if (!string.IsNullOrEmpty(jrOrSr)) + lines.Add($"suffix: {jrOrSr.Trim()}"); + lines.Add($"sex: {genealogicalDataCommunication.Sex}"); + if (genealogicalDataCommunication.Birth is not null) + { + Models.PersonBirthday personBirthday = new(genealogicalDataCommunication.Birth.Value); + if (!IPersonBirthday.IsCounterPersonBirthday(personBirthday)) + lines.Add($"birthDate: {genealogicalDataCommunication.Birth.Value:dd MMM yyyy}"); + } + if (genealogicalDataCommunication.Death is not null) + { + if (genealogicalDataCommunication?.Death is null || genealogicalDataCommunication.Death == genealogicalDataCommunication.Birth || IPersonBirthday.IsCounterPersonBirthday(new(genealogicalDataCommunication.Death.Value))) + lines.Add("isDead: true"); + else + lines.Add($"deathDate: {genealogicalDataCommunication.Death.Value:dd MMM yyyy}"); + } + if (isDefaultName) + lines.Add($"{nameof(isDefaultName)}: {isDefaultName}"); + lines.Add($"{nameof(personKeyFormatted)}: {personKeyFormatted}"); + lines.Add("---"); + lines.Add(string.Empty); + lines.Add($"# {lowerHyphenFullName}"); + lines.Add(string.Empty); + const char father = 'F'; + const char mother = 'M'; + const string wife = "WIFE"; + const string child = "CHIL"; + const string husband = "HUSB"; + foreach (GenealogicalDataCommunicationRelation genealogicalDataCommunicationRelation in genealogicalDataCommunicationRelations) + { + if (genealogicalDataCommunicationRelation.Relation != child) + continue; + if (genealogicalDataCommunicationRelation.NickName != personKeyFormatted) + continue; + if (!familyIndexToCollection.TryGetValue(genealogicalDataCommunicationRelation.FamilyIndex, out relations)) + continue; + foreach (GenealogicalDataCommunicationRelation relation in relations) + { + if (relation.FamilyIndex != genealogicalDataCommunicationRelation.FamilyIndex) + continue; + if (relation.Relation is husband or wife) + { + if (!hasRelation) + { + lines.Add("## Relations"); + lines.Add(string.Empty); + hasRelation = true; + } + if (!personKeyFormattedToPersonFullName.TryGetValue(relation.NickName, out personFullName)) + lowerHyphenRelation = relation.NickName; + else + lowerHyphenRelation = Regex.Replace(personFullName.ToLower(), "[^a-z0-9-]", "-"); + if (string.IsNullOrEmpty(genealogicalDataCommunicationRelation.LineTwo)) + lines.Add($"- [[{lowerHyphenRelation}]]"); + else + { + if (genealogicalDataCommunicationRelation.LineTwo[1] == father) + { + if (relation.Relation == wife) + lines.Add($"- [[{lowerHyphenRelation}]] {nameof(mother)}"); + else if (relation.Relation == husband) + lines.Add($"- [[{lowerHyphenRelation}]] {genealogicalDataCommunicationRelation.LineTwo.Split(' ').Last()} {nameof(father)}"); + } + else if (genealogicalDataCommunicationRelation.LineTwo[1] == mother) + { + if (relation.Relation == husband) + lines.Add($"- [[{lowerHyphenRelation}]] {nameof(father)}"); + else if (relation.Relation == wife) + lines.Add($"- [[{lowerHyphenRelation}]] {genealogicalDataCommunicationRelation.LineTwo.Split(' ').Last()} {nameof(mother)}"); + } + } + } + } + } + if (hasRelation) + lines.Add(string.Empty); + string text = string.Join(Environment.NewLine, lines); + _ = IPath.WriteAllText(Path.Combine(directory, $"{lowerHyphenFullName}.md"), text, updateDateWhenMatches: false, compareBeforeWrite: true); + } + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/PersonContainer.cs b/Shared/Models/Stateless/Methods/PersonContainer.cs index 268cfba..f6cbc26 100644 --- a/Shared/Models/Stateless/Methods/PersonContainer.cs +++ b/Shared/Models/Stateless/Methods/PersonContainer.cs @@ -1,3 +1,6 @@ +using System.Collections.ObjectModel; +using System.Text.RegularExpressions; + namespace View_by_Distance.Shared.Models.Stateless.Methods; internal abstract class PersonContainer @@ -90,17 +93,19 @@ internal abstract class PersonContainer return results; } - private static List GetPersonContainersCollections(string mappingDefaultName, string facesFileNameExtension, char[] personCharacters, PersonDirectory personDirectory, char numberSign, string personDisplayDirectory, string personDisplayDirectoryName, bool isDefaultName, int? approximateYears, List<(string PersonKeyFormatted, Models.PersonBirthday PersonBirthday)> collection) + private static List GetPersonContainersCollections(string mappingDefaultName, string facesFileNameExtension, char[] personCharacters, ReadOnlyDictionary individuals, PersonDirectory personDirectory, char numberSign, string personDisplayDirectory, string personDisplayDirectoryName, bool isDefaultName, int? approximateYears, List<(string PersonKeyFormatted, Models.PersonBirthday PersonBirthday)> collection) { List results = new(); long personKey; const int zero = 0; Models.Person person; + string[]? individualsLines; Models.PersonContainer personContainer; Models.PersonBirthday[] orderedPersonBirthdays; string[] personDisplayDirectoryAllFiles = GetFiles(facesFileNameExtension, personDisplayDirectory, isDefaultName); foreach ((string personKeyFormatted, Models.PersonBirthday personBirthday) in collection) { + _ = individuals.TryGetValue(personKeyFormatted, out individualsLines); orderedPersonBirthdays = (from l in collection where !l.PersonKeyFormatted.Contains(numberSign) orderby l.PersonBirthday.Value.Ticks descending select l.PersonBirthday).ToArray(); if (!orderedPersonBirthdays.Any()) personKey = collection[zero].PersonBirthday.Value.Ticks; @@ -111,7 +116,7 @@ internal abstract class PersonContainer personKey = orderedPersonBirthdays[zero].Value.Ticks; } person = IPerson.GetPerson(mappingDefaultName, personCharacters, personDisplayDirectoryName, personDisplayDirectoryAllFiles, personKey); - personContainer = new(approximateYears, orderedPersonBirthdays, personDisplayDirectoryAllFiles, personDisplayDirectoryName, personKey, person, personDirectory); + personContainer = new(approximateYears, orderedPersonBirthdays, personDisplayDirectoryAllFiles, personDisplayDirectoryName, individualsLines, personKey, person, personDirectory); results.Add(personContainer); } return results; @@ -148,48 +153,7 @@ internal abstract class PersonContainer return result; } - private static void MaybeGenealogicalDataCommunicationWriteFile(string mappingDefaultName, string personBirthdayFormat, Dictionary> individuals, PersonDirectory personDirectory, string personDisplayDirectoryName, bool isDefaultName, List personContainers) - { - bool? first; - bool? male; - string[] matches; - string? directory; - bool verify = true; - const int zero = 0; - string personKeyFormatted; - List? individualsLines = null; - Models.GenealogicalDataCommunication genealogicalDataCommunication; - GenealogicalDataCommunicationLines? genealogicalDataCommunicationLines; - foreach (Models.PersonContainer personContainer in personContainers) - { - if (personContainer.Key is null || personContainer.Birthdays is null || personContainer.Person is null || !personContainer.Birthdays.Any()) - continue; - male = personDirectory.Sex == 'U' ? null : personDirectory.Sex == 'M' || (personDirectory.Sex == 'F' ? false : throw new Exception()); - first = personDirectory.First == 'U' ? null : personDirectory.First == 'Y' || (personDirectory.First == 'N' ? false : throw new Exception()); - if (first is null) - continue; - isDefaultName = IPerson.IsDefaultName(mappingDefaultName, personDisplayDirectoryName); - personKeyFormatted = IPersonBirthday.GetFormatted(personBirthdayFormat, personContainer.Key.Value); - _ = individuals.TryGetValue(personKeyFormatted, out individualsLines); - matches = (from l in personContainer.DisplayDirectoryAllFiles where l.Contains(personKeyFormatted) select l).ToArray(); - if (!matches.Any()) - continue; - directory = Path.GetDirectoryName(matches[zero]); - if (directory is null) - continue; - genealogicalDataCommunicationLines = individualsLines is null ? null : GenealogicalDataCommunication.GetGenealogicalDataCommunicationLines(personContainer.Birthdays[0], individualsLines); - if (genealogicalDataCommunicationLines is null) - continue; - genealogicalDataCommunication = GenealogicalDataCommunication.GetGenealogicalDataCommunication(genealogicalDataCommunicationLines); - if (genealogicalDataCommunication.Sex != personDirectory.Sex) - continue; - if (genealogicalDataCommunication.Death is null && personDirectory.Status == 'D' || genealogicalDataCommunication.Death is not null && personDirectory.Status == 'A') - continue; - GenealogicalDataCommunication.WriteFile(personKeyFormatted, personContainer.Person.Name, individualsLines, isDefaultName, directory, genealogicalDataCommunication, verify, first.Value); - } - } - - private static (List, List) GetPersonContainersGroup(string mappingDefaultName, string personBirthdayFormat, string facesFileNameExtension, char[] personCharacters, Dictionary> individuals, PersonDirectory personDirectory, string[] personDisplayDirectories) + private static (List, List) GetPersonContainersGroup(string mappingDefaultName, string personBirthdayFormat, string facesFileNameExtension, char[] personCharacters, ReadOnlyDictionary individuals, PersonDirectory personDirectory, string[] personDisplayDirectories) { List results = new(); string? minusOne; @@ -223,8 +187,7 @@ internal abstract class PersonContainer continue; if (collection.Any()) { - personContainers = GetPersonContainersCollections(mappingDefaultName, facesFileNameExtension, personCharacters, personDirectory, numberSign, personDisplayDirectory, personDisplayDirectoryName, isDefaultName, approximateYears, collection); - MaybeGenealogicalDataCommunicationWriteFile(mappingDefaultName, personBirthdayFormat, individuals, personDirectory, personDisplayDirectoryName, isDefaultName, personContainers); + personContainers = GetPersonContainersCollections(mappingDefaultName, facesFileNameExtension, personCharacters, individuals, personDirectory, numberSign, personDisplayDirectory, personDisplayDirectoryName, isDefaultName, approximateYears, collection); results.AddRange(personContainers); } else @@ -237,7 +200,7 @@ internal abstract class PersonContainer return new(changes, results); } - private static (List, List) GetPersonContainersInnerGroups(string mappingDefaultName, string personBirthdayFormat, string facesFileNameExtension, char[] personCharacters, Dictionary> individuals, string groupDirectory, string groupDirectoryName) + private static (List, List) GetPersonContainersInnerGroups(string mappingDefaultName, string personBirthdayFormat, string facesFileNameExtension, char[] personCharacters, ReadOnlyDictionary individuals, string groupDirectory, string groupDirectoryName) { List results = new(); string[] segments; @@ -284,7 +247,7 @@ internal abstract class PersonContainer return new(allChanges, results); } - private static List GetPersonContainersGroups(string mappingDefaultName, string personBirthdayFormat, string facesFileNameExtension, char[] personCharacters, Dictionary> individuals, string[] groupDirectories) + private static List GetPersonContainersGroups(string mappingDefaultName, string personBirthdayFormat, string facesFileNameExtension, char[] personCharacters, ReadOnlyDictionary individuals, string[] groupDirectories) { List results; const int zero = 0; @@ -308,7 +271,7 @@ internal abstract class PersonContainer return results; } - internal static List GetPersonContainers(Properties.IStorage storage, string mappingDefaultName, string personBirthdayFormat, char[] personCharacters, string facesFileNameExtension, Dictionary> individuals) + internal static List GetPersonContainers(Properties.IStorage storage, string mappingDefaultName, string personBirthdayFormat, char[] personCharacters, string facesFileNameExtension, ReadOnlyDictionary individuals) { List results; string a2PeopleSingletonDirectory = Path.Combine(storage.PeopleRootDirectory, "{}"); @@ -391,4 +354,92 @@ internal abstract class PersonContainer return results; } + private static ReadOnlyDictionary GetPersonKeyFormattedToPersonFullName(string personBirthdayFormat, List personContainers) + { + Dictionary results = new(); + string? value; + string personFullName; + string personKeyFormatted; + foreach (Models.PersonContainer personContainer in personContainers) + { + if (personContainer.Key is null || personContainer.Birthdays is null || personContainer.Person is null || personContainer.PersonDirectory is null || !personContainer.Birthdays.Any()) + continue; + personKeyFormatted = IPersonBirthday.GetFormatted(personBirthdayFormat, personContainer.Key.Value); + personFullName = PersonName.GetFullName(personContainer.Person.Name); + if (!results.TryGetValue(personKeyFormatted, out value)) + results.Add(personKeyFormatted, personFullName); + else if (value != personFullName) + throw new NotSupportedException(); + } + return new(results); + } + + private static ReadOnlyDictionary> GetFamilyIndexToCollection(List genealogicalDataCommunicationRelations) + { + Dictionary> results = new(); + List? relations; + foreach (GenealogicalDataCommunicationRelation genealogicalDataCommunicationRelation in genealogicalDataCommunicationRelations) + { + if (!results.TryGetValue(genealogicalDataCommunicationRelation.FamilyIndex, out relations)) + { + results.Add(genealogicalDataCommunicationRelation.FamilyIndex, new()); + if (!results.TryGetValue(genealogicalDataCommunicationRelation.FamilyIndex, out relations)) + throw new NotSupportedException(); + } + relations.Add(genealogicalDataCommunicationRelation); + } + return new(results); + } + + internal static void MaybeWriteFiles(string mappingDefaultName, string personBirthdayFormat, List genealogicalDataCommunicationRelations, List personContainers) + { + bool? male; + bool? first; + string fullName; + string[] matches; + string? directory; + bool isDefaultName; + bool verify = true; + const int zero = 0; + string personKeyFormatted; + string lowerHyphenFullName; + List distinct = new(); + Models.GenealogicalDataCommunication genealogicalDataCommunication; + GenealogicalDataCommunicationLines? genealogicalDataCommunicationLines; + ReadOnlyDictionary personKeyFormattedToPersonFullName = GetPersonKeyFormattedToPersonFullName(personBirthdayFormat, personContainers); + ReadOnlyDictionary> familyIndexToCollection = GetFamilyIndexToCollection(genealogicalDataCommunicationRelations); + foreach (Models.PersonContainer personContainer in personContainers.OrderByDescending(l => l.Key)) + { + if (personContainer.Key is null || personContainer.Birthdays is null || personContainer.Person is null || personContainer.PersonDirectory is null || !personContainer.Birthdays.Any()) + continue; + male = personContainer.PersonDirectory.Sex == 'U' ? null : personContainer.PersonDirectory.Sex == 'M' || (personContainer.PersonDirectory.Sex == 'F' ? false : throw new Exception()); + first = personContainer.PersonDirectory.First == 'U' ? null : personContainer.PersonDirectory.First == 'Y' || (personContainer.PersonDirectory.First == 'N' ? false : throw new Exception()); + if (first is null) + continue; + isDefaultName = IPerson.IsDefaultName(mappingDefaultName, personContainer.DisplayDirectoryName); + personKeyFormatted = IPersonBirthday.GetFormatted(personBirthdayFormat, personContainer.Key.Value); + matches = (from l in personContainer.DisplayDirectoryAllFiles where l.Contains(personKeyFormatted) select l).ToArray(); + if (!matches.Any()) + continue; + directory = Path.GetDirectoryName(matches[zero]); + if (directory is null) + continue; + genealogicalDataCommunicationLines = personContainer.GenealogicalDataCommunicationRelationIndividualsLines is null ? null : GenealogicalDataCommunication.GetGenealogicalDataCommunicationLines(personContainer.Birthdays[zero], personContainer.GenealogicalDataCommunicationRelationIndividualsLines); + if (genealogicalDataCommunicationLines is null) + continue; + genealogicalDataCommunication = GenealogicalDataCommunication.GetGenealogicalDataCommunication(genealogicalDataCommunicationLines); + if (genealogicalDataCommunication.Sex != personContainer.PersonDirectory.Sex) + continue; + if (genealogicalDataCommunication.Death is null && personContainer.PersonDirectory.Status == 'D' || genealogicalDataCommunication.Death is not null && personContainer.PersonDirectory.Status == 'A') + continue; + GenealogicalDataCommunication.WriteFile(personKeyFormatted, personContainer.Person.Name, personContainer.GenealogicalDataCommunicationRelationIndividualsLines, isDefaultName, directory, genealogicalDataCommunication, verify, first.Value); + fullName = PersonName.GetFullName(personContainer.Person.Name); + lowerHyphenFullName = $"{Regex.Replace(fullName.ToLower(), "[^a-z0-9-]", "-")}"; + if (distinct.Contains(lowerHyphenFullName)) + continue; + MarkDown.WriteFile(personKeyFormatted, personContainer.Person.Name, genealogicalDataCommunicationRelations, personKeyFormattedToPersonFullName, familyIndexToCollection, isDefaultName, directory, genealogicalDataCommunication, first.Value, fullName, lowerHyphenFullName); + distinct.Add(lowerHyphenFullName); + } + } + } \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/PersonName.cs b/Shared/Models/Stateless/Methods/PersonName.cs index 5935757..61ebfb5 100644 --- a/Shared/Models/Stateless/Methods/PersonName.cs +++ b/Shared/Models/Stateless/Methods/PersonName.cs @@ -62,12 +62,19 @@ internal abstract class PersonName internal static string GetFullName(Models.PersonName personName) { StringBuilder result = new(); - if (personName.First is not null) + if (personName.First is not null && !string.IsNullOrEmpty(personName.First.Value)) _ = result.Append(personName.First.Value); - if (personName.Middle is not null) + if (personName.Middle is not null && !string.IsNullOrEmpty(personName.Middle.Value)) _ = result.Append(' ').Append(personName.Middle.Value); - if (personName.Last is not null) + if (personName.Last is not null && !string.IsNullOrEmpty(personName.Last.Value)) _ = result.Append(' ').Append(personName.Last.Value); + if (personName.Alias is not null && !string.IsNullOrEmpty(personName.Alias.Value)) + { + if (personName.Alias.Value.Contains(" Jr")) + _ = result.Append(" Jr"); + else if (personName.Alias.Value.Contains(" Sr")) + _ = result.Append(" Sr"); + } return result.ToString(); } diff --git a/Tests/UnitTestHardCoded.cs b/Tests/UnitTestHardCoded.cs index 18372e7..1ef43c5 100644 --- a/Tests/UnitTestHardCoded.cs +++ b/Tests/UnitTestHardCoded.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.VisualStudio.TestTools.UnitTesting; using Phares.Shared; using Serilog; +using System.Collections.ObjectModel; using System.Diagnostics; using System.Reflection; using View_by_Distance.Shared.Models; @@ -264,7 +265,7 @@ public partial class UnitTestHardCoded if (Directory.Exists(Path.GetPathRoot(directory)) && Directory.Exists(directory)) { List mappedLines; - Dictionary> individuals; + ReadOnlyDictionary individuals; GenealogicalDataCommunication genealogicalDataCommunication; GenealogicalDataCommunicationLines genealogicalDataCommunicationLines; List<(bool, string)> genealogicalDataCommunicationFiles = new() @@ -274,18 +275,18 @@ public partial class UnitTestHardCoded // new(true, Path.Combine(directory, "Code-638160708345114583/638160708345114583.ged")), // new(true, Path.Combine(directory, "Code-638160728606500015/638160728606500015.ged")), // new(true, Path.Combine(directory, "Code-638160738845419877/638160738845419877.ged")), - // new(true, Path.Combine(directory, "Code-638160743318283885/638160743318283885.ged")), - new(true, Path.Combine(directory, "Code-638245446208013055/638245446208013055.ged")), + new(true, Path.Combine(directory, "Code-638160743318283885/638160743318283885.ged")), + // new(true, Path.Combine(directory, "Code-638245446208013055/638245446208013055.ged")), // new(false, Path.Combine(directory, "Ancestry-Porterfield/Porterfield Family Tree.ged")), // new(true, Path.Combine(directory, "Code-638160708345114583/638160708345114583-Export.ged")), // new(true, Path.Combine(directory, "Code-638160738845419877/638160738845419877-Export.ged")), // new(true, Path.Combine(directory, "Code-638160743318283885/638160743318283885-Export.ged")), - new(true, Path.Combine(directory, "Code-638245446208013055/638245446208013055-Export.ged")), + // new(true, Path.Combine(directory, "Code-638245446208013055/638245446208013055-Export.ged")), }; foreach ((bool requireNickName, string genealogicalDataCommunicationFile) in genealogicalDataCommunicationFiles) { - (_, individuals, _) = IGenealogicalDataCommunication.GetIndividuals(genealogicalDataCommunicationFile, requireNickName); - foreach (KeyValuePair> keyValuePair in individuals) + (_, individuals, _, _, _) = IGenealogicalDataCommunication.GetIndividuals(genealogicalDataCommunicationFile, requireNickName); + foreach (KeyValuePair keyValuePair in individuals) { genealogicalDataCommunicationLines = IGenealogicalDataCommunication.GetGenealogicalDataCommunicationLines(keyValuePair.Value); Assert.IsNotNull(genealogicalDataCommunicationLines.Name); @@ -316,7 +317,7 @@ public partial class UnitTestHardCoded PersonName? personName; string personKeyFormatted; bool isDefaultName = false; - Dictionary> individuals; + ReadOnlyDictionary individuals; GenealogicalDataCommunication genealogicalDataCommunication; GenealogicalDataCommunicationLines genealogicalDataCommunicationLines; List<(bool, bool, string)> genealogicalDataCommunicationFiles = new() @@ -326,19 +327,19 @@ public partial class UnitTestHardCoded // new(true, true, Path.Combine(saveDirectory, "Code-638160708345114583/638160708345114583.ged.cln")), // new(true, true, Path.Combine(saveDirectory, "Code-638160728606500015/638160728606500015.ged.cln")), // new(true, true, Path.Combine(saveDirectory, "Code-638160738845419877/638160738845419877.ged.cln")), - // new(true, true, Path.Combine(saveDirectory, "Code-638160743318283885/638160743318283885.ged.cln")), - new(true, true, Path.Combine(saveDirectory, "Code-638245446208013055/638245446208013055.ged.cln")), + new(true, true, Path.Combine(saveDirectory, "Code-638160743318283885/638160743318283885.ged.cln")), + // new(true, true, Path.Combine(saveDirectory, "Code-638245446208013055/638245446208013055.ged.cln")), // new(false, false, Path.Combine(saveDirectory, "Ancestry-Porterfield/Porterfield Family Tree.ged.cln")), // new(true, true, Path.Combine(saveDirectory, "Code-638160708345114583/638160708345114583-Export.ged.cln")), // new(true, true, Path.Combine(saveDirectory, "Code-638160738845419877/638160738845419877-Export.ged.cln")), // new(true, true, Path.Combine(saveDirectory, "Code-638160743318283885/638160743318283885-Export.ged.cln")), - new(true, true, Path.Combine(saveDirectory, "Code-638245446208013055/638245446208013055-Export.ged.cln")), + // new(true, true, Path.Combine(saveDirectory, "Code-638245446208013055/638245446208013055-Export.ged.cln")), }; foreach ((bool verify, bool requireNickName, string genealogicalDataCommunicationFile) in genealogicalDataCommunicationFiles) { fileName = Path.GetFileNameWithoutExtension(genealogicalDataCommunicationFile).Split(' ')[0]; - (_, individuals, _) = IGenealogicalDataCommunication.GetIndividuals(genealogicalDataCommunicationFile, requireNickName); - foreach (KeyValuePair> keyValuePair in individuals) + (_, individuals, _, _, _) = IGenealogicalDataCommunication.GetIndividuals(genealogicalDataCommunicationFile, requireNickName); + foreach (KeyValuePair keyValuePair in individuals) { genealogicalDataCommunicationLines = IGenealogicalDataCommunication.GetGenealogicalDataCommunicationLines(keyValuePair.Value); Assert.IsNotNull(genealogicalDataCommunicationLines.Name);