From 2b2a53466f7f1474d106ff949b6e906b8f969887 Mon Sep 17 00:00:00 2001
From: Mira <56395159+TheXorog@users.noreply.github.com>
Date: Fri, 16 Jun 2023 14:20:32 +0200
Subject: [PATCH] Structure because structure
---
.../ConvertExtensions.cs | 2 +-
Extensions/ExceptionExtensions.cs | 5 +
Extensions/FileExtensions.cs | 98 +++
Extensions/HashingExtensions.cs | 27 +
Extensions/ListExtensions.cs | 40 +
Extensions/ScheduledTaskExtensions.cs | 111 +++
Extensions/StringExtensions.cs | 105 +++
Extensions/TimeExtensions.cs | 112 +++
Internal/Internal.cs | 2 +-
Tools/ColorTools.cs | 38 +
Tools/MathTools.cs | 20 +
Tools/StringTools.cs | 34 +
Tools/WebTools.cs | 78 ++
.../WindowsAPI}/WindowsUtils.cs | 0
{WindowsAPI => Tools/WindowsAPI}/kernel32.cs | 0
{WindowsAPI => Tools/WindowsAPI}/psapi.cs | 0
UniversalExtensions.cs | 695 +-----------------
17 files changed, 693 insertions(+), 674 deletions(-)
rename ConvertExtensions.cs => Extensions/ConvertExtensions.cs (99%)
create mode 100644 Extensions/ExceptionExtensions.cs
create mode 100644 Extensions/FileExtensions.cs
create mode 100644 Extensions/HashingExtensions.cs
create mode 100644 Extensions/ListExtensions.cs
create mode 100644 Extensions/ScheduledTaskExtensions.cs
create mode 100644 Extensions/StringExtensions.cs
create mode 100644 Extensions/TimeExtensions.cs
create mode 100644 Tools/ColorTools.cs
create mode 100644 Tools/MathTools.cs
create mode 100644 Tools/StringTools.cs
create mode 100644 Tools/WebTools.cs
rename {WindowsAPI => Tools/WindowsAPI}/WindowsUtils.cs (100%)
rename {WindowsAPI => Tools/WindowsAPI}/kernel32.cs (100%)
rename {WindowsAPI => Tools/WindowsAPI}/psapi.cs (100%)
diff --git a/ConvertExtensions.cs b/Extensions/ConvertExtensions.cs
similarity index 99%
rename from ConvertExtensions.cs
rename to Extensions/ConvertExtensions.cs
index 31a9ee2..58007db 100644
--- a/ConvertExtensions.cs
+++ b/Extensions/ConvertExtensions.cs
@@ -1,4 +1,4 @@
-namespace Xorog.UniversalExtensions;
+namespace Xorog.UniversalExtensions.Converters;
public static class ConvertExtensions
{
diff --git a/Extensions/ExceptionExtensions.cs b/Extensions/ExceptionExtensions.cs
new file mode 100644
index 0000000..8a77f65
--- /dev/null
+++ b/Extensions/ExceptionExtensions.cs
@@ -0,0 +1,5 @@
+namespace Xorog.UniversalExtensions;
+
+public static class ExceptionExtensions
+{
+}
diff --git a/Extensions/FileExtensions.cs b/Extensions/FileExtensions.cs
new file mode 100644
index 0000000..2479a21
--- /dev/null
+++ b/Extensions/FileExtensions.cs
@@ -0,0 +1,98 @@
+namespace Xorog.UniversalExtensions;
+
+public static class FileExtensions
+{
+ ///
+ /// Copy a directory recursively
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void CopyRecursively(this DirectoryInfo sourceDir, DirectoryInfo destDir, bool copySubDirs = true)
+ {
+ if (!sourceDir.Exists)
+ {
+ throw new DirectoryNotFoundException($"Source directory does not exist or could not be found: {sourceDir.FullName}")
+ .AttachData("sourceDir", sourceDir)
+ .AttachData("destDir", destDir)
+ .AttachData("copySubDirs", copySubDirs);
+ }
+
+ DirectoryInfo[] dirs = sourceDir.GetDirectories();
+
+ destDir.Create();
+
+ FileInfo[] files = sourceDir.GetFiles();
+ foreach (FileInfo file in files)
+ {
+ var newFilePath = Path.Combine(destDir.FullName, file.Name);
+
+ _logger?.LogDebug("Copying '{file}' to '{dest}'", file.FullName, newFilePath);
+ file.CopyTo(newFilePath, false);
+ }
+
+ if (copySubDirs)
+ foreach (DirectoryInfo subdir in dirs)
+ CopyRecursively(subdir, new DirectoryInfo(Path.Combine(destDir.FullName, subdir.Name)), copySubDirs);
+ }
+
+ ///
+ /// Try deleting the given files and directories until able to or reaching maximum retry count.
+ ///
+ /// A list of directories to clean up
+ /// A list of files to clean up
+ /// The maximum amount of retries
+ ///
+ public static async Task CleanupFilesAndDirectories(List? directoryPaths, List? filePaths, int maxRetryCount = 100)
+ {
+ var failCount = 0;
+
+ List exceptions = new();
+
+ if (directoryPaths?.IsNotNullAndNotEmpty() ?? false)
+ foreach (string DirectoryPath in directoryPaths)
+ {
+ while (Directory.Exists(DirectoryPath))
+ {
+ try
+ {
+ Directory.Delete(DirectoryPath, true);
+ }
+ catch (Exception ex)
+ {
+ exceptions.Add(ex);
+ await Task.Delay(5000);
+ failCount++;
+
+ if (failCount > maxRetryCount)
+ throw new AggregateException("Failed to delete directory", exceptions);
+ }
+ }
+ }
+
+ failCount = 0;
+ exceptions.Clear();
+
+ if (filePaths?.IsNotNullAndNotEmpty() ?? false)
+ foreach (string file in filePaths)
+ {
+ while (File.Exists(file))
+ {
+ try
+ {
+ File.Delete(file);
+ }
+ catch (Exception ex)
+ {
+ exceptions.Add(ex);
+ await Task.Delay(5000);
+ failCount++;
+
+ if (failCount > maxRetryCount)
+ throw new AggregateException("Failed to delete file", exceptions);
+ }
+ }
+ }
+ }
+}
diff --git a/Extensions/HashingExtensions.cs b/Extensions/HashingExtensions.cs
new file mode 100644
index 0000000..16c9d3a
--- /dev/null
+++ b/Extensions/HashingExtensions.cs
@@ -0,0 +1,27 @@
+namespace Xorog.UniversalExtensions;
+
+public static class HashingExtensions
+{
+ ///
+ /// Compute the SHA256-Hash for the given string
+ ///
+ ///
+ ///
+ public static string ComputeSHA256Hash(string str)
+ {
+ using SHA256 _SHA256 = SHA256.Create();
+ return BitConverter.ToString(_SHA256.ComputeHash(Encoding.ASCII.GetBytes(str))).Replace("-", "").ToLowerInvariant();
+ }
+
+ ///
+ /// Compute the SHA256-Hash for a given file
+ ///
+ ///
+ ///
+ public static string ComputeSHA256Hash(FileInfo filePath)
+ {
+ using SHA256 _SHA256 = SHA256.Create();
+ using FileStream fileStream = filePath.OpenRead();
+ return BitConverter.ToString(_SHA256.ComputeHash(fileStream)).Replace("-", "").ToLowerInvariant();
+ }
+}
diff --git a/Extensions/ListExtensions.cs b/Extensions/ListExtensions.cs
new file mode 100644
index 0000000..d405b28
--- /dev/null
+++ b/Extensions/ListExtensions.cs
@@ -0,0 +1,40 @@
+namespace Xorog.UniversalExtensions;
+
+public static class ListExtensions
+{
+ ///
+ /// Select a random item from a list
+ ///
+ ///
+ ///
+ /// The randomly selected item
+ /// The list is null
+ /// The list is empty
+ public static T SelectRandom(this IEnumerable? obj)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException();
+ }
+
+ if (!obj.Any())
+ {
+ throw new ArgumentException("The sequence is empty.");
+ }
+
+
+ int rng = new Random().Next(0, obj.Count());
+ return obj.ElementAt(rng) ?? throw new ArgumentNullException();
+ }
+
+
+
+ ///
+ /// Check whether a list contains elements and is not null
+ ///
+ ///
+ ///
+ /// Whether the list contains elements and is not null
+ public static bool IsNotNullAndNotEmpty(this IEnumerable? obj)
+ => obj is not null && obj.Any();
+}
diff --git a/Extensions/ScheduledTaskExtensions.cs b/Extensions/ScheduledTaskExtensions.cs
new file mode 100644
index 0000000..395d9b1
--- /dev/null
+++ b/Extensions/ScheduledTaskExtensions.cs
@@ -0,0 +1,111 @@
+namespace Xorog.UniversalExtensions;
+public static class ScheduledTaskExtensions
+{
+ ///
+ /// Create a scheduled task
+ ///
+ /// The task to run
+ /// The time to run the task
+ /// Any custom data you wish to provide.
+ /// An unique identifier of the task
+
+ public static string CreateScheduledTask(this Task task, DateTime runTime, object? customData = null)
+ {
+ if (task.Status != TaskStatus.WaitingForActivation)
+ throw new InvalidOperationException("The task is already being executed or has been scheduled for execution.")
+ .AttachData("Task", task)
+ .AttachData("RunTime", runTime)
+ .AttachData("CustomData", customData);
+
+ string UID = Guid.NewGuid().ToString();
+ CancellationTokenSource CancellationToken = new CancellationTokenSource();
+
+ if (Math.Ceiling(runTime.GetTimespanUntil().TotalMilliseconds) < 0)
+ runTime = DateTime.UtcNow.AddSeconds(1);
+
+ _ = LongDelay(runTime.GetTimespanUntil(), CancellationToken).ContinueWith(x =>
+ {
+ lock (RegisteredScheduledTasks)
+ {
+ RegisteredScheduledTasks.Remove(UID);
+ }
+
+ _logger?.LogDebug("Running scheduled task with UID '{UID}'", UID, runTime.GetTimespanUntil().GetHumanReadable());
+
+ if (x.IsCompletedSuccessfully)
+ task.Start();
+ });
+
+ lock (RegisteredScheduledTasks)
+ {
+ _logger?.LogDebug("Creating scheduled task with UID '{UID}' running in {RunTime}", UID, runTime.GetTimespanUntil().GetHumanReadable());
+
+ RegisteredScheduledTasks.Add(UID, new ScheduledTask
+ {
+ Uid = UID,
+ RunTime = runTime,
+ TokenSource = CancellationToken,
+ CustomData = customData,
+ });
+ }
+ return UID;
+ }
+
+
+
+ ///
+ /// Deletes a scheduled task
+ ///
+ /// The task's unique identifier
+ /// Throws if the task hasn't been found or if an internal error occurred
+ public static void DeleteScheduledTask(string UID)
+ {
+ if (!RegisteredScheduledTasks.ContainsKey(UID))
+ throw new KeyNotFoundException($"No scheduled task has been found with UID '{UID}'");
+
+ if (RegisteredScheduledTasks[UID].TokenSource is null)
+ throw new Exception($"Internal: There is no token source registered the specified task.");
+
+ _logger?.LogDebug("Deleting scheduled task with UID '{UID}'", UID);
+
+ lock (RegisteredScheduledTasks)
+ {
+ RegisteredScheduledTasks[UID].TokenSource?.Cancel();
+ RegisteredScheduledTasks.Remove(UID);
+ }
+ return;
+ }
+
+
+
+ ///
+ /// Gets a list of all registered tasks
+ ///
+ /// A list of all registered tasks
+ public static IReadOnlyList? GetScheduledTasks()
+ => RegisteredScheduledTasks.Select(x => x.Value).ToList().AsReadOnly();
+
+ ///
+ /// Gets a specific task
+ ///
+ /// The unique identifier of what task to get
+ /// The task
+ /// Throws if the task has not been found
+ public static ScheduledTask GetScheduledTask(string UID)
+ => RegisteredScheduledTasks[UID];
+
+ internal static async Task LongDelay(TimeSpan delay, CancellationTokenSource token)
+ {
+ var st = new Stopwatch();
+ st.Start();
+ while (true && !token.IsCancellationRequested)
+ {
+ var remaining = (delay - st.Elapsed).TotalMilliseconds;
+ if (remaining <= 0)
+ break;
+ if (remaining > Int16.MaxValue)
+ remaining = Int16.MaxValue;
+ await Task.Delay(TimeSpan.FromMilliseconds(remaining), token.Token);
+ }
+ }
+}
diff --git a/Extensions/StringExtensions.cs b/Extensions/StringExtensions.cs
new file mode 100644
index 0000000..342f561
--- /dev/null
+++ b/Extensions/StringExtensions.cs
@@ -0,0 +1,105 @@
+namespace Xorog.UniversalExtensions;
+
+public static class StringExtensions
+{
+ ///
+ /// Extensions for string.IsNullOrWhiteSpace
+ ///
+ ///
+ /// Whether the string is null, empty or only contains whitespaces
+ public static bool IsNullOrWhiteSpace(this string str)
+ => string.IsNullOrWhiteSpace(str);
+
+ ///
+ /// Extensions for string.IsNullOrEmpty
+ ///
+ ///
+ /// Whether the string is null or empty
+ public static bool IsNullOrEmpty(this string str)
+ => string.IsNullOrEmpty(str);
+
+ ///
+ /// Check if a string contains only digits
+ ///
+ ///
+ ///
+ public static bool IsDigitsOnly(this string str)
+ {
+ foreach (char c in str)
+ {
+ if (c is < '0' or > '9')
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Get all digits from a string
+ ///
+ ///
+ ///
+ public static string GetAllDigits(this string str) =>
+ new(str.Where(Char.IsDigit).ToArray());
+
+ ///
+ /// Get country flag emoji based on Iso Country Code
+ ///
+ ///
+ ///
+ public static string IsoCountryCodeToFlagEmoji(this string country)
+ {
+ return string.Concat(country.ToUpper().Select(x => char.ConvertFromUtf32(x + 0x1F1A5)));
+ }
+
+ ///
+ /// Shorten a string to the given length
+ ///
+ ///
+ ///
+ ///
+ public static string Truncate(this string value, int maxLength)
+ {
+ if (string.IsNullOrEmpty(value))
+ return value;
+ return value.Length <= maxLength ? value : value[..maxLength];
+ }
+
+ ///
+ /// Shorten a string to the given length and add ".." at the end
+ ///
+ ///
+ ///
+ ///
+ public static string TruncateWithIndication(this string value, int maxLength)
+ {
+ if (string.IsNullOrEmpty(value))
+ return value;
+
+ return value.Length <= maxLength ? value : $"{value[..(maxLength - 2)]}..";
+ }
+
+ ///
+ /// Remove unsupported characters from string to generate a valid filename
+ ///
+ /// The string with potentionally unwanted characters
+ /// The character the unwanted characters get replaced with (default: _)
+ /// A valid filename
+ public static string MakeValidFileName(this string name, char replace_char = '_')
+ {
+ string invalidChars = System.Text.RegularExpressions.Regex.Escape(new string(System.IO.Path.GetInvalidFileNameChars()));
+ string invalidRegStr = string.Format(@"([{0}]*\.+$)|([{0}]+)", invalidChars);
+
+ return System.Text.RegularExpressions.Regex.Replace(name, invalidRegStr, replace_char.ToString()).Replace('&', replace_char);
+ }
+
+ ///
+ /// Changes the first letter to upper.
+ ///
+ /// The string to modify.
+ /// The string with the first letter changed to upper.
+ public static string FirstLetterToUpper(this string str)
+ {
+ return $"{str.First().ToString().ToUpper()}{str.Remove(0, 1)}";
+ }
+}
diff --git a/Extensions/TimeExtensions.cs b/Extensions/TimeExtensions.cs
new file mode 100644
index 0000000..7f2e88d
--- /dev/null
+++ b/Extensions/TimeExtensions.cs
@@ -0,0 +1,112 @@
+namespace Xorog.UniversalExtensions;
+
+public static class TimeExtensions
+{
+ ///
+ /// Get a timespan from now to the given time. Negative on values in the past.
+ ///
+ ///
+ ///
+ public static TimeSpan GetTimespanUntil(this DateTime until) =>
+ (until.ToUniversalTime() - DateTime.UtcNow);
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static TimeSpan GetTimespanUntil(this DateTimeOffset until) =>
+ (until.ToUniversalTime() - DateTime.UtcNow);
+
+ ///
+ /// Get the total seconds until a given DateTime. Negative on values in the past.
+ ///
+ ///
+ ///
+ public static long GetTotalSecondsUntil(this DateTime until) =>
+ ((long)Math.Ceiling((until.ToUniversalTime() - DateTime.UtcNow).TotalSeconds));
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static long GetTotalSecondsUntil(this DateTimeOffset until) =>
+ ((long)Math.Ceiling((until.ToUniversalTime() - DateTime.UtcNow).TotalSeconds));
+
+ ///
+ /// Get a timespan from now to the given time. Negative on values in the future.
+ ///
+ ///
+ ///
+ public static TimeSpan GetTimespanSince(this DateTime until) =>
+ (DateTime.UtcNow - until.ToUniversalTime());
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static TimeSpan GetTimespanSince(this DateTimeOffset until) =>
+ (DateTime.UtcNow - until.ToUniversalTime());
+
+ ///
+ /// Get the total seconds since a given DateTime. Negative on values in the future.
+ ///
+ ///
+ ///
+ public static long GetTotalSecondsSince(this DateTime until) =>
+ ((long)Math.Ceiling((DateTime.UtcNow - until.ToUniversalTime()).TotalSeconds));
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static long GetTotalSecondsSince(this DateTimeOffset until) =>
+ ((long)Math.Ceiling((DateTime.UtcNow - until.ToUniversalTime()).TotalSeconds));
+
+ ///
+ /// Get a short human readable string for the given amount of seconds
+ ///
+ ///
+ ///
+ ///
+ public static string GetShortHumanReadable(this int seconds, TimeFormat timeFormat = TimeFormat.DAYS) =>
+ TimeSpan.FromSeconds(seconds).GetShortTimeFormat(timeFormat);
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static string GetShortHumanReadable(this long seconds, TimeFormat timeFormat = TimeFormat.DAYS) =>
+ TimeSpan.FromSeconds(seconds).GetShortTimeFormat(timeFormat);
+
+ ///
+ /// Get a short human readable string for the given amount of time
+ ///
+ ///
+ ///
+ ///
+ public static string GetShortHumanReadable(this TimeSpan timespan, TimeFormat timeFormat = TimeFormat.DAYS) =>
+ timespan.GetShortTimeFormat(timeFormat);
+
+ ///
+ /// Get a human readable string for the given amount of time.
+ ///
+ ///
+ ///
+ ///
+ public static string GetHumanReadable(this int seconds, TimeFormat timeFormat = TimeFormat.DAYS, HumanReadableTimeFormatConfig? config = null) =>
+ TimeSpan.FromSeconds(seconds).GetTimeFormat(timeFormat, config);
+
+ ///
+ public static string GetHumanReadable(this long seconds, TimeFormat timeFormat = TimeFormat.DAYS, HumanReadableTimeFormatConfig? config = null) =>
+ TimeSpan.FromSeconds(seconds).GetTimeFormat(timeFormat, config);
+
+ ///
+ public static string GetHumanReadable(this TimeSpan timeSpan, TimeFormat timeFormat = TimeFormat.DAYS, HumanReadableTimeFormatConfig? config = null) =>
+ timeSpan.GetTimeFormat(timeFormat, config);
+}
diff --git a/Internal/Internal.cs b/Internal/Internal.cs
index 597b952..69b4697 100644
--- a/Internal/Internal.cs
+++ b/Internal/Internal.cs
@@ -114,7 +114,7 @@ public class InternalSheduler
/// Delete this task.
///
public void Delete() =>
- UniversalExtensions.DeleteScheduledTask(Uid);
+ ScheduledTaskExtensions.DeleteScheduledTask(Uid);
}
}
diff --git a/Tools/ColorTools.cs b/Tools/ColorTools.cs
new file mode 100644
index 0000000..71b4789
--- /dev/null
+++ b/Tools/ColorTools.cs
@@ -0,0 +1,38 @@
+namespace Xorog.UniversalExtensions;
+
+public static class ColorTools
+{
+ ///
+ /// Get closest Color to given Color
+ ///
+ ///
+ ///
+ ///
+ public static Color GetClosestColor(List colorArray, Color baseColor)
+ {
+ var colors = colorArray.Select(x => new { Value = x, Diff = Internal.GetDiff(x, baseColor) }).ToList();
+ var min = colors.Min(x => x.Diff);
+ return colors.Find(x => x.Diff == min).Value;
+ }
+
+ ///
+ /// Convert Hex to Color
+ ///
+ /// The converted color
+ public static Color ToColor(this string str)
+ {
+ return ColorTranslator.FromHtml(str);
+ }
+
+ ///
+ /// Convert RGB Value to Hex
+ ///
+ /// Red
+ /// Green
+ /// Blue
+ /// A string that represents the color in hex (e.g. 255, 0, 0 -> #FF0000)
+ public static string ToHex(int R, int G, int B)
+ {
+ return "#" + R.ToString("X2") + G.ToString("X2") + B.ToString("X2");
+ }
+}
diff --git a/Tools/MathTools.cs b/Tools/MathTools.cs
new file mode 100644
index 0000000..5a8010c
--- /dev/null
+++ b/Tools/MathTools.cs
@@ -0,0 +1,20 @@
+namespace Xorog.UniversalExtensions;
+
+public static class MathTools
+{
+ ///
+ /// Calculates the percentage of the given 2 values.
+ ///
+ /// The current value.
+ /// The maximum value.
+ /// The percentage.
+ ///
+ public static int CalculatePercentage(double current, double max)
+ {
+ if (max == 0)
+ throw new ArgumentException("Max cannot be zero.");
+
+ double percentage = (current / max) * 100;
+ return Convert.ToInt32(percentage);
+ }
+}
diff --git a/Tools/StringTools.cs b/Tools/StringTools.cs
new file mode 100644
index 0000000..04dacdd
--- /dev/null
+++ b/Tools/StringTools.cs
@@ -0,0 +1,34 @@
+namespace Xorog.UniversalExtensions;
+
+public static class StringTools
+{
+ ///
+ /// Generate an ASCII Progressbar
+ ///
+ /// The current progress
+ /// The maximum progress
+ /// How long the ASCII Progressbar should be (default: 44)
+ /// What character the filled part should be (default: █)
+ /// What character the not-filled part should be (default: ∙)
+ /// What character the start-part should be (default: [)
+ /// What character the end-part part should be (default: ])
+ /// A progressbar
+ public static string GenerateASCIIProgressbar(double current, double max, int charlength = 44, char fill = '█', char empty = '∙', char start = '[', char end = ']')
+ {
+ long first = (long)Math.Round((current / max) * charlength, 0);
+
+ long second = charlength - first;
+
+ string mediadisplay = start.ToString();
+
+ for (long i = 0; i < first; i++)
+ mediadisplay += fill;
+
+ for (long i = 0; i < second; i++)
+ mediadisplay += empty;
+
+ mediadisplay += end;
+
+ return mediadisplay;
+ }
+}
diff --git a/Tools/WebTools.cs b/Tools/WebTools.cs
new file mode 100644
index 0000000..4135731
--- /dev/null
+++ b/Tools/WebTools.cs
@@ -0,0 +1,78 @@
+namespace Xorog.UniversalExtensions;
+
+public static class WebTools
+{
+ ///
+ /// Get the URL a redirect leads to (limited to StatusCodes 301, 303, 307, 308)
+ ///
+ /// The shortened URL
+ /// The URL the redirect leads to
+ public static async Task UnshortenUrl(string url, bool UseHeadMethod = true)
+ {
+ _logger?.LogDebug("Unshortening Url '{Url}', using head method: {UseHeadMethod}", url, UseHeadMethod);
+
+ HttpClient client = new(new HttpClientHandler()
+ {
+ AllowAutoRedirect = false,
+ AutomaticDecompression = DecompressionMethods.GZip,
+
+ });
+ client.Timeout = TimeSpan.FromSeconds(60);
+ client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36");
+ client.DefaultRequestHeaders.Add("upgrade-insecure-requests", "1");
+ client.DefaultRequestHeaders.Add("accept-encoding", "gzip, deflate, br");
+ client.DefaultRequestHeaders.Add("accept-language", "en-US,en;q=0.9");
+ client.MaxResponseContentBufferSize = 4096;
+
+ HttpRequestMessage requestMessage = new HttpRequestMessage((UseHeadMethod ? HttpMethod.Head : HttpMethod.Get), url);
+
+ CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
+ var request_task = client.SendAsync(requestMessage, cancellationTokenSource.Token);
+
+ try
+ {
+ await request_task.WaitAsync(TimeSpan.FromSeconds(3));
+ }
+ catch (Exception)
+ {
+ if (UseHeadMethod)
+ return await UnshortenUrl(url, false);
+
+ throw;
+ }
+
+ if (!request_task.IsCompleted)
+ cancellationTokenSource.Cancel();
+
+ if (UseHeadMethod && request_task.IsFaulted && request_task.Exception.InnerException.GetType() == typeof(HttpRequestException))
+ {
+ _logger?.LogWarning("Unshortening Url '{Url}' failed, falling back to non-head method", url);
+ return await UnshortenUrl(url, false);
+ }
+
+ var statuscode = request_task.Result.StatusCode;
+ var header = request_task.Result.Headers;
+
+ if (UseHeadMethod && statuscode is HttpStatusCode.NotFound or HttpStatusCode.InternalServerError)
+ {
+ _logger?.LogWarning("Unshortening Url '{Url}' failed, falling back to non-head method", url);
+ return await UnshortenUrl(url, false);
+ }
+
+ if (statuscode is HttpStatusCode.Found
+ or HttpStatusCode.Redirect
+ or HttpStatusCode.SeeOther
+ or HttpStatusCode.RedirectKeepVerb
+ or HttpStatusCode.RedirectMethod
+ or HttpStatusCode.PermanentRedirect
+ or HttpStatusCode.TemporaryRedirect)
+ {
+ if (header is not null && header.Location is not null)
+ return await UnshortenUrl(header.Location.AbsoluteUri);
+ else
+ return url;
+ }
+ else
+ return url;
+ }
+}
diff --git a/WindowsAPI/WindowsUtils.cs b/Tools/WindowsAPI/WindowsUtils.cs
similarity index 100%
rename from WindowsAPI/WindowsUtils.cs
rename to Tools/WindowsAPI/WindowsUtils.cs
diff --git a/WindowsAPI/kernel32.cs b/Tools/WindowsAPI/kernel32.cs
similarity index 100%
rename from WindowsAPI/kernel32.cs
rename to Tools/WindowsAPI/kernel32.cs
diff --git a/WindowsAPI/psapi.cs b/Tools/WindowsAPI/psapi.cs
similarity index 100%
rename from WindowsAPI/psapi.cs
rename to Tools/WindowsAPI/psapi.cs
diff --git a/UniversalExtensions.cs b/UniversalExtensions.cs
index 5c74aec..1391efd 100644
--- a/UniversalExtensions.cs
+++ b/UniversalExtensions.cs
@@ -4,7 +4,21 @@ namespace Xorog.UniversalExtensions;
public static class UniversalExtensions
{
- public static void LoadAllReferencedAssemblies(AppDomain domain)
+ ///
+ /// Attaches a logger to UniversalExtensions. Used for Debugging.
+ ///
+ ///
+ public static void AttachLogger(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ ///
+ /// Loads all referenced dependencies in an .
+ /// Prevents s that are caused by utilizing not yet loaded assemblies after the system has been updated.
+ ///
+ /// The to load all dependencies from.
+ public static void LoadAllReferencedAssemblies(this AppDomain domain)
{
_logger?.LogDebug("Loading all assemblies..");
@@ -38,70 +52,19 @@ public static class UniversalExtensions
}
///
- /// Attaches a logger to UniversalExtensions. Used for Debug purposes.
- ///
- ///
- public static void AttachLogger(ILogger logger)
- {
- _logger = logger;
- }
-
-
- ///
- /// Extensions for string.IsNullOrWhiteSpace
- ///
- ///
- /// Whether the string is null, empty or only contains whitespaces
- public static bool IsNullOrWhiteSpace(this string str) => string.IsNullOrWhiteSpace(str);
-
-
-
- ///
- /// Extensions for string.IsNullOrEmpty
- ///
- ///
- /// Whether the string is null or empty
- public static bool IsNullOrEmpty(this string str) => string.IsNullOrEmpty(str);
-
-
-
- ///
- /// Select a random item from a list
+ /// Adds additional data to an exception.
///
///
- ///
- /// The randomly selected item
- /// The list is null
- /// The list is empty
- public static T SelectRandom(this IEnumerable obj)
+ ///
+ ///
+ ///
+ ///
+ public static T AttachData(this T exception, string Key, object? Data) where T : Exception
{
- if (obj == null)
- {
- throw new ArgumentNullException();
- }
-
- if (!obj.Any())
- {
- throw new ArgumentException("The sequence is empty.");
- }
-
-
- int rng = new Random().Next(0, obj.Count());
- return obj.ElementAt(rng) ?? throw new ArgumentNullException();
+ exception.Data.Add(Key, Data);
+ return exception;
}
-
-
- ///
- /// Check whether a list contains elements and is not null
- ///
- ///
- ///
- /// Whether the list contains elements and is not null
- public static bool IsNotNullAndNotEmpty(this IEnumerable obj) => obj is not null && obj.Any();
-
-
-
///
/// Get the current CPU Usage on all platforms
///
@@ -119,616 +82,4 @@ public static class UniversalExtensions
var cpuUsageTotal = cpuUsedMs / (Environment.ProcessorCount * totalMsPassed);
return cpuUsageTotal * 100;
}
-
-
-
- ///
- /// Copy a directory recursively
- ///
- ///
- ///
- ///
- ///
- public static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)
- {
- // Get the subdirectories for the specified directory.
- DirectoryInfo dir = new(sourceDirName);
-
- if (!dir.Exists)
- {
- throw new DirectoryNotFoundException(
- "Source directory does not exist or could not be found: "
- + sourceDirName);
- }
-
- DirectoryInfo[] dirs = dir.GetDirectories();
-
- // If the destination directory doesn't exist, create it.
- Directory.CreateDirectory(destDirName);
-
- // Get the files in the directory and copy them to the new location.
- FileInfo[] files = dir.GetFiles();
- foreach (FileInfo file in files)
- {
- string tempPath = Path.Combine(destDirName, file.Name);
- file.CopyTo(tempPath, false);
- }
-
- // If copying subdirectories, copy them and their contents to new location.
- if (copySubDirs)
- {
- foreach (DirectoryInfo subdir in dirs)
- {
- string tempPath = Path.Combine(destDirName, subdir.Name);
- DirectoryCopy(subdir.FullName, tempPath, copySubDirs);
- }
- }
- }
-
-
-
- ///
- /// Generate an ASCII Progressbar
- ///
- /// The current progress
- /// The maximum progress
- /// How long the ASCII Progressbar should be (default: 44)
- /// What character the filled part should be (default: █)
- /// What character the not-filled part should be (default: ∙)
- /// What character the start-part should be (default: [)
- /// What character the end-part part should be (default: ])
- /// A progressbar
- public static string GenerateASCIIProgressbar(double current, double max, int charlength = 44, char fill = '█', char empty = '∙', char start = '[', char end = ']')
- {
- long first = (long)Math.Round((current / max) * charlength, 0);
-
- long second = charlength - first;
-
- string mediadisplay = start.ToString();
-
- for (long i = 0; i < first; i++)
- mediadisplay += fill;
-
- for (long i = 0; i < second; i++)
- mediadisplay += empty;
-
- mediadisplay += end;
-
- return mediadisplay;
- }
-
-
-
- ///
- /// Get the URL a redirect leads to (limited to StatusCodes 301, 303, 307, 308)
- ///
- /// The shortened URL
- /// The URL the redirect leads to
- public static async Task UnshortenUrl(string url, bool UseHeadMethod = true)
- {
- _logger?.LogDebug("Unshortening Url '{Url}', using head method: {UseHeadMethod}", url, UseHeadMethod);
-
- HttpClient client = new(new HttpClientHandler()
- {
- AllowAutoRedirect = false,
- AutomaticDecompression = DecompressionMethods.GZip,
-
- });
- client.Timeout = TimeSpan.FromSeconds(60);
- client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36");
- client.DefaultRequestHeaders.Add("upgrade-insecure-requests", "1");
- client.DefaultRequestHeaders.Add("accept-encoding", "gzip, deflate, br");
- client.DefaultRequestHeaders.Add("accept-language", "en-US,en;q=0.9");
- client.MaxResponseContentBufferSize = 4096;
-
- HttpRequestMessage requestMessage = new HttpRequestMessage((UseHeadMethod ? HttpMethod.Head : HttpMethod.Get), url);
-
- CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
- var request_task = client.SendAsync(requestMessage, cancellationTokenSource.Token);
-
- try
- {
- await request_task.WaitAsync(TimeSpan.FromSeconds(3));
- }
- catch (Exception)
- {
- if (UseHeadMethod)
- return await UnshortenUrl(url, false);
-
- throw;
- }
-
- if (!request_task.IsCompleted)
- cancellationTokenSource.Cancel();
-
- if (UseHeadMethod && request_task.IsFaulted && request_task.Exception.InnerException.GetType() == typeof(HttpRequestException))
- {
- _logger?.LogWarning("Unshortening Url '{Url}' failed, falling back to non-head method", url);
- return await UnshortenUrl(url, false);
- }
-
- var statuscode = request_task.Result.StatusCode;
- var header = request_task.Result.Headers;
-
- if (UseHeadMethod && statuscode is HttpStatusCode.NotFound or HttpStatusCode.InternalServerError)
- {
- _logger?.LogWarning("Unshortening Url '{Url}' failed, falling back to non-head method", url);
- return await UnshortenUrl(url, false);
- }
-
- if (statuscode is HttpStatusCode.Found
- or HttpStatusCode.Redirect
- or HttpStatusCode.SeeOther
- or HttpStatusCode.RedirectKeepVerb
- or HttpStatusCode.RedirectMethod
- or HttpStatusCode.PermanentRedirect
- or HttpStatusCode.TemporaryRedirect)
- {
- if (header is not null && header.Location is not null)
- return await UnshortenUrl(header.Location.AbsoluteUri);
- else
- return url;
- }
- else
- return url;
- }
-
-
-
- ///
- /// Try deleting the given files and directories until able to
- ///
- /// A list of directories to clean up
- /// A list of files to clean up
- ///
- public static async Task CleanupFilesAndDirectories(List DirectoryPaths, List FilePaths)
- {
- foreach (string DirectoryPath in DirectoryPaths)
- {
- while (Directory.Exists(DirectoryPath))
- {
- try
- {
- Directory.Delete(DirectoryPath, true);
- }
- catch (Exception)
- {
- await Task.Delay(5000);
- }
- }
- }
-
- foreach (string FilePath in FilePaths)
- {
- while (File.Exists(FilePath))
- {
- try
- {
- File.Delete(FilePath);
- }
- catch (Exception)
- {
- await Task.Delay(5000);
- }
- }
- }
- }
-
-
-
- ///
- /// Runs a long non-blocking delay, a work-around for Task.Delay only supporting Int32
- ///
- /// A timespan of how long the delay should last
- /// A cancellation token source to cancel the action
- ///
- internal static async Task LongDelay(TimeSpan delay, CancellationTokenSource token)
- {
- var st = new Stopwatch();
- st.Start();
- while (true && !token.IsCancellationRequested)
- {
- var remaining = (delay - st.Elapsed).TotalMilliseconds;
- if (remaining <= 0)
- break;
- if (remaining > Int16.MaxValue)
- remaining = Int16.MaxValue;
- await Task.Delay(TimeSpan.FromMilliseconds(remaining), token.Token);
- }
- }
-
-
-
- ///
- /// Create a scheduled task
- ///
- /// The task to run
- /// The time to run the task
- /// Any custom data you wish to provide.
- /// An unique identifier of the task
-
- public static string CreateScheduledTask(this Task task, DateTime runTime, object? customData = null)
- {
- string UID = Guid.NewGuid().ToString();
- CancellationTokenSource CancellationToken = new CancellationTokenSource();
-
- if (Math.Ceiling(runTime.GetTimespanUntil().TotalMilliseconds) < 0)
- runTime = DateTime.UtcNow.AddSeconds(1);
-
- _ = LongDelay(runTime.GetTimespanUntil(), CancellationToken).ContinueWith(x =>
- {
- lock (RegisteredScheduledTasks)
- {
- RegisteredScheduledTasks.Remove(UID);
- }
-
- _logger?.LogDebug("Running scheduled task with UID '{UID}'", UID, runTime.GetTimespanUntil().GetHumanReadable());
-
- if (x.IsCompletedSuccessfully)
- task.Start();
- });
-
- lock (RegisteredScheduledTasks)
- {
- _logger?.LogDebug("Creating scheduled task with UID '{UID}' running in {RunTime}", UID, runTime.GetTimespanUntil().GetHumanReadable());
-
- RegisteredScheduledTasks.Add(UID, new ScheduledTask
- {
- Uid = UID,
- RunTime = runTime,
- TokenSource = CancellationToken,
- CustomData = customData,
- });
- }
- return UID;
- }
-
-
-
- ///
- /// Deletes a scheduled task
- ///
- /// The task's unique identifier
- /// Throws if the task hasn't been found or if an internal error occurred
- public static void DeleteScheduledTask(string UID)
- {
- if (!RegisteredScheduledTasks.ContainsKey(UID))
- throw new KeyNotFoundException($"No scheduled task has been found with UID '{UID}'");
-
- if (RegisteredScheduledTasks[UID].TokenSource is null)
- throw new Exception($"Internal: There is no token source registered the specified task.");
-
- _logger?.LogDebug("Deleting scheduled task with UID '{UID}'", UID);
-
- lock (RegisteredScheduledTasks)
- {
- RegisteredScheduledTasks[UID].TokenSource?.Cancel();
- RegisteredScheduledTasks.Remove(UID);
- }
- return;
- }
-
-
-
- ///
- /// Gets a list of all registered tasks
- ///
- /// A list of all registered tasks
- public static IReadOnlyList? GetScheduledTasks()
- => RegisteredScheduledTasks.Select(x => x.Value).ToList().AsReadOnly();
-
- ///
- /// Gets a specific task
- ///
- /// The unique identifier of what task to get
- /// The task
- /// Throws if the task has not been found
- public static ScheduledTask GetScheduledTask(string UID)
- => RegisteredScheduledTasks[UID];
-
-
-
- ///
- /// Compute the SHA256-Hash for the given string
- ///
- ///
- ///
- public static string ComputeSHA256Hash(string str)
- {
- using SHA256 _SHA256 = SHA256.Create();
- return BitConverter.ToString(_SHA256.ComputeHash(Encoding.ASCII.GetBytes(str))).Replace("-", "").ToLowerInvariant();
- }
-
-
-
- ///
- /// Compute the SHA256-Hash for a given file
- ///
- ///
- ///
- public static string ComputeSHA256Hash(FileInfo filePath)
- {
- using SHA256 _SHA256 = SHA256.Create();
- using FileStream fileStream = filePath.OpenRead();
- return BitConverter.ToString(_SHA256.ComputeHash(fileStream)).Replace("-", "").ToLowerInvariant();
- }
-
-
-
- ///
- /// Get a timespan from now to the given time. Negative on values in the past.
- ///
- ///
- ///
- public static TimeSpan GetTimespanUntil(this DateTime until) =>
- (until.ToUniversalTime() - DateTime.UtcNow);
-
-
-
- ///
- ///
- ///
- ///
- ///
- public static TimeSpan GetTimespanUntil(this DateTimeOffset until) =>
- (until.ToUniversalTime() - DateTime.UtcNow);
-
-
- ///
- /// Get the total seconds until a given DateTime. Negative on values in the past.
- ///
- ///
- ///
- public static long GetTotalSecondsUntil(this DateTime until) =>
- ((long)Math.Ceiling((until.ToUniversalTime() - DateTime.UtcNow).TotalSeconds));
-
-
-
- ///
- ///
- ///
- ///
- ///
- public static long GetTotalSecondsUntil(this DateTimeOffset until) =>
- ((long)Math.Ceiling((until.ToUniversalTime() - DateTime.UtcNow).TotalSeconds));
-
-
-
- ///
- /// Get a timespan from now to the given time. Negative on values in the future.
- ///
- ///
- ///
- public static TimeSpan GetTimespanSince(this DateTime until) =>
- (DateTime.UtcNow - until.ToUniversalTime());
-
-
-
- ///
- ///
- ///
- ///
- ///
- public static TimeSpan GetTimespanSince(this DateTimeOffset until) =>
- (DateTime.UtcNow - until.ToUniversalTime());
-
-
-
- ///
- /// Get the total seconds since a given DateTime. Negative on values in the future.
- ///
- ///
- ///
- public static long GetTotalSecondsSince(this DateTime until) =>
- ((long)Math.Ceiling((DateTime.UtcNow - until.ToUniversalTime()).TotalSeconds));
-
-
-
- ///
- ///
- ///
- ///
- ///
- public static long GetTotalSecondsSince(this DateTimeOffset until) =>
- ((long)Math.Ceiling((DateTime.UtcNow - until.ToUniversalTime()).TotalSeconds));
-
-
-
- ///
- /// Get a short human readable string for the given amount of seconds
- ///
- ///
- ///
- ///
- public static string GetShortHumanReadable(this int seconds, TimeFormat timeFormat = TimeFormat.DAYS) =>
- TimeSpan.FromSeconds(seconds).GetShortTimeFormat(timeFormat);
-
-
-
- ///
- ///
- ///
- ///
- ///
- ///
- public static string GetShortHumanReadable(this long seconds, TimeFormat timeFormat = TimeFormat.DAYS) =>
- TimeSpan.FromSeconds(seconds).GetShortTimeFormat(timeFormat);
-
- public static string GetShortHumanReadable(this TimeSpan timespan, TimeFormat timeFormat = TimeFormat.DAYS) =>
- timespan.GetShortTimeFormat(timeFormat);
-
-
-
- ///
- /// Get a human readable string for the given amount of time.
- ///
- ///
- ///
- ///
- public static string GetHumanReadable(this int seconds, TimeFormat timeFormat = TimeFormat.DAYS, HumanReadableTimeFormatConfig? config = null) =>
- TimeSpan.FromSeconds(seconds).GetTimeFormat(timeFormat,config);
-
-
-
- ///
- public static string GetHumanReadable(this long seconds, TimeFormat timeFormat = TimeFormat.DAYS, HumanReadableTimeFormatConfig? config = null) =>
- TimeSpan.FromSeconds(seconds).GetTimeFormat(timeFormat, config);
-
- ///
- public static string GetHumanReadable(this TimeSpan timeSpan, TimeFormat timeFormat = TimeFormat.DAYS, HumanReadableTimeFormatConfig? config = null) =>
- timeSpan.GetTimeFormat(timeFormat, config);
-
-
-
- ///
- /// Check if a string contains only digits
- ///
- ///
- ///
- public static bool IsDigitsOnly(this string str)
- {
- foreach (char c in str)
- {
- if (c is < '0' or > '9')
- return false;
- }
-
- return true;
- }
-
-
-
- ///
- /// Get all digits from a string
- ///
- ///
- ///
- public static string GetAllDigits(this string str) =>
- new(str.Where(Char.IsDigit).ToArray());
-
-
-
- ///
- /// Get country flag emoji based on Iso Country Code
- ///
- ///
- ///
- public static string IsoCountryCodeToFlagEmoji(this string country)
- {
- return string.Concat(country.ToUpper().Select(x => char.ConvertFromUtf32(x + 0x1F1A5)));
- }
-
-
- ///
- /// Get closest Color to given Color
- ///
- ///
- ///
- ///
- public static Color GetClosestColor(List colorArray, Color baseColor)
- {
- var colors = colorArray.Select(x => new { Value = x, Diff = Internal.GetDiff(x, baseColor) }).ToList();
- var min = colors.Min(x => x.Diff);
- return colors.Find(x => x.Diff == min).Value;
- }
-
-
- ///
- /// Convert Hex to Color
- ///
- /// The converted color
- public static Color ToColor(this string str)
- {
- return ColorTranslator.FromHtml(str);
- }
-
-
- ///
- /// Convert RGB Value to Hex
- ///
- /// Red
- /// Green
- /// Blue
- /// A string that represents the color in hex (e.g. 255, 0, 0 -> #FF0000)
- public static string ToHex(int R, int G, int B)
- {
- return "#" + R.ToString("X2") + G.ToString("X2") + B.ToString("X2");
- }
-
-
-
- ///
- /// Calculates the percentage of the given 2 values.
- ///
- /// The current value.
- /// The maximum value.
- /// The percentage.
- ///
- public static int CalculatePercentage(double current, double max)
- {
- if (max == 0)
- throw new ArgumentException("Max cannot be zero.");
-
- double percentage = (current / max) * 100;
- return Convert.ToInt32(percentage);
- }
-
-
-
- ///
- /// Shorten a string to the given length
- ///
- ///
- ///
- ///
- public static string Truncate(this string value, int maxLength)
- {
- if (string.IsNullOrEmpty(value))
- return value;
- return value.Length <= maxLength ? value : value[..maxLength];
- }
-
-
-
- ///
- /// Shorten a string to the given length and add ".." at the end
- ///
- ///
- ///
- ///
- public static string TruncateWithIndication(this string value, int maxLength)
- {
- if (string.IsNullOrEmpty(value))
- return value;
-
- return value.Length <= maxLength ? value : $"{value[..(maxLength - 2)]}..";
- }
-
-
-
- ///
- /// Remove unsupported characters from string to generate a valid filename
- ///
- /// The string with potentionally unwanted characters
- /// The character the unwanted characters get replaced with (default: _)
- /// A valid filename
- public static string MakeValidFileName(this string name, char replace_char = '_')
- {
- string invalidChars = System.Text.RegularExpressions.Regex.Escape(new string(System.IO.Path.GetInvalidFileNameChars()));
- string invalidRegStr = string.Format(@"([{0}]*\.+$)|([{0}]+)", invalidChars);
-
- return System.Text.RegularExpressions.Regex.Replace(name, invalidRegStr, replace_char.ToString()).Replace('&', replace_char);
- }
-
-
-
- ///
- /// Changes the first letter to upper.
- ///
- /// The string to modify.
- /// The string with the first letter changed to upper.
- public static string FirstLetterToUpper(this string str)
- {
- return $"{str.First().ToString().ToUpper()}{str.Remove(0, 1)}";
- }
}
\ No newline at end of file