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; } }