diff --git a/ADO2025/PI5/Helper-2025-04-04.cs b/ADO2025/PI5/Helper-2025-04-04.cs index c8439b7..b7078ac 100644 --- a/ADO2025/PI5/Helper-2025-04-04.cs +++ b/ADO2025/PI5/Helper-2025-04-04.cs @@ -123,10 +123,9 @@ internal static partial class Helper20250404 { internal static void KumaToGatus(ILogger logger, List args) { string url = args[4]; - string check = args[5]; string fileName = args[3]; string searchPattern = args[2]; - ParseMetrics(logger, fileName, url, check); + ParseMetrics(logger, fileName, url); string sourceDirectory = Path.GetFullPath(args[0]); string[] files = Directory.GetFiles(sourceDirectory, searchPattern, SearchOption.AllDirectories); if (files.Length == 0) @@ -135,7 +134,7 @@ internal static partial class Helper20250404 { KumaToGatus(files); } - private static void ParseMetrics(ILogger logger, string fileName, string url, string check) { + private static void ParseMetrics(ILogger logger, string fileName, string url) { FileStream fileStream = new(fileName, FileMode.Truncate); HttpClient httpClient = new(); Task streamTask = httpClient.GetStreamAsync(url); @@ -170,7 +169,7 @@ internal static partial class Helper20250404 { } private static void KumaToGatus(string[] files) { - Kuma kuma; + Kuma? kuma; string json; string checkFile; foreach (string file in files) { diff --git a/ADO2025/PI5/Helper-2025-04-07.cs b/ADO2025/PI5/Helper-2025-04-07.cs new file mode 100644 index 0000000..8b609c4 --- /dev/null +++ b/ADO2025/PI5/Helper-2025-04-07.cs @@ -0,0 +1,693 @@ +using System.Collections.ObjectModel; +using System.Text.Json; +using System.Text.Json.Serialization; + +using File_Folder_Helper.Models; + +using Microsoft.Extensions.FileSystemGlobbing; +using Microsoft.Extensions.Logging; + +#if ShellProgressBar +using ShellProgressBar; +#endif + +namespace File_Folder_Helper.ADO2025.PI5; + +internal static partial class Helper20250407 { + + private record Record(string RelativePath, + long Size, + long Ticks); + + private record Download(string Directory, + string Display, + string File, + long Size, + long Ticks, + string UniformResourceLocator); + + private record Segment(Record? Left, + string? LeftDirectory, + Record? Right, + string RightDirectory, + string RootUniformResourceLocator); + + private record Logic(char GreaterThan, + bool? LeftSideIsNewer, + int LeftSideIsNewerIndex, + bool? LeftSideOnly, + int LeftSideOnlyIndex, + char LessThan, + char Minus, + bool? NotEqualBut, + int NotEqualButIndex, + char Plus, + string[] Raw, + bool? RightSideIsNewer, + int RightSideIsNewerIndex, + bool? RightSideOnly, + int RightSideOnlyIndex) { + + internal static Logic? Get(string[] segments) { + Logic? result; + bool check = true; + bool? notEqualBut; + bool? leftSideOnly; + bool? rightSideOnly; + bool? leftSideIsNewer; + const char plus = '+'; + bool? rightSideIsNewer; + const char minus = '-'; + const char lessThan = 'L'; + const char greaterThan = 'G'; + const int notEqualButIndex = 2; + const int leftSideOnlyIndex = 0; + const int rightSideOnlyIndex = 4; + const int leftSideIsNewerIndex = 1; + const int rightSideIsNewerIndex = 3; + if (string.IsNullOrEmpty(segments[leftSideOnlyIndex])) + leftSideOnly = null; + else if (segments[leftSideOnlyIndex][0] == plus) + leftSideOnly = true; + else if (segments[leftSideOnlyIndex][0] == minus) + leftSideOnly = false; + else { + check = false; + leftSideOnly = null; + } + if (string.IsNullOrEmpty(segments[leftSideIsNewerIndex])) + leftSideIsNewer = null; + else if (segments[leftSideIsNewerIndex][0] == greaterThan) + leftSideIsNewer = true; + else if (segments[leftSideIsNewerIndex][0] == lessThan) + leftSideIsNewer = false; + else { + check = false; + leftSideIsNewer = null; + } + if (string.IsNullOrEmpty(segments[notEqualButIndex])) + notEqualBut = null; + else if (segments[notEqualButIndex][0] == greaterThan) + notEqualBut = true; + else if (segments[notEqualButIndex][0] == lessThan) + notEqualBut = false; + else { + check = false; + notEqualBut = null; + } + if (string.IsNullOrEmpty(segments[rightSideIsNewerIndex])) + rightSideIsNewer = null; + else if (segments[rightSideIsNewerIndex][0] == greaterThan) + rightSideIsNewer = true; + else if (segments[rightSideIsNewerIndex][0] == lessThan) + rightSideIsNewer = false; + else { + check = false; + rightSideIsNewer = null; + } + if (string.IsNullOrEmpty(segments[rightSideOnlyIndex])) + rightSideOnly = null; + else if (segments[rightSideOnlyIndex][0] == plus) + rightSideOnly = true; + else if (segments[rightSideOnlyIndex][0] == minus) + rightSideOnly = false; + else { + check = false; + rightSideOnly = null; + } + result = !check ? null : new(GreaterThan: greaterThan, + LeftSideIsNewerIndex: leftSideIsNewerIndex, + LeftSideIsNewer: leftSideIsNewer, + LeftSideOnly: leftSideOnly, + LeftSideOnlyIndex: leftSideOnlyIndex, + LessThan: lessThan, + Minus: minus, + NotEqualBut: notEqualBut, + NotEqualButIndex: notEqualButIndex, + Plus: plus, + RightSideIsNewer: rightSideIsNewer, + RightSideIsNewerIndex: rightSideIsNewerIndex, + RightSideOnly: rightSideOnly, + Raw: segments, + RightSideOnlyIndex: rightSideOnlyIndex); + return result; + } + + } + + private record Review(Segment[]? AreEqual, + Segment[]? LeftSideIsNewer, + Segment[]? LeftSideOnly, + Segment[]? NotEqualBut, + Record[]? Records, + Segment[]? RightSideIsNewer, + Segment[]? RightSideOnly); + + [JsonSourceGenerationOptions(WriteIndented = true)] + [JsonSerializable(typeof(Review))] + private partial class ReviewCommonSourceGenerationContext : JsonSerializerContext { + } + + internal static void Sync(ILogger logger, List args) { + Matcher matcher = new(); + string fileName = $"{args[1]}.json"; + string[] segments = args[5].Split('|'); + string rightDirectory = Path.GetFullPath(args[0].Split('|')[0]); + Logic? logic = segments.Length != 5 ? null : Logic.Get(segments); + string includePatternsFile = Path.Combine(rightDirectory, args[2]); + string excludePatternsFile = Path.Combine(rightDirectory, args[3]); + string[] rootUniformResourceLocators = args.Count < 5 ? [] : args[4].Split('|'); + matcher.AddIncludePatterns(!File.Exists(includePatternsFile) ? ["*"] : File.ReadAllLines(includePatternsFile)); + matcher.AddExcludePatterns(!File.Exists(excludePatternsFile) ? ["System Volume Information"] : File.ReadAllLines(excludePatternsFile)); + ReadOnlyCollection rightRecords = GetRecords(rightDirectory, matcher); + if (rightRecords.Count == 0) + logger.LogInformation("No source records"); + else { + string checkFile = Path.Combine(rightDirectory, fileName); + Review review = new(AreEqual: null, + LeftSideIsNewer: null, + LeftSideOnly: null, + NotEqualBut: null, + Records: rightRecords.ToArray(), + RightSideIsNewer: null, + RightSideOnly: null); + string json = JsonSerializer.Serialize(review, ReviewCommonSourceGenerationContext.Default.Review); + WriteAllText(checkFile, json); + if (rootUniformResourceLocators.Length == 0) + logger.LogInformation("No urls"); + else { + string format = NginxFileSystem.GetFormat(); + TimeZoneInfo timeZoneInfo = TimeZoneInfo.Local; + Sync(logger, rightDirectory, fileName, logic, rootUniformResourceLocators, rightRecords, format, timeZoneInfo); + } + } + } + + private static ReadOnlyCollection GetRecords(string rightDirectory, Matcher matcher) { + List results = [ + new(RelativePath: rightDirectory, + Size: 0, + Ticks: 0)]; + Record record; + FileInfo fileInfo; + string relativePath; + ReadOnlyCollection> collection = Helpers.HelperDirectory.GetFilesCollection(rightDirectory, "*", "*"); + foreach (ReadOnlyCollection c in collection) { + foreach (string f in c) { + if (!matcher.Match(rightDirectory, f).HasMatches) + continue; + fileInfo = new(f); + if (fileInfo.Length == 0) + continue; + relativePath = Path.GetRelativePath(rightDirectory, fileInfo.FullName); + record = new(RelativePath: relativePath, + Size: fileInfo.Length, + Ticks: fileInfo.LastWriteTime.ToUniversalTime().Ticks); + results.Add(record); + } + } + return results.AsReadOnly(); + } + + private static void WriteAllText(string path, string text) { + string check = !File.Exists(path) ? string.Empty : File.ReadAllText(path); + if (check != text) + File.WriteAllText(path, text); + } + + private static void Sync(ILogger logger, string rightDirectory, string fileName, Logic? logic, string[] rootUniformResourceLocators, ReadOnlyCollection rightRecords, string format, TimeZoneInfo timeZoneInfo) { + Review? review; + foreach (string rootUniformResourceLocator in rootUniformResourceLocators) { + if (!rootUniformResourceLocator.StartsWith("https:")) + logger.LogInformation("Not supported URL <{url}>", rootUniformResourceLocator); + else { + review = GetJsonResponse(logger, fileName, rootUniformResourceLocator, format, timeZoneInfo); + if (review?.Records is null || review.Records.Length == 0) + logger.LogInformation("No response records"); + else { + ReadOnlyCollection leftRecords = review.Records.AsReadOnly(); + Sync(logger, rightDirectory, fileName, logic, rightRecords, rootUniformResourceLocator, leftRecords); + } + } + } + } + + private static Review? GetJsonResponse(ILogger logger, string fileName, string rootUniformResourceLocator, string format, TimeZoneInfo timeZoneInfo) { + Review? result; + Task response; + HttpClient httpClient = new(); + Task httpResponseMessage; + string url = new(rootUniformResourceLocator.EndsWith('/') ? + $"{rootUniformResourceLocator[..^1]}/{fileName}" : + $"{rootUniformResourceLocator}/{fileName}"); + httpResponseMessage = httpClient.GetAsync(rootUniformResourceLocator); + httpResponseMessage.Wait(); + if (!httpResponseMessage.Result.IsSuccessStatusCode) { + logger.LogInformation("Failed to download: <{rootUniformResourceLocator}>;", rootUniformResourceLocator); + result = null; + } else { + response = httpResponseMessage.Result.Content.ReadAsStringAsync(); + response.Wait(); + NginxFileSystem[]? nginxFileSystems = JsonSerializer.Deserialize(response.Result, NginxFileSystemCollectionSourceGenerationContext.Default.NginxFileSystemArray); + bool isNewest = nginxFileSystems is not null && IsNewest(fileName, format, timeZoneInfo, new(rootUniformResourceLocator), nginxFileSystems); + if (nginxFileSystems is null) { + logger.LogInformation("Failed to parse: <{rootUniformResourceLocator}>;", rootUniformResourceLocator); + result = null; + } else if (!isNewest) { + logger.LogInformation("Outdated remote file: <{rootUniformResourceLocator}>;", rootUniformResourceLocator); + result = null; + } else { + httpResponseMessage = httpClient.GetAsync(url); + httpResponseMessage.Wait(); + if (!httpResponseMessage.Result.IsSuccessStatusCode) { + logger.LogInformation("Failed to download: <{url}>;", url); + result = null; + } else { + response = httpResponseMessage.Result.Content.ReadAsStringAsync(); + response.Wait(); + result = string.IsNullOrEmpty(response.Result) ? + null : + JsonSerializer.Deserialize(response.Result, ReviewCommonSourceGenerationContext.Default.Review); + } + } + } + return result; + } + + private static bool IsNewest(string fileName, string format, TimeZoneInfo timeZoneInfo, Uri uri, NginxFileSystem[] nginxFileSystems) { + bool result; + DateTime? match = null; + NginxFileSystem nginxFileSystem; + DateTime dateTime = DateTime.MinValue; + for (int i = 0; i < nginxFileSystems.Length; i++) { + nginxFileSystem = NginxFileSystem.Get(format, timeZoneInfo, uri, nginxFileSystems[i]); + if (nginxFileSystem.LastModified is not null && nginxFileSystem.Name == fileName) { + match = nginxFileSystem.LastModified.Value; + continue; + } + if (nginxFileSystem.LastModified is null || nginxFileSystem.LastModified <= dateTime) + continue; + dateTime = nginxFileSystem.LastModified.Value; + } + result = match is not null && match.Value > dateTime; + return result; + } + + private static void Sync(ILogger logger, string rightDirectory, string fileName, Logic? l, ReadOnlyCollection rightRecords, string rootUniformResourceLocators, ReadOnlyCollection leftRecords) { + string json; + string checkFile; + HttpClient httpClient = new(); + checkFile = Path.Combine(rightDirectory, fileName); + if (File.Exists(checkFile)) + File.Delete(checkFile); + ReadOnlyCollection areEqual = GetAreEqual(rightDirectory, fileName, rightRecords, rootUniformResourceLocators, leftRecords); + ReadOnlyCollection notEqualBut = GetNotEqualBut(rightDirectory, fileName, rightRecords, rootUniformResourceLocators, leftRecords); + ReadOnlyCollection leftSideOnly = GetLeftSideOnly(rightDirectory, fileName, rightRecords, rootUniformResourceLocators, leftRecords); + ReadOnlyCollection rightSideOnly = GetRightSideOnly(rightDirectory, fileName, rightRecords, rootUniformResourceLocators, leftRecords); + ReadOnlyCollection leftSideIsNewer = GetLeftSideIsNewer(rightDirectory, fileName, rightRecords, rootUniformResourceLocators, leftRecords); + ReadOnlyCollection rightSideIsNewer = GetRightSideIsNewer(rightDirectory, fileName, rightRecords, rootUniformResourceLocators, leftRecords); + Review review = new(AreEqual: areEqual.ToArray(), + LeftSideIsNewer: leftSideIsNewer.ToArray(), + LeftSideOnly: leftSideOnly.ToArray(), + NotEqualBut: notEqualBut.ToArray(), + Records: null, + RightSideIsNewer: rightSideIsNewer.ToArray(), + RightSideOnly: rightSideOnly.ToArray()); + json = JsonSerializer.Serialize(review, ReviewCommonSourceGenerationContext.Default.Review); + checkFile = Path.Combine(rightDirectory, fileName); + WriteAllText(checkFile, json); + if (notEqualBut.Count > 0 && l is not null && l.NotEqualBut is not null && l.Raw[l.NotEqualButIndex][0] == l.Minus && !l.NotEqualBut.Value) + logger.LogDebug("Doing nothing with {name}", nameof(Logic.NotEqualBut)); + if (leftSideOnly.Count > 0 && l is not null && l.LeftSideOnly is not null && l.Raw[l.LeftSideOnlyIndex][0] == l.Minus && !l.LeftSideOnly.Value) + throw new NotImplementedException("Not possible with https!"); + if (leftSideIsNewer.Count > 0 && l is not null && l.LeftSideIsNewer is not null && l.Raw[l.LeftSideIsNewerIndex][0] == l.LessThan && !l.LeftSideIsNewer.Value) + throw new NotImplementedException("Not possible with https!"); + if (rightSideIsNewer.Count > 0 && l is not null && l.RightSideIsNewer is not null && l.Raw[l.RightSideIsNewerIndex][0] == l.LessThan && !l.RightSideIsNewer.Value) + throw new NotImplementedException("Not possible with https!"); + if (rightSideOnly.Count > 0 && l is not null && l.RightSideOnly is not null && l.Raw[l.RightSideOnlyIndex][0] == l.Plus && l.RightSideOnly.Value) + throw new NotImplementedException("Not possible with https!"); + if (rightSideOnly.Count > 0 && l is not null && l.RightSideOnly is not null && l.Raw[l.RightSideOnlyIndex][0] == l.Minus && !l.RightSideOnly.Value) + DoWork(logger, rightDirectory, httpClient, rightSideOnly, delete: true, download: false); + if (leftSideOnly.Count > 0 && l is not null && l.LeftSideOnly is not null && l.Raw[l.LeftSideOnlyIndex][0] == l.Plus && l.LeftSideOnly.Value) + DoWork(logger, rightDirectory, httpClient, leftSideOnly, delete: false, download: true); + if (leftSideIsNewer.Count > 0 && l is not null && l.LeftSideIsNewer is not null && l.Raw[l.LeftSideIsNewerIndex][0] == l.GreaterThan && l.LeftSideIsNewer.Value) + DoWork(logger, rightDirectory, httpClient, leftSideIsNewer, delete: true, download: true); + if (notEqualBut.Count > 0 && l is not null && l.NotEqualBut is not null && l.Raw[l.NotEqualButIndex][0] == l.Plus && l.NotEqualBut.Value) + DoWork(logger, rightDirectory, httpClient, notEqualBut, delete: true, download: true); + if (rightSideIsNewer.Count > 0 && l is not null && l.RightSideIsNewer is not null && l.Raw[l.RightSideIsNewerIndex][0] == l.GreaterThan && l.RightSideIsNewer.Value) + DoWork(logger, rightDirectory, httpClient, rightSideIsNewer, delete: true, download: true); + } + + private static ReadOnlyCollection GetAreEqual(string rightDirectory, string fileName, ReadOnlyCollection rightRecords, string rootUniformResourceLocators, ReadOnlyCollection leftRecords) { + List results = []; + Record? record; + Segment segment; + double totalSeconds; + string? checkDirectory = null; + ReadOnlyDictionary keyValuePairs = GetKeyValuePairs(rightRecords); + foreach (Record r in leftRecords) { + if (checkDirectory is null && r.Size == 0 && r.Ticks == 0) { + checkDirectory = r.RelativePath; + continue; + } + if (r.RelativePath == rightDirectory || r.RelativePath == fileName) + continue; + if (!keyValuePairs.TryGetValue(r.RelativePath, out record)) + continue; + totalSeconds = new TimeSpan(record.Ticks - r.Ticks).TotalSeconds; + if (record.Size != r.Size || totalSeconds is > 2 or < -2) + continue; + segment = new(Left: r, + LeftDirectory: checkDirectory, + Right: record, + RightDirectory: rightDirectory, + RootUniformResourceLocator: rootUniformResourceLocators); + results.Add(segment); + } + return results.AsReadOnly(); + } + + private static ReadOnlyDictionary GetKeyValuePairs(ReadOnlyCollection records) { + Dictionary results = []; + foreach (Record record in records) + results.Add(record.RelativePath, record); + return new(results); + } + + private static ReadOnlyCollection GetNotEqualBut(string rightDirectory, string fileName, ReadOnlyCollection rightRecords, string rootUniformResourceLocators, ReadOnlyCollection leftRecords) { + List results = []; + Record? record; + Segment segment; + double totalSeconds; + string? checkDirectory = null; + ReadOnlyDictionary keyValuePairs = GetKeyValuePairs(rightRecords); + foreach (Record r in leftRecords) { + if (checkDirectory is null && r.Size == 0 && r.Ticks == 0) { + checkDirectory = r.RelativePath; + continue; + } + if (r.RelativePath == rightDirectory || r.RelativePath == fileName) + continue; + if (!keyValuePairs.TryGetValue(r.RelativePath, out record)) + continue; + if (record.Size == r.Size) + continue; + totalSeconds = new TimeSpan(record.Ticks - r.Ticks).TotalSeconds; + if (totalSeconds is >= 2 or <= -2) + continue; + segment = new(Left: r, + LeftDirectory: checkDirectory, + Right: record, + RightDirectory: rightDirectory, + RootUniformResourceLocator: rootUniformResourceLocators); + results.Add(segment); + } + return results.AsReadOnly(); + } + + private static ReadOnlyCollection GetLeftSideOnly(string rightDirectory, string fileName, ReadOnlyCollection rightRecords, string rootUniformResourceLocators, ReadOnlyCollection leftRecords) { + List results = []; + Record? record; + Segment segment; + string? checkDirectory = null; + ReadOnlyDictionary keyValuePairs = GetKeyValuePairs(rightRecords); + foreach (Record r in leftRecords) { + if (checkDirectory is null && r.Size == 0 && r.Ticks == 0) { + checkDirectory = r.RelativePath; + continue; + } + if (r.RelativePath == rightDirectory || r.RelativePath == fileName) + continue; + if (keyValuePairs.TryGetValue(r.RelativePath, out record)) + continue; + segment = new(Left: r, + LeftDirectory: checkDirectory, + Right: record, + RightDirectory: rightDirectory, + RootUniformResourceLocator: rootUniformResourceLocators); + results.Add(segment); + } + return results.AsReadOnly(); + } + + private static ReadOnlyCollection GetRightSideOnly(string rightDirectory, string fileName, ReadOnlyCollection rightRecords, string rootUniformResourceLocators, ReadOnlyCollection leftRecords) { + List results = []; + Record? record; + Segment segment; + string? checkDirectory = null; + ReadOnlyDictionary keyValuePairs = GetKeyValuePairs(leftRecords); + foreach (Record r in rightRecords) { + if (checkDirectory is null && r.Size == 0 && r.Ticks == 0) { + checkDirectory = r.RelativePath; + continue; + } + if (r.RelativePath == rightDirectory || r.RelativePath == fileName) + continue; + if (keyValuePairs.TryGetValue(r.RelativePath, out record)) + continue; + segment = new(Left: record, + LeftDirectory: null, + Right: r, + RightDirectory: rightDirectory, + RootUniformResourceLocator: rootUniformResourceLocators); + results.Add(segment); + } + return results.AsReadOnly(); + } + + private static ReadOnlyCollection GetLeftSideIsNewer(string rightDirectory, string fileName, ReadOnlyCollection rightRecords, string rootUniformResourceLocators, ReadOnlyCollection leftRecords) { + List results = []; + Record? record; + Segment segment; + double totalSeconds; + string? checkDirectory = null; + ReadOnlyDictionary keyValuePairs = GetKeyValuePairs(rightRecords); + foreach (Record r in leftRecords) { + if (checkDirectory is null && r.Size == 0 && r.Ticks == 0) { + checkDirectory = r.RelativePath; + continue; + } + if (r.RelativePath == rightDirectory || r.RelativePath == fileName) + continue; + if (!keyValuePairs.TryGetValue(r.RelativePath, out record)) + continue; + totalSeconds = new TimeSpan(record.Ticks - r.Ticks).TotalSeconds; + if (totalSeconds is > -2) + continue; + segment = new(Left: r, + LeftDirectory: checkDirectory, + Right: record, + RightDirectory: rightDirectory, + RootUniformResourceLocator: rootUniformResourceLocators); + results.Add(segment); + } + return results.AsReadOnly(); + } + + private static ReadOnlyCollection GetRightSideIsNewer(string rightDirectory, string fileName, ReadOnlyCollection rightRecords, string rootUniformResourceLocators, ReadOnlyCollection leftRecords) { + List results = []; + Record? record; + Segment segment; + double totalSeconds; + string? checkDirectory = null; + ReadOnlyDictionary keyValuePairs = GetKeyValuePairs(leftRecords); + foreach (Record r in rightRecords) { + if (checkDirectory is null && r.Size == 0 && r.Ticks == 0) { + checkDirectory = r.RelativePath; + continue; + } + if (r.RelativePath == rightDirectory || r.RelativePath == fileName) + continue; + if (!keyValuePairs.TryGetValue(r.RelativePath, out record)) + continue; + totalSeconds = new TimeSpan(record.Ticks - r.Ticks).TotalSeconds; + if (totalSeconds is > -2) + continue; + segment = new(Left: record, + LeftDirectory: null, + Right: r, + RightDirectory: rightDirectory, + RootUniformResourceLocator: rootUniformResourceLocators); + results.Add(segment); + } + return results.AsReadOnly(); + } + + private static void DoWork(ILogger logger, string rightDirectory, HttpClient httpClient, ReadOnlyCollection segments, bool delete, bool download) { + long sum; + Record[] records = (from l in segments where l.Left is not null select l.Left).ToArray(); + try { sum = records.Sum(l => l.Size); } catch (Exception) { sum = 0; } + string size = GetSizeWithSuffix(sum); + if (delete) { + logger.LogInformation("Starting to delete {count} file(s) [{sum}]", segments.Count, size); + DoDeletes(logger, rightDirectory, segments); + logger.LogInformation("Deleted {count} file(s) [{sum}]", segments.Count, size); + } + if (download) { + logger.LogInformation("Starting to download {count} file(s) [{sum}]", segments.Count, size); + DoDownloads(logger, rightDirectory, segments, httpClient); + logger.LogInformation("Downloaded {count} file(s) [{sum}]", segments.Count, size); + } + } + + private static string GetSizeWithSuffix(long value) { + string result; + int i = 0; + string[] SizeSuffixes = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + if (value < 0) { + result = "-" + GetSizeWithSuffix(-value); + } else { + while (Math.Round(value / 1024f) >= 1) { + value /= 1024; + i++; + } + result = string.Format("{0:n1} {1}", value, SizeSuffixes[i]); + } + return result; + } + + private static string GetDurationWithSuffix(long ticks) { + string result; + TimeSpan timeSpan = new(DateTime.Now.Ticks - ticks); + if (timeSpan.TotalMilliseconds < 1000) + result = $"{timeSpan.Milliseconds} ms"; + else if (timeSpan.TotalMilliseconds < 60000) + result = $"{Math.Floor(timeSpan.TotalSeconds)} s"; + else if (timeSpan.TotalMilliseconds < 3600000) + result = $"{Math.Floor(timeSpan.TotalMinutes)} m"; + else + result = $"{Math.Floor(timeSpan.TotalHours)} h"; + return result; + } + + private static void DoDeletes(ILogger logger, string rightDirectory, ReadOnlyCollection segments) { + Record? record; + string size; + string count = segments.Count.ToString("000000"); +#if ShellProgressBar + ProgressBar progressBar = new(segments.Count, $"Deleting: {count};", new ProgressBarOptions() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }); +#endif + for (int i = 0; i < segments.Count; i++) { +#if ShellProgressBar + progressBar.Tick(); +#endif + record = segments[i].Right; + if (record is null) + continue; + size = GetSizeWithSuffix(record.Size); + try { + File.Delete(Path.Combine(rightDirectory, record.RelativePath)); + logger.LogInformation("{i} of {count} - Deleted: <{RelativePath}> - {size};", i.ToString("000000"), count, record.RelativePath, size); + } catch (Exception) { + logger.LogInformation("Failed to delete: <{RelativePath}> - {size};", record.RelativePath, size); + } + } +#if ShellProgressBar + progressBar.Dispose(); +#endif + } + + private static void DoDownloads(ILogger logger, string rightDirectory, ReadOnlyCollection segments, HttpClient httpClient) { + int i = 0; + long ticks; + string size; + string duration; + DateTime dateTime; + Task response; + string count = segments.Count.ToString("000000"); + ReadOnlyCollection downloads = GetDownloads(rightDirectory, segments); + Task httpResponseMessage; +#if ShellProgressBar + ProgressBar progressBar = new(downloads.Count, $"Downloading: {count};", new ProgressBarOptions() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }); +#endif + foreach (Download download in downloads) { +#if ShellProgressBar + progressBar.Tick(); +#endif + i += 1; + ticks = DateTime.Now.Ticks; + size = GetSizeWithSuffix(download.Size); + httpResponseMessage = httpClient.GetAsync(download.UniformResourceLocator); + httpResponseMessage.Wait(-1); + if (!httpResponseMessage.Result.IsSuccessStatusCode) + logger.LogInformation("Failed to download: <{checkURL}> - {size};", download.UniformResourceLocator, size); + else { + response = httpResponseMessage.Result.Content.ReadAsStringAsync(); + response.Wait(); + try { + File.WriteAllText(download.File, response.Result); + duration = GetDurationWithSuffix(ticks); + dateTime = new DateTime(download.Ticks).ToLocalTime(); + File.SetLastWriteTime(download.File, dateTime); + logger.LogInformation("{i} of {count} - Downloaded: <{checkURL}> - {size} - {timeSpan};", + i.ToString("000000"), + count, + download.Display, + size, + duration); + } catch (Exception) { + logger.LogInformation("Failed to download: <{checkURL}> - {size};", download.UniformResourceLocator, size); + } + } + } +#if ShellProgressBar + progressBar.Dispose(); +#endif + } + + private static ReadOnlyCollection GetDownloads(string rightDirectory, ReadOnlyCollection segments) { + List results = []; + string checkFile; + Download download; + string? checkDirectory; + List collection = []; + string? checkUniformResourceLocator; + foreach (Segment segment in segments) { + if (segment.Left is null) + continue; + checkFile = Path.Combine(rightDirectory, segment.Left.RelativePath); + checkDirectory = Path.GetDirectoryName(checkFile); + if (string.IsNullOrEmpty(checkDirectory)) + continue; + if (!Directory.Exists(checkDirectory)) + _ = Directory.CreateDirectory(checkDirectory); + if (File.Exists(checkFile) && new FileInfo(checkFile).Length == 0) + File.Delete(checkFile); + checkUniformResourceLocator = ConvertTo(segment.RootUniformResourceLocator, segment.Left.RelativePath); + if (string.IsNullOrEmpty(checkUniformResourceLocator)) + continue; + download = new(Directory: checkDirectory, + Display: checkUniformResourceLocator[segment.RootUniformResourceLocator.Length..], + File: checkFile, + Size: segment.Left.Size, + Ticks: segment.Left.Ticks, + UniformResourceLocator: checkUniformResourceLocator); + collection.Add(download); + } + Download[] sorted = (from l in collection orderby l.Size select l).ToArray(); + int stop = sorted.Length < 100 ? sorted.Length : 100; + for (int i = 0; i < stop; i++) + results.Add(sorted[i]); + for (int i = sorted.Length - 1; i > stop - 1; i--) + results.Add(sorted[i]); + if (collection.Count != results.Count) + throw new Exception(); + return results.AsReadOnly(); + } + + private static string? ConvertTo(string rootURL, string relativePath) { + string? result = rootURL.EndsWith('/') ? rootURL[..^1] : rootURL; + string windowsRoot = "c:\\"; + string windowsMock = $"{windowsRoot}{relativePath}"; + string fileName = Path.GetFileName(windowsMock); + ReadOnlyCollection directoryNames = Helpers.HelperDirectory.GetDirectoryNames(windowsMock); + foreach (string directoryName in directoryNames) { + if (directoryName == windowsRoot || directoryName == fileName) + continue; + result = $"{result}/{directoryName}"; + } + result = result == rootURL ? null : $"{result}/{fileName}"; + return result; + } + +} \ No newline at end of file diff --git a/Day/HelperDay.cs b/Day/HelperDay.cs index 5fa2d78..ba8b357 100644 --- a/Day/HelperDay.cs +++ b/Day/HelperDay.cs @@ -153,6 +153,8 @@ internal static class HelperDay ADO2025.PI5.Helper20250321.MoveToLast(logger, args); else if (args[1] == "Day-Helper-2025-04-04") ADO2025.PI5.Helper20250404.KumaToGatus(logger, args); + else if (args[1] == "Day-Helper-2025-04-07") + ADO2025.PI5.Helper20250407.Sync(logger, args); else throw new Exception(appSettings.Company); } diff --git a/Helpers/HelperDirectory.cs b/Helpers/HelperDirectory.cs index e051501..97216b7 100644 --- a/Helpers/HelperDirectory.cs +++ b/Helpers/HelperDirectory.cs @@ -41,4 +41,31 @@ internal static class HelperDirectory return new(results); } + internal static ReadOnlyCollection> GetFilesCollection(string directory, string directorySearchFilter, string fileSearchFilter) + { + List> results = []; + string[] files; + if (!fileSearchFilter.Contains('*')) + fileSearchFilter = string.Concat('*', fileSearchFilter); + if (!directorySearchFilter.Contains('*')) + directorySearchFilter = string.Concat('*', directorySearchFilter); + if (!Directory.Exists(directory)) + _ = Directory.CreateDirectory(directory); + results.Add(Directory.GetFiles(directory, fileSearchFilter, SearchOption.TopDirectoryOnly).AsReadOnly()); + string[] directories = Directory.GetDirectories(directory, directorySearchFilter, SearchOption.TopDirectoryOnly); + foreach (string innerDirectory in directories) + { + try + { + files = Directory.GetFiles(innerDirectory, fileSearchFilter, SearchOption.AllDirectories); + if (files.Length == 0) + continue; + results.Add(files.AsReadOnly()); + } + catch (UnauthorizedAccessException) + { continue; } + } + return results.AsReadOnly(); + } + } \ No newline at end of file diff --git a/Worker.cs b/Worker.cs index 8bc6dab..356cd52 100644 --- a/Worker.cs +++ b/Worker.cs @@ -147,7 +147,7 @@ public class Worker : BackgroundService _Logger.LogWarning("Must pass a argument!"); CreateWindowsShortcut(); } - else if (Directory.Exists(_Args[0])) + else if (Directory.Exists(_Args[0].Split('|')[0]) || Directory.Exists(_Args[0])) { if (!_ConsoleKeys.Contains(consoleKey)) {