From 235d787a6ed2105d07f9ebb298ad3874d6d44840 Mon Sep 17 00:00:00 2001
From: Toastie <toastie@toastiet0ast.com>
Date: Tue, 7 Jan 2025 15:58:34 +1300
Subject: [PATCH] Replaced some utility methods with library methods

---
 EllieHub/Common/Utilities.cs                  | 26 +++++++++--------
 .../Services/Abstractions/FfmpegResolver.cs   | 14 +++++----
 .../AppConfig/Services/AppConfigManager.cs    |  5 ++--
 .../AppConfig/Services/FfmpegLinuxResolver.cs | 23 ++++++++-------
 .../AppConfig/Services/FfmpegMacResolver.cs   | 23 ++++++++-------
 .../Services/FfmpegWindowsResolver.cs         | 29 ++++++++++---------
 .../AppConfig/Services/YtdlpResolver.cs       | 23 ++++++++-------
 .../AppWindow/Views/Windows/AppView.axaml.cs  | 22 +++++++++++---
 .../ViewModels/BotConfigViewModel.cs          |  4 +--
 .../Common/ViewModels/UriInputBarViewModel.cs |  5 ++--
 .../Features/Home/Services/AppResolver.cs     | 29 +++++++++++++------
 11 files changed, 119 insertions(+), 84 deletions(-)

diff --git a/EllieHub/Common/Utilities.cs b/EllieHub/Common/Utilities.cs
index eb2bc3c..e79ba96 100644
--- a/EllieHub/Common/Utilities.cs
+++ b/EllieHub/Common/Utilities.cs
@@ -1,5 +1,7 @@
 using Avalonia.Platform;
 using SkiaSharp;
+using System.ComponentModel;
+using System.Diagnostics;
 
 namespace EllieHub.Common;
 
@@ -9,29 +11,29 @@ namespace EllieHub.Common;
 internal static class Utilities
 {
     /// <summary>
-    /// Loads an image embedded with this application.
+    /// Loads an image embeded with this application.
     /// </summary>
     /// <param name="uri">An uri that starts with "avares://"</param>
     /// <remarks>Valid uris must start with "avares://".</remarks>
-    /// <returns>The embedded image or the default bot avatar placeholder.</returns>
-    /// <exception cref="FileNotFoundException">Occurs when the embedded resource does not exist.</exception>
-    public static SKBitmap LoadEmbeddedImage(string? uri = default)
+    /// <returns>The embeded image or the default bot avatar placeholder.</returns>
+    /// <exception cref="FileNotFoundException">Occurs when the embeded resource does not exist.</exception>
+    public static SKBitmap LoadEmbededImage(string? uri = default)
     {
         return (string.IsNullOrWhiteSpace(uri) || !uri.StartsWith("avares://", StringComparison.Ordinal))
-            ? SKBitmap.Decode(AssetLoader.Open(new(AppConstants.BotAvatarUri)))
-            : SKBitmap.Decode(AssetLoader.Open(new(uri)));
+            ? SKBitmap.Decode(AssetLoader.Open(new Uri(AppConstants.BotAvatarUri)))
+            : SKBitmap.Decode(AssetLoader.Open(new Uri(uri)));
     }
 
     /// <summary>
     /// Loads the image at the specified location or the bot avatar placeholder if it was not found.
     /// </summary>
-    /// <param name="imagePath">The absolute path to the image file or <see langword="null"/> to get the avatar placeholder.</param>
-    /// <remarks>This fallsback to <see cref="LoadEmbeddedImage"/> if <paramref name="imagePath"/> doesn't point to a valid image file.</remarks>
+    /// <param name="uri">The absolute path to the image file or <see langword="null"/> to get the avatar placeholder.</param>
+    /// <remarks>This fallsback to <see cref="LoadEmbededImage(string?)"/> if <paramref name="uri"/> doesn't point to a valid image file.</remarks>
     /// <returns>The requested image or the default bot avatar placeholder.</returns>
-    public static SKBitmap LoadLocalImage(string? imagePath)
+    public static SKBitmap LoadLocalImage(string? uri = default)
     {
-        return (File.Exists(imagePath))
-            ? SKBitmap.Decode(imagePath)
-            : LoadEmbeddedImage(imagePath);
+        return (File.Exists(uri))
+            ? SKBitmap.Decode(uri)
+            : LoadEmbededImage(uri);
     }
 }
