From 90380fdd43c73abe1afbd1547deb7ed05ef791c0 Mon Sep 17 00:00:00 2001 From: Mike Phares Date: Mon, 25 Dec 2023 17:10:26 -0700 Subject: [PATCH] CUDA -> ConvertId KeePass -> ConvertKeePassExport NMap -> SplitJsonFile --- Day/Helper-2023-12-12.cs | 169 ++++++++++++++++++---- Day/Helper-2023-12-21.cs | 295 ++++++++++++++++++++++++++++++++++++++ Day/Helper-2023-12-22.cs | 231 +++++++++++++++++++++++++++++ Day/HelperDay.cs | 4 + Helpers/HelperMarkdown.cs | 4 +- 5 files changed, 670 insertions(+), 33 deletions(-) create mode 100644 Day/Helper-2023-12-21.cs create mode 100644 Day/Helper-2023-12-22.cs diff --git a/Day/Helper-2023-12-12.cs b/Day/Helper-2023-12-12.cs index f1f607c..17b865a 100644 --- a/Day/Helper-2023-12-12.cs +++ b/Day/Helper-2023-12-12.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using System.Collections.ObjectModel; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.RegularExpressions; @@ -92,8 +93,19 @@ internal static partial class Helper20231212 { } + private record HostName( + [property: JsonPropertyName("Name")] string Name, + [property: JsonPropertyName("Type")] string Type + ); + + [JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] + [JsonSerializable(typeof(HostName))] + private partial class HostNameSourceGenerationContext : JsonSerializerContext + { + } + private record HostNames( - [property: JsonPropertyName("HostName")] object HostName + [property: JsonPropertyName("HostName")] IReadOnlyList HostName ); [JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] @@ -324,99 +336,180 @@ internal static partial class Helper20231212 { } - private static string? GetStrippedIPV4(string value) + private static string[] GetIPV4Segments(string value) { - string? result; + string[] result; string[] subSegments = value.Split('.'); if (subSegments.Length != 4) - result = null; + result = []; else { if (!subSegments.All(l => int.TryParse(l, out _))) - result = null; + result = []; else - result = value.Replace(".", string.Empty); + result = value.Split('.'); } return result; } - private static string? GetStrippedMacAddress(string value) + private static string[] GetMacAddressSegments(string value) { - string? result; + string[] result; if (value.Length != 17) - result = null; + result = []; else { - if (value[2] is not ':' or '-' || value[5] is not ':' or '-' || value[8] is not ':' or '-' || value[11] is not ':' or '-' || value[14] is not ':' or '-') - result = null; + string v = value.ToLower(); + if (v[2] is not ':' or '-' || v[5] is not ':' or '-' || v[8] is not ':' or '-' || v[11] is not ':' or '-' || v[14] is not ':' or '-') + result = []; else - result = $"{value[0]}{value[1]}{value[3]}{value[4]}{value[6]}{value[7]}{value[9]}{value[10]}{value[12]}{value[13]}{value[15]}{value[16]}".ToLower(); + { + result = [$"{v[0]}{v[1]}", $"{v[3]}{v[4]}", $"{v[6]}{v[7]}", $"{v[9]}{v[10]}", $"{v[12]}{v[13]}", $"{v[15]}{v[16]}"]; + } } return result; } + private static ReadOnlyCollection> GetHostLinesSpaceSegments() + { + List> results = []; + string hostFile = "C:/Windows/System32/drivers/etc/hosts"; + string[] lines = !File.Exists(hostFile) ? [] : File.ReadAllLines(hostFile); + foreach (string line in lines) + results.Add(new(line.Split(' '))); + return new(results); + } + internal static void SplitJsonFile(ILogger logger, List args) { string json; + string title; Record? record; string fileName; + FileInfo fileInfo; string checkFileName; - string? strippedIpV4; + string sourceFileName; + string[] ipV4Segments; + string outputDirectory; List lines = []; List links = []; - string? strippedMacAddress; + List allLines = []; + string[] macAddressSegments; + string macAddressWithHyphens; + string ipV4SegmentsWithPeriods; + List titleSegments = []; string fileNameWithoutExtension; + const int ipV4SegmentsLength = 4; string sourceDirectory = args[0]; + const int macAddressSegmentsLength = 6; + string[] fileNameWithoutExtensionSegments; + string fileNameWithoutExtensionFirstThreeSegments; if (!Directory.Exists(sourceDirectory)) throw new Exception(sourceDirectory); - string outputDirectory = Path.Combine(sourceDirectory, Path.GetFileNameWithoutExtension(args[2])); - if (!Directory.Exists(outputDirectory)) - _ = Directory.CreateDirectory(outputDirectory); - string[] files = Directory.GetFiles(args[0], args[2], SearchOption.TopDirectoryOnly); + List> hostLineMatchSpaceSegments; + ReadOnlyCollection> hostLinesSpaceSegments = GetHostLinesSpaceSegments(); + string[] files = Directory.GetFiles(sourceDirectory, args[2], SearchOption.TopDirectoryOnly); foreach (string file in files) { links.Clear(); + fileInfo = new(file); json = File.ReadAllText(file); + fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file); + outputDirectory = Path.Combine(sourceDirectory, fileNameWithoutExtension); + fileNameWithoutExtensionSegments = GetIPV4Segments(fileNameWithoutExtension); + if (fileNameWithoutExtensionSegments.Length != ipV4SegmentsLength) + continue; + fileNameWithoutExtensionFirstThreeSegments = $"{fileNameWithoutExtensionSegments[0]}.{fileNameWithoutExtensionSegments[1]}.{fileNameWithoutExtensionSegments[2]}."; + if (!Directory.Exists(outputDirectory)) + _ = Directory.CreateDirectory(outputDirectory); record = JsonSerializer.Deserialize(json, RecordSourceGenerationContext.Default.Record); if (record is null) continue; foreach (Host host in record.Hosts) { lines.Clear(); - strippedIpV4 = null; - strippedMacAddress = null; + ipV4Segments = []; + titleSegments.Clear(); + macAddressSegments = []; foreach (HostAddress hostAddress in host.HostAddresses) { if (hostAddress.AddressType == "ipv4") - strippedIpV4 = GetStrippedIPV4(hostAddress.Address); + ipV4Segments = GetIPV4Segments(hostAddress.Address); else if (hostAddress.AddressType == "mac") - strippedMacAddress = GetStrippedMacAddress(hostAddress.Address); + macAddressSegments = GetMacAddressSegments(hostAddress.Address); else continue; } - if (strippedMacAddress is null && strippedIpV4 is null) + if (ipV4Segments.Length != ipV4SegmentsLength) continue; + ipV4SegmentsWithPeriods = string.Join('.', ipV4Segments); + macAddressWithHyphens = string.Join('-', macAddressSegments); + if (macAddressSegments.Length != macAddressSegmentsLength) + hostLineMatchSpaceSegments = (from l in hostLinesSpaceSegments where l.Count > 0 && l.Contains(ipV4SegmentsWithPeriods) select l).ToList(); + else + { + hostLineMatchSpaceSegments = (from l in hostLinesSpaceSegments where l.Count > 0 && l.Contains(macAddressWithHyphens) select l).ToList(); + if (hostLineMatchSpaceSegments.Count == 1) + title = string.Join(' ', hostLineMatchSpaceSegments[0]); + else + hostLineMatchSpaceSegments = (from l in hostLineMatchSpaceSegments where l.Count > 0 && l[0].StartsWith(fileNameWithoutExtensionFirstThreeSegments) select l).ToList(); + } + if (ipV4Segments.Length == ipV4SegmentsLength) + titleSegments.Add(ipV4SegmentsWithPeriods); + if (hostLineMatchSpaceSegments.Count == 1) + title = $"{ipV4SegmentsWithPeriods} {string.Join(' ', hostLineMatchSpaceSegments[0].Skip(1))}"; + else + { + if (host.HostNames.HostName is null) + titleSegments.Add("unknown #"); + else + titleSegments.Add($"{string.Join("_", host.HostNames.HostName.Select(l => l.Name.Replace(' ', '-')))} #"); + if (macAddressSegments.Length == macAddressSegmentsLength) + titleSegments.Add(macAddressWithHyphens); + title = string.Join(" ", titleSegments); + logger.LogInformation("{title} Type DeviceInfo ~ needs to be added to host file!", title); + } json = JsonSerializer.Serialize(host, HostSourceGenerationContext.Default.Host); - fileNameWithoutExtension = strippedMacAddress is null ? $"ipv4-{strippedIpV4}" : $"mac-{strippedMacAddress}"; + fileNameWithoutExtension = macAddressSegments.Length != macAddressSegmentsLength ? $"ipv4-{string.Join(string.Empty, ipV4Segments)}" : $"mac-{string.Join(string.Empty, macAddressSegments)}"; fileName = $"{fileNameWithoutExtension}.md"; - links.Add($"- [{string.Join(" - ", host.HostAddresses.Select(l => l.Address))}]({fileName})"); - lines.Add($"# {fileNameWithoutExtension}"); + links.Add($"- [{title}]({fileName})"); + lines.Add("---"); + allLines.Add(title); + lines.Add(string.Empty); + lines.Add($"# {title}"); + lines.Add(string.Empty); + lines.Add($"## {fileNameWithoutExtension}"); + if (host.Ports is not null) + { + lines.Add(string.Empty); + lines.Add("## Port(s)"); + lines.Add(string.Empty); + lines.Add("| Id | Protocol | State | Service |"); + lines.Add("| - | - | - | - |"); + foreach (Port port in host.Ports) + lines.Add($"| {port.PortID} | {port.Protocol} | {port.State.Value} | {port.Service.Name} |"); + } lines.Add(string.Empty); lines.Add("```json"); lines.Add(json); lines.Add("```"); - lines.Add(string.Empty); - logger.LogInformation("{fileName} created", fileName); checkFileName = Path.Combine(outputDirectory, fileName); if (File.Exists(checkFileName)) File.Delete(checkFileName); File.WriteAllLines(checkFileName, lines); + File.SetCreationTime(checkFileName, fileInfo.CreationTime); + File.SetLastWriteTime(checkFileName, fileInfo.LastWriteTime); + logger.LogInformation("{title} created", title); } lines.Clear(); record.Hosts.Clear(); - fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file); + title = Path.GetFileNameWithoutExtension(file); json = JsonSerializer.Serialize(record, RecordSourceGenerationContext.Default.Record); - lines.Add($"# {fileNameWithoutExtension}"); + lines.Add("---"); + lines.Add(string.Empty); + lines.Add($"# {title}"); + lines.Add(string.Empty); + lines.Add($"## {title}"); lines.Add(string.Empty); lines.Add("## Hosts"); lines.Add(string.Empty); @@ -425,11 +518,25 @@ internal static partial class Helper20231212 lines.Add("```json"); lines.Add(json); lines.Add("```"); - checkFileName = Path.Combine(outputDirectory, $"{fileNameWithoutExtension}-links.md"); + checkFileName = Path.Combine(outputDirectory, $"{title}.md"); if (File.Exists(checkFileName)) File.Delete(checkFileName); File.WriteAllLines(checkFileName, lines); + File.SetCreationTime(checkFileName, fileInfo.CreationTime); + File.SetLastWriteTime(checkFileName, fileInfo.LastWriteTime); + logger.LogInformation("{title} created", title); + checkFileName = Path.Combine(outputDirectory, Path.GetFileName(file)); + if (File.Exists(checkFileName)) + File.Delete(checkFileName); + File.Move(file, checkFileName); + checkFileName = Path.Combine(outputDirectory, $"{Path.GetFileNameWithoutExtension(file)}.xml"); + if (File.Exists(checkFileName)) + File.Delete(checkFileName); + sourceFileName = Path.ChangeExtension(file, ".xml"); + if (File.Exists(sourceFileName)) + File.Move(sourceFileName, checkFileName); } + File.WriteAllLines(Path.Combine(sourceDirectory, $"{DateTime.Now.Ticks}.ssv"), allLines); } } \ No newline at end of file diff --git a/Day/Helper-2023-12-21.cs b/Day/Helper-2023-12-21.cs new file mode 100644 index 0000000..2dfeeb9 --- /dev/null +++ b/Day/Helper-2023-12-21.cs @@ -0,0 +1,295 @@ +using Microsoft.Extensions.Logging; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Xml.Linq; + +namespace File_Folder_Helper.Day; + +internal static partial class Helper20231221 +{ + + // Folders with these names will be put in the root instead. + private static readonly string[] _BlacklistedFolders = + [ + "KeePassHttp Passwords", + "KeePassXC-Browser Passwords" + ]; + + private static readonly string[] _BlacklistedFields = [ + "KeePassXC-Browser Settings", + "KeePassHttp Settings" + ]; + + private static Func OtherFields() + { + return x => + { + string? key = x.Element("Key")?.Value; + return key != "Title" && key != "Notes" && key != "UserName" && key != "Password" && + key != "URL" && !_BlacklistedFields.Contains(key); + }; + } + + private record Field( + [property: JsonPropertyName("name")] string? Name, + [property: JsonPropertyName("value")] string? Value, + [property: JsonPropertyName("type")] int? Type + ); + + [JsonSourceGenerationOptions(WriteIndented = true)] + [JsonSerializable(typeof(Field))] + private partial class FieldSourceGenerationContext : JsonSerializerContext + { + } + + private record Folder( + [property: JsonPropertyName("id")] string? Id, + [property: JsonPropertyName("name")] string? Name + ); + + [JsonSourceGenerationOptions(WriteIndented = true)] + [JsonSerializable(typeof(Folder))] + private partial class FolderSourceGenerationContext : JsonSerializerContext + { + } + + private record Item( + [property: JsonPropertyName("revisionDate")] string? RevisionDate, + [property: JsonPropertyName("creationDate")] string? CreationDate, + [property: JsonPropertyName("folderId")] string? FolderId, + [property: JsonPropertyName("type")] int Type, + [property: JsonPropertyName("name")] string? Name, + [property: JsonPropertyName("notes")] string? Notes, + [property: JsonPropertyName("fields")] IReadOnlyList? Fields, + [property: JsonPropertyName("login")] Login Login + ); + + [JsonSourceGenerationOptions(WriteIndented = true)] + [JsonSerializable(typeof(Item))] + private partial class ItemSourceGenerationContext : JsonSerializerContext + { + } + + private record Login( + [property: JsonPropertyName("uris")] IReadOnlyList Uris, + [property: JsonPropertyName("username")] string? Username, + [property: JsonPropertyName("password")] string? Password + ); + + [JsonSourceGenerationOptions(WriteIndented = true)] + [JsonSerializable(typeof(Login))] + private partial class LoginSourceGenerationContext : JsonSerializerContext + { + } + + private record Root( + [property: JsonPropertyName("folders")] IReadOnlyList Folders, + [property: JsonPropertyName("items")] IReadOnlyList Items + ); + + [JsonSourceGenerationOptions(WriteIndented = true)] + [JsonSerializable(typeof(Root))] + private partial class RootSourceGenerationContext : JsonSerializerContext + { + } + + private record Uri( + [property: JsonPropertyName("uri")] string? Value, + [property: JsonPropertyName("host")] string? Host + ); + + [JsonSourceGenerationOptions(WriteIndented = true)] + [JsonSerializable(typeof(Uri))] + private partial class UriSourceGenerationContext : JsonSerializerContext + { + } + + private static Item? GetEntry(string folderId, XElement entry) + { + Item? result; + XElement[] stringFields = entry.Elements("String").ToArray(); + string? name = stringFields.Where(x => x.Element("Key")?.Value == "Title").Select(x => x.Element("Value")?.Value).FirstOrDefault(); + if (_BlacklistedFields.Contains(name)) + result = null; + else + { + XElement[] timesFields = entry.Elements("Times").ToArray(); + string? creationTime = timesFields.Elements("CreationTime").FirstOrDefault()?.Value; + string? revisionDate = timesFields.Elements("LastModificationTime").FirstOrDefault()?.Value; + string? uri = stringFields.Where(x => x.Element("Key")?.Value == "URL").Select(x => x.Element("Value")?.Value).FirstOrDefault(); + string? notes = stringFields.Where(x => x.Element("Key")?.Value == "Notes").Select(x => x.Element("Value")?.Value).FirstOrDefault(); + string? username = stringFields.Where(x => x.Element("Key")?.Value == "UserName").Select(x => x.Element("Value")?.Value).FirstOrDefault(); + string? password = stringFields.Where(x => x.Element("Key")?.Value == "Password").Select(x => x.Element("Value")?.Value).FirstOrDefault(); + string? host = string.IsNullOrEmpty(uri) || !uri.Contains(':') ? null : new System.Uri(uri).Host; + Login login = new(new Uri[] { new(uri, host) }, username, password); + List itemFields = stringFields.Where(OtherFields()).Select(x => new Field(x.Element("Key")?.Value, x.Element("Value")?.Value, 0)).ToList(); + result = new(revisionDate, + creationTime, + folderId, + 1, + name, + string.IsNullOrEmpty(notes) ? null : notes, + itemFields.Count > 0 ? itemFields : null, + login); + } + return result; + } + + private static List Filter(List items) + { + List results = []; + string key; + Item result; + Login login; + string? uri; + string? host; + string? name; + string? folderId; + string? username; + List? check; + string? creationTime; + string? revisionDate; + List notes = []; + string? password = null; + string?[] checkPasswords; + Dictionary> keyValuePairs = []; + foreach (Item item in items) + { + key = string.Concat(item.Login.Username, '-', string.Join('-', item.Login.Uris.Select(l => l.Host))); + if (!keyValuePairs.TryGetValue(key, out check)) + { + keyValuePairs.Add(key, []); + if (!keyValuePairs.TryGetValue(key, out check)) + throw new NotSupportedException(); + } + check.Add(item); + } + foreach (KeyValuePair> keyValuePair in keyValuePairs) + { + if (keyValuePair.Value.Count == 1) + results.AddRange(keyValuePair.Value); + else + { + checkPasswords = keyValuePair.Value.Select(l => l.Login.Password).Distinct().ToArray(); + if (checkPasswords.Length == 1) + results.Add(keyValuePair.Value[0]); + else + { + uri = null; + host = null; + name = null; + notes.Clear(); + folderId = null; + username = null; + creationTime = null; + revisionDate = null; + notes.Add("Unset Password"); + foreach (Item item in from l in keyValuePair.Value orderby l.RevisionDate, l.Login.Password?.Length descending select l) + { + if (item.Login.Uris.Count == 1) + { + uri = item.Login.Uris[0].Value; + host = item.Login.Uris[0].Host; + } + name = item.Name; + folderId = item.FolderId; + username = item.Login.Username; + creationTime = item.CreationDate; + revisionDate = item.RevisionDate; + notes.Add($"{item.Login.Password} on {item.RevisionDate}"); + } + login = new(new Uri[] { new(uri, host) }, username, password); + result = new(revisionDate, + creationTime, + folderId, + 1, + name, + string.Join(Environment.NewLine, notes), + null, + login); + results.Add(result); + } + } + } + results = (from l in results orderby l.Login.Uris[0].Host, l.Login.Username select l).ToList(); + return results; + } + + private static void SaveTabSeparatedValueFiles(List items, string xmlFile) + { + List lines = []; + foreach (Item item in items) + lines.Add($"{item.Login.Uris[0].Host}\t{item.Login.Username}\t{item.Login.Password}"); + File.WriteAllLines(Path.ChangeExtension(xmlFile, ".tvs"), lines); + } + + internal static void ConvertKeePassExport(ILogger logger, List args) + { + Root root; + Item? item; + string json; + string folderId; + string groupKey; + List items; + XElement element; + string newGroupKey; + XDocument xDocument; + List folders; + string childGroupName; + string outputPath = args[0]; + string newExtension = args[3]; + IEnumerable childEntries; + IEnumerable childElements; + Dictionary namesToIds; + Queue> groupsToProcess; + string[] xmlFiles = Directory.GetFiles(args[0], args[2]); + foreach (string xmlFile in xmlFiles) + { + xDocument = XDocument.Load(xmlFile); + if (xDocument.Root is null) + throw new Exception("Root element missing"); + items = []; + folders = []; + namesToIds = []; + groupsToProcess = []; + logger.LogInformation($"Loaded XML {xmlFile}.", xmlFile); + groupsToProcess.Enqueue(new KeyValuePair("", xDocument.Root.Descendants("Group").First())); + while (groupsToProcess.TryDequeue(out KeyValuePair valuePair)) + { + groupKey = valuePair.Key; + element = valuePair.Value; + folderId = Guid.NewGuid().ToString(); + childElements = element.Elements("Group"); + folders.Add(new Folder(folderId, groupKey)); + foreach (XElement childElement in childElements) + { + childGroupName = (childElement.Element("Name")?.Value) ?? throw new Exception("Found group with no name, malformed file"); + if (_BlacklistedFolders.Contains(childGroupName)) + childGroupName = ""; + newGroupKey = $"{groupKey}/{childGroupName}"; + if (groupKey == "") + newGroupKey = childGroupName; + logger.LogInformation($"Found group '{newGroupKey}'"); + groupsToProcess.Enqueue(new KeyValuePair(newGroupKey, childElement)); + } + childEntries = element.Elements("Entry"); + foreach (XElement entry in childEntries) + { + item = GetEntry(folderId, entry); + if (item is null) + continue; + items.Add(item); + } + } + items = Filter(items); + root = new(folders, items); + logger.LogInformation("Serializing output file..."); + json = JsonSerializer.Serialize(root, RootSourceGenerationContext.Default.Root); + logger.LogInformation("Writing output file..."); + File.WriteAllText(Path.ChangeExtension(xmlFile, newExtension), json); + SaveTabSeparatedValueFiles(items, xmlFile); + } + logger.LogInformation("Done!"); + } + +} \ No newline at end of file diff --git a/Day/Helper-2023-12-22.cs b/Day/Helper-2023-12-22.cs new file mode 100644 index 0000000..69b0ac5 --- /dev/null +++ b/Day/Helper-2023-12-22.cs @@ -0,0 +1,231 @@ +using Microsoft.Extensions.Logging; +using System.Collections.ObjectModel; +using System.Text; + +namespace File_Folder_Helper.Day; + +internal static partial class Helper20231222 +{ + + private record Record(string File, + string DestinationDirectory, + string DestinationFile); + + private record IntelligentIdRecord(int Key, + ReadOnlyCollection ResultAllInOneSubdirectoryChars, + string Reverse); + + private record FilePath(long CreationTicks, + string DirectoryName, + string ExtensionLowered, + string FileNameFirstSegment, + string FullName, + int? Id, + bool IsIntelligentIdFormat, + long LastWriteTicks, + long Length, + string Name, + string NameWithoutExtension, + int? SortOrder); + + public record MetadataConfiguration(int ResultAllInOneSubdirectoryLength, int Offset, int IntMinValueLength); + + private static short GetSortOrderOnlyLengthIndex(MetadataConfiguration metadataConfiguration) => + (short)metadataConfiguration.Offset.ToString().Length; + + private static int GetId(MetadataConfiguration metadataConfiguration, string intelligentId) + { + int result; + StringBuilder results = new(); + if (metadataConfiguration.IntMinValueLength < (metadataConfiguration.ResultAllInOneSubdirectoryLength + 2)) + throw new NotSupportedException(); + for (int i = intelligentId.Length - (metadataConfiguration.ResultAllInOneSubdirectoryLength + 2); i > -1; i--) + _ = results.Append(intelligentId[i]); + _ = results.Append(intelligentId[^3]).Append(intelligentId[^2]); + result = int.Parse(results.ToString()); + if (intelligentId[^1] is '1' or '2') + result *= -1; + else if (intelligentId[^1] is not '9' and not '8') + throw new NotSupportedException(); + return result; + } + + private static IntelligentIdRecord GetIntelligentIdRecord(MetadataConfiguration metadataConfiguration, long id, bool ignore) + { + IntelligentIdRecord result; + StringBuilder stringBuilder = new(); + if (metadataConfiguration.IntMinValueLength < (metadataConfiguration.ResultAllInOneSubdirectoryLength + 2)) + throw new NotSupportedException(); + int key; + string value; + List chars = []; + if (id > -1) + { + key = ignore ? 8 : 9; + value = id.ToString().PadLeft(metadataConfiguration.IntMinValueLength, '0'); + } + else + { + key = ignore ? 2 : 1; + value = id.ToString()[1..].PadLeft(metadataConfiguration.IntMinValueLength, '0'); + } + for (int i = value.Length - metadataConfiguration.ResultAllInOneSubdirectoryLength - 1; i > -1; i--) + _ = stringBuilder.Append(value[i]); + for (int i = value.Length - metadataConfiguration.ResultAllInOneSubdirectoryLength; i < value.Length; i++) + chars.Add(value[i]); + result = new(key, new(chars), stringBuilder.ToString()); + return result; + } + + private static string GetIntelligentId(IntelligentIdRecord intelligentId) => + $"{intelligentId.Reverse}{string.Join(string.Empty, intelligentId.ResultAllInOneSubdirectoryChars)}{intelligentId.Key}"; + + private static bool NameWithoutExtensionIsIntelligentIdFormat(MetadataConfiguration metadataConfiguration, string fileNameFirstSegment) => + fileNameFirstSegment.Length - 1 == metadataConfiguration.IntMinValueLength && fileNameFirstSegment[^1] is '1' or '2' or '8' or '9' && fileNameFirstSegment.All(char.IsNumber); + + private static bool NameWithoutExtensionIsPaddedIntelligentIdFormat(MetadataConfiguration metadataConfiguration, short sortOrderOnlyLengthIndex, string fileNameFirstSegment) => + fileNameFirstSegment.Length == metadataConfiguration.IntMinValueLength + sortOrderOnlyLengthIndex + 1 + && fileNameFirstSegment[^1] is '1' or '2' or '8' or '9' + && fileNameFirstSegment.All(char.IsNumber); + + private static bool NameWithoutExtensionIsIdFormat(MetadataConfiguration metadataConfiguration, string fileNameFirstSegment) + { + bool result; + if (fileNameFirstSegment.Length < 5 || fileNameFirstSegment.Length > metadataConfiguration.IntMinValueLength) + result = false; + else + { + bool skipOneAllAreNumbers = fileNameFirstSegment[1..].All(char.IsNumber); + result = (skipOneAllAreNumbers && fileNameFirstSegment[0] == '-') || (skipOneAllAreNumbers && char.IsNumber(fileNameFirstSegment[0])); + } + return result; + } + + private static FilePath GetFilePath(MetadataConfiguration metadataConfiguration, FileInfo fileInfo, int? index) + { + FilePath result; + int? id; + int? sortOder; + string fileNameFirstSegment = fileInfo.Name.Split('.')[0]; + short sortOrderOnlyLengthIndex = GetSortOrderOnlyLengthIndex(metadataConfiguration); + string fileDirectoryName = fileInfo.DirectoryName ?? throw new NullReferenceException(); + bool fileNameFirstSegmentIsIntelligentIdFormat = NameWithoutExtensionIsIntelligentIdFormat(metadataConfiguration, fileNameFirstSegment); + bool fileNameFirstSegmentIsPaddedIntelligentIdFormat = NameWithoutExtensionIsPaddedIntelligentIdFormat(metadataConfiguration, sortOrderOnlyLengthIndex, fileNameFirstSegment); + bool fileNameFirstSegmentIsIdFormat = !fileNameFirstSegmentIsPaddedIntelligentIdFormat && !fileNameFirstSegmentIsIntelligentIdFormat && NameWithoutExtensionIsIdFormat(metadataConfiguration, fileNameFirstSegment); + if (fileNameFirstSegmentIsIdFormat) + { + if (index is null) + throw new NullReferenceException(nameof(index)); + if (!int.TryParse(fileNameFirstSegment, out int valueOfFileNameFirstSegment)) + throw new NotSupportedException(); + (id, sortOder) = (valueOfFileNameFirstSegment, metadataConfiguration.Offset + index); + } + else if (!fileNameFirstSegmentIsIntelligentIdFormat && !fileNameFirstSegmentIsPaddedIntelligentIdFormat) + (id, sortOder) = (null, null); + else if (fileNameFirstSegmentIsIntelligentIdFormat) + (id, sortOder) = (GetId(metadataConfiguration, fileNameFirstSegment), null); + else if (fileNameFirstSegmentIsPaddedIntelligentIdFormat) + { + if (!int.TryParse(fileNameFirstSegment[..sortOrderOnlyLengthIndex], out int absoluteValueOfSortOrder)) + (id, sortOder) = (null, null); + else + (id, sortOder) = (GetId(metadataConfiguration, fileNameFirstSegment[sortOrderOnlyLengthIndex..]), absoluteValueOfSortOrder); + } + else + throw new NotSupportedException(); + result = new(fileInfo.CreationTime.Ticks, + fileDirectoryName, + fileInfo.Extension.ToLower(), + fileNameFirstSegment, + fileInfo.FullName, + id, + fileNameFirstSegmentIsIntelligentIdFormat, + fileInfo.LastWriteTime.Ticks, + fileInfo.Length, + fileInfo.Name, + Path.GetFileNameWithoutExtension(fileInfo.Name), + sortOder); + return result; + } + + private static ReadOnlyCollection GetRecords(MetadataConfiguration metadataConfiguration, string sourceDirectory, string searchPattern) + { + List results = []; + int check; + int index = -1; + FileInfo fileInfo; + FilePath filePath; + string? directory; + string[] segments; + bool ignore = false; + string directoryName; + string intelligentId; + string? parentDirectory; + IntelligentIdRecord intelligentIdRecord; + string sourceParentDirectory = Path.GetDirectoryName(sourceDirectory) ?? throw new NotSupportedException(); + string[] files = Directory.GetFiles(sourceDirectory, searchPattern, SearchOption.AllDirectories); + foreach (string file in files) + { + index += 1; + directory = Path.GetDirectoryName(file); + if (directory is null) + continue; + parentDirectory = Path.GetDirectoryName(directory); + if (parentDirectory is null) + continue; + fileInfo = new(file); + directoryName = Path.GetFileName(directory); + filePath = GetFilePath(metadataConfiguration, fileInfo, index); + if (filePath.Id is null) + continue; + intelligentIdRecord = GetIntelligentIdRecord(metadataConfiguration, filePath.Id.Value, ignore); + intelligentId = GetIntelligentId(intelligentIdRecord); + check = GetId(metadataConfiguration, intelligentId); + if (check != filePath.Id.Value) + throw new NotSupportedException(); + segments = Path.GetFileName(file).Split('.'); + if (segments.Length == 2) + { + if (filePath.SortOrder is not null) + results.Add(new(file, directory, $"{filePath.SortOrder.Value}{intelligentId}.{segments[1]}")); + else + results.Add(new(file, Path.Combine(sourceParentDirectory, intelligentIdRecord.Key.ToString(), string.Join(string.Empty, intelligentIdRecord.ResultAllInOneSubdirectoryChars)), $"{intelligentId}.{segments[1]}")); + } + else if (segments.Length == 3) + results.Add(new(file, Path.Combine(sourceParentDirectory, intelligentIdRecord.Key.ToString(), string.Join(string.Empty, intelligentIdRecord.ResultAllInOneSubdirectoryChars)), $"{intelligentId}.{segments[1]}.{segments[2]}")); + else if (segments.Length == 4) + { + if (directoryName != segments[0]) + results.Add(new(file, directory, $"{intelligentId}.{segments[1]}.{segments[2]}.{segments[3]}")); + else + results.Add(new(file, Path.Combine(sourceParentDirectory, intelligentIdRecord.Key.ToString(), string.Join(string.Empty, intelligentIdRecord.ResultAllInOneSubdirectoryChars), intelligentId), $"{intelligentId}.{segments[1]}.{segments[2]}.{segments[3]}")); + } + else if (segments.Length == 5) + results.Add(new(file, directory, $"{intelligentId}.{segments[1]}.{segments[2]}.{segments[3]}.{segments[4]}")); + else + continue; + } + return new(results); + } + + internal static void ConvertId(ILogger logger, List args) + { + List distinct = []; + string searchPattern = args[2]; + string sourceDirectory = args[0]; + logger.LogInformation("{sourceDirectory}", sourceDirectory); + MetadataConfiguration metadataConfiguration = new(2, 1000000, int.MinValue.ToString().Length); + ReadOnlyCollection records = GetRecords(metadataConfiguration, sourceDirectory, searchPattern); + foreach (Record record in records) + { + if (distinct.Contains(record.DestinationDirectory)) + continue; + distinct.Add(record.DestinationDirectory); + if (!Directory.Exists(record.DestinationDirectory)) + _ = Directory.CreateDirectory(record.DestinationDirectory); + } + foreach (Record record in records) + File.Move(record.File, Path.Combine(record.DestinationDirectory, record.DestinationFile)); + } + +} \ No newline at end of file diff --git a/Day/HelperDay.cs b/Day/HelperDay.cs index 9f87229..c4a675b 100644 --- a/Day/HelperDay.cs +++ b/Day/HelperDay.cs @@ -30,6 +30,10 @@ internal static class HelperDay Day.Helper20231205.SplitMarkdownFile(logger, args); else if (args[1] == "Day-Helper-2023-12-12") Day.Helper20231212.SplitJsonFile(logger, args); + else if (args[1] == "Day-Helper-2023-12-21") + Day.Helper20231221.ConvertKeePassExport(logger, args); + else if (args[1] == "Day-Helper-2023-12-22") + Day.Helper20231222.ConvertId(logger, args); else throw new Exception(appSettings.Company); } diff --git a/Helpers/HelperMarkdown.cs b/Helpers/HelperMarkdown.cs index 74ce318..8de04dc 100644 --- a/Helpers/HelperMarkdown.cs +++ b/Helpers/HelperMarkdown.cs @@ -405,12 +405,12 @@ internal static partial class HelperMarkdown jsonLines.Add($"{segmentsLast},"); else if (DateTime.TryParse(segmentsLast, out DateTime dateTime)) jsonLines.Add($"\"{segmentsLast}\","); - else if (segmentsLast.All(l => char.IsNumber(l))) + else if (segmentsLast.All(char.IsNumber)) jsonLines.Add($"{segmentsLast},"); else { segmentsB = segmentsLast.Split('.'); - if (segmentsB.Length == 2 && segmentsB[0].Length < 7 && segmentsB[^1].Length < 7 && segmentsB[0].All(l => char.IsNumber(l)) && segmentsB[^1].All(l => char.IsNumber(l))) + if (segmentsB.Length == 2 && segmentsB[0].Length < 7 && segmentsB[^1].Length < 7 && segmentsB[0].All(char.IsNumber) && segmentsB[^1].All(char.IsNumber)) jsonLines.Add($"{segmentsLast},"); else if (!segmentsLast.Contains('[') && !segmentsLast.Contains('{')) jsonLines.Add($"\"{segmentsLast}\",");