diff --git a/StabilityMatrix.Avalonia/Assets.cs b/StabilityMatrix.Avalonia/Assets.cs
index caf3cbc5..aec7ea14 100644
--- a/StabilityMatrix.Avalonia/Assets.cs
+++ b/StabilityMatrix.Avalonia/Assets.cs
@@ -3,6 +3,7 @@ using System.IO;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using Avalonia.Platform;
+using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Core.Helper;
namespace StabilityMatrix.Avalonia;
@@ -17,39 +18,18 @@ internal static class Assets
[SupportedOSPlatform("windows")]
[SupportedOSPlatform("linux")]
- public static Uri SevenZipExecutable
- {
- get
- {
- if (Compat.IsWindows)
- {
- return new Uri("avares://StabilityMatrix.Avalonia/Assets/win-x64/7za.exe");
- }
- if (Compat.Platform.HasFlag(PlatformKind.Linux | PlatformKind.X64))
- {
- return new Uri("avares://StabilityMatrix.Avalonia/Assets/linux-x64/7zzs");
- }
- throw new PlatformNotSupportedException();
- }
- }
+ public static AvaloniaResource SevenZipExecutable => Compat.Switch(
+ (PlatformKind.Windows,
+ new AvaloniaResource("avares://StabilityMatrix.Avalonia/Assets/win-x64/7za.exe")),
+ (PlatformKind.Linux | PlatformKind.X64,
+ new AvaloniaResource("avares://StabilityMatrix.Avalonia/Assets/linux-x64/7zzs",
+ UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute)));
[SupportedOSPlatform("windows")]
[SupportedOSPlatform("linux")]
- public static Uri SevenZipLicense
- {
- get
- {
- if (Compat.IsWindows)
- {
- return new Uri("avares://StabilityMatrix.Avalonia/Assets/win-x64/7za - LICENSE.txt");
- }
- if (Compat.Platform.HasFlag(PlatformKind.Linux | PlatformKind.X64))
- {
- return new Uri("avares://StabilityMatrix.Avalonia/Assets/linux-x64/7zzs - LICENSE.txt");
- }
- throw new PlatformNotSupportedException();
- }
- }
+ public static AvaloniaResource SevenZipLicense => Compat.Switch(
+ (PlatformKind.Windows, new AvaloniaResource("avares://StabilityMatrix.Avalonia/Assets/win-x64/7za - LICENSE.txt")),
+ (PlatformKind.Linux | PlatformKind.X64, new AvaloniaResource("avares://StabilityMatrix.Avalonia/Assets/linux-x64/7zzs - LICENSE.txt")));
[SupportedOSPlatform("windows")]
[SupportedOSPlatform("linux")]
diff --git a/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs
index 9137da7f..3d3336e2 100644
--- a/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs
+++ b/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs
@@ -5,6 +5,7 @@ using System.Runtime.Versioning;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Platform;
+using Avalonia.Utilities;
using NLog;
using StabilityMatrix.Core.Exceptions;
using StabilityMatrix.Core.Helper;
@@ -57,11 +58,11 @@ public class UnixPrerequisiteHelper : IPrerequisiteHelper
};
progress?.Report(new ProgressReport(0, message: "Unpacking resources", isIndeterminate: true));
-
+
Directory.CreateDirectory(AssetsDir);
- foreach (var (assetUri, extractTo) in assets)
+ foreach (var (asset, extractDir) in assets)
{
- await Assets.ExtractAsset(assetUri, extractTo);
+ await asset.ExtractTo(extractDir);
}
progress?.Report(new ProgressReport(1, message: "Unpacking resources", isIndeterminate: false));
@@ -112,7 +113,7 @@ public class UnixPrerequisiteHelper : IPrerequisiteHelper
await PythonDir.DeleteAsync(true);
}
progress?.Report(new ProgressReport(0, "Installing Python", isIndeterminate: true));
- await ArchiveHelper.ExtractManaged(downloadPath, PythonDir);
+ await ArchiveHelper.Extract7ZAuto(downloadPath, PythonDir);
// For Linux, move the inner 'python' folder up to the root PythonDir
if (Compat.IsLinux)
diff --git a/StabilityMatrix.Avalonia/Models/AvaloniaResource.cs b/StabilityMatrix.Avalonia/Models/AvaloniaResource.cs
new file mode 100644
index 00000000..25e85c82
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Models/AvaloniaResource.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Threading.Tasks;
+using Avalonia.Platform;
+using StabilityMatrix.Core.Helper;
+
+namespace StabilityMatrix.Avalonia.Models;
+
+[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
+public readonly record struct AvaloniaResource(
+ Uri UriPath,
+ UnixFileMode WriteUnixFileMode = UnixFileMode.None)
+{
+ public string FileName => UriPath.Segments[^1];
+
+ public AvaloniaResource(string uriPath, UnixFileMode writeUnixFileMode = UnixFileMode.None)
+ : this(new Uri(uriPath), writeUnixFileMode)
+ {
+ }
+
+ ///
+ /// Extracts this resource to the output directory.
+ ///
+ public async Task ExtractTo(string outputDir, bool overwrite = true)
+ {
+ var targetPath = Path.Combine(outputDir, FileName);
+ if (File.Exists(targetPath) && !overwrite)
+ {
+ return;
+ }
+ var stream = AssetLoader.Open(UriPath);
+ await using var fileStream = File.Create(targetPath);
+ await stream.CopyToAsync(fileStream);
+ // Write permissions
+ if (!Compat.IsWindows && Compat.IsUnix && WriteUnixFileMode != UnixFileMode.None)
+ {
+ File.SetUnixFileMode(targetPath, WriteUnixFileMode);
+ }
+ }
+}
diff --git a/StabilityMatrix.Core/Helper/ArchiveHelper.cs b/StabilityMatrix.Core/Helper/ArchiveHelper.cs
index 5f41ac79..d8612f8c 100644
--- a/StabilityMatrix.Core/Helper/ArchiveHelper.cs
+++ b/StabilityMatrix.Core/Helper/ArchiveHelper.cs
@@ -122,6 +122,58 @@ public static partial class ArchiveHelper
var compressed = ulong.Parse(matches[1].Value);
return new ArchiveInfo(size, compressed);
}
+
+ ///
+ /// Extracts a zipped tar (i.e. '.tar.gz') archive.
+ /// First extracts the zipped tar, then extracts the tar and removes the tar.
+ ///
+ ///
+ ///
+ ///
+ public static async Task Extract7ZTar(string archivePath, string extractDirectory)
+ {
+ if (!archivePath.EndsWith(".tar.gz"))
+ {
+ throw new ArgumentException("Archive must be a zipped tar.");
+ }
+ // Extract the tar.gz to tar
+ await Extract7Z(archivePath, extractDirectory);
+
+ // Extract the tar
+ var tarPath = Path.Combine(extractDirectory, Path.GetFileNameWithoutExtension(archivePath) + ".tar");
+ if (!File.Exists(tarPath))
+ {
+ throw new FileNotFoundException("Tar file not found.", tarPath);
+ }
+
+ try
+ {
+ return await Extract7Z(tarPath, extractDirectory);
+ }
+ finally
+ {
+ // Remove the tar
+ if (File.Exists(tarPath))
+ {
+ File.Delete(tarPath);
+ }
+ }
+ }
+
+ ///
+ /// Extracts with auto handling of tar.gz files.
+ ///
+ public static async Task Extract7ZAuto(string archivePath, string extractDirectory)
+ {
+ if (archivePath.EndsWith(".tar.gz"))
+ {
+ return await Extract7ZTar(archivePath, extractDirectory);
+ }
+ else
+ {
+ return await Extract7Z(archivePath, extractDirectory);
+ }
+ }
///
/// Extract an archive to the output directory.
@@ -212,6 +264,8 @@ public static partial class ArchiveHelper
var entry = reader.Entry;
var outputPath = Path.Combine(outputDirectory, entry.Key);
+ Logger.Debug($"Extracting key: {entry.Key.ToRepr()}");
+
if (entry.IsDirectory)
{
if (!Directory.Exists(outputPath))
@@ -255,7 +309,7 @@ public static partial class ArchiveHelper
// Delete path if exists
File.Delete(outputPath);
File.CreateSymbolicLink(outputPath, entry.LinkTarget);
- return;
+ continue;
}
catch (IOException e)
{
diff --git a/StabilityMatrix.Core/Helper/Compat.cs b/StabilityMatrix.Core/Helper/Compat.cs
index ad8bf5ad..f30f21a7 100644
--- a/StabilityMatrix.Core/Helper/Compat.cs
+++ b/StabilityMatrix.Core/Helper/Compat.cs
@@ -2,6 +2,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
+using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Models.FileInterfaces;
namespace StabilityMatrix.Core.Helper;
@@ -104,6 +105,27 @@ public static class Compat
AppData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
AppDataHome = AppData + AppName;
}
+
+ ///
+ /// Generic function to return different objects based on platform flags.
+ /// Parameters are checked in sequence with Compat.Platform.HasFlag,
+ /// the first match is returned.
+ ///
+ /// Thrown when no targets match
+ public static T Switch(params (PlatformKind platform, T target)[] targets)
+ {
+ foreach (var (platform, target) in targets)
+ {
+ if (Platform.HasFlag(platform))
+ {
+ return target;
+ }
+ }
+
+ throw new PlatformNotSupportedException(
+ $"Platform {Platform.ToString()} is not in supported targets: " +
+ $"{string.Join(", ", targets.Select(t => t.platform.ToString()))}");
+ }
///
/// Get the current application executable name.