mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-04-24 07:32:56 +00:00
updated decompression support (#26)
- update decompression functions to use native libs - add support for 7zip and tar.xz archives with SharpCompress - add .7z and tar.xz artifacts to releases - remove linux non-appimage builds from PRs Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/26
This commit is contained in:
@@ -46,6 +46,7 @@
|
||||
<PackageReference Include="Avalonia.Diagnostics" Condition="'$(Configuration)'=='Debug'" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" />
|
||||
<PackageReference Include="Avalonia.Markup.Xaml.Loader" />
|
||||
<PackageReference Include="SharpCompress" />
|
||||
<PackageReference Include="Svg.Controls.Avalonia" />
|
||||
<PackageReference Include="Svg.Controls.Skia.Avalonia" />
|
||||
<PackageReference Include="DynamicData" />
|
||||
@@ -68,7 +69,6 @@
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
|
||||
<PackageReference Include="SPB" />
|
||||
<PackageReference Include="SharpZipLib" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace Ryujinx.Ava.Systems
|
||||
}
|
||||
|
||||
// If build URL not found, assume no new update is available.
|
||||
if (_versionResponse.ArtifactUrl is null or "")
|
||||
if (string.IsNullOrEmpty(_versionResponse.ArtifactUrl))
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
{
|
||||
@@ -123,6 +123,8 @@ namespace Ryujinx.Ava.Systems
|
||||
return default;
|
||||
}
|
||||
|
||||
_connectionCount = (int)_versionResponse.MaxConcurrency;
|
||||
|
||||
return (currentVersion, newVersion);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Gommon;
|
||||
using ICSharpCode.SharpZipLib.GZip;
|
||||
using ICSharpCode.SharpZipLib.Tar;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.Utilities;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Helper;
|
||||
using Ryujinx.Common.Logging;
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Compressors.Xz;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Formats.Tar;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
@@ -21,7 +22,6 @@ using System.Net.NetworkInformation;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -32,8 +32,8 @@ namespace Ryujinx.Ava.Systems
|
||||
private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
||||
private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish");
|
||||
private const int ConnectionCount = 4;
|
||||
|
||||
private static int _connectionCount = 1;
|
||||
private static long _buildSize;
|
||||
private static bool _updateSuccessful;
|
||||
private static bool _running;
|
||||
@@ -73,27 +73,6 @@ namespace Ryujinx.Ava.Systems
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch build size information to learn chunk sizes.
|
||||
using HttpClient buildSizeClient = ConstructHttpClient();
|
||||
try
|
||||
{
|
||||
buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0");
|
||||
|
||||
// Forgejo instance is located in Ukraine. Connection times will vary across the world.
|
||||
buildSizeClient.Timeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_versionResponse.ArtifactUrl), HttpCompletionOption.ResponseHeadersRead);
|
||||
|
||||
_buildSize = message.Content.Headers.ContentRange.Length.Value;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, ex.Message);
|
||||
Logger.Warning?.Print(LogClass.Application, "Couldn't determine build size for update, using single-threaded updater");
|
||||
|
||||
_buildSize = -1;
|
||||
}
|
||||
|
||||
await Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
string newVersionString = ReleaseInformation.IsCanaryBuild
|
||||
@@ -143,6 +122,14 @@ namespace Ryujinx.Ava.Systems
|
||||
|
||||
Directory.CreateDirectory(_updateDir);
|
||||
|
||||
// If we get a .zip url switch it to the preferred .7z file instead
|
||||
// The update server still returns the .zip url by default for legacy support
|
||||
downloadUrl = downloadUrl.Replace(".zip", ".7z");
|
||||
|
||||
// If we get a .tar.gz url switch it to the preferred .tar.xz file instead
|
||||
// The update server still returns the .tar.gz url by default for legacy support
|
||||
downloadUrl = downloadUrl.Replace(".tar.gz", ".tar.xz");
|
||||
|
||||
string updateFile = Path.Combine(_updateDir, "update.bin");
|
||||
|
||||
TaskDialog taskDialog = new()
|
||||
@@ -153,6 +140,27 @@ namespace Ryujinx.Ava.Systems
|
||||
ShowProgressBar = true,
|
||||
XamlRoot = RyujinxApp.MainWindow,
|
||||
};
|
||||
|
||||
// Fetch build size information to learn chunk sizes.
|
||||
using HttpClient buildSizeClient = ConstructHttpClient();
|
||||
try
|
||||
{
|
||||
buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0");
|
||||
|
||||
// Forgejo instance is located in Ukraine. Connection times will vary across the world.
|
||||
buildSizeClient.Timeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_versionResponse.ArtifactUrl), HttpCompletionOption.ResponseHeadersRead);
|
||||
|
||||
_buildSize = message.Content.Headers.ContentRange.Length.Value;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, ex.Message);
|
||||
Logger.Warning?.Print(LogClass.Application, "Couldn't determine build size for update, using single-threaded updater");
|
||||
|
||||
_buildSize = -1;
|
||||
}
|
||||
|
||||
taskDialog.Opened += (s, e) =>
|
||||
{
|
||||
@@ -234,22 +242,22 @@ namespace Ryujinx.Ava.Systems
|
||||
private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
||||
{
|
||||
// Multi-Threaded Updater
|
||||
long chunkSize = _buildSize / ConnectionCount;
|
||||
long remainderChunk = _buildSize % ConnectionCount;
|
||||
long chunkSize = _buildSize / _connectionCount;
|
||||
long remainderChunk = _buildSize % _connectionCount;
|
||||
|
||||
int completedRequests = 0;
|
||||
int totalProgressPercentage = 0;
|
||||
int[] progressPercentage = new int[ConnectionCount];
|
||||
int[] progressPercentage = new int[_connectionCount];
|
||||
|
||||
List<byte[]> list = new(ConnectionCount);
|
||||
List<WebClient> webClients = new(ConnectionCount);
|
||||
List<byte[]> list = new(_connectionCount);
|
||||
List<WebClient> webClients = new(_connectionCount);
|
||||
|
||||
for (int i = 0; i < ConnectionCount; i++)
|
||||
for (int i = 0; i < _connectionCount; i++)
|
||||
{
|
||||
list.Add([]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < ConnectionCount; i++)
|
||||
for (int i = 0; i < _connectionCount; i++)
|
||||
{
|
||||
#pragma warning disable SYSLIB0014
|
||||
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
|
||||
@@ -258,7 +266,7 @@ namespace Ryujinx.Ava.Systems
|
||||
|
||||
webClients.Add(client);
|
||||
|
||||
if (i == ConnectionCount - 1)
|
||||
if (i == _connectionCount - 1)
|
||||
{
|
||||
client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}");
|
||||
}
|
||||
@@ -275,7 +283,7 @@ namespace Ryujinx.Ava.Systems
|
||||
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
|
||||
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
|
||||
|
||||
taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal);
|
||||
taskDialog.SetProgressBarState(totalProgressPercentage / _connectionCount, TaskDialogProgressState.Normal);
|
||||
};
|
||||
|
||||
client.DownloadDataCompleted += (_, args) =>
|
||||
@@ -294,10 +302,10 @@ namespace Ryujinx.Ava.Systems
|
||||
list[index] = args.Result;
|
||||
Interlocked.Increment(ref completedRequests);
|
||||
|
||||
if (Equals(completedRequests, ConnectionCount))
|
||||
if (Equals(completedRequests, _connectionCount))
|
||||
{
|
||||
byte[] mergedFileBytes = new byte[_buildSize];
|
||||
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++)
|
||||
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < _connectionCount; connectionIndex++)
|
||||
{
|
||||
Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length);
|
||||
destinationOffset += list[connectionIndex].Length;
|
||||
@@ -402,73 +410,33 @@ namespace Ryujinx.Ava.Systems
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
[SupportedOSPlatform("macos")]
|
||||
private static void ExtractTarGzipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
|
||||
private static void ExtractTarGzipFile(string archivePath, string outputDirectoryPath)
|
||||
{
|
||||
using FileStream inStream = File.OpenRead(archivePath);
|
||||
using GZipInputStream gzipStream = new(inStream);
|
||||
using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
|
||||
|
||||
TarEntry tarEntry;
|
||||
|
||||
while ((tarEntry = tarStream.GetNextEntry()) is not null)
|
||||
{
|
||||
if (tarEntry.IsDirectory)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string outPath = Path.Combine(outputDirectoryPath, tarEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using FileStream outStream = File.OpenWrite(outPath);
|
||||
tarStream.CopyEntryContents(outStream);
|
||||
|
||||
File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (tarEntry is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
taskDialog.SetProgressBarState(GetPercentage(tarEntry.Size, inStream.Length), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
using GZipStream gzipStream = new(inStream, CompressionMode.Decompress);
|
||||
|
||||
TarFile.ExtractToDirectory(gzipStream, outputDirectoryPath, true);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
[SupportedOSPlatform("macos")]
|
||||
private static void ExtractTarXzipFile(string archivePath, string outputDirectoryPath)
|
||||
{
|
||||
using FileStream inStream = File.OpenRead(archivePath);
|
||||
using XZStream gzipStream = new(inStream);
|
||||
|
||||
TarFile.ExtractToDirectory(gzipStream, outputDirectoryPath, true);
|
||||
}
|
||||
|
||||
private static void ExtractZipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
|
||||
private static void ExtractZipFile(string archivePath, string outputDirectoryPath)
|
||||
{
|
||||
using Stream inStream = File.OpenRead(archivePath);
|
||||
using ZipFile zipFile = new(inStream);
|
||||
|
||||
double count = 0;
|
||||
foreach (ZipEntry zipEntry in zipFile)
|
||||
{
|
||||
count++;
|
||||
if (zipEntry.IsDirectory)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string outPath = Path.Combine(outputDirectoryPath, zipEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using Stream zipStream = zipFile.GetInputStream(zipEntry);
|
||||
using FileStream outStream = File.OpenWrite(outPath);
|
||||
|
||||
zipStream.CopyTo(outStream);
|
||||
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
ZipFile.ExtractToDirectory(archivePath, outputDirectoryPath);
|
||||
}
|
||||
|
||||
private static void Extract7ZipFile(string archivePath, string outputDirectoryPath)
|
||||
{
|
||||
IArchive archive = ArchiveFactory.OpenArchive(archivePath);
|
||||
archive.WriteToDirectory(outputDirectoryPath);
|
||||
}
|
||||
|
||||
private static void InstallUpdate(TaskDialog taskDialog, string updateFile)
|
||||
@@ -479,16 +447,20 @@ namespace Ryujinx.Ava.Systems
|
||||
|
||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
ExtractTarGzipFile(taskDialog, updateFile, _updateDir);
|
||||
ExtractTarXzipFile(updateFile, _updateDir);
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
ExtractZipFile(taskDialog, updateFile, _updateDir);
|
||||
Extract7ZipFile(updateFile, _updateDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
// The new decompression implementations don't have a way to show progress
|
||||
// so the progressbar is just set to 100% after the decompression is done
|
||||
taskDialog.SetProgressBarState(100, TaskDialogProgressState.Normal);
|
||||
|
||||
// Delete downloaded zip
|
||||
File.Delete(updateFile);
|
||||
|
||||
Reference in New Issue
Block a user