diff --git a/.vscode/settings.json b/.vscode/settings.json index 7d21eb0..7cdc830 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,13 +11,26 @@ }, "cSpell.words": [ "ASPNETCORE", + "BIRT", + "CHIL", + "DEAT", "endianness", + "FAMC", + "FAMS", + "GIVN", + "HUSB", + "INDI", "Infineon", "Kanban", "kanbn", + "NSFX", + "OBJE", "onenote", + "pged", "Phares", "Serilog", + "SUBM", + "SURN", "SYSLIB" ] } \ No newline at end of file diff --git a/Helpers/HelperGenealogicalDataCommunication.cs b/Helpers/HelperGenealogicalDataCommunication.cs index 1292c97..fc36a0a 100644 --- a/Helpers/HelperGenealogicalDataCommunication.cs +++ b/Helpers/HelperGenealogicalDataCommunication.cs @@ -2,70 +2,59 @@ using File_Folder_Helper.Models; using Microsoft.Extensions.Logging; using System.Collections.ObjectModel; using System.Globalization; +using System.Text.Json; +using System.Text.RegularExpressions; namespace File_Folder_Helper.Helpers; internal static partial class HelperGenealogicalDataCommunication { - private record Input(string People, + private record Input(string GenealogicalDataCommunicationDirectory, string? GenealogicalDataCommunicationFile, - string? GenealogicalDataCommunicationDirectory, + string? SingletonDirectory, string? Destination); - private record GenealogicalDataCommunicationCollections(string[] HeaderLines, - ReadOnlyDictionary IndividualsLines, - List FamilyGroupLines, - ReadOnlyDictionary PersonKeyToId, - string[] FooterLines); + private record GenealogicalDataCommunicationCollections(ReadOnlyCollection HeaderLines, + ReadOnlyDictionary> IndividualsToLines, + ReadOnlyCollection> FamilyGroupLines, + ReadOnlyCollection FooterLines); + + private record GenealogicalDataCommunicationRelation(int FamilyIndex, + string Relation, + int Id, + string? LineTwo); private record PersonHour(char Status, char Sex, char First); - private record PersonName(string? First, - string? Middle, - string? Last, - string? Alias); - - private record PersonDirectory(string[] DirectoryNames, - DateTime? Birthday, - int? ApproximateYears, - PersonHour? PersonHour, - PersonName? PersonName, - string[]? BirthdayNotes, - string[]? Notes); + [GeneratedRegex("[\\\\,\\/,\\:,\\*,\\?,\\\",\\<,\\>,\\|]")] + private static partial Regex WindowsFileSystem(); private static Input GetInput(List args) { Input result; string? destination = null; - string? genealogicalDataCommunicationFile = null; - string? genealogicalDataCommunicationDirectory = null; + string? singletonDirectory = null; + string genealogicalDataCommunicationRootDirectory = Path.GetFullPath(args[0]); + string fileName = Path.GetFileName(genealogicalDataCommunicationRootDirectory); + string[] files = Directory.GetFiles(genealogicalDataCommunicationRootDirectory, $"{fileName}.ged", SearchOption.TopDirectoryOnly); + string genealogicalDataCommunicationDirectory = Path.Combine(genealogicalDataCommunicationRootDirectory, fileName); + if (!Directory.Exists(genealogicalDataCommunicationDirectory)) + _ = Directory.CreateDirectory(genealogicalDataCommunicationDirectory); for (int i = 1; i < args.Count; i++) { if (args[i].Length == 2 && i + 1 < args.Count) { - if (args[i][1] == 'g') - genealogicalDataCommunicationFile = Path.GetFullPath(args[i + 1]); + if (args[i][1] == 's') + singletonDirectory = Path.GetFullPath(args[i + 1]); else if (args[i][1] == 'd') destination = Path.GetFullPath(args[i + 1]); i++; } } - if (genealogicalDataCommunicationFile is not null) - { - string? root = Path.GetPathRoot(genealogicalDataCommunicationFile); - if (root is null || !Directory.Exists(root)) - throw new NotSupportedException($"This method requires valid -g path <{root}>!"); - if (!File.Exists(genealogicalDataCommunicationFile)) - throw new NotSupportedException($"This method requires valid -g path <{root}>!"); - string? directoryName = Path.GetDirectoryName(genealogicalDataCommunicationFile) ?? - throw new NotSupportedException($"This method requires valid -g path <{root}>!"); - genealogicalDataCommunicationDirectory = Path.Combine(directoryName, Path.GetFileNameWithoutExtension(genealogicalDataCommunicationFile)); - if (!Directory.Exists(genealogicalDataCommunicationDirectory)) - _ = Directory.CreateDirectory(genealogicalDataCommunicationDirectory); - } + string? genealogicalDataCommunicationFile = files.Length != 1 ? null : files[0]; if (destination is not null) { string? root = Path.GetPathRoot(destination); @@ -74,11 +63,11 @@ internal static partial class HelperGenealogicalDataCommunication if (!Directory.Exists(destination)) _ = Directory.CreateDirectory(destination); } - result = new(Path.GetFullPath(args.First()), genealogicalDataCommunicationFile, genealogicalDataCommunicationDirectory, destination); + result = new(genealogicalDataCommunicationDirectory, genealogicalDataCommunicationFile, singletonDirectory, destination); return result; } - private static string[] GetHeaderLines(string startsWith, string[] sourceLines) + private static ReadOnlyCollection GetHeaderLines(string startsWith, string[] sourceLines) { List results = new(); for (int i = 0; i < sourceLines.Length; i++) @@ -87,54 +76,57 @@ internal static partial class HelperGenealogicalDataCommunication break; results.Add(sourceLines[i]); } - return results.ToArray(); - } - - private static ReadOnlyDictionary Convert(Dictionary> keyValuePairs) - { - Dictionary results = new(); - foreach (KeyValuePair> keyValuePair in keyValuePairs) - results.Add(keyValuePair.Key, keyValuePair.Value.ToArray()); return new(results); } - private static GenealogicalDataCommunicationCollections GetGenealogicalDataCommunicationCollections(AppSettings appSettings, Input input) + private static ReadOnlyDictionary> Convert(Dictionary> keyValuePairs) + { + Dictionary> results = new(); + foreach (KeyValuePair> keyValuePair in keyValuePairs) + results.Add(keyValuePair.Key, new(keyValuePair.Value)); + return new(results); + } + + private static long? GetId(string line) + { + long? result; + string[] segments = line.Split('@'); + result = segments[1].Length < 2 || !long.TryParse(segments[1][1..], out long idValue) ? null : idValue; + return result; + } + + private static GenealogicalDataCommunicationCollections GetGenealogicalDataCommunicationCollections(Input input) { GenealogicalDataCommunicationCollections result; - long? personKey; - string[] segments; - string personKeyFormatted; + long? id; List lines = new(); const string startsWith = "0 @"; List footerLines = new(); - List familyGroupLines = new(); - Dictionary personKeyToId = new(); - ReadOnlyDictionary individualsLines; Dictionary> keyValuePairs = new(); - int length = appSettings.PersonBirthdayFormat.Length; + List> familyGroupLines = new(); string[] sourceLines = string.IsNullOrEmpty(input.GenealogicalDataCommunicationFile) || !File.Exists(input.GenealogicalDataCommunicationFile) ? Array.Empty() : File.ReadAllLines(input.GenealogicalDataCommunicationFile); - string[] headerLines = GetHeaderLines(startsWith, sourceLines); - for (int i = headerLines.Length; i < sourceLines.Length; i++) + ReadOnlyCollection headerLines = GetHeaderLines(startsWith, sourceLines); + for (int i = headerLines.Count; i < sourceLines.Length; i++) { if (!sourceLines[i].StartsWith(startsWith)) continue; - lines.Add(sourceLines[i]); if (sourceLines[i].EndsWith("@ SOUR") || sourceLines[i].EndsWith("@ SUBM") || sourceLines[i].EndsWith("@ OBJE") || sourceLines[i].EndsWith("@ REPO")) continue; - else if (sourceLines[i].EndsWith("@ FAM")) + lines.Add(sourceLines[i]); + if (sourceLines[i].EndsWith("@ FAM")) { lines.Clear(); for (int j = sourceLines.Length - 1; j >= i; j--) { lines.Add(sourceLines[j]); - if (sourceLines[j].First() == '0') + if (sourceLines[j][0] == '0') { if (!sourceLines[j].EndsWith("@ FAM")) footerLines.AddRange(lines); else { lines.Reverse(); - familyGroupLines.Add(lines.ToArray()); + familyGroupLines.Add(new(lines.ToArray())); } lines.Clear(); } @@ -145,150 +137,26 @@ internal static partial class HelperGenealogicalDataCommunication } else if (sourceLines[i].EndsWith("@ INDI")) { - personKey = null; - segments = sourceLines[i].Split('@'); + id = GetId(sourceLines[i]); for (int j = i + 1; j < sourceLines.Length; j++) { if (sourceLines[j].StartsWith(startsWith)) break; lines.Add(sourceLines[j]); - if (sourceLines[j].StartsWith("2 NICK ")) - personKeyFormatted = sourceLines[j][7..]; - else if (sourceLines[j] == "1 BIRT" && sourceLines[j + 1].StartsWith("2 DATE") && sourceLines[j + 2].StartsWith("2 NOTE ")) - personKeyFormatted = sourceLines[j + 2][7..]; - else - continue; - if (segments.Length != 3 || personKeyFormatted.Length != length) - continue; - if (!DateTime.TryParseExact(personKeyFormatted, appSettings.PersonBirthdayFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateTime)) - continue; - personKey = dateTime.Ticks; - if (segments[1].Length < 2 || !int.TryParse(segments[1][1..], out int id)) - continue; - personKeyToId.Add(personKey.Value, id); } - if (personKey is null) + if (id is null) throw new Exception(string.Join(Environment.NewLine, lines)); - keyValuePairs.Add(personKey.Value, new()); - if (!lines.Any()) + keyValuePairs.Add(id.Value, new()); + if (lines.Count == 0) continue; - keyValuePairs[personKey.Value].AddRange(lines); + keyValuePairs[id.Value].AddRange(lines); lines.Clear(); } else throw new NotSupportedException(); } - individualsLines = Convert(keyValuePairs); - result = new(headerLines, individualsLines, familyGroupLines, new(personKeyToId), footerLines.ToArray()); - return result; - } - - private static List GetDirectoryNames(string directory) - { - List results = new(); - string? fileName; - string? checkDirectory = directory; - string? pathRoot = Path.GetPathRoot(directory); - string extension = Path.GetExtension(directory); - if (string.IsNullOrEmpty(pathRoot)) - throw new NullReferenceException(nameof(pathRoot)); - if (Directory.Exists(directory)) - { - fileName = Path.GetFileName(directory); - if (!string.IsNullOrEmpty(fileName)) - results.Add(fileName); - } - else if ((string.IsNullOrEmpty(extension) || extension.Length > 3) && !File.Exists(directory)) - { - fileName = Path.GetFileName(directory); - if (!string.IsNullOrEmpty(fileName)) - results.Add(fileName); - } - for (int i = 0; i < int.MaxValue; i++) - { - checkDirectory = Path.GetDirectoryName(checkDirectory); - if (string.IsNullOrEmpty(checkDirectory) || checkDirectory == pathRoot) - break; - fileName = Path.GetFileName(checkDirectory); - if (string.IsNullOrEmpty(fileName)) - continue; - results.Add(fileName); - } - results.Add(pathRoot); - results.Reverse(); - return results; - } - - private static List GetPersonDirectoriesNull(AppSettings appSettings, Input input) - { - List results = new(); - DateTime? birthday; - string directoryName; - string subDirectoryName; - List directoryNames; - IEnumerable subDirectories; - int length = appSettings.PersonBirthdayFormat.Length; - string[] directories = Directory.GetDirectories(input.People, "*", SearchOption.TopDirectoryOnly); - foreach (string directory in directories) - { - directoryName = Path.GetFileName(directory); - if (directoryName.Length != 1 || !appSettings.PersonCharacters.Any(l => directoryName.First() == l)) - continue; - subDirectories = Directory.GetDirectories(directory, "*", SearchOption.AllDirectories); - foreach (string subDirectory in subDirectories) - { - subDirectoryName = Path.GetFileName(subDirectory); - if (subDirectoryName.Length != length) - continue; - if (subDirectoryName.First() is not '1' and not '2') - continue; - directoryNames = GetDirectoryNames(subDirectory); - birthday = !DateTime.TryParseExact(subDirectoryName, appSettings.PersonBirthdayFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateTime) ? null : dateTime; - results.Add(new(directoryNames.ToArray(), birthday, null, null, null, null, null)); - } - } - return results; - } - - private static PersonName GetPersonName(string name) - { - PersonName result; - string? personNameLast; - string? personNameAlias; - string? personNameFirst; - string? personNameMiddle; - string[] segmentsA = name.Split('('); - string[] segmentsB = segmentsA.First().Trim().Split(' '); - if (segmentsB.Length == 1) - { - personNameFirst = null; - personNameLast = null; - personNameMiddle = null; - personNameAlias = segmentsA.Length == 1 ? segmentsB.First() : segmentsA.Last().Split(')').First(); - } - else - { - if (segmentsB.Length == 2) - { - personNameFirst = segmentsB.First(); - personNameLast = segmentsB.Last(); - personNameMiddle = null; - } - else if (segmentsB.Length == 3) - { - personNameFirst = segmentsB.First(); - personNameLast = segmentsB.Last(); - personNameMiddle = segmentsB[1]; - } - else - { - personNameFirst = segmentsB.First(); - personNameLast = string.Join(' ', segmentsB, 1, segmentsB.Length - 1); - personNameMiddle = null; - } - personNameAlias = segmentsA.Length == 1 ? null : segmentsA.Last().Split(')').First(); - } - result = new(personNameFirst, personNameMiddle, personNameLast, personNameAlias); + ReadOnlyDictionary> individualsToLines = Convert(keyValuePairs); + result = new(headerLines, individualsToLines, new(familyGroupLines), new(footerLines)); return result; } @@ -324,292 +192,582 @@ internal static partial class HelperGenealogicalDataCommunication hour == 17 ? "Dead-Male-No" : throw new NotImplementedException(personDisplayDirectoryName); - private static List GetNotes(PersonDirectory personDirectory) + private static (int, Name) GetName(ReadOnlyCollection lines, int i) + { + Name result; + string seven; + string? surName = null; + string? nickName = null; + string? givenName = null; + string? suffixName = null; + string? forwardSlashFullName = lines[i][..7] == "1 NAME " ? lines[i][7..] : null; + for (int j = i + 1; j < lines.Count; j++) + { + if (lines[j][0] == '1') + break; + i++; + if (lines[j].Length < 7) + throw new NotImplementedException(); + seven = lines[j][..7]; + if (seven == "2 GIVN ") + givenName = lines[j][7..]; + else if (seven == "2 SURN ") + surName = lines[j][7..]; + else if (seven == "2 NICK ") + nickName = lines[j][7..]; + else if (seven == "2 NSFX ") + suffixName = lines[j][7..]; + else + throw new NotImplementedException(); + } + result = new(forwardSlashFullName, givenName, surName, nickName, suffixName); + return (i, result); + } + + private static (int, Birth, bool) GetBirth(Dictionary> keyValuePairs, ReadOnlyCollection lines, int i) + { + Birth result; + string seven; + string? note = null; + bool moreAdded = false; + List? collection; + DateOnly? dateOnly = null; + List distinct = new(); + List @continue = new(); + for (int j = i + 1; j < lines.Count; j++) + { + if (lines[j][0] == '1') + break; + i++; + if (lines[j].Length < 7) + throw new NotImplementedException(); + seven = lines[j][..7]; + if (seven == "2 DATE ") + dateOnly = !DateOnly.TryParseExact(lines[j][7..], "d MMM yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly dateOnlyValue) ? null : dateOnlyValue; + else if (seven == "2 NOTE ") + note = lines[j][7..]; + else if (seven == "3 CONT ") + { + for (int k = j; k < lines.Count; k++) + { + seven = lines[k][..7]; + if (seven != "3 CONT ") + break; + j++; + @continue.Add(lines[k][7..]); + } + if (note is not null && keyValuePairs.TryGetValue(note, out collection)) + { + distinct.Add(note); + distinct.AddRange(@continue); + foreach (string text in collection) + { + if (distinct.Contains(text)) + continue; + distinct.Add(text); + if (!moreAdded) + moreAdded = true; + @continue.Add(text); + } + } + } + else + continue; + } + result = new(dateOnly, note, @continue); + return (i, result, moreAdded); + } + + private static (int, Death) GetDeath(ReadOnlyCollection lines, int i) + { + Death result; + string seven; + string? note = null; + DateOnly? dateOnly = null; + List @continue = new(); + bool? isDead = lines[i].Length == 8 && lines[i][..8] == "1 DEAT Y" ? true : lines[i].Length == 8 && lines[i][..8] == "1 DEAT N" ? false : null; + for (int j = i + 1; j < lines.Count; j++) + { + if (lines[j][0] == '1') + break; + i++; + if (lines[j].Length < 7) + throw new NotImplementedException(); + seven = lines[j][..7]; + if (seven == "2 DATE ") + dateOnly = !DateOnly.TryParseExact(lines[j][7..], "d MMM yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly dateOnlyValue) ? null : dateOnlyValue; + else if (seven == "2 NOTE ") + note = lines[j][7..]; + else if (seven == "3 CONT ") + { + for (int k = j; k < lines.Count; k++) + { + seven = lines[k][..7]; + if (seven != "3 CONT ") + break; + j++; + @continue.Add(lines[k][7..]); + } + } + else + throw new NotImplementedException(); + } + result = new(dateOnly is not null ? true : isDead, dateOnly, note, @continue); + return (i, result); + } + + private static (int, Change) GetChange(ReadOnlyCollection lines, int i) + { + Change result; + string seven; + string? note = null; + DateOnly? dateOnly = null; + List @continue = new(); + for (int j = i + 1; j < lines.Count; j++) + { + if (lines[j][0] == '1') + break; + i++; + if (lines[j].Length < 7) + throw new NotImplementedException(); + seven = lines[j][..7]; + if (seven == "2 DATE ") + dateOnly = !DateOnly.TryParseExact(lines[j][7..], "d MMM yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly dateOnlyValue) ? null : dateOnlyValue; + else if (seven == "2 NOTE ") + note = lines[j][7..]; + else if (seven == "3 CONT ") + { + for (int k = j; k < lines.Count; k++) + { + seven = lines[k][..7]; + if (seven != "3 CONT ") + break; + j++; + @continue.Add(lines[k][7..]); + } + } + else + throw new NotImplementedException(); + } + result = new(dateOnly, note, @continue); + return (i, result); + } + + private static Dictionary> GetTxtFileCollection(Input input) + { + Dictionary> results = new(); + string[] lines; + string[] directories; + string directoryName; + string? sourceDirectory; + string? parentDirectory; + List? collectionA; + List? collectionB; + string siblingDirectoryName; + string[] files = input.SingletonDirectory is null || !Directory.Exists(input.SingletonDirectory) ? Array.Empty() : Directory.GetFiles(input.SingletonDirectory, "*.txt", SearchOption.AllDirectories); + foreach (string file in files) + { + sourceDirectory = Path.GetDirectoryName(file); + if (sourceDirectory is null) + continue; + parentDirectory = Path.GetDirectoryName(sourceDirectory); + if (parentDirectory is null) + continue; + lines = File.ReadAllLines(file); + if (lines.Length != 1 || lines.Length == 2 && string.IsNullOrEmpty(lines[1])) + continue; + directoryName = Path.GetFileName(sourceDirectory); + if (!results.TryGetValue(directoryName, out collectionA)) + { + results.Add(directoryName, new()); + if (!results.TryGetValue(directoryName, out collectionA)) + throw new Exception(); + } + collectionA.Add(lines[0]); + directories = Directory.GetDirectories(parentDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string directory in directories) + { + siblingDirectoryName = Path.GetFileName(directory); + collectionA.Add(siblingDirectoryName); + } + foreach (string directory in directories) + { + siblingDirectoryName = Path.GetFileName(directory); + if (!results.TryGetValue(siblingDirectoryName, out collectionB)) + { + results.Add(siblingDirectoryName, new()); + if (!results.TryGetValue(siblingDirectoryName, out collectionB)) + throw new Exception(); + } + collectionB.AddRange(collectionA); + collectionB.Add(lines[0]); + } + } + return results; + } + + private static string[] GetNewLines(string[] lines, Birth? birth) { List results = new(); - string directory = string.Join(Path.DirectorySeparatorChar, personDirectory.DirectoryNames); - if (Directory.Exists(directory)) + string six; + string text; + string seven; + List @continue = birth is null ? new() : birth.Continue.ToList(); + for (int i = 0; i < lines.Length; i++) { - string[] lines; - string[] files = Directory.GetFiles(directory, "*", SearchOption.AllDirectories); - foreach (string file in files) + if (birth is null) + throw new NotSupportedException(); + if (lines[i].Length < 6) + throw new NotImplementedException(); + results.Add(lines[i]); + six = lines[i][..6]; + if (lines[i][0] == '1') { - if (file.EndsWith(".lnk") || file.EndsWith(".rel") || file.EndsWith(".pged") || file.EndsWith(".gif") || file.EndsWith(".jpg") || file.EndsWith(".png")) + if (six != "1 BIRT") continue; - lines = File.ReadAllLines(file); - if (lines.Length > 0 && lines.First().StartsWith("0 @I")) - continue; - if (!lines.Any(l => l.StartsWith("http"))) - continue; - results.AddRange(lines.Where(l => !string.IsNullOrEmpty(l))); + for (int j = i + 1; j < lines.Length; j++) + { + if (lines[j].Length < 7) + throw new NotImplementedException(); + if (lines[j][0] == '1') + break; + i++; + seven = lines[j][..7]; + if (seven != "3 CONT ") + results.Add(lines[j]); + else + { + text = lines[j][7..]; + if (@continue.Contains(text)) + { + results.Add(lines[j]); + _ = @continue.Remove(text); + } + } + } + results.AddRange(from l in @continue orderby l select $"3 CONT {l}"); } } - return results; + return results.ToArray(); } - private static string GetApproximateYears(AppSettings appSettings, string personDisplayDirectoryName) => - $"{appSettings.PersonCharacters.Where(l => personDisplayDirectoryName.Contains(l)).FirstOrDefault()}{personDisplayDirectoryName.Split(appSettings.PersonCharacters).Last()}"; - - private static List GetPersonDirectories(AppSettings appSettings, ReadOnlyDictionary> keyValuePairs) + private static Dictionary GetPeople(Input input, GenealogicalDataCommunicationCollections genealogicalDataCommunicationCollections) { - List results = new(); - PersonDirectory[] sorted; - List notes = new(); - PersonDirectory personDirectory; - List birthdayNotes = new(); - foreach (KeyValuePair> keyValuePair in keyValuePairs) + Dictionary results = new(); + string six; + Person person; + long? id = null; + char? sex = null; + Name? name = null; + string? uId = null; + Birth? birth = null; + Death? death = null; + Change? change = null; + bool? moreAdded = null; + ReadOnlyCollection lines; + Dictionary> keyValuePairs = GetTxtFileCollection(input); + foreach (KeyValuePair> keyValuePair in genealogicalDataCommunicationCollections.IndividualsToLines) { - if (keyValuePair.Value.Count == 0) - throw new NotSupportedException(); - sorted = (from l in keyValuePair.Value orderby l.Birthday is null, l.Birthday descending select l).ToArray(); - personDirectory = sorted.First(); - if (personDirectory.Birthday is null) - continue; - for (int i = 0; i < sorted.Length; i++) + lines = keyValuePair.Value; + for (int i = 0; i < lines.Count; i++) { - notes.AddRange(GetNotes(sorted[i])); - if (i == 0) - birthdayNotes.Add(GetApproximateYears(appSettings, sorted[i].DirectoryNames[^2])); - if (i > 0) - birthdayNotes.Add(sorted[i].DirectoryNames.Last()); - if (i == sorted.Length - 1) + if (lines[i].Length < 6) + throw new NotImplementedException(); + six = lines[i][..6]; + if (lines[i][0] == '0') { - results.Add(new(personDirectory.DirectoryNames, personDirectory.Birthday, personDirectory.ApproximateYears, personDirectory.PersonHour, personDirectory.PersonName, birthdayNotes.ToArray(), notes.ToArray())); - notes.Clear(); - birthdayNotes.Clear(); + if (lines[i][^6..] == "@ INDI") + id = GetId(lines[i]); + else + throw new NotImplementedException(); + } + else if (lines[i][0] == '1') + { + if (six == "1 NAME") + (i, name) = GetName(lines, i); + else if (six == "1 SEX ") + sex = lines[i].Length != 7 ? null : lines[i][6]; + else if (six is "1 UID " or "1 _UID") + uId = lines[i].Length == 6 ? null : lines[i][7..]; + else if (six == "1 BIRT") + (i, birth, moreAdded) = GetBirth(keyValuePairs, lines, i); + else if (six == "1 DEAT") + (i, death) = GetDeath(lines, i); + else if (six == "1 CHAN") + (i, change) = GetChange(lines, i); + else if (six is "1 FAMC" or "1 FAMS") + continue; + else + throw new NotImplementedException(); } } + if (id is null) + throw new Exception(string.Join(Environment.NewLine, lines)); + if (moreAdded is null || !moreAdded.Value) + person = new(id.Value, name, sex, uId, birth, death, change, lines.ToArray()); + else + person = new(id.Value, name, sex, uId, birth, death, change, GetNewLines(lines.ToArray(), birth)); + results.Add(id.Value, person); } return results; } - private static ReadOnlyDictionary> Convert(List collection) + private static (ReadOnlyDictionary, ReadOnlyDictionary, ReadOnlyDictionary, ReadOnlyCollection<(long, ReadOnlyCollection, string, char[], DateTime, long)>) GetCollections(AppSettings appSettings, Dictionary people) { - Dictionary> results = new(); - string personDisplayDirectoryName; - List? personDirectories; - foreach (PersonDirectory personDirectory in collection) + long personKey; + char[] ageCollection; + Dictionary idToName = new(); + Dictionary idToPersonKey = new(); + Dictionary idToGivenName = new(); + int length = appSettings.PersonBirthdayFormat.Length; + List<(long, ReadOnlyCollection, string, char[], DateTime, long)> collection = new(); + foreach (KeyValuePair keyValuePair in people) { - personDisplayDirectoryName = personDirectory.DirectoryNames[^2]; - if (!results.TryGetValue(personDisplayDirectoryName, out personDirectories)) + if (keyValuePair.Value.Birth?.Note is null) + continue; + if (string.IsNullOrEmpty(keyValuePair.Value.Name?.ForwardSlashFull)) + continue; + if (keyValuePair.Value.Birth.Note.Length != length) + continue; + if (!DateTime.TryParseExact(keyValuePair.Value.Birth.Note, appSettings.PersonBirthdayFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateTime)) + continue; + personKey = dateTime.Ticks; + idToPersonKey.Add(keyValuePair.Key, personKey); + idToName.Add(keyValuePair.Key, WindowsFileSystem().Replace(keyValuePair.Value.Name.ForwardSlashFull, "_")); + ageCollection = keyValuePair.Value.Birth.Continue.Count == 0 ? Array.Empty() : keyValuePair.Value.Birth.Continue[0].ToArray(); + idToGivenName.Add(keyValuePair.Key, string.IsNullOrEmpty(keyValuePair.Value.Name.Given) ? WindowsFileSystem().Replace(keyValuePair.Value.Name.ForwardSlashFull, "_") : WindowsFileSystem().Replace(keyValuePair.Value.Name.Given, "_")); + collection.Add((keyValuePair.Key, new(keyValuePair.Value.Lines), keyValuePair.Value.Birth.Note, ageCollection, dateTime, personKey)); + } + return (new(idToPersonKey), new(idToName), new(idToGivenName), new(collection)); + } + + private static void ExportFamilies(AppSettings appSettings, Input input, GenealogicalDataCommunicationCollections genealogicalDataCommunicationCollections, ReadOnlyDictionary idToPersonKey, ReadOnlyDictionary idToName, ReadOnlyDictionary idToGivenName) + { + string? name; + long personKey; + DateTime dateTime; + string? givenName; + string familyIndex; + string? destinationRoot; + const string wife = "WIFE"; + const string child = "CHIL"; + string destinationDirectory; + const string husband = "HUSB"; + string wifeName = string.Empty; + string? lastFamilyIndex = null; + string husbandName = string.Empty; + ReadOnlyCollection genealogicalDataCommunicationRelations = GetRelations(genealogicalDataCommunicationCollections.FamilyGroupLines); + foreach (GenealogicalDataCommunicationRelation genealogicalDataCommunicationRelation in genealogicalDataCommunicationRelations) + { + if (idToName.Count == 0 || idToGivenName.Count == 0) + break; + destinationRoot = Path.GetDirectoryName(input.GenealogicalDataCommunicationFile); + if (string.IsNullOrEmpty(destinationRoot)) + continue; + if (!idToName.TryGetValue(genealogicalDataCommunicationRelation.Id, out name)) + continue; + if (!idToGivenName.TryGetValue(genealogicalDataCommunicationRelation.Id, out givenName)) + continue; + familyIndex = genealogicalDataCommunicationRelation.FamilyIndex.ToString("0000"); + if (lastFamilyIndex is not null && lastFamilyIndex != familyIndex) { - results.Add(personDisplayDirectoryName, new()); - if (!results.TryGetValue(personDisplayDirectoryName, out personDirectories)) - throw new NotSupportedException(); + wifeName = string.Empty; + husbandName = string.Empty; } - personDirectories.Add(personDirectory); + lastFamilyIndex = familyIndex; + if (genealogicalDataCommunicationRelation.Relation == husband) + { + husbandName = givenName; + continue; + } + if (genealogicalDataCommunicationRelation.Relation == wife) + { + wifeName = givenName; + continue; + } + if (genealogicalDataCommunicationRelation.Relation != child) + continue; + if (!idToPersonKey.TryGetValue(genealogicalDataCommunicationRelation.Id, out personKey)) + continue; + dateTime = new(personKey); + destinationDirectory = Path.Combine(destinationRoot, string.Concat($"{husbandName}-{wifeName}".Trim('-'), "-", familyIndex), name, dateTime.ToString(appSettings.PersonBirthdayFormat)); + if (!Directory.Exists(destinationDirectory)) + _ = Directory.CreateDirectory(destinationDirectory); + File.WriteAllText(Path.Combine(destinationDirectory, ".txt"), genealogicalDataCommunicationRelation.LineTwo); } - return new(results); - } - - private static ReadOnlyCollection GetPersonDirectories(AppSettings appSettings, Input input) - { - List results; - string[] segments; - PersonName personName; - PersonHour? personHour; - string personDisplayDirectoryName; - List collection = new(); - List personDirectories = GetPersonDirectoriesNull(appSettings, input); - foreach (PersonDirectory personDirectory in personDirectories) + foreach (GenealogicalDataCommunicationRelation genealogicalDataCommunicationRelation in genealogicalDataCommunicationRelations) { - if (personDirectory.DirectoryNames.Length < 3) + if (idToName.Count == 0 || idToGivenName.Count == 0) + break; + destinationRoot = Path.GetDirectoryName(input.GenealogicalDataCommunicationFile); + if (string.IsNullOrEmpty(destinationRoot)) continue; - personDisplayDirectoryName = personDirectory.DirectoryNames[^2]; - segments = personDisplayDirectoryName.Split(appSettings.PersonCharacters); - if (segments.Length == 1 || !int.TryParse(segments.Last().Split('-').First(), out int years)) + if (!idToName.TryGetValue(genealogicalDataCommunicationRelation.Id, out name)) continue; - personName = GetPersonName(segments.First()); - personHour = personDirectory.Birthday is null ? null : GetPersonHour(personDisplayDirectoryName, personDirectory.Birthday.Value.Hour); - collection.Add(new(personDirectory.DirectoryNames, personDirectory.Birthday, years, personHour, personName, null, null)); + if (!idToPersonKey.TryGetValue(genealogicalDataCommunicationRelation.Id, out personKey)) + continue; + dateTime = new(personKey); + destinationDirectory = Path.Combine(destinationRoot, "A-A-0000", name, dateTime.ToString(appSettings.PersonBirthdayFormat)); + if (!Directory.Exists(destinationDirectory)) + _ = Directory.CreateDirectory(destinationDirectory); + File.WriteAllText(Path.Combine(destinationDirectory, ".txt"), genealogicalDataCommunicationRelation.LineTwo); } - ReadOnlyDictionary> keyValuePairs = Convert(collection); - results = GetPersonDirectories(appSettings, keyValuePairs); - return new(results); } - private static Dictionary GetCopyOfPersonKeyToId(GenealogicalDataCommunicationCollections genealogicalDataCommunicationCollections) + private static void WriteGenealogicalDataCommunicationCollections(ILogger logger, Input input, GenealogicalDataCommunicationCollections genealogicalDataCommunicationCollections, Dictionary people) { - Dictionary results = new(); - foreach (KeyValuePair keyValuePair in genealogicalDataCommunicationCollections.PersonKeyToId) - results.Add(keyValuePair.Key, keyValuePair.Value); - return results; - } - - private static ReadOnlyDictionary GetIndividualsLines(AppSettings appSettings, GenealogicalDataCommunicationCollections genealogicalDataCommunicationCollections, ReadOnlyCollection personDirectories) - { - Dictionary results = new(); - int id; - string suffix; - string segmentsFirst; List lines = new(); - string personDisplayDirectoryName; - Dictionary copyOfPersonKeyToId = GetCopyOfPersonKeyToId(genealogicalDataCommunicationCollections); - int maxId = copyOfPersonKeyToId.Count == 0 ? 1 : copyOfPersonKeyToId.Values.Max(); - foreach (PersonDirectory personDirectory in personDirectories) + List allLines = new(); + if (genealogicalDataCommunicationCollections.HeaderLines.Count > 0) + { + allLines.AddRange(genealogicalDataCommunicationCollections.HeaderLines); + File.WriteAllLines(Path.Combine(input.GenealogicalDataCommunicationDirectory, "a.pged"), genealogicalDataCommunicationCollections.HeaderLines); + } + if (people.Count > 0) { lines.Clear(); - if (personDirectory.Birthday is null || personDirectory.PersonName is null) - continue; - if (personDirectory.DirectoryNames.Length < 3) - continue; - if (!string.IsNullOrEmpty(personDirectory.PersonName.Alias) && personDirectory.PersonName.Alias.Contains("Jr")) - suffix = " Jr"; - else if (!string.IsNullOrEmpty(personDirectory.PersonName.Alias) && personDirectory.PersonName.Alias.Contains("Sr")) - suffix = " Sr"; - else - suffix = string.Empty; - if (copyOfPersonKeyToId.TryGetValue(personDirectory.Birthday.Value.Ticks, out id)) - _ = copyOfPersonKeyToId.Remove(personDirectory.Birthday.Value.Ticks); - else + ReadOnlyCollection? collection; + foreach (KeyValuePair keyValuePair in people) { - maxId += 1; - id = maxId; + if (!genealogicalDataCommunicationCollections.IndividualsToLines.TryGetValue(keyValuePair.Key, out collection)) + throw new NotSupportedException(); + if (keyValuePair.Value.Lines.Length != collection.Count) + logger.LogInformation("{name} has been changed", keyValuePair.Value.Name?.ForwardSlashFull); + lines.AddRange(keyValuePair.Value.Lines); } - personDisplayDirectoryName = personDirectory.DirectoryNames[^2]; - segmentsFirst = personDisplayDirectoryName.Split(appSettings.PersonCharacters).First(); - lines.Add($"0 @I{id}@ INDI"); - lines.Add($"1 NAME {personDirectory.PersonName.First} /{personDirectory.PersonName.Last}/{suffix}"); - if (!string.IsNullOrEmpty(personDirectory.PersonName.First)) - lines.Add($"2 GIVN {personDirectory.PersonName.First}"); - if (!string.IsNullOrEmpty(personDirectory.PersonName.Last)) - lines.Add($"2 SURN {personDirectory.PersonName.Last}"); - if (!string.IsNullOrEmpty(suffix)) - lines.Add($"2 NSFX {suffix.Trim()}"); - if ($"{personDirectory.PersonName.First} {personDirectory.PersonName.Last}" != segmentsFirst) - lines.Add($"2 NICK {segmentsFirst}"); - if (personDirectory.PersonHour is not null && personDirectory.PersonHour.Sex != 'U') - lines.Add($"1 SEX {personDirectory.PersonHour.Sex}"); - lines.Add($"1 _UID {personDirectory.Birthday.Value.Ticks}"); - lines.Add("1 BIRT"); - lines.Add($"2 DATE {personDirectory.Birthday.Value:d MMM yyyy}".ToUpper()); - lines.Add($"2 NOTE {personDirectory.Birthday.Value.ToString(appSettings.PersonBirthdayFormat)}"); - if (personDirectory.BirthdayNotes is not null) - { - foreach (string birthdayNote in personDirectory.BirthdayNotes) - lines.Add($"3 CONT {birthdayNote}"); - } - if (personDirectory.PersonHour is not null) - { - if (personDirectory.PersonHour.Status == 'D') - lines.Add("1 DEAT Y"); - } - if (personDirectory.Notes is not null) - { - if (personDirectory.Notes.Length > 0) - { - lines.Add($"1 NOTE {personDirectory.Notes.First()}"); - for (int j = 1; j < personDirectory.Notes.Length; j++) - lines.Add($"2 CONT {personDirectory.Notes[j]}"); - } - } - results.Add(id, lines.ToArray()); + allLines.AddRange(lines); + File.WriteAllLines(Path.Combine(input.GenealogicalDataCommunicationDirectory, "b.pged"), lines); } - if (copyOfPersonKeyToId.Count > 0) - throw new Exception(string.Join(Environment.NewLine, copyOfPersonKeyToId.Keys.Select(l => new DateTime(l).ToString(appSettings.PersonBirthdayFormat)))); - return new(results); - } - - private static void WriteGenealogicalDataCommunicationCollections(Input input, GenealogicalDataCommunicationCollections genealogicalDataCommunicationCollections, ReadOnlyDictionary individualsLines) - { - if (input.GenealogicalDataCommunicationDirectory is not null) + if (genealogicalDataCommunicationCollections.FamilyGroupLines.Count > 0) { - List lines = new(); - List allLines = new(); - allLines.AddRange(genealogicalDataCommunicationCollections.HeaderLines); - if (genealogicalDataCommunicationCollections.HeaderLines.Length > 0) - File.WriteAllLines(Path.Combine(input.GenealogicalDataCommunicationDirectory, "a.pged"), genealogicalDataCommunicationCollections.HeaderLines); - if (individualsLines.Count > 0) - { - foreach (IEnumerable keyValuePair in from l in individualsLines orderby l.Key select l.Value) - allLines.AddRange(keyValuePair); - } - if (genealogicalDataCommunicationCollections.IndividualsLines.Count > 0) - { - lines.Clear(); - foreach (KeyValuePair keyValuePair in genealogicalDataCommunicationCollections.IndividualsLines) - lines.AddRange(keyValuePair.Value); - if (individualsLines.Count == 0) - allLines.AddRange(lines); - File.WriteAllLines(Path.Combine(input.GenealogicalDataCommunicationDirectory, "b.pged"), lines); - } - if (genealogicalDataCommunicationCollections.FamilyGroupLines.Count > 0) - { - lines.Clear(); - foreach (string[] keyValuePair in genealogicalDataCommunicationCollections.FamilyGroupLines) - lines.AddRange(keyValuePair); - allLines.AddRange(lines); - File.WriteAllLines(Path.Combine(input.GenealogicalDataCommunicationDirectory, "c.pged"), lines); - } - allLines.AddRange(genealogicalDataCommunicationCollections.FooterLines); - if (genealogicalDataCommunicationCollections.FooterLines.Length > 0) - File.WriteAllLines(Path.Combine(input.GenealogicalDataCommunicationDirectory, "d.pged"), genealogicalDataCommunicationCollections.FooterLines); - File.WriteAllLines(Path.Combine(input.GenealogicalDataCommunicationDirectory, "e.ged"), allLines); + lines.Clear(); + foreach (ReadOnlyCollection keyValuePair in genealogicalDataCommunicationCollections.FamilyGroupLines) + lines.AddRange(keyValuePair); + allLines.AddRange(lines); + File.WriteAllLines(Path.Combine(input.GenealogicalDataCommunicationDirectory, "c.pged"), lines); } + if (genealogicalDataCommunicationCollections.FooterLines.Count > 0) + { + allLines.AddRange(genealogicalDataCommunicationCollections.FooterLines); + File.WriteAllLines(Path.Combine(input.GenealogicalDataCommunicationDirectory, "d.pged"), genealogicalDataCommunicationCollections.FooterLines); + } + File.WriteAllLines(Path.Combine(input.GenealogicalDataCommunicationDirectory, "e.ged"), allLines); } private static string? GetYearGroup(string year) => !int.TryParse(year[2..], out int part) ? null : string.Concat(year[..^2], part < 50 ? "--" : "++"); - private static void Export(AppSettings appSettings, Input input, long ticks, GenealogicalDataCommunicationCollections genealogicalDataCommunicationCollections, ReadOnlyCollection personDirectories, ReadOnlyDictionary individualsLines) + private static (int, TimeSpan) GetAge(long minuendTicks, long subtrahendTicks) { - if (input.Destination is not null && personDirectories.Count > 0) + TimeSpan result; + int years = 0; + DateTime check = new(subtrahendTicks); + for (int i = 0; i < int.MaxValue; i++) { - int id; - string[]? lines; - string directory; - string hourGroup; - string? yearGroup; - long count = ticks; - string personDisplayDirectoryName; - foreach (PersonDirectory personDirectory in personDirectories) + check = check.AddYears(1); + if (check.Ticks > minuendTicks) + break; + years += 1; + } + result = new(minuendTicks - check.AddYears(-1).Ticks); + return (years, result); + } + + private static void Export(Input input, long ticks, ReadOnlyDictionary idToName, ReadOnlyCollection<(long, ReadOnlyCollection, string, char[], DateTime, long)> collection) + { + int age; + string text; + string? name; + string directory; + string hourGroup; + string? yearGroup; + long count = ticks; + string rootDirectory; + string approximateYears; + List distinct = new(); + List duplicates = new(); + string personDisplayDirectoryName; + foreach ((long id, ReadOnlyCollection lines, string personKeyFormatted, char[] ageCollection, DateTime dateTime, long personKey) in collection) + { + if (input.Destination is null) + break; + if (!idToName.TryGetValue(id, out name)) + continue; + hourGroup = GetHourGroup(name, dateTime.Hour); + (age, _) = GetAge(DateTime.Now.Ticks, personKey); + for (int i = 1; i < 3; i++) { - if (personDirectory.Birthday is null || personDirectory.PersonName is null) - continue; - if (personDirectory.DirectoryNames.Length < 3) - continue; - personDisplayDirectoryName = personDirectory.DirectoryNames[^2]; - hourGroup = GetHourGroup(personDisplayDirectoryName, personDirectory.Birthday.Value.Hour); - for (int i = 1; i < 3; i++) + if (i == 2) { - if (i == 2) - yearGroup = GetYearGroup(personDirectory.Birthday.Value.Year.ToString()); - else if (i == 1) - yearGroup = appSettings.PersonCharacters.Where(l => personDisplayDirectoryName.Contains(l)).First().ToString(); - else - throw new NotSupportedException(); + yearGroup = GetYearGroup(dateTime.Year.ToString()); + personDisplayDirectoryName = name; if (string.IsNullOrEmpty(yearGroup)) continue; - directory = Path.Combine(input.Destination, yearGroup, hourGroup, personDisplayDirectoryName, personDirectory.Birthday.Value.ToString(appSettings.PersonBirthdayFormat)); - if (!Directory.Exists(directory)) - _ = Directory.CreateDirectory(directory); + } + else if (i == 1) + { + yearGroup = ageCollection[0].ToString(); + approximateYears = yearGroup[0] == '^' ? $"^{age}" : new string(ageCollection); + personDisplayDirectoryName = $"{name}{approximateYears}"; + if (distinct.Contains(personDisplayDirectoryName)) + { + duplicates.Add(personDisplayDirectoryName); + continue; + } + distinct.Add(personDisplayDirectoryName); + } + else + throw new NotSupportedException(); + rootDirectory = i == 1 ? input.Destination : i == 2 ? input.GenealogicalDataCommunicationDirectory : throw new NotSupportedException(); + directory = Path.Combine(rootDirectory, yearGroup, hourGroup, personDisplayDirectoryName, personKeyFormatted); + if (!Directory.Exists(directory)) + _ = Directory.CreateDirectory(directory); + if (i == 2) + { + text = string.Join(Environment.NewLine, lines); + count += 1; + File.WriteAllText(Path.Combine(directory, $"{count}-A.pged"), text); + } + text = string.Join(Environment.NewLine, lines); + if (!string.IsNullOrEmpty(text)) + { + count += 1; if (i == 2) - { - if (!genealogicalDataCommunicationCollections.IndividualsLines.TryGetValue(personDirectory.Birthday.Value.Ticks, out lines)) - lines = Array.Empty(); - count += 1; - File.WriteAllLines(Path.Combine(directory, $"{count}-A.pged"), lines); - } - if (!genealogicalDataCommunicationCollections.PersonKeyToId.TryGetValue(personDirectory.Birthday.Value.Ticks, out id)) - lines = null; + File.WriteAllText(Path.Combine(directory, $"{count}-B.pged"), text); else - { - if (!individualsLines.TryGetValue(id, out lines)) - lines = null; - } - if (i == 1 && lines is null) - lines = Array.Empty(); - if (lines is not null) - { - count += 1; - if (i == 1) - File.WriteAllLines(Path.Combine(directory, $"B.pged"), lines); - else - File.WriteAllLines(Path.Combine(directory, $"{count}-B.pged"), lines); - } + File.WriteAllText(Path.Combine(directory, $"{personKeyFormatted}.pged"), text); } } } + if (duplicates.Count > 0) + throw new NotSupportedException(); + } + + private static ReadOnlyCollection GetRelations(ReadOnlyCollection> familyGroupLines) + { + List results = new(); + int id; + string relation; + string[] segments; + ReadOnlyCollection familyLines; + for (int i = 0; i < familyGroupLines.Count; i++) + { + familyLines = familyGroupLines[i]; + for (int j = 0; j < familyLines.Count; j++) + { + segments = familyLines[j].Split('@'); + if (segments[0].Length < 3 || segments.Length != 3) + continue; + if (!int.TryParse(segments[1][1..], out id)) + continue; + relation = segments[0][2..].Trim(); + if (j + 1 >= familyLines.Count || familyLines[j + 1].Length < 3 || familyLines[j + 1][..3] != "2 _") + results.Add(new(i, relation, id, null)); + else + results.Add(new(i, relation, id, familyLines[j + 1][2..])); + } + } + return new(results.OrderBy(l => l.FamilyIndex).ToArray()); } internal static void FileSystemToGenealogicalDataCommunication(AppSettings appSettings, ILogger logger, List args) @@ -617,18 +775,26 @@ internal static partial class HelperGenealogicalDataCommunication Input input = GetInput(args); long ticks = DateTime.Now.Ticks; logger.LogInformation("{ticks}", ticks); - logger.LogInformation("{newValue}", new DateTime(638258293638438812).AddDays(4).Ticks); - GenealogicalDataCommunicationCollections genealogicalDataCommunicationCollections = GetGenealogicalDataCommunicationCollections(appSettings, input); - ReadOnlyCollection personDirectories = GetPersonDirectories(appSettings, input); - ReadOnlyDictionary individualsLines = GetIndividualsLines(appSettings, genealogicalDataCommunicationCollections, personDirectories); - if (string.IsNullOrEmpty(input.GenealogicalDataCommunicationDirectory)) - logger.LogInformation("{distance} is null?", input.GenealogicalDataCommunicationDirectory); - else if (individualsLines.Count == 0) - logger.LogInformation("{count} lines?", individualsLines.Count); - if (input.GenealogicalDataCommunicationDirectory is not null) - WriteGenealogicalDataCommunicationCollections(input, genealogicalDataCommunicationCollections, individualsLines); + logger.LogInformation("{old} {days} day(s) => {new}", 638258293638438812, 4, new DateTime(638258293638438812).AddDays(4.0001).Ticks); + GenealogicalDataCommunicationCollections genealogicalDataCommunicationCollections = GetGenealogicalDataCommunicationCollections(input); + Dictionary people = GetPeople(input, genealogicalDataCommunicationCollections); + if (people.Count != genealogicalDataCommunicationCollections.IndividualsToLines.Count) + throw new NotSupportedException(); + string json = JsonSerializer.Serialize(people, PeopleSourceGenerationContext.Default.DictionaryInt64Person); + File.WriteAllText(Path.Combine(input.GenealogicalDataCommunicationDirectory, ".json"), json); + Dictionary? result = JsonSerializer.Deserialize(json, PeopleSourceGenerationContext.Default.DictionaryInt64Person); + if (result is null) + throw new NullReferenceException(nameof(result)); + (ReadOnlyDictionary idToPersonKey, ReadOnlyDictionary idToName, ReadOnlyDictionary idToGivenName, ReadOnlyCollection<(long, ReadOnlyCollection, string, char[], DateTime, long)> collection) = GetCollections(appSettings, people); + if (idToPersonKey.Count != people.Count || idToPersonKey.Count != idToName.Count || idToPersonKey.Count != idToGivenName.Count) + throw new NotSupportedException(); + WriteGenealogicalDataCommunicationCollections(logger, input, genealogicalDataCommunicationCollections, people); if (input.Destination is not null) - Export(appSettings, input, ticks, genealogicalDataCommunicationCollections, personDirectories, individualsLines); + ExportFamilies(appSettings, input, genealogicalDataCommunicationCollections, idToPersonKey, idToName, idToGivenName); + if (input.Destination is not null) + Export(input, ticks, idToName, collection); + if (string.IsNullOrEmpty(input.GenealogicalDataCommunicationFile)) + logger.LogInformation("{file} is null?", input.GenealogicalDataCommunicationDirectory); } } \ No newline at end of file diff --git a/Helpers/HelperHardcodedFileSearchAndSort.cs b/Helpers/HelperHardcodedFileSearchAndSort.cs index 5320ac1..078404f 100644 --- a/Helpers/HelperHardcodedFileSearchAndSort.cs +++ b/Helpers/HelperHardcodedFileSearchAndSort.cs @@ -76,7 +76,7 @@ internal static class HelperHardcodedFileSearchAndSort } } } - log.LogInformation(sourceDirectory); + log.LogInformation("{sourceDirectory}", sourceDirectory); } } \ No newline at end of file diff --git a/Helpers/HelperLogMerge.cs b/Helpers/HelperLogMerge.cs index d2531f3..7c6e2b2 100644 --- a/Helpers/HelperLogMerge.cs +++ b/Helpers/HelperLogMerge.cs @@ -41,7 +41,7 @@ internal static class HelperLogMerge continue; segment1 = segments[1]; hour = int.Parse(sourceFileNameWithoutExtension.Substring(8, 2)); - if (keyValuePairs[day].ContainsKey(hour)) + if (keyValuePairs[day].TryGetValue(hour, out _)) continue; keyValuePairs[day].Add(hour, File.ReadAllLines(sourceFile)); moveFiles.Add(sourceFile); diff --git a/Helpers/HelperRenameToOldMoveDeleteOld.cs b/Helpers/HelperRenameToOldMoveDeleteOld.cs index a389e17..7797a72 100644 --- a/Helpers/HelperRenameToOldMoveDeleteOld.cs +++ b/Helpers/HelperRenameToOldMoveDeleteOld.cs @@ -9,14 +9,14 @@ internal static class HelperRenameToOldMoveDeleteOldMerge { string checkDirectory = argsZero[0..^1]; if (!Directory.Exists(checkDirectory)) - log.LogInformation(string.Concat("<", checkDirectory, "> doesn't exist!")); + log.LogInformation("<{checkDirectory}> doesn't exist!", checkDirectory); else { string renameFile; string destinationFile; List deleteFiles = new(); string[] moveFiles = Directory.GetFiles(argsZero, "*", SearchOption.TopDirectoryOnly); - log.LogInformation(string.Concat("<", moveFiles.Length, "> to move")); + log.LogInformation("<{moveFiles.Length}> to move", moveFiles.Length); foreach (string moveFile in moveFiles) { destinationFile = string.Concat(checkDirectory, moveFile[argsZero.Length..]); @@ -30,7 +30,7 @@ internal static class HelperRenameToOldMoveDeleteOldMerge deleteFiles.Add(renameFile); } } - log.LogInformation(string.Concat("<", deleteFiles.Count, "> to delete")); + log.LogInformation("<{deleteFiles.Count}> to delete", deleteFiles.Count); foreach (string deleteFile in deleteFiles) { for (short i = 0; i < short.MaxValue; i++) diff --git a/Helpers/HelperZipFilesByDate.cs b/Helpers/HelperZipFilesByDate.cs index d65c7c0..25d9640 100644 --- a/Helpers/HelperZipFilesByDate.cs +++ b/Helpers/HelperZipFilesByDate.cs @@ -5,30 +5,33 @@ using System.Text.RegularExpressions; namespace File_Folder_Helper.Helpers; -internal static class HelperZipFilesByDate +internal static partial class HelperZipFilesByDate { + [GeneratedRegex("[a-zA-Z0-9]{1,}")] + private static partial Regex LowerAlphaAlphaAndNumber(); + internal static bool ZipFilesByDate(ILogger log, string sourceDirectory, SearchOption searchOption = SearchOption.TopDirectoryOnly, string dayFormat = "") { bool result = false; string key; bool addFile; - string? zipPath; string fileName; + string? zipPath; + FileInfo fileInfo; + string weekOfYear; string[] segments; string[] subFiles; - string weekOfYear; - FileInfo fileInfo; string zipDirectory; DateTime creationTime; string? directoryName; DateTime lastWriteTime; DateTime nowDateTime = DateTime.Now; - Regex regex = new("[a-zA-Z0-9]{1,}"); DateTime dateTime = DateTime.MinValue; DateTime firstEmail = new(2019, 3, 8); CultureInfo cultureInfo = new("en-US"); Calendar calendar = cultureInfo.Calendar; + Regex regex = LowerAlphaAlphaAndNumber(); Dictionary weeks = new(); int ticksLength = nowDateTime.AddDays(-6).Ticks.ToString().Length; for (int i = 0; i < int.MaxValue; i++) @@ -107,14 +110,12 @@ internal static class HelperZipFilesByDate { key = Path.Combine(zipDirectory, $"{element.Key}.zip"); if (File.Exists(key)) - { for (short i = 101; i < short.MaxValue; i++) { key = Path.Combine(zipDirectory, $"{element.Key}_{i}.zip"); if (!File.Exists(key)) break; } - } using ZipArchive zip = ZipFile.Open(key, ZipArchiveMode.Create); foreach (string file in element.Value) { @@ -122,11 +123,9 @@ internal static class HelperZipFilesByDate File.Delete(file); } if (zipPath is not null && Directory.Exists(zipPath) && !string.IsNullOrEmpty(directoryName)) - { try { Directory.SetLastWriteTime(directoryName, DateTime.Now); } catch (Exception) { } - } } subFiles = Directory.GetFiles(zipDirectory, "*.zip", SearchOption.TopDirectoryOnly); foreach (string subFile in subFiles) @@ -136,7 +135,6 @@ internal static class HelperZipFilesByDate if (segments.Length > 2) fileName = string.Concat(segments[0], '_', segments[1], '_', segments[2]); if (weeks.TryGetValue(fileName, out DateTime value)) - { try { if (!result) @@ -144,14 +142,11 @@ internal static class HelperZipFilesByDate File.SetLastWriteTime(subFile, value); } catch (Exception) { } - } } if (topDirectory != sourceDirectory) - { try { _ = HelperDeleteEmptyDirectories.DeleteEmptyDirectories(topDirectory); } catch (Exception) { } - } log.LogInformation("{topDirectory}", topDirectory); } return result; @@ -166,7 +161,6 @@ internal static class HelperZipFilesByDate string? zipDirectory; DateTimeOffset? dateTimeOffset; foreach (string zipFile in zipFiles) - { try { dateTimeOffset = null; @@ -240,7 +234,6 @@ internal static class HelperZipFilesByDate { File.Move(zipFile, checkFile); } catch (Exception) { log.LogInformation("<{zipFile}> couldn't be moved!", zipFile); } } - } return result; } diff --git a/Models/Birth.cs b/Models/Birth.cs new file mode 100644 index 0000000..c887a1f --- /dev/null +++ b/Models/Birth.cs @@ -0,0 +1,23 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models; + +public record Birth(DateOnly? Date, + string? Note, + List Continue) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, BirthSourceGenerationContext.Default.Birth); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(Birth))] +internal partial class BirthSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Models/Change.cs b/Models/Change.cs new file mode 100644 index 0000000..c467e9f --- /dev/null +++ b/Models/Change.cs @@ -0,0 +1,23 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models; + +public record Change(DateOnly? Date, + string? Note, + List Continue) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, ChangeSourceGenerationContext.Default.Change); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(Change))] +internal partial class ChangeSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Models/Death.cs b/Models/Death.cs new file mode 100644 index 0000000..b43a7e8 --- /dev/null +++ b/Models/Death.cs @@ -0,0 +1,24 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models; + +public record Death(bool? Is, + DateOnly? Date, + string? Note, + List Continue) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, DeathSourceGenerationContext.Default.Death); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(Death))] +internal partial class DeathSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Models/Name.cs b/Models/Name.cs new file mode 100644 index 0000000..f2dbb84 --- /dev/null +++ b/Models/Name.cs @@ -0,0 +1,25 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models; + +public record Name(string? ForwardSlashFull, + string? Given, + string? Sur, + string? Nick, + string? Suffix) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, NameSourceGenerationContext.Default.Name); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(Name))] +internal partial class NameSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Models/Person.cs b/Models/Person.cs new file mode 100644 index 0000000..115644e --- /dev/null +++ b/Models/Person.cs @@ -0,0 +1,34 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace File_Folder_Helper.Models; + +public record Person(long Id, + Name? Name, + char? Sex, + string? UId, + Birth? Birth, + Death? Death, + Change? Change, + string[] Lines) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, PersonSourceGenerationContext.Default.Person); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(Person))] +internal partial class PersonSourceGenerationContext : JsonSerializerContext +{ +} + +[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(Dictionary))] +internal partial class PeopleSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Program.cs b/Program.cs index e742fb9..30f4973 100644 --- a/Program.cs +++ b/Program.cs @@ -22,7 +22,7 @@ internal class Program _ = builder.Services.AddHostedService(); using IHost host = builder.Build(); ILogger logger = host.Services.GetRequiredService>(); - logger.LogCritical(appSettings.Company); + logger.LogCritical("appSettings.Company", appSettings.Company); host.Run(); } diff --git a/Worker.cs b/Worker.cs index 8a3d98a..b7ffa6d 100644 --- a/Worker.cs +++ b/Worker.cs @@ -176,7 +176,7 @@ public class Worker : BackgroundService throw new Exception(_Args[0]); } catch (Exception ex) - { _Logger.LogError(string.Concat(ex.Message, Environment.NewLine, ex.StackTrace)); } + { _Logger.LogError("{Message}{NewLine}{StackTrace}", ex.Message, Environment.NewLine, ex.StackTrace); } if (_IsSilent) _Logger.LogInformation("Done. Bye"); else