\ No newline at end of file
diff --git a/EllieHub/Features/AppConfig/Services/Abstractions/FfmpegResolver.cs b/EllieHub/Features/AppConfig/Services/Abstractions/FfmpegResolver.cs
index 4760834..4fa6f9e 100644
--- a/EllieHub/Features/AppConfig/Services/Abstractions/FfmpegResolver.cs
+++ b/EllieHub/Features/AppConfig/Services/Abstractions/FfmpegResolver.cs
@@ -1,3 +1,5 @@
+using Toastie.Utilities;
+
 namespace EllieHub.Features.AppConfig.Services.Abstractions;
 
 /// <summary>
@@ -22,7 +24,7 @@ public abstract class FfmpegResolver : IFfmpegResolver
     public virtual async ValueTask<bool?> CanUpdateAsync(CancellationToken cToken = default)
     {
         // Check where ffmpeg is referenced.
-        using var whereProcess = Utilities.StartProcess(_programVerifier, FfmpegProcessName);
+        using var whereProcess = ToastieUtilities.StartProcess(_programVerifier, FfmpegProcessName, true);
         var installationPath = await whereProcess.StandardOutput.ReadToEndAsync(cToken);
 
         // If ffmpeg is present but not managed by us, just report it is installed.
@@ -32,7 +34,7 @@ public abstract class FfmpegResolver : IFfmpegResolver
         var currentVer = await GetCurrentVersionAsync(cToken);
 
         // If ffmpeg or ffprobe are absent, a reinstall needs to be performed.
-        if (currentVer is null || !await Utilities.ProgramExistsAsync("ffprobe", cToken))
+        if (currentVer is null || !ToastieUtilities.ProgramExists("ffprobe"))
             return null;
 
         var latestVer = await GetLatestVersionAsync(cToken);
@@ -44,20 +46,20 @@ public abstract class FfmpegResolver : IFfmpegResolver
     public virtual async ValueTask<string?> GetCurrentVersionAsync(CancellationToken cToken = default)
     {
         // If ffmpeg is not accessible from the shell...
-        if (!await Utilities.ProgramExistsAsync(FfmpegProcessName, cToken))
+        if (!ToastieUtilities.ProgramExists(FfmpegProcessName))
         {
             // And doesn't exist in the dependencies folder,
             // report that ffmpeg is not installed.
-            if (!File.Exists(Path.Combine(AppStatics.AppDepsUri, FileName)))
+            if (!File.Exists(Path.Join(AppStatics.AppDepsUri, FileName)))
                 return null;
 
             // Else, add the dependencies directory to the PATH envar,
             // then try again.
-            Utilities.AddPathToPATHEnvar(AppStatics.AppDepsUri);
+            ToastieUtilities.AddPathToPATHEnvar(AppStatics.AppDepsUri);
             return await GetCurrentVersionAsync(cToken);
         }
 
-        using var ffmpeg = Utilities.StartProcess(FfmpegProcessName, "-version");
+        using var ffmpeg = ToastieUtilities.StartProcess(FfmpegProcessName, "-version", true);
         var match = AppStatics.FfmpegVersionRegex.Match(await ffmpeg.StandardOutput.ReadLineAsync(cToken) ?? string.Empty);
 
         return match.Groups[1].Value;
diff --git a/EllieHub/Features/AppConfig/Services/AppConfigManager.cs b/EllieHub/Features/AppConfig/Services/AppConfigManager.cs
index 3c72c77..084d322 100644
--- a/EllieHub/Features/AppConfig/Services/AppConfigManager.cs
+++ b/EllieHub/Features/AppConfig/Services/AppConfigManager.cs
@@ -1,3 +1,4 @@
+using Toastie.Utilities;
 using EllieHub.Features.AppConfig.Models;
 using EllieHub.Features.AppConfig.Services.Abstractions;
 using EllieHub.Features.AppWindow.Models;
@@ -33,7 +34,7 @@ public sealed class AppConfigManager : IAppConfigManager
         var newId = CreateNewId();
         var newPosition = _appConfig.BotEntries.Count is 0 ? 0 : _appConfig.BotEntries.Values.Max(x => x.Position) + 1;
         var newBotName = "NewBot_" + newPosition;
-        var newEntry = new BotInstanceInfo(newBotName, Path.Combine(_appConfig.BotsDirectoryUri, newBotName), newPosition);
+        var newEntry = new BotInstanceInfo(newBotName, Path.Join(_appConfig.BotsDirectoryUri, newBotName), newPosition);
 
         if (!_appConfig.BotEntries.TryAdd(newId, newEntry))
             throw new InvalidOperationException($"Could not create a new bot entry with Id {newId}.");
@@ -49,7 +50,7 @@ public sealed class AppConfigManager : IAppConfigManager
         if (!_appConfig.BotEntries.TryRemove(id, out var removedEntry))
             return null;
 
-        Utilities.TryDeleteDirectory(removedEntry.InstanceDirectoryUri);
+        ToastieUtilities.TryDeleteDirectory(removedEntry.InstanceDirectoryUri);
 
         await SaveAsync(cToken);
 
diff --git a/EllieHub/Features/AppConfig/Services/FfmpegLinuxResolver.cs b/EllieHub/Features/AppConfig/Services/FfmpegLinuxResolver.cs
index 4c6774e..74290b5 100644
--- a/EllieHub/Features/AppConfig/Services/FfmpegLinuxResolver.cs
+++ b/EllieHub/Features/AppConfig/Services/FfmpegLinuxResolver.cs
@@ -1,3 +1,4 @@
+using Toastie.Utilities;
 using EllieHub.Features.AppConfig.Services.Abstractions;
 using System.Runtime.InteropServices;
 using System.Runtime.Versioning;
@@ -64,8 +65,8 @@ public sealed partial class FfmpegLinuxResolver : FfmpegResolver
             if (currentVersion == newVersion)
                 return (currentVersion, null);
 
-            Utilities.TryDeleteFile(Path.Combine(installationUri, FileName));
-            Utilities.TryDeleteFile(Path.Combine(installationUri, "ffprobe"));
+            ToastieUtilities.TryDeleteFile(Path.Join(installationUri, FileName));
+            ToastieUtilities.TryDeleteFile(Path.Join(installationUri, "ffprobe"));
         }
 
         // Install
@@ -77,29 +78,29 @@ public sealed partial class FfmpegLinuxResolver : FfmpegResolver
         await using var downloadStream = await http.GetStreamAsync($"https://johnvansickle.com/ffmpeg/releases/{tarFileName}", cToken);
 
         // Save tar file to the temporary directory.
-        var tarFilePath = Path.Combine(_tempDirectory, tarFileName);
-        var tarExtractDir = Path.Combine(_tempDirectory, $"ffmpeg-{newVersion}-{architecture}64-static");
+        var tarFilePath = Path.Join(_tempDirectory, tarFileName);
+        var tarExtractDir = Path.Join(_tempDirectory, $"ffmpeg-{newVersion}-{architecture}64-static");
         await using (var fileStream = new FileStream(tarFilePath, FileMode.Create))
             await downloadStream.CopyToAsync(fileStream, cToken);
 
         // Extract the tar file.
-        using var extractProcess = Utilities.StartProcess("tar", $"xf \"{tarFilePath}\" --directory=\"{_tempDirectory}\"");
+        using var extractProcess = ToastieUtilities.StartProcess("tar", ["xf", tarFilePath, $"--directory=\"{_tempDirectory}\""]);
         await extractProcess.WaitForExitAsync(cToken);
 
         // Move ffmpeg to the dependencies directory.
-        File.Move(Path.Combine(tarExtractDir, FileName), Path.Combine(installationUri, FileName), true);
-        File.Move(Path.Combine(tarExtractDir, "ffprobe"), Path.Combine(installationUri, "ffprobe"), true);
+        ToastieUtilities.TryMoveFile(Path.Join(tarExtractDir, FileName), Path.Join(installationUri, FileName), true);
+        ToastieUtilities.TryMoveFile(Path.Join(tarExtractDir, "ffprobe"), Path.Join(installationUri, "ffprobe"), true);
 
         // Mark the files as executable.
-        using var chmod = Utilities.StartProcess("chmod", $"+x \"{Path.Combine(installationUri, FileName)}\" \"{Path.Combine(installationUri, "ffprobe")}\"");
+        using var chmod = ToastieUtilities.StartProcess("chmod", ["+x", Path.Join(installationUri, FileName), Path.Join(installationUri, "ffprobe")]);
         await chmod.WaitForExitAsync(cToken);
 
         // Cleanup
-        File.Delete(tarFilePath);
-        Directory.Delete(tarExtractDir, true);
+        ToastieUtilities.TryDeleteFile(tarFilePath);
+        ToastieUtilities.TryDeleteDirectory(tarExtractDir, true);
 
         // Update environment variable
-        Utilities.AddPathToPathEnvar(installationUri);
+        ToastieUtilities.AddPathToPATHEnvar(installationUri);
 
         _isUpdating = false;
         return (currentVersion, newVersion);
diff --git a/EllieHub/Features/AppConfig/Services/FfmpegMacResolver.cs b/EllieHub/Features/AppConfig/Services/FfmpegMacResolver.cs
index 7d04e2f..baaf149 100644
--- a/EllieHub/Features/AppConfig/Services/FfmpegMacResolver.cs
+++ b/EllieHub/Features/AppConfig/Services/FfmpegMacResolver.cs
@@ -1,3 +1,4 @@
+using Toastie.Utilities;
 using EllieHub.Features.AppConfig.Models.Api.Evermeet;
 using EllieHub.Features.AppConfig.Services.Abstractions;
 using System.IO.Compression;
@@ -15,7 +16,7 @@ public sealed class FfmpegMacResolver : FfmpegResolver
     private const string _apiFfmpegInfoEndpoint = "https://evermeet.cx/ffmpeg/info/ffmpeg/release";
     private const string _apiFfprobeInfoEndpoint = "https://evermeet.cx/ffmpeg/info/ffprobe/release";
     private readonly string _tempDirectory = Path.GetTempPath();
-    private bool _isUpdating = false;
+    private bool _isUpdating;
     private readonly IHttpClientFactory _httpClientFactory;
 
     /// <inheritdoc/>
@@ -57,8 +58,8 @@ public sealed class FfmpegMacResolver : FfmpegResolver
                 return (currentVersion, null);
             }
 
-            Utilities.TryDeleteFile(Path.Combine(installationUri, FileName));
-            Utilities.TryDeleteFile(Path.Combine(installationUri, "ffprobe"));
+            ToastieUtilities.TryDeleteFile(Path.Join(installationUri, FileName));
+            ToastieUtilities.TryDeleteFile(Path.Join(installationUri, "ffprobe"));
         }
 
         // Install
@@ -74,7 +75,7 @@ public sealed class FfmpegMacResolver : FfmpegResolver
         );
 
         // Update environment variable
-        Utilities.AddPathToPathEnvar(installationUri);
+        ToastieUtilities.AddPathToPATHEnvar(installationUri);
 
         _isUpdating = false;
         return (currentVersion, newVersion);
@@ -91,26 +92,26 @@ public sealed class FfmpegMacResolver : FfmpegResolver
         var http = _httpClientFactory.CreateClient();
         var downloadUrl = downloadInfo.Download["zip"].Url;
         var zipFileName = downloadUrl[(downloadUrl.LastIndexOf('/') + 1)..];
-        var zipFilePath = Path.Combine(_tempDirectory, zipFileName);
+        var zipFilePath = Path.Join(_tempDirectory, zipFileName);
 
         // Download the zip file and save it to the temporary directory.
-        using var zipStream = await http.GetStreamAsync(downloadUrl, cToken);
+        await using var zipStream = await http.GetStreamAsync(downloadUrl, cToken);
 
-        using (var fileStream = new FileStream(zipFilePath, FileMode.Create))
+        await using (var fileStream = new FileStream(zipFilePath, FileMode.Create))
             await zipStream.CopyToAsync(fileStream, cToken);
 
         // Extract the zip file.
         ZipFile.ExtractToDirectory(zipFileName, _tempDirectory);
 
         // Move the dependency binary.
-        var finalFileUri = Path.Combine(dependenciesUri, downloadInfo.Name);
-        File.Move(Path.Combine(_tempDirectory, downloadInfo.Name), finalFileUri, true);
+        var finalFileUri = Path.Join(dependenciesUri, downloadInfo.Name);
+        ToastieUtilities.TryMoveFile(Path.Join(_tempDirectory, downloadInfo.Name), finalFileUri, true);
 
         // Mark binary as executable.
-        using var chmod = Utilities.StartProcess("chmod", $"+x \"{finalFileUri}\"");
+        using var chmod = ToastieUtilities.StartProcess("chmod", $"+x \"{finalFileUri}\"");
         await chmod.WaitForExitAsync(cToken);
 
         // Cleanup.
-        File.Delete(zipFilePath);
+        ToastieUtilities.TryDeleteFile(zipFilePath);
     }
 }
\ No newline at end of file
diff --git a/EllieHub/Features/AppConfig/Services/FfmpegWindowsResolver.cs b/EllieHub/Features/AppConfig/Services/FfmpegWindowsResolver.cs
index 40a5f1c..846c8da 100644
--- a/EllieHub/Features/AppConfig/Services/FfmpegWindowsResolver.cs
+++ b/EllieHub/Features/AppConfig/Services/FfmpegWindowsResolver.cs
@@ -1,3 +1,4 @@
+using Toastie.Utilities;
 using EllieHub.Features.AppConfig.Services.Abstractions;
 using System.IO.Compression;
 using System.Runtime.InteropServices;
@@ -13,7 +14,7 @@ namespace EllieHub.Features.AppConfig.Services;
 public sealed class FfmpegWindowsResolver : FfmpegResolver
 {
     private readonly string _tempDirectory = Path.GetTempPath();
-    private bool _isUpdating = false;
+    private bool _isUpdating;
     private readonly IHttpClientFactory _httpClientFactory;
 
     /// <inheritdoc />
@@ -68,9 +69,9 @@ public sealed class FfmpegWindowsResolver : FfmpegResolver
                 return (currentVersion, null);
             }
 
-            Utilities.TryDeleteFile(Path.Combine(installationUri, FileName));
-            Utilities.TryDeleteFile(Path.Combine(installationUri, "ffprobe.exe"));
-            //Utilities.TryDeleteFile(Path.Combine(dependenciesUri, "ffplay.exe"));
+            ToastieUtilities.TryDeleteFile(Path.Join(installationUri, FileName));
+            ToastieUtilities.TryDeleteFile(Path.Join(installationUri, "ffprobe.exe"));
+            //ToastieUtilities.TryDeleteFile(Path.Join(dependenciesUri, "ffplay.exe"));
         }
 
         // Install
@@ -78,12 +79,12 @@ public sealed class FfmpegWindowsResolver : FfmpegResolver
 
         var zipFileName = $"ffmpeg-{newVersion}-full_build.zip";
         var http = _httpClientFactory.CreateClient();
-        using var downloadStream = await http.GetStreamAsync($"https://github.com/GyanD/codexffmpeg/releases/download/{newVersion}/{zipFileName}", cToken);
+        await using var downloadStream = await http.GetStreamAsync($"https://github.com/GyanD/codexffmpeg/releases/download/{newVersion}/{zipFileName}", cToken);
 
         // Save zip file to the temporary directory.
-        var zipFilePath = Path.Combine(_tempDirectory, zipFileName);
-        var zipExtractDir = Path.Combine(_tempDirectory, zipFileName[..^4]);
-        using (var fileStream = new FileStream(zipFilePath, FileMode.Create))
+        var zipFilePath = Path.Join(_tempDirectory, zipFileName);
+        var zipExtractDir = Path.Join(_tempDirectory, zipFileName[..^4]);
+        await using (var fileStream = new FileStream(zipFilePath, FileMode.Create))
             await downloadStream.CopyToAsync(fileStream, cToken);
 
         // Schedule installation to the thread-pool because ffmpeg is pretty
@@ -94,17 +95,17 @@ public sealed class FfmpegWindowsResolver : FfmpegResolver
             ZipFile.ExtractToDirectory(zipFilePath, _tempDirectory);
 
             // Move ffmpeg to the dependencies directory.
-            File.Move(Path.Combine(zipExtractDir, "bin", FileName), Path.Combine(installationUri, FileName), true);
-            File.Move(Path.Combine(zipExtractDir, "bin", "ffprobe.exe"), Path.Combine(installationUri, "ffprobe.exe"), true);
-            //File.Move(Path.Combine(zipExtractDir, "bin", "ffplay.exe"), Path.Combine(dependenciesUri, "ffplay.exe"));
+            ToastieUtilities.TryMoveFile(Path.Join(zipExtractDir, "bin", FileName), Path.Join(installationUri, FileName), true);
+            ToastieUtilities.TryMoveFile(Path.Join(zipExtractDir, "bin", "ffprobe.exe"), Path.Join(installationUri, "ffprobe.exe"), true);
+            //ToastieUtilities.TryMoveFile(Path.Join(zipExtractDir, "bin", "ffplay.exe"), Path.Join(dependenciesUri, "ffplay.exe"));
 
             // Cleanup
-            File.Delete(zipFilePath);
-            Directory.Delete(zipExtractDir, true);
+            ToastieUtilities.TryDeleteFile(zipFilePath);
+            ToastieUtilities.TryDeleteDirectory(zipExtractDir);
         }, cToken);
 
         // Update environment variable
-        Utilities.AddPathToPathEnvar(installationUri);
+        ToastieUtilities.AddPathToPATHEnvar(installationUri);
 
         _isUpdating = false;
         return (currentVersion, newVersion);
diff --git a/EllieHub/Features/AppConfig/Services/YtdlpResolver.cs b/EllieHub/Features/AppConfig/Services/YtdlpResolver.cs
index dc30dae..ee8b40c 100644
--- a/EllieHub/Features/AppConfig/Services/YtdlpResolver.cs
+++ b/EllieHub/Features/AppConfig/Services/YtdlpResolver.cs
@@ -1,3 +1,4 @@
+using Toastie.Utilities;
 using Microsoft.Extensions.Caching.Memory;
 using EllieHub.Features.AppConfig.Services.Abstractions;
 using System.Runtime.InteropServices;
@@ -13,7 +14,7 @@ public sealed class YtdlpResolver : IYtdlpResolver
     private const string _cachedCurrentVersionKey = "currentVersion:yt-dlp";
     private const string _ytdlpProcessName = "yt-dlp";
     private static readonly string _downloadedFileName = GetDownloadFileName();
-    private bool _isUpdating = false;
+    private bool _isUpdating;
     private readonly IHttpClientFactory _httpClientFactory;
     private readonly IMemoryCache _memoryCache;
 
@@ -56,16 +57,16 @@ public sealed class YtdlpResolver : IYtdlpResolver
     public async ValueTask<string?> GetCurrentVersionAsync(CancellationToken cToken = default)
     {
         // If yt-dlp is not accessible from the shell...
-        if (!await Utilities.ProgramExistsAsync(_ytdlpProcessName, cToken))
+        if (!ToastieUtilities.ProgramExists(_ytdlpProcessName))
         {
             // And doesn't exist in the dependencies folder,
             // report that yt-dlp is not installed.
-            if (!File.Exists(Path.Combine(AppStatics.AppDepsUri, FileName)))
+            if (!File.Exists(Path.Join(AppStatics.AppDepsUri, FileName)))
                 return null;
 
             // Else, add the dependencies directory to the PATH envar,
             // then try again.
-            Utilities.AddPathToPathEnvar(AppStatics.AppDepsUri);
+            ToastieUtilities.AddPathToPATHEnvar(AppStatics.AppDepsUri);
             return await GetCurrentVersionAsync(cToken);
         }
 
@@ -73,7 +74,7 @@ public sealed class YtdlpResolver : IYtdlpResolver
         if (_memoryCache.TryGetValue<string>(_cachedCurrentVersionKey, out var currentVersion) && currentVersion is not null)
             return currentVersion;
 
-        using var ytdlp = Utilities.StartProcess(_ytdlpProcessName, "--version");
+        using var ytdlp = ToastieUtilities.StartProcess(_ytdlpProcessName, "--version", true);
 
         var currentProcessVersion = (await ytdlp.StandardOutput.ReadToEndAsync(cToken)).Trim();
         _memoryCache.Set(_cachedCurrentVersionKey, currentProcessVersion, TimeSpan.FromMinutes(1.5));
@@ -116,7 +117,7 @@ public sealed class YtdlpResolver : IYtdlpResolver
                 return (currentVersion, null);
             }
 
-            using var ytdlp = Utilities.StartProcess(_ytdlpProcessName, "-U");
+            using var ytdlp = ToastieUtilities.StartProcess(_ytdlpProcessName, "-U");
             await ytdlp.WaitForExitAsync(cToken);
 
             _isUpdating = false;
@@ -126,19 +127,19 @@ public sealed class YtdlpResolver : IYtdlpResolver
         // Install
         Directory.CreateDirectory(installationUri);
 
-        var finalFilePath = Path.Combine(installationUri, FileName);
+        var finalFilePath = Path.Join(installationUri, FileName);
         var http = _httpClientFactory.CreateClient();
-        using var downloadStream = await http.GetStreamAsync($"https://github.com/yt-dlp/yt-dlp/releases/download/{newVersion}/{_downloadedFileName}", cToken);
-        using (var fileStream = new FileStream(finalFilePath, FileMode.Create))
+        await using var downloadStream = await http.GetStreamAsync($"https://github.com/yt-dlp/yt-dlp/releases/download/{newVersion}/{_downloadedFileName}", cToken);
+        await using (var fileStream = new FileStream(finalFilePath, FileMode.Create))
             await downloadStream.CopyToAsync(fileStream, cToken);
 
         // Update environment variable
-        Utilities.AddPathToPathEnvar(installationUri);
+        ToastieUtilities.AddPathToPATHEnvar(installationUri);
 
         // On Linux and MacOS, we need to mark the file as executable.
         if (Environment.OSVersion.Platform is PlatformID.Unix)
         {
-            using var chmod = Utilities.StartProcess("chmod", $"+x \"{finalFilePath}\"");
+            using var chmod = ToastieUtilities.StartProcess("chmod", ["+x", finalFilePath]);
             await chmod.WaitForExitAsync(cToken);
         }
 
diff --git a/EllieHub/Features/AppWindow/Views/Windows/AppView.axaml.cs b/EllieHub/Features/AppWindow/Views/Windows/AppView.axaml.cs
index f021f89..b379fac 100644
--- a/EllieHub/Features/AppWindow/Views/Windows/AppView.axaml.cs
+++ b/EllieHub/Features/AppWindow/Views/Windows/AppView.axaml.cs
@@ -125,7 +125,7 @@ public partial class AppView : ReactiveWindow<AppViewModel>
         base.Height = _appConfigManager.AppConfig.WindowSize.Height;
         base.Width = _appConfigManager.AppConfig.WindowSize.Width;
 
-        // Set the user prefered theme
+        // Set the user preferred theme
         base.RequestedThemeVariant = _appConfigManager.AppConfig.Theme switch
         {
             ThemeType.Auto => ThemeVariant.Default,
@@ -256,9 +256,23 @@ public partial class AppView : ReactiveWindow<AppViewModel>
 
         _ = new UpdateView().ShowDialog(this);
 
-        await _appResolver.InstallOrUpdateAsync(AppContext.BaseDirectory);
-        _appResolver.LaunchNewVersion();
 
+        try
+        {
+            Console.WriteLine("Downloading new updater...");
+            await _appResolver.InstallOrUpdateAsync(AppContext.BaseDirectory);
+        }
+        catch (Exception ex)
+        {
+            Console.WriteLine(ex);
+        }
+        finally
+        {
+            Console.WriteLine("Launching new updater...");
+            _appResolver.LaunchNewVersion();
+        }
+
+        Console.WriteLine("Finished update, closing down...");
         base.Close();
     }
 
@@ -295,7 +309,7 @@ public partial class AppView : ReactiveWindow<AppViewModel>
     private static BotConfigViewModel GetBotConfigViewModel(Button button, IServiceScopeFactory scopeFactory)
     {
         using var scope = scopeFactory.CreateScope();
-        var botId = (button.Content ?? throw new InvalidOperationException("Bot button has no valid Id."));
+        var botId = button.Content ?? throw new InvalidOperationException("Bot button has no valid Id.");
         var botResolver = scope.ServiceProvider.GetParameterizedService<EllieResolver>(botId);
 
         return scope.ServiceProvider.GetParameterizedService<BotConfigViewModel>(botResolver);
diff --git a/EllieHub/Features/BotConfig/ViewModels/BotConfigViewModel.cs b/EllieHub/Features/BotConfig/ViewModels/BotConfigViewModel.cs
index 0ffbbe0..0d0337f 100644
--- a/EllieHub/Features/BotConfig/ViewModels/BotConfigViewModel.cs
+++ b/EllieHub/Features/BotConfig/ViewModels/BotConfigViewModel.cs
@@ -183,7 +183,7 @@ public class BotConfigViewModel : ViewModelBase<BotConfigView>, IDisposable
             EnableButtons(!File.Exists(Path.Combine(botEntry.InstanceDirectoryUri, Resolver.FileName)), true);
 
         // Dispose when the view is deactivated
-        this.WhenActivated(disposables => Disposable.Create(() => Dispose()).DisposeWith(disposables));
+        this.WhenActivated(disposables => Disposable.Create(Dispose).DisposeWith(disposables));
     }
 
     /// <summary>
@@ -425,7 +425,7 @@ public class BotConfigViewModel : ViewModelBase<BotConfigView>, IDisposable
     /// </summary>
     /// <param name="botResolver">The bot resolver.</param>
     /// <param name="updateBotBar">The update bar.</param>
-    private async static Task LoadUpdateBarAsync(IBotResolver botResolver, DependencyButtonViewModel updateBotBar)
+    private static async Task LoadUpdateBarAsync(IBotResolver botResolver, DependencyButtonViewModel updateBotBar)
     {
         updateBotBar.DependencyName = "Checking...";
         updateBotBar.Status = DependencyStatus.Checking;
diff --git a/EllieHub/Features/Common/ViewModels/UriInputBarViewModel.cs b/EllieHub/Features/Common/ViewModels/UriInputBarViewModel.cs
index a08cc5f..7eccf61 100644
--- a/EllieHub/Features/Common/ViewModels/UriInputBarViewModel.cs
+++ b/EllieHub/Features/Common/ViewModels/UriInputBarViewModel.cs
@@ -1,4 +1,5 @@
 using Avalonia.Platform.Storage;
+using Toastie.Utilities;
 using EllieHub.Features.Abstractions;
 using EllieHub.Features.Common.Models;
 using EllieHub.Features.Common.Views.Controls;
@@ -14,7 +15,7 @@ public class UriInputBarViewModel : ViewModelBase<UriInputBar>
 {
     private static readonly FolderPickerOpenOptions _folderPickerOptions = new();
     private string _lastValidUri = AppStatics.AppDefaultConfigDirectoryUri;
-    private bool _isDirectoryValid = false;
+    private bool _isDirectoryValid;
     private string _currentUri = string.Empty;
     private readonly IStorageProvider _storageProvider;
 
@@ -92,5 +93,5 @@ public class UriInputBarViewModel : ViewModelBase<UriInputBar>
     /// <param name="directoryUri">The absolute path to a directory.</param>
     /// <returns><see langword="true"/> if the directory is valid, <see langword="false"/> otherwise.</returns>
     private bool IsValidDirectory(string directoryUri)
-        => Directory.Exists(directoryUri) && Utilities.CanWriteTo(directoryUri);
+        => Directory.Exists(directoryUri) && ToastieUtilities.HasWritePermissionAt(directoryUri);
 }
\ No newline at end of file
diff --git a/EllieHub/Features/Home/Services/AppResolver.cs b/EllieHub/Features/Home/Services/AppResolver.cs
index 20a46bb..950dbc5 100644
--- a/EllieHub/Features/Home/Services/AppResolver.cs
+++ b/EllieHub/Features/Home/Services/AppResolver.cs
@@ -43,7 +43,7 @@ public sealed class AppResolver : IAppResolver
         _httpClientFactory = httpClientFactory;
         _memoryCache = memoryCache;
         FileName = OperatingSystem.IsWindows() ? "EllieHub.exe" : "EllieHub";
-        BinaryUri = Path.Combine(AppContext.BaseDirectory, FileName);
+        BinaryUri = Path.Join(AppContext.BaseDirectory, FileName);
     }
 
     /// <inheritdoc/>
@@ -117,18 +117,18 @@ public sealed class AppResolver : IAppResolver
             return (currentVersion, null);
 
         var http = _httpClientFactory.CreateClient();   // Do not initialize a ToastielabClient here, it returns 302 with no data
-        var appTempLocation = Path.Combine(_tempDirectory, _downloadedFileName[.._downloadedFileName.LastIndexOf('.')]);
-        var zipTempLocation = Path.Combine(_tempDirectory, _downloadedFileName);
+        var appTempLocation = Path.Join(_tempDirectory, _downloadedFileName[.._downloadedFileName.LastIndexOf('.')]);
+        var zipTempLocation = Path.Join(_tempDirectory, _downloadedFileName);
 
         try
         {
-            using var downloadStream = await http.GetStreamAsync(
+            await using var downloadStream = await http.GetStreamAsync(
                 await GetDownloadUrlAsync(latestVersion, cToken),
                 cToken
             );
 
             // Save the zip file
-            using (var fileStream = new FileStream(zipTempLocation, FileMode.Create))
+            await using (var fileStream = new FileStream(zipTempLocation, FileMode.Create))
                 await downloadStream.CopyToAsync(fileStream, cToken);
 
             // Extract the zip file
@@ -139,19 +139,30 @@ public sealed class AppResolver : IAppResolver
 
             foreach (var newFileUri in newFilesUris)
             {
-                var destinationUri = Path.Combine(AppContext.BaseDirectory, newFileUri[(newFileUri.LastIndexOf(Path.DirectorySeparatorChar) + 1)..]);
+                var destinationUri = Path.Join(AppContext.BaseDirectory, newFileUri[(newFileUri.LastIndexOf(Path.DirectorySeparatorChar) + 1)..]);
 
                 // Rename the original file from "file" to "file_old".
                 if (File.Exists(destinationUri))
-                    File.Move(destinationUri, destinationUri + OldFileSuffix, true);
+                    File.Move(destinationUri, destinationUri + OldFileSuffix, true); // This executes fine
 
-                ToastieUtilities.TryMoveFile(newFileUri, destinationUri, true);
+                // Move the new file to the application's directory.
+                // ...
+                // This is a workaround for really weird bug with applications published as single-file, where
+                // FileNotFoundException: Could not load file or assembly 'System.IO.Pipes, Version=9.0.0.0 [...] 
+                if (Environment.OSVersion.Platform is not PlatformID.Unix)
+                    File.Move(newFileUri, destinationUri, true);
+                else
+                {
+                    // Circumvent this issue on Unix systems: https://github.com/dotnet/runtime/issues/31149
+                    using var moveProcess = ToastieUtilities.StartProcess("mv", [newFileUri, destinationUri]);
+                    await moveProcess.WaitForExitAsync(cToken);
+                }
             }
 
             // Mark the new binary file as executable.
             if (Environment.OSVersion.Platform is PlatformID.Unix)
             {
-                using var chmod = ToastieUtilities.StartProcess("chmod", $"+x \"{BinaryUri}\"");
+                using var chmod = ToastieUtilities.StartProcess("chmod", ["+x", BinaryUri]);
                 await chmod.WaitForExitAsync(cToken);
             }