From d901463ffb9fdd92bbfc5b4d4c11b9ade31f9de8 Mon Sep 17 00:00:00 2001 From: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com> Date: Tue, 16 Apr 2024 21:52:55 +0700 Subject: [PATCH 01/18] Refactor part 1 Cache httpClient and remove ConstructHttpClient() --- src/Ryujinx/Modules/Updater/Updater.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Ryujinx/Modules/Updater/Updater.cs b/src/Ryujinx/Modules/Updater/Updater.cs index 9f186f2b38..e5eca0a7a0 100644 --- a/src/Ryujinx/Modules/Updater/Updater.cs +++ b/src/Ryujinx/Modules/Updater/Updater.cs @@ -95,10 +95,9 @@ namespace Ryujinx.Modules // Get latest version number from GitHub API try { - using HttpClient jsonClient = ConstructHttpClient(); string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest"; - string fetchedJson = await jsonClient.GetStringAsync(buildInfoUrl); + string fetchedJson = await httpClient.GetStringAsync(buildInfoUrl); var fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse); _buildVer = fetched.Name; @@ -185,12 +184,11 @@ namespace Ryujinx.Modules } // Fetch build size information to learn chunk sizes. - using HttpClient buildSizeClient = ConstructHttpClient(); try { - buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0"); + httpClient.DefaultRequestHeaders.Add("Range", "bytes=0-0"); - HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_buildUrl), HttpCompletionOption.ResponseHeadersRead); + HttpResponseMessage message = await httpClient.GetAsync(new Uri(_buildUrl), HttpCompletionOption.ResponseHeadersRead); _buildSize = message.Content.Headers.ContentRange.Length.Value; } @@ -221,6 +219,15 @@ namespace Ryujinx.Modules }); } + private static readonly HttpClient httpClient = new HttpClient + { + // Required by GitHub to interact with APIs. + DefaultRequestHeaders = + { + { "User-Agent", "Ryujinx-Updater/1.0.0" } + } + }; + private static HttpClient ConstructHttpClient() { HttpClient result = new(); From 6acac56ef6f8dd579545a842698da0ef9f665af6 Mon Sep 17 00:00:00 2001 From: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com> Date: Tue, 16 Apr 2024 21:53:46 +0700 Subject: [PATCH 02/18] Update Updater.cs --- src/Ryujinx/Modules/Updater/Updater.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Ryujinx/Modules/Updater/Updater.cs b/src/Ryujinx/Modules/Updater/Updater.cs index e5eca0a7a0..a6481ba122 100644 --- a/src/Ryujinx/Modules/Updater/Updater.cs +++ b/src/Ryujinx/Modules/Updater/Updater.cs @@ -228,16 +228,6 @@ namespace Ryujinx.Modules } }; - private static HttpClient ConstructHttpClient() - { - HttpClient result = new(); - - // Required by GitHub to interact with APIs. - result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0"); - - return result; - } - private static async Task UpdateRyujinx(Window parent, string downloadUrl) { _updateSuccessful = false; From 87ea3157871084393ced33fda426dbabc45157e3 Mon Sep 17 00:00:00 2001 From: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com> Date: Tue, 16 Apr 2024 23:02:20 +0700 Subject: [PATCH 03/18] Refactor part 2 Replace `WebClient` with `HttpClient`, plus updating DoUpdateWithSingleThreadWorker and DoUpdateWithMultipleThreads --- src/Ryujinx/Modules/Updater/Updater.cs | 255 ++++++++++++------------- 1 file changed, 126 insertions(+), 129 deletions(-) diff --git a/src/Ryujinx/Modules/Updater/Updater.cs b/src/Ryujinx/Modules/Updater/Updater.cs index a6481ba122..88f02ffa55 100644 --- a/src/Ryujinx/Modules/Updater/Updater.cs +++ b/src/Ryujinx/Modules/Updater/Updater.cs @@ -19,6 +19,7 @@ using System.IO; using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Http.Headers; using System.Net.NetworkInformation; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -186,11 +187,13 @@ namespace Ryujinx.Modules // Fetch build size information to learn chunk sizes. try { - httpClient.DefaultRequestHeaders.Add("Range", "bytes=0-0"); + httpClient.DefaultRequestHeaders.Range = new RangeHeaderValue(0, 0); HttpResponseMessage message = await httpClient.GetAsync(new Uri(_buildUrl), HttpCompletionOption.ResponseHeadersRead); _buildSize = message.Content.Headers.ContentRange.Length.Value; + + httpClient.DefaultRequestHeaders.Remove("Range"); } catch (Exception ex) { @@ -251,11 +254,11 @@ namespace Ryujinx.Modules XamlRoot = parent, }; - taskDialog.Opened += (s, e) => + taskDialog.Opened += async (s, e) => { if (_buildSize >= 0) { - DoUpdateWithMultipleThreads(taskDialog, downloadUrl, updateFile); + await DoUpdateWithMultipleThreads(taskDialog, downloadUrl, updateFile); } else { @@ -335,79 +338,33 @@ namespace Ryujinx.Modules } } - private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile) + private static async Task DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile) { - // Multi-Threaded Updater long chunkSize = _buildSize / ConnectionCount; long remainderChunk = _buildSize % ConnectionCount; int completedRequests = 0; - int totalProgressPercentage = 0; int[] progressPercentage = new int[ConnectionCount]; + List chunkDataList = new List(new byte[ConnectionCount][]); - List list = new(ConnectionCount); - List webClients = new(ConnectionCount); + List downloadTasks = new List(); for (int i = 0; i < ConnectionCount; i++) { - list.Add(Array.Empty()); - } + long rangeStart = i * chunkSize; + long rangeEnd = (i == ConnectionCount - 1) ? (rangeStart + chunkSize + remainderChunk - 1) : (rangeStart + chunkSize - 1); + int index = 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. - using WebClient client = new(); -#pragma warning restore SYSLIB0014 - - webClients.Add(client); - - if (i == ConnectionCount - 1) + downloadTasks.Add(Task.Run(async () => { - client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}"); - } - else - { - client.Headers.Add("Range", $"bytes={chunkSize * i}-{chunkSize * (i + 1) - 1}"); - } + byte[] chunkData = await DownloadFileChunk(downloadUrl, rangeStart, rangeEnd, index, taskDialog, progressPercentage); + chunkDataList[index] = chunkData; - client.DownloadProgressChanged += (_, args) => - { - int index = (int)args.UserState; - - Interlocked.Add(ref totalProgressPercentage, -1 * progressPercentage[index]); - Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage); - Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage); - - taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal); - }; - - client.DownloadDataCompleted += (_, args) => - { - int index = (int)args.UserState; - - if (args.Cancelled) - { - webClients[index].Dispose(); - - taskDialog.Hide(); - - return; - } - - list[index] = args.Result; Interlocked.Increment(ref completedRequests); - - if (Equals(completedRequests, ConnectionCount)) + if (Interlocked.Equals(completedRequests, ConnectionCount)) { - byte[] mergedFileBytes = new byte[_buildSize]; - for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++) - { - Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length); - destinationOffset += list[connectionIndex].Length; - } - - File.WriteAllBytes(updateFile, mergedFileBytes); + byte[] allData = CombineChunks(chunkDataList, _buildSize); + File.WriteAllBytes(updateFile, allData); // On macOS, ensure that we remove the quarantine bit to prevent Gatekeeper from blocking execution. if (OperatingSystem.IsMacOS()) @@ -417,73 +374,108 @@ namespace Ryujinx.Modules xattrProcess.WaitForExit(); } - try + // Ensure that the install update is run on the UI thread. + await Dispatcher.UIThread.InvokeAsync(async () => { - InstallUpdate(taskDialog, updateFile); - } - catch (Exception e) - { - Logger.Warning?.Print(LogClass.Application, e.Message); - Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater."); - - DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); - } + try + { + await InstallUpdate(taskDialog, updateFile); + } + catch (Exception e) + { + Logger.Warning?.Print(LogClass.Application, e.Message); + Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater."); + DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); + } + }); } - }; - - try - { - client.DownloadDataAsync(new Uri(downloadUrl), i); - } - catch (WebException ex) - { - Logger.Warning?.Print(LogClass.Application, ex.Message); - Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater."); - - foreach (WebClient webClient in webClients) - { - webClient.CancelAsync(); - } - - DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); - - return; - } + })); } + + await Task.WhenAll(downloadTasks); } - private static void DoUpdateWithSingleThreadWorker(TaskDialog taskDialog, string downloadUrl, string updateFile) + private static byte[] CombineChunks(List chunks, long totalSize) { - using HttpClient client = new(); - // We do not want to timeout while downloading - client.Timeout = TimeSpan.FromDays(1); - - using HttpResponseMessage response = client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead).Result; - using Stream remoteFileStream = response.Content.ReadAsStreamAsync().Result; - using Stream updateFileStream = File.Open(updateFile, FileMode.Create); - - long totalBytes = response.Content.Headers.ContentLength.Value; - long byteWritten = 0; - - byte[] buffer = new byte[32 * 1024]; - - while (true) + byte[] data = new byte[totalSize]; + long position = 0; + foreach (byte[] chunk in chunks) { - int readSize = remoteFileStream.Read(buffer); + Buffer.BlockCopy(chunk, 0, data, (int)position, chunk.Length); + position += chunk.Length; + } + return data; + } - if (readSize == 0) + private static async Task DownloadFileChunk(string url, long start, long end, int index, TaskDialog taskDialog, int[] progressPercentage) + { + byte[] buffer = new byte[8192]; + using var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Range = new RangeHeaderValue(start, end); + HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); + using var stream = await response.Content.ReadAsStreamAsync(); + using var memoryStream = new MemoryStream(); + int bytesRead; + long totalRead = 0; + + while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + memoryStream.Write(buffer, 0, bytesRead); + totalRead += bytesRead; + int progress = (int)((totalRead * 100) / (end - start + 1)); + progressPercentage[index] = progress; + + Dispatcher.UIThread.Post(() => { - break; - } - - byteWritten += readSize; - - taskDialog.SetProgressBarState(GetPercentage(byteWritten, totalBytes), TaskDialogProgressState.Normal); - - updateFileStream.Write(buffer, 0, readSize); + taskDialog.SetProgressBarState(progressPercentage.Sum() / ConnectionCount, TaskDialogProgressState.Normal); + }); } - InstallUpdate(taskDialog, updateFile); + return memoryStream.ToArray(); + } + + private static async Task DoUpdateWithSingleThreadWorker(TaskDialog taskDialog, string downloadUrl, string updateFile) + { + // We do not want to timeout while downloading + httpClient.Timeout = TimeSpan.FromDays(1); + + // Use the existing httpClient instance, correctly configured + HttpResponseMessage response = await httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead); + if (!response.IsSuccessStatusCode) + { + throw new HttpRequestException($"Failed to download file: {response.ReasonPhrase}"); + } + + long totalBytes = response.Content.Headers.ContentLength ?? 0; + long byteWritten = 0; + + // Ensure the entire content body is read asynchronously + using Stream remoteFileStream = await response.Content.ReadAsStreamAsync(); + using Stream updateFileStream = File.Open(updateFile, FileMode.Create); + + byte[] buffer = new byte[32 * 1024]; + int readSize; + + while ((readSize = await remoteFileStream.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + updateFileStream.Write(buffer, 0, readSize); + byteWritten += readSize; + + int progress = GetPercentage(byteWritten, totalBytes); + Dispatcher.UIThread.Post(() => + { + taskDialog.SetProgressBarState(progress, TaskDialogProgressState.Normal); + }); + } + + await InstallUpdate(taskDialog, updateFile); + } + + private static int GetPercentage(long value, long total) + { + if (total == 0) + return 0; + return (int)((value * 100) / total); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -494,12 +486,10 @@ namespace Ryujinx.Modules private static void DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile) { - Thread worker = new(() => DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile)) + Task.Run(async () => { - Name = "Updater.SingleThreadWorker", - }; - - worker.Start(); + await DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile); + }); } [SupportedOSPlatform("linux")] @@ -573,11 +563,14 @@ namespace Ryujinx.Modules } } - private static void InstallUpdate(TaskDialog taskDialog, string updateFile) + private static async Task InstallUpdate(TaskDialog taskDialog, string updateFile) { // Extract Update - taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting]; - taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); + await Dispatcher.UIThread.InvokeAsync(() => + { + taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting]; + taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); + }); if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) { @@ -597,8 +590,12 @@ namespace Ryujinx.Modules List allFiles = EnumerateFilesToDelete().ToList(); - taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterRenaming]; - taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); + await Dispatcher.UIThread.InvokeAsync(() => + { + taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterRenaming]; + taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); + taskDialog.Hide(); + }); // NOTE: On macOS, replacement is delayed to the restart phase. if (!OperatingSystem.IsMacOS()) @@ -612,7 +609,7 @@ namespace Ryujinx.Modules { File.Move(file, file + ".ryuold"); - Dispatcher.UIThread.InvokeAsync(() => + await Dispatcher.UIThread.InvokeAsync(() => { taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal); }); @@ -623,7 +620,7 @@ namespace Ryujinx.Modules } } - Dispatcher.UIThread.InvokeAsync(() => + await Dispatcher.UIThread.InvokeAsync(() => { taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles]; taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); From 2bfc3d59fb4205f0918b9227155720b3ca74d572 Mon Sep 17 00:00:00 2001 From: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com> Date: Tue, 16 Apr 2024 23:58:03 +0700 Subject: [PATCH 04/18] Refactor part 3 --- src/Ryujinx/Modules/Updater/Updater.cs | 306 +++++++++++++------------ 1 file changed, 156 insertions(+), 150 deletions(-) diff --git a/src/Ryujinx/Modules/Updater/Updater.cs b/src/Ryujinx/Modules/Updater/Updater.cs index 88f02ffa55..fb247bf350 100644 --- a/src/Ryujinx/Modules/Updater/Updater.cs +++ b/src/Ryujinx/Modules/Updater/Updater.cs @@ -49,6 +49,14 @@ namespace Ryujinx.Modules private static readonly string[] _windowsDependencyDirs = Array.Empty(); + private static readonly HttpClient httpClient = new HttpClient + { + DefaultRequestHeaders = + { + { "User-Agent", "Ryujinx-Updater/1.0.0" } + } + }; + public static async Task BeginParse(Window mainWindow, bool showVersionUpToDate) { if (_running) @@ -58,7 +66,35 @@ namespace Ryujinx.Modules _running = true; - // Detect current platform + DetectPlatform(); + + Version currentVersion = GetCurrentVersion(); + if (currentVersion == null) + { + return; + } + + string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest"; + if (!await TryUpdateVersionInfo(buildInfoUrl, showVersionUpToDate)) + { + return; + } + + if (!await HandleVersionComparison(currentVersion, showVersionUpToDate)) + { + return; + } + + await FetchBuildSizeInfo(); + + await Dispatcher.UIThread.InvokeAsync(async () => + { + await ShowUpdateDialogAndExecute(mainWindow); + }); + } + + private static void DetectPlatform() + { if (OperatingSystem.IsMacOS()) { _platformExt = "macos_universal.app.tar.gz"; @@ -72,164 +108,134 @@ namespace Ryujinx.Modules var arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64"; _platformExt = $"linux_{arch}.tar.gz"; } + } - Version newVersion; - Version currentVersion; - + private static Version GetCurrentVersion() + { try { - currentVersion = Version.Parse(Program.Version); + return Version.Parse(Program.Version); } catch { Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!"); - await ContentDialogHelper.CreateWarningDialog( + ContentDialogHelper.CreateWarningDialog( LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage], LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); - _running = false; - - return; + return null; } + } - // Get latest version number from GitHub API + private static async Task TryUpdateVersionInfo(string buildInfoUrl, bool showVersionUpToDate) + { try { - - string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest"; - string fetchedJson = await httpClient.GetStringAsync(buildInfoUrl); + HttpResponseMessage response = await SendAsyncWithHeaders(buildInfoUrl); + string fetchedJson = await response.Content.ReadAsStringAsync(); var fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse); _buildVer = fetched.Name; foreach (var asset in fetched.Assets) { - if (asset.Name.StartsWith("ryujinx") && asset.Name.EndsWith(_platformExt)) + if (asset.Name.StartsWith("ryujinx") && asset.Name.EndsWith(_platformExt) && asset.State == "uploaded") { _buildUrl = asset.BrowserDownloadUrl; - - if (asset.State != "uploaded") - { - if (showVersionUpToDate) - { - await ContentDialogHelper.CreateUpdaterInfoDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], - ""); - } - - _running = false; - - return; - } - - break; + return true; } } - // If build not done, assume no new update are available. - if (_buildUrl is null) + if (_buildUrl == null && showVersionUpToDate) { - if (showVersionUpToDate) - { - await ContentDialogHelper.CreateUpdaterInfoDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], - ""); - } - - _running = false; - - return; + await ContentDialogHelper.CreateUpdaterInfoDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], ""); } + + _running = false; + return false; } catch (Exception exception) { Logger.Error?.Print(LogClass.Application, exception.Message); - await ContentDialogHelper.CreateErrorDialog( LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]); - _running = false; - - return; + return false; } + } + private static async Task HandleVersionComparison(Version currentVersion, bool showVersionUpToDate) + { try { - newVersion = Version.Parse(_buildVer); + Version newVersion = Version.Parse(_buildVer); + if (newVersion <= currentVersion) + { + if (showVersionUpToDate) + { + await ContentDialogHelper.CreateUpdaterInfoDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], ""); + } + + _running = false; + return false; + } + + return true; } catch { Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!"); - await ContentDialogHelper.CreateWarningDialog( LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage], LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); - _running = false; - - return; + return false; } + } - if (newVersion <= currentVersion) - { - if (showVersionUpToDate) - { - await ContentDialogHelper.CreateUpdaterInfoDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], - ""); - } - - _running = false; - - return; - } - - // Fetch build size information to learn chunk sizes. + private static async Task FetchBuildSizeInfo() + { try { - httpClient.DefaultRequestHeaders.Range = new RangeHeaderValue(0, 0); - - HttpResponseMessage message = await httpClient.GetAsync(new Uri(_buildUrl), HttpCompletionOption.ResponseHeadersRead); - + HttpResponseMessage message = await SendAsyncWithHeaders(_buildUrl, new RangeHeaderValue(0, 0)); _buildSize = message.Content.Headers.ContentRange.Length.Value; - - httpClient.DefaultRequestHeaders.Remove("Range"); } 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 () => - { - // Show a message asking the user if they want to update - var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog( - LocaleManager.Instance[LocaleKeys.RyujinxUpdater], - LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage], - $"{Program.Version} -> {newVersion}"); - - if (shouldUpdate) - { - await UpdateRyujinx(mainWindow, _buildUrl); - } - else - { - _running = false; - } - }); } - private static readonly HttpClient httpClient = new HttpClient + private static async Task ShowUpdateDialogAndExecute(Window mainWindow) { - // Required by GitHub to interact with APIs. - DefaultRequestHeaders = + var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog( + LocaleManager.Instance[LocaleKeys.RyujinxUpdater], + LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage], + $"{Program.Version} -> {_buildVer}"); + + if (shouldUpdate) { - { "User-Agent", "Ryujinx-Updater/1.0.0" } + await UpdateRyujinx(mainWindow, _buildUrl); } - }; + else + { + _running = false; + } + } + + private static async Task SendAsyncWithHeaders(string url, RangeHeaderValue range = null) + { + using var request = new HttpRequestMessage(HttpMethod.Get, url); + if (range != null) + { + request.Headers.Range = range; + } + return await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); + } private static async Task UpdateRyujinx(Window parent, string downloadUrl) { @@ -274,70 +280,70 @@ namespace Ryujinx.Modules if (!OperatingSystem.IsMacOS()) { - shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater], + shouldRestart = await ContentDialogHelper.CreateChoiceDialog( + LocaleManager.Instance[LocaleKeys.RyujinxUpdater], LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage], LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]); } if (shouldRestart) { - List arguments = CommandLineState.Arguments.ToList(); - string executableDirectory = AppDomain.CurrentDomain.BaseDirectory; - - // On macOS we perform the update at relaunch. - if (OperatingSystem.IsMacOS()) - { - string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", "..")); - string newBundlePath = Path.Combine(_updateDir, "Ryujinx.app"); - string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh"); - string currentPid = Environment.ProcessId.ToString(); - - arguments.InsertRange(0, new List { updaterScriptPath, baseBundlePath, newBundlePath, currentPid }); - Process.Start("/bin/bash", arguments); - } - else - { - // Find the process name. - string ryuName = Path.GetFileName(Environment.ProcessPath) ?? string.Empty; - - // Migration: Start the updated binary. - // TODO: Remove this in a future update. - if (ryuName.StartsWith("Ryujinx.Ava")) - { - ryuName = ryuName.Replace(".Ava", ""); - } - - // Some operating systems can see the renamed executable, so strip off the .ryuold if found. - if (ryuName.EndsWith(".ryuold")) - { - ryuName = ryuName[..^7]; - } - - // Fallback if the executable could not be found. - if (ryuName.Length == 0 || !Path.Exists(Path.Combine(executableDirectory, ryuName))) - { - ryuName = OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx"; - } - - ProcessStartInfo processStart = new(ryuName) - { - UseShellExecute = true, - WorkingDirectory = executableDirectory, - }; - - foreach (string argument in CommandLineState.Arguments) - { - processStart.ArgumentList.Add(argument); - } - - Process.Start(processStart); - } - - Environment.Exit(0); + RestartApplication(parent); } } } + private static void RestartApplication(Window parent) + { + List arguments = CommandLineState.Arguments.ToList(); + string executableDirectory = AppDomain.CurrentDomain.BaseDirectory; + + if (OperatingSystem.IsMacOS()) + { + string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", "..")); + string newBundlePath = Path.Combine(_updateDir, "Ryujinx.app"); + string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh"); + string currentPid = Environment.ProcessId.ToString(); + + arguments.InsertRange(0, new List { updaterScriptPath, baseBundlePath, newBundlePath, currentPid }); + Process.Start("/bin/bash", arguments); + } + else + { + string ryuName = Path.GetFileName(Environment.ProcessPath) ?? (OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx"); + if (ryuName.StartsWith("Ryujinx.Ava")) + { + ryuName = ryuName.Replace(".Ava", ""); + } + + if (ryuName.EndsWith(".ryuold")) + { + ryuName = ryuName[..^7]; + } + + // Fallback if the executable could not be found. + if (ryuName.Length == 0 || !Path.Exists(Path.Combine(executableDirectory, ryuName))) + { + ryuName = OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx"; + } + + ProcessStartInfo processStart = new ProcessStartInfo(ryuName) + { + UseShellExecute = true, + WorkingDirectory = executableDirectory, + }; + + foreach (string argument in arguments) + { + processStart.ArgumentList.Add(argument); + } + + Process.Start(processStart); + } + + Environment.Exit(0); + } + private static async Task DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile) { long chunkSize = _buildSize / ConnectionCount; From b2c5d4f8d77b481002c73ded4b6bdca8a6b871f3 Mon Sep 17 00:00:00 2001 From: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com> Date: Wed, 17 Apr 2024 00:41:18 +0700 Subject: [PATCH 05/18] Refactor part 4 --- src/Ryujinx/Modules/Updater/Updater.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Ryujinx/Modules/Updater/Updater.cs b/src/Ryujinx/Modules/Updater/Updater.cs index fb247bf350..82debc332f 100644 --- a/src/Ryujinx/Modules/Updater/Updater.cs +++ b/src/Ryujinx/Modules/Updater/Updater.cs @@ -68,7 +68,7 @@ namespace Ryujinx.Modules DetectPlatform(); - Version currentVersion = GetCurrentVersion(); + Version currentVersion = await GetCurrentVersion(); if (currentVersion == null) { return; @@ -110,7 +110,7 @@ namespace Ryujinx.Modules } } - private static Version GetCurrentVersion() + private static async Task GetCurrentVersion() { try { @@ -120,7 +120,7 @@ namespace Ryujinx.Modules { Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!"); - ContentDialogHelper.CreateWarningDialog( + await ContentDialogHelper.CreateWarningDialog( LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage], LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); _running = false; @@ -195,6 +195,7 @@ namespace Ryujinx.Modules } } + // Fetch build size information to learn chunk sizes. private static async Task FetchBuildSizeInfo() { try @@ -310,7 +311,10 @@ namespace Ryujinx.Modules } else { - string ryuName = Path.GetFileName(Environment.ProcessPath) ?? (OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx"); + string ryuName = Path.GetFileName(Environment.ProcessPath) ?? string.Empty; + + // Migration: Start the updated binary. + // TODO: Remove this in a future update. if (ryuName.StartsWith("Ryujinx.Ava")) { ryuName = ryuName.Replace(".Ava", ""); @@ -327,7 +331,7 @@ namespace Ryujinx.Modules ryuName = OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx"; } - ProcessStartInfo processStart = new ProcessStartInfo(ryuName) + ProcessStartInfo processStart = new(ryuName) { UseShellExecute = true, WorkingDirectory = executableDirectory, @@ -418,7 +422,7 @@ namespace Ryujinx.Modules byte[] buffer = new byte[8192]; using var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Range = new RangeHeaderValue(start, end); - HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); + HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); using var stream = await response.Content.ReadAsStreamAsync(); using var memoryStream = new MemoryStream(); int bytesRead; From 7fbdef396ccd87651b48a9ba3b3f55980698f2dc Mon Sep 17 00:00:00 2001 From: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com> Date: Wed, 17 Apr 2024 00:43:34 +0700 Subject: [PATCH 06/18] FOR TESTING!!! This is just for testing the update functionality! --- src/Ryujinx/Modules/Updater/Updater.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Ryujinx/Modules/Updater/Updater.cs b/src/Ryujinx/Modules/Updater/Updater.cs index 82debc332f..65a1a4ac70 100644 --- a/src/Ryujinx/Modules/Updater/Updater.cs +++ b/src/Ryujinx/Modules/Updater/Updater.cs @@ -74,7 +74,8 @@ namespace Ryujinx.Modules return; } - string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest"; + //string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest"; + string buildInfoUrl = $"{GitHubApiUrl}/repos/Ryujinx/release-channel-master/releases/latest"; // Temporary code, will revert back if (!await TryUpdateVersionInfo(buildInfoUrl, showVersionUpToDate)) { return; @@ -114,7 +115,8 @@ namespace Ryujinx.Modules { try { - return Version.Parse(Program.Version); + //return Version.Parse(Program.Version); + return Version.Parse("1.1.0"); // Temporary code, will revert back } catch { @@ -663,7 +665,7 @@ namespace Ryujinx.Modules return false; } - if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid) + /*if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid) { if (showWarnings) { @@ -675,7 +677,7 @@ namespace Ryujinx.Modules } return false; - } + }*/ // Temporary commented out, will revert back return true; #else From c2118aacec9e4a0c7bafabfddc51d052b60a7340 Mon Sep 17 00:00:00 2001 From: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com> Date: Wed, 17 Apr 2024 14:12:01 +0700 Subject: [PATCH 07/18] Decouple code --- src/Ryujinx/Modules/Updater/Updater.cs | 760 +----------------- .../Modules/Updater/Utils/CanUpdate.cs | 70 ++ .../Modules/Updater/Utils/CleanupUpdate.cs | 56 ++ .../Modules/Updater/Utils/DetectPlatform.cs | 27 + .../Utils/DoUpdateWithMultipleThreads.cs | 123 +++ .../Updater/Utils/DoUpdateWithSingleThread.cs | 78 ++ .../Updater/Utils/EnumerateFilesToDelete.cs | 47 ++ .../Updater/Utils/ExtractTarGzipFile.cs | 56 ++ .../Modules/Updater/Utils/ExtractZipFile.cs | 44 + .../Updater/Utils/FetchBuildSizeInfo.cs | 30 + .../Updater/Utils/GetCurrentVersion.cs | 31 + .../Updater/Utils/HandleVersionComparison.cs | 44 + .../Modules/Updater/Utils/InstallUpdate.cs | 92 +++ .../Modules/Updater/Utils/MoveAllFilesOver.cs | 39 + .../Updater/Utils/RestartApplication.cs | 68 ++ .../Updater/Utils/SendAsyncWithHeaders.cs | 20 + .../Utils/ShowUpdateDialogAndExecute.cs | 33 + .../Updater/Utils/TryUpdateVersionInfo.cs | 56 ++ .../Modules/Updater/Utils/UpdateRyujinx.cs | 83 ++ 19 files changed, 1002 insertions(+), 755 deletions(-) create mode 100644 src/Ryujinx/Modules/Updater/Utils/CanUpdate.cs create mode 100644 src/Ryujinx/Modules/Updater/Utils/CleanupUpdate.cs create mode 100644 src/Ryujinx/Modules/Updater/Utils/DetectPlatform.cs create mode 100644 src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs create mode 100644 src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs create mode 100644 src/Ryujinx/Modules/Updater/Utils/EnumerateFilesToDelete.cs create mode 100644 src/Ryujinx/Modules/Updater/Utils/ExtractTarGzipFile.cs create mode 100644 src/Ryujinx/Modules/Updater/Utils/ExtractZipFile.cs create mode 100644 src/Ryujinx/Modules/Updater/Utils/FetchBuildSizeInfo.cs create mode 100644 src/Ryujinx/Modules/Updater/Utils/GetCurrentVersion.cs create mode 100644 src/Ryujinx/Modules/Updater/Utils/HandleVersionComparison.cs create mode 100644 src/Ryujinx/Modules/Updater/Utils/InstallUpdate.cs create mode 100644 src/Ryujinx/Modules/Updater/Utils/MoveAllFilesOver.cs create mode 100644 src/Ryujinx/Modules/Updater/Utils/RestartApplication.cs create mode 100644 src/Ryujinx/Modules/Updater/Utils/SendAsyncWithHeaders.cs create mode 100644 src/Ryujinx/Modules/Updater/Utils/ShowUpdateDialogAndExecute.cs create mode 100644 src/Ryujinx/Modules/Updater/Utils/TryUpdateVersionInfo.cs create mode 100644 src/Ryujinx/Modules/Updater/Utils/UpdateRyujinx.cs diff --git a/src/Ryujinx/Modules/Updater/Updater.cs b/src/Ryujinx/Modules/Updater/Updater.cs index 65a1a4ac70..bb4402fcd3 100644 --- a/src/Ryujinx/Modules/Updater/Updater.cs +++ b/src/Ryujinx/Modules/Updater/Updater.cs @@ -1,53 +1,16 @@ using Avalonia.Controls; using Avalonia.Threading; -using FluentAvalonia.UI.Controls; -using ICSharpCode.SharpZipLib.GZip; -using ICSharpCode.SharpZipLib.Tar; -using ICSharpCode.SharpZipLib.Zip; -using Ryujinx.Ava; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Common; -using Ryujinx.Common.Logging; -using Ryujinx.Common.Utilities; -using Ryujinx.UI.Common.Helper; -using Ryujinx.UI.Common.Models.Github; using System; -using System.Collections.Generic; -using System.Diagnostics; using System.IO; -using System.Linq; -using System.Net; using System.Net.Http; using System.Net.Http.Headers; -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; namespace Ryujinx.Modules { - internal static class Updater + internal static partial class Updater { private const string GitHubApiUrl = "https://api.github.com"; - private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - - 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 string _buildVer; - private static string _platformExt; - private static string _buildUrl; - private static long _buildSize; - private static bool _updateSuccessful; - private static bool _running; - - private static readonly string[] _windowsDependencyDirs = Array.Empty(); private static readonly HttpClient httpClient = new HttpClient { @@ -57,6 +20,10 @@ namespace Ryujinx.Modules } }; + private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update"); + + private static bool _running; + public static async Task BeginParse(Window mainWindow, bool showVersionUpToDate) { if (_running) @@ -93,722 +60,5 @@ namespace Ryujinx.Modules await ShowUpdateDialogAndExecute(mainWindow); }); } - - private static void DetectPlatform() - { - if (OperatingSystem.IsMacOS()) - { - _platformExt = "macos_universal.app.tar.gz"; - } - else if (OperatingSystem.IsWindows()) - { - _platformExt = "win_x64.zip"; - } - else if (OperatingSystem.IsLinux()) - { - var arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64"; - _platformExt = $"linux_{arch}.tar.gz"; - } - } - - private static async Task GetCurrentVersion() - { - try - { - //return Version.Parse(Program.Version); - return Version.Parse("1.1.0"); // Temporary code, will revert back - } - catch - { - Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!"); - - await ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); - _running = false; - return null; - } - } - - private static async Task TryUpdateVersionInfo(string buildInfoUrl, bool showVersionUpToDate) - { - try - { - HttpResponseMessage response = await SendAsyncWithHeaders(buildInfoUrl); - string fetchedJson = await response.Content.ReadAsStringAsync(); - var fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse); - _buildVer = fetched.Name; - - foreach (var asset in fetched.Assets) - { - if (asset.Name.StartsWith("ryujinx") && asset.Name.EndsWith(_platformExt) && asset.State == "uploaded") - { - _buildUrl = asset.BrowserDownloadUrl; - return true; - } - } - - if (_buildUrl == null && showVersionUpToDate) - { - await ContentDialogHelper.CreateUpdaterInfoDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], ""); - } - - _running = false; - return false; - } - catch (Exception exception) - { - Logger.Error?.Print(LogClass.Application, exception.Message); - await ContentDialogHelper.CreateErrorDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]); - _running = false; - return false; - } - } - - private static async Task HandleVersionComparison(Version currentVersion, bool showVersionUpToDate) - { - try - { - Version newVersion = Version.Parse(_buildVer); - if (newVersion <= currentVersion) - { - if (showVersionUpToDate) - { - await ContentDialogHelper.CreateUpdaterInfoDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], ""); - } - - _running = false; - return false; - } - - return true; - } - catch - { - Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!"); - await ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); - _running = false; - return false; - } - } - - // Fetch build size information to learn chunk sizes. - private static async Task FetchBuildSizeInfo() - { - try - { - HttpResponseMessage message = await SendAsyncWithHeaders(_buildUrl, new RangeHeaderValue(0, 0)); - _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; - } - } - - private static async Task ShowUpdateDialogAndExecute(Window mainWindow) - { - var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog( - LocaleManager.Instance[LocaleKeys.RyujinxUpdater], - LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage], - $"{Program.Version} -> {_buildVer}"); - - if (shouldUpdate) - { - await UpdateRyujinx(mainWindow, _buildUrl); - } - else - { - _running = false; - } - } - - private static async Task SendAsyncWithHeaders(string url, RangeHeaderValue range = null) - { - using var request = new HttpRequestMessage(HttpMethod.Get, url); - if (range != null) - { - request.Headers.Range = range; - } - return await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); - } - - private static async Task UpdateRyujinx(Window parent, string downloadUrl) - { - _updateSuccessful = false; - - // Empty update dir, although it shouldn't ever have anything inside it - if (Directory.Exists(_updateDir)) - { - Directory.Delete(_updateDir, true); - } - - Directory.CreateDirectory(_updateDir); - - string updateFile = Path.Combine(_updateDir, "update.bin"); - - TaskDialog taskDialog = new() - { - Header = LocaleManager.Instance[LocaleKeys.RyujinxUpdater], - SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading], - IconSource = new SymbolIconSource { Symbol = Symbol.Download }, - ShowProgressBar = true, - XamlRoot = parent, - }; - - taskDialog.Opened += async (s, e) => - { - if (_buildSize >= 0) - { - await DoUpdateWithMultipleThreads(taskDialog, downloadUrl, updateFile); - } - else - { - DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); - } - }; - - await taskDialog.ShowAsync(true); - - if (_updateSuccessful) - { - bool shouldRestart = true; - - if (!OperatingSystem.IsMacOS()) - { - shouldRestart = await ContentDialogHelper.CreateChoiceDialog( - LocaleManager.Instance[LocaleKeys.RyujinxUpdater], - LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]); - } - - if (shouldRestart) - { - RestartApplication(parent); - } - } - } - - private static void RestartApplication(Window parent) - { - List arguments = CommandLineState.Arguments.ToList(); - string executableDirectory = AppDomain.CurrentDomain.BaseDirectory; - - if (OperatingSystem.IsMacOS()) - { - string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", "..")); - string newBundlePath = Path.Combine(_updateDir, "Ryujinx.app"); - string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh"); - string currentPid = Environment.ProcessId.ToString(); - - arguments.InsertRange(0, new List { updaterScriptPath, baseBundlePath, newBundlePath, currentPid }); - Process.Start("/bin/bash", arguments); - } - else - { - string ryuName = Path.GetFileName(Environment.ProcessPath) ?? string.Empty; - - // Migration: Start the updated binary. - // TODO: Remove this in a future update. - if (ryuName.StartsWith("Ryujinx.Ava")) - { - ryuName = ryuName.Replace(".Ava", ""); - } - - if (ryuName.EndsWith(".ryuold")) - { - ryuName = ryuName[..^7]; - } - - // Fallback if the executable could not be found. - if (ryuName.Length == 0 || !Path.Exists(Path.Combine(executableDirectory, ryuName))) - { - ryuName = OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx"; - } - - ProcessStartInfo processStart = new(ryuName) - { - UseShellExecute = true, - WorkingDirectory = executableDirectory, - }; - - foreach (string argument in arguments) - { - processStart.ArgumentList.Add(argument); - } - - Process.Start(processStart); - } - - Environment.Exit(0); - } - - private static async Task DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile) - { - long chunkSize = _buildSize / ConnectionCount; - long remainderChunk = _buildSize % ConnectionCount; - - int completedRequests = 0; - int[] progressPercentage = new int[ConnectionCount]; - List chunkDataList = new List(new byte[ConnectionCount][]); - - List downloadTasks = new List(); - - for (int i = 0; i < ConnectionCount; i++) - { - long rangeStart = i * chunkSize; - long rangeEnd = (i == ConnectionCount - 1) ? (rangeStart + chunkSize + remainderChunk - 1) : (rangeStart + chunkSize - 1); - int index = i; - - downloadTasks.Add(Task.Run(async () => - { - byte[] chunkData = await DownloadFileChunk(downloadUrl, rangeStart, rangeEnd, index, taskDialog, progressPercentage); - chunkDataList[index] = chunkData; - - Interlocked.Increment(ref completedRequests); - if (Interlocked.Equals(completedRequests, ConnectionCount)) - { - byte[] allData = CombineChunks(chunkDataList, _buildSize); - File.WriteAllBytes(updateFile, allData); - - // On macOS, ensure that we remove the quarantine bit to prevent Gatekeeper from blocking execution. - if (OperatingSystem.IsMacOS()) - { - using Process xattrProcess = Process.Start("xattr", new List { "-d", "com.apple.quarantine", updateFile }); - - xattrProcess.WaitForExit(); - } - - // Ensure that the install update is run on the UI thread. - await Dispatcher.UIThread.InvokeAsync(async () => - { - try - { - await InstallUpdate(taskDialog, updateFile); - } - catch (Exception e) - { - Logger.Warning?.Print(LogClass.Application, e.Message); - Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater."); - DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); - } - }); - } - })); - } - - await Task.WhenAll(downloadTasks); - } - - private static byte[] CombineChunks(List chunks, long totalSize) - { - byte[] data = new byte[totalSize]; - long position = 0; - foreach (byte[] chunk in chunks) - { - Buffer.BlockCopy(chunk, 0, data, (int)position, chunk.Length); - position += chunk.Length; - } - return data; - } - - private static async Task DownloadFileChunk(string url, long start, long end, int index, TaskDialog taskDialog, int[] progressPercentage) - { - byte[] buffer = new byte[8192]; - using var request = new HttpRequestMessage(HttpMethod.Get, url); - request.Headers.Range = new RangeHeaderValue(start, end); - HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); - using var stream = await response.Content.ReadAsStreamAsync(); - using var memoryStream = new MemoryStream(); - int bytesRead; - long totalRead = 0; - - while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0) - { - memoryStream.Write(buffer, 0, bytesRead); - totalRead += bytesRead; - int progress = (int)((totalRead * 100) / (end - start + 1)); - progressPercentage[index] = progress; - - Dispatcher.UIThread.Post(() => - { - taskDialog.SetProgressBarState(progressPercentage.Sum() / ConnectionCount, TaskDialogProgressState.Normal); - }); - } - - return memoryStream.ToArray(); - } - - private static async Task DoUpdateWithSingleThreadWorker(TaskDialog taskDialog, string downloadUrl, string updateFile) - { - // We do not want to timeout while downloading - httpClient.Timeout = TimeSpan.FromDays(1); - - // Use the existing httpClient instance, correctly configured - HttpResponseMessage response = await httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead); - if (!response.IsSuccessStatusCode) - { - throw new HttpRequestException($"Failed to download file: {response.ReasonPhrase}"); - } - - long totalBytes = response.Content.Headers.ContentLength ?? 0; - long byteWritten = 0; - - // Ensure the entire content body is read asynchronously - using Stream remoteFileStream = await response.Content.ReadAsStreamAsync(); - using Stream updateFileStream = File.Open(updateFile, FileMode.Create); - - byte[] buffer = new byte[32 * 1024]; - int readSize; - - while ((readSize = await remoteFileStream.ReadAsync(buffer, 0, buffer.Length)) > 0) - { - updateFileStream.Write(buffer, 0, readSize); - byteWritten += readSize; - - int progress = GetPercentage(byteWritten, totalBytes); - Dispatcher.UIThread.Post(() => - { - taskDialog.SetProgressBarState(progress, TaskDialogProgressState.Normal); - }); - } - - await InstallUpdate(taskDialog, updateFile); - } - - private static int GetPercentage(long value, long total) - { - if (total == 0) - return 0; - return (int)((value * 100) / total); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static double GetPercentage(double value, double max) - { - return max == 0 ? 0 : value / max * 100; - } - - private static void DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile) - { - Task.Run(async () => - { - await DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile); - }); - } - - [SupportedOSPlatform("linux")] - [SupportedOSPlatform("macos")] - private static void ExtractTarGzipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath) - { - using Stream 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); - }); - } - } - - private static void ExtractZipFile(TaskDialog taskDialog, 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); - }); - } - } - - private static async Task InstallUpdate(TaskDialog taskDialog, string updateFile) - { - // Extract Update - await Dispatcher.UIThread.InvokeAsync(() => - { - taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting]; - taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); - }); - - if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) - { - ExtractTarGzipFile(taskDialog, updateFile, _updateDir); - } - else if (OperatingSystem.IsWindows()) - { - ExtractZipFile(taskDialog, updateFile, _updateDir); - } - else - { - throw new NotSupportedException(); - } - - // Delete downloaded zip - File.Delete(updateFile); - - List allFiles = EnumerateFilesToDelete().ToList(); - - await Dispatcher.UIThread.InvokeAsync(() => - { - taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterRenaming]; - taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); - taskDialog.Hide(); - }); - - // NOTE: On macOS, replacement is delayed to the restart phase. - if (!OperatingSystem.IsMacOS()) - { - // Replace old files - double count = 0; - foreach (string file in allFiles) - { - count++; - try - { - File.Move(file, file + ".ryuold"); - - await Dispatcher.UIThread.InvokeAsync(() => - { - taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal); - }); - } - catch - { - Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file)); - } - } - - await Dispatcher.UIThread.InvokeAsync(() => - { - taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles]; - taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); - }); - - MoveAllFilesOver(_updatePublishDir, _homeDir, taskDialog); - - Directory.Delete(_updateDir, true); - } - - _updateSuccessful = true; - - taskDialog.Hide(); - } - - public static bool CanUpdate(bool showWarnings) - { -#if !DISABLE_UPDATER - if (!NetworkInterface.GetIsNetworkAvailable()) - { - if (showWarnings) - { - Dispatcher.UIThread.InvokeAsync(() => - ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage]) - ); - } - - return false; - } - - /*if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid) - { - if (showWarnings) - { - Dispatcher.UIThread.InvokeAsync(() => - ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]) - ); - } - - return false; - }*/ // Temporary commented out, will revert back - - return true; -#else - if (showWarnings) - { - if (ReleaseInformation.IsFlatHubBuild) - { - Dispatcher.UIThread.InvokeAsync(() => - ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], - LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage]) - ); - } - else - { - Dispatcher.UIThread.InvokeAsync(() => - ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], - LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]) - ); - } - } - - return false; -#endif - } - - // NOTE: This method should always reflect the latest build layout. - private static IEnumerable EnumerateFilesToDelete() - { - var files = Directory.EnumerateFiles(_homeDir); // All files directly in base dir. - - // Determine and exclude user files only when the updater is running, not when cleaning old files - if (_running && !OperatingSystem.IsMacOS()) - { - // Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list. - var oldFiles = Directory.EnumerateFiles(_homeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName); - var newFiles = Directory.EnumerateFiles(_updatePublishDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName); - var userFiles = oldFiles.Except(newFiles).Select(filename => Path.Combine(_homeDir, filename)); - - // Remove user files from the paths in files. - files = files.Except(userFiles); - } - - if (OperatingSystem.IsWindows()) - { - foreach (string dir in _windowsDependencyDirs) - { - string dirPath = Path.Combine(_homeDir, dir); - if (Directory.Exists(dirPath)) - { - files = files.Concat(Directory.EnumerateFiles(dirPath, "*", SearchOption.AllDirectories)); - } - } - } - - return files.Where(f => !new FileInfo(f).Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System)); - } - - private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDialog) - { - int total = Directory.GetFiles(root, "*", SearchOption.AllDirectories).Length; - foreach (string directory in Directory.GetDirectories(root)) - { - string dirName = Path.GetFileName(directory); - - if (!Directory.Exists(Path.Combine(dest, dirName))) - { - Directory.CreateDirectory(Path.Combine(dest, dirName)); - } - - MoveAllFilesOver(directory, Path.Combine(dest, dirName), taskDialog); - } - - double count = 0; - foreach (string file in Directory.GetFiles(root)) - { - count++; - - File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true); - - Dispatcher.UIThread.InvokeAsync(() => - { - taskDialog.SetProgressBarState(GetPercentage(count, total), TaskDialogProgressState.Normal); - }); - } - } - - public static void CleanupUpdate() - { - foreach (string file in Directory.GetFiles(_homeDir, "*.ryuold", SearchOption.AllDirectories)) - { - File.Delete(file); - } - - // Migration: Delete old Ryujinx binary. - // TODO: Remove this in a future update. - if (!OperatingSystem.IsMacOS()) - { - string[] oldRyuFiles = Directory.GetFiles(_homeDir, "Ryujinx.Ava*", SearchOption.TopDirectoryOnly); - // Assume we are running the new one if the process path is not available. - // This helps to prevent an infinite loop of restarts. - string currentRyuName = Path.GetFileName(Environment.ProcessPath) ?? (OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx"); - - string newRyuName = Path.Combine(_homeDir, currentRyuName.Replace(".Ava", "")); - if (!currentRyuName.Contains("Ryujinx.Ava")) - { - foreach (string oldRyuFile in oldRyuFiles) - { - File.Delete(oldRyuFile); - } - } - // Should we be running the old binary, start the new one if possible. - else if (File.Exists(newRyuName)) - { - ProcessStartInfo processStart = new(newRyuName) - { - UseShellExecute = true, - WorkingDirectory = _homeDir, - }; - - foreach (string argument in CommandLineState.Arguments) - { - processStart.ArgumentList.Add(argument); - } - - Process.Start(processStart); - - Environment.Exit(0); - } - } - } } } diff --git a/src/Ryujinx/Modules/Updater/Utils/CanUpdate.cs b/src/Ryujinx/Modules/Updater/Utils/CanUpdate.cs new file mode 100644 index 0000000000..f021fb985d --- /dev/null +++ b/src/Ryujinx/Modules/Updater/Utils/CanUpdate.cs @@ -0,0 +1,70 @@ +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using System; +using System.Net.NetworkInformation; +using System.Runtime.InteropServices; + +namespace Ryujinx.Modules +{ + internal static partial class Updater + { + public static bool CanUpdate(bool showWarnings) + { +#if !DISABLE_UPDATER + if (!NetworkInterface.GetIsNetworkAvailable()) + { + if (showWarnings) + { + Dispatcher.UIThread.InvokeAsync(() => + ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage], + LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage]) + ); + } + + return false; + } + + /*if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid) + { + if (showWarnings) + { + Dispatcher.UIThread.InvokeAsync(() => + ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage], + LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]) + ); + } + + return false; + }*/ // Temporary commented out, will revert back + + return true; +#else + if (showWarnings) + { + if (ReleaseInformation.IsFlatHubBuild) + { + Dispatcher.UIThread.InvokeAsync(() => + ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], + LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage]) + ); + } + else + { + Dispatcher.UIThread.InvokeAsync(() => + ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], + LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]) + ); + } + } + + return false; +#endif + } + } +} \ No newline at end of file diff --git a/src/Ryujinx/Modules/Updater/Utils/CleanupUpdate.cs b/src/Ryujinx/Modules/Updater/Utils/CleanupUpdate.cs new file mode 100644 index 0000000000..bedca8efaf --- /dev/null +++ b/src/Ryujinx/Modules/Updater/Utils/CleanupUpdate.cs @@ -0,0 +1,56 @@ +using Ryujinx.UI.Common.Helper; +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; + +namespace Ryujinx.Modules +{ + internal static partial class Updater + { + public static void CleanupUpdate() + { + foreach (string file in Directory.GetFiles(_homeDir, "*.ryuold", SearchOption.AllDirectories)) + { + File.Delete(file); + } + + // Migration: Delete old Ryujinx binary. + // TODO: Remove this in a future update. + if (!OperatingSystem.IsMacOS()) + { + string[] oldRyuFiles = Directory.GetFiles(_homeDir, "Ryujinx.Ava*", SearchOption.TopDirectoryOnly); + // Assume we are running the new one if the process path is not available. + // This helps to prevent an infinite loop of restarts. + string currentRyuName = Path.GetFileName(Environment.ProcessPath) ?? (OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx"); + + string newRyuName = Path.Combine(_homeDir, currentRyuName.Replace(".Ava", "")); + if (!currentRyuName.Contains("Ryujinx.Ava")) + { + foreach (string oldRyuFile in oldRyuFiles) + { + File.Delete(oldRyuFile); + } + } + // Should we be running the old binary, start the new one if possible. + else if (File.Exists(newRyuName)) + { + ProcessStartInfo processStart = new(newRyuName) + { + UseShellExecute = true, + WorkingDirectory = _homeDir, + }; + + foreach (string argument in CommandLineState.Arguments) + { + processStart.ArgumentList.Add(argument); + } + + Process.Start(processStart); + + Environment.Exit(0); + } + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx/Modules/Updater/Utils/DetectPlatform.cs b/src/Ryujinx/Modules/Updater/Utils/DetectPlatform.cs new file mode 100644 index 0000000000..b08258eb20 --- /dev/null +++ b/src/Ryujinx/Modules/Updater/Utils/DetectPlatform.cs @@ -0,0 +1,27 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Modules +{ + internal static partial class Updater + { + private static string _platformExt; + + private static void DetectPlatform() + { + if (OperatingSystem.IsMacOS()) + { + _platformExt = "macos_universal.app.tar.gz"; + } + else if (OperatingSystem.IsWindows()) + { + _platformExt = "win_x64.zip"; + } + else if (OperatingSystem.IsLinux()) + { + var arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64"; + _platformExt = $"linux_{arch}.tar.gz"; + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs b/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs new file mode 100644 index 0000000000..63c37351ec --- /dev/null +++ b/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs @@ -0,0 +1,123 @@ +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Ryujinx.Modules +{ + internal static partial class Updater + { + private static long _buildSize; + private const int ConnectionCount = 4; + + private static async Task DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile) + { + long chunkSize = _buildSize / ConnectionCount; + long remainderChunk = _buildSize % ConnectionCount; + + int completedRequests = 0; + int[] progressPercentage = new int[ConnectionCount]; + List chunkDataList = new List(new byte[ConnectionCount][]); + + List downloadTasks = new List(); + + for (int i = 0; i < ConnectionCount; i++) + { + long rangeStart = i * chunkSize; + long rangeEnd = (i == ConnectionCount - 1) ? (rangeStart + chunkSize + remainderChunk - 1) : (rangeStart + chunkSize - 1); + int index = i; + + downloadTasks.Add(Task.Run(async () => + { + byte[] chunkData = await DownloadFileChunk(downloadUrl, rangeStart, rangeEnd, index, taskDialog, progressPercentage); + chunkDataList[index] = chunkData; + + Interlocked.Increment(ref completedRequests); + if (Interlocked.Equals(completedRequests, ConnectionCount)) + { + byte[] allData = CombineChunks(chunkDataList, _buildSize); + File.WriteAllBytes(updateFile, allData); + + // On macOS, ensure that we remove the quarantine bit to prevent Gatekeeper from blocking execution. + if (OperatingSystem.IsMacOS()) + { + using Process xattrProcess = Process.Start("xattr", new List { "-d", "com.apple.quarantine", updateFile }); + + xattrProcess.WaitForExit(); + } + + // Ensure that the install update is run on the UI thread. + await Dispatcher.UIThread.InvokeAsync(async () => + { + try + { + await InstallUpdate(taskDialog, updateFile); + } + catch (Exception e) + { + Logger.Warning?.Print(LogClass.Application, e.Message); + Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater."); + DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); + } + }); + } + })); + } + + await Task.WhenAll(downloadTasks); + } + + private static byte[] CombineChunks(List chunks, long totalSize) + { + byte[] data = new byte[totalSize]; + long position = 0; + foreach (byte[] chunk in chunks) + { + Buffer.BlockCopy(chunk, 0, data, (int)position, chunk.Length); + position += chunk.Length; + } + return data; + } + + private static async Task DownloadFileChunk(string url, long start, long end, int index, TaskDialog taskDialog, int[] progressPercentage) + { + byte[] buffer = new byte[8192]; + using var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Range = new RangeHeaderValue(start, end); + HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); + using var stream = await response.Content.ReadAsStreamAsync(); + using var memoryStream = new MemoryStream(); + int bytesRead; + long totalRead = 0; + + while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + memoryStream.Write(buffer, 0, bytesRead); + totalRead += bytesRead; + int progress = (int)((totalRead * 100) / (end - start + 1)); + progressPercentage[index] = progress; + + Dispatcher.UIThread.Post(() => + { + taskDialog.SetProgressBarState(progressPercentage.Sum() / ConnectionCount, TaskDialogProgressState.Normal); + }); + } + + return memoryStream.ToArray(); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs b/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs new file mode 100644 index 0000000000..b6e6f5a7be --- /dev/null +++ b/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs @@ -0,0 +1,78 @@ +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Common.Logging; +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Ryujinx.Modules +{ + internal static partial class Updater + { + private static async Task DoUpdateWithSingleThreadWorker(TaskDialog taskDialog, string downloadUrl, string updateFile) + { + // We do not want to timeout while downloading + httpClient.Timeout = TimeSpan.FromDays(1); + + HttpResponseMessage response = await httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead); + if (!response.IsSuccessStatusCode) + { + throw new HttpRequestException($"Failed to download file: {response.ReasonPhrase}"); + } + + long totalBytes = response.Content.Headers.ContentLength ?? 0; + long byteWritten = 0; + + // Ensure the entire content body is read asynchronously + using Stream remoteFileStream = await response.Content.ReadAsStreamAsync(); + using Stream updateFileStream = File.Open(updateFile, FileMode.Create); + + byte[] buffer = new byte[32 * 1024]; + int readSize; + + while ((readSize = await remoteFileStream.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + updateFileStream.Write(buffer, 0, readSize); + byteWritten += readSize; + + int progress = GetPercentage(byteWritten, totalBytes); + Dispatcher.UIThread.Post(() => + { + taskDialog.SetProgressBarState(progress, TaskDialogProgressState.Normal); + }); + } + + await InstallUpdate(taskDialog, updateFile); + } + + private static int GetPercentage(long value, long total) + { + if (total == 0) + return 0; + return (int)((value * 100) / total); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static double GetPercentage(double value, double max) + { + return max == 0 ? 0 : value / max * 100; + } + + private static void DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile) + { + Task.Run(async () => + { + await DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile); + }); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx/Modules/Updater/Utils/EnumerateFilesToDelete.cs b/src/Ryujinx/Modules/Updater/Utils/EnumerateFilesToDelete.cs new file mode 100644 index 0000000000..00802bb0cf --- /dev/null +++ b/src/Ryujinx/Modules/Updater/Utils/EnumerateFilesToDelete.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Ryujinx.Modules +{ + internal static partial class Updater + { + private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory; + private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish"); + private static readonly string[] _windowsDependencyDirs = Array.Empty(); + + // NOTE: This method should always reflect the latest build layout. + private static IEnumerable EnumerateFilesToDelete() + { + var files = Directory.EnumerateFiles(_homeDir); // All files directly in base dir. + + // Determine and exclude user files only when the updater is running, not when cleaning old files + if (_running && !OperatingSystem.IsMacOS()) + { + // Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list. + var oldFiles = Directory.EnumerateFiles(_homeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName); + var newFiles = Directory.EnumerateFiles(_updatePublishDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName); + var userFiles = oldFiles.Except(newFiles).Select(filename => Path.Combine(_homeDir, filename)); + + // Remove user files from the paths in files. + files = files.Except(userFiles); + } + + if (OperatingSystem.IsWindows()) + { + foreach (string dir in _windowsDependencyDirs) + { + string dirPath = Path.Combine(_homeDir, dir); + if (Directory.Exists(dirPath)) + { + files = files.Concat(Directory.EnumerateFiles(dirPath, "*", SearchOption.AllDirectories)); + } + } + } + + return files.Where(f => !new FileInfo(f).Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System)); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx/Modules/Updater/Utils/ExtractTarGzipFile.cs b/src/Ryujinx/Modules/Updater/Utils/ExtractTarGzipFile.cs new file mode 100644 index 0000000000..a0f91b3544 --- /dev/null +++ b/src/Ryujinx/Modules/Updater/Utils/ExtractTarGzipFile.cs @@ -0,0 +1,56 @@ +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; +using ICSharpCode.SharpZipLib.GZip; +using ICSharpCode.SharpZipLib.Tar; +using System; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Text; + +namespace Ryujinx.Modules +{ + internal static partial class Updater + { + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + private static void ExtractTarGzipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath) + { + using Stream 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); + }); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx/Modules/Updater/Utils/ExtractZipFile.cs b/src/Ryujinx/Modules/Updater/Utils/ExtractZipFile.cs new file mode 100644 index 0000000000..b184becad1 --- /dev/null +++ b/src/Ryujinx/Modules/Updater/Utils/ExtractZipFile.cs @@ -0,0 +1,44 @@ +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; +using ICSharpCode.SharpZipLib.Zip; +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Ryujinx.Modules +{ + internal static partial class Updater + { + private static void ExtractZipFile(TaskDialog taskDialog, 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); + }); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx/Modules/Updater/Utils/FetchBuildSizeInfo.cs b/src/Ryujinx/Modules/Updater/Utils/FetchBuildSizeInfo.cs new file mode 100644 index 0000000000..6fb48e5119 --- /dev/null +++ b/src/Ryujinx/Modules/Updater/Utils/FetchBuildSizeInfo.cs @@ -0,0 +1,30 @@ +using Ryujinx.Common.Logging; +using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; + +namespace Ryujinx.Modules +{ + internal static partial class Updater + { + private static string _buildUrl; + + // Fetch build size information to learn chunk sizes. + private static async Task FetchBuildSizeInfo() + { + try + { + HttpResponseMessage message = await SendAsyncWithHeaders(_buildUrl, new RangeHeaderValue(0, 0)); + _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; + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx/Modules/Updater/Utils/GetCurrentVersion.cs b/src/Ryujinx/Modules/Updater/Utils/GetCurrentVersion.cs new file mode 100644 index 0000000000..a36bdc9c8a --- /dev/null +++ b/src/Ryujinx/Modules/Updater/Utils/GetCurrentVersion.cs @@ -0,0 +1,31 @@ +using System; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Common.Logging; +using System.Threading; +using System.Threading.Tasks; + +namespace Ryujinx.Modules +{ + internal static partial class Updater + { + private static string _buildVer; + + private static async Task GetCurrentVersion() + { + try + { + //return Version.Parse(Program.Version); + return Version.Parse("1.1.0"); // Temporary code, will revert back + } + catch + { + Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!"); + await ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage], + LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); + return null; + } + } + } +} diff --git a/src/Ryujinx/Modules/Updater/Utils/HandleVersionComparison.cs b/src/Ryujinx/Modules/Updater/Utils/HandleVersionComparison.cs new file mode 100644 index 0000000000..37dc32a8fb --- /dev/null +++ b/src/Ryujinx/Modules/Updater/Utils/HandleVersionComparison.cs @@ -0,0 +1,44 @@ +using Ryujinx.Ava; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.UI.Common.Helper; +using System; +using System.Threading.Tasks; + +namespace Ryujinx.Modules +{ + internal static partial class Updater + { + private static async Task HandleVersionComparison(Version currentVersion, bool showVersionUpToDate) + { + try + { + Version newVersion = Version.Parse(_buildVer); + if (newVersion <= currentVersion) + { + if (showVersionUpToDate) + { + await ContentDialogHelper.CreateUpdaterInfoDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], ""); + } + + _running = false; + return false; + } + + return true; + } + catch + { + Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!"); + await ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage], + LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); + _running = false; + return false; + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx/Modules/Updater/Utils/InstallUpdate.cs b/src/Ryujinx/Modules/Updater/Utils/InstallUpdate.cs new file mode 100644 index 0000000000..61e4cc3355 --- /dev/null +++ b/src/Ryujinx/Modules/Updater/Utils/InstallUpdate.cs @@ -0,0 +1,92 @@ +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; +using ICSharpCode.SharpZipLib.GZip; +using ICSharpCode.SharpZipLib.Tar; +using ICSharpCode.SharpZipLib.Zip; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Common.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace Ryujinx.Modules +{ + internal static partial class Updater + { + private static async Task InstallUpdate(TaskDialog taskDialog, string updateFile) + { + // Extract Update + await Dispatcher.UIThread.InvokeAsync(() => + { + taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting]; + taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); + }); + + if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + ExtractTarGzipFile(taskDialog, updateFile, _updateDir); + } + else if (OperatingSystem.IsWindows()) + { + ExtractZipFile(taskDialog, updateFile, _updateDir); + } + else + { + throw new NotSupportedException(); + } + + // Delete downloaded zip + File.Delete(updateFile); + + List allFiles = EnumerateFilesToDelete().ToList(); + + await Dispatcher.UIThread.InvokeAsync(() => + { + taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterRenaming]; + taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); + taskDialog.Hide(); + }); + + // NOTE: On macOS, replacement is delayed to the restart phase. + if (!OperatingSystem.IsMacOS()) + { + // Replace old files + double count = 0; + foreach (string file in allFiles) + { + count++; + try + { + File.Move(file, file + ".ryuold"); + + await Dispatcher.UIThread.InvokeAsync(() => + { + taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal); + }); + } + catch + { + Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file)); + } + } + + await Dispatcher.UIThread.InvokeAsync(() => + { + taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles]; + taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); + }); + + MoveAllFilesOver(_updatePublishDir, _homeDir, taskDialog); + + Directory.Delete(_updateDir, true); + } + + _updateSuccessful = true; + + taskDialog.Hide(); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx/Modules/Updater/Utils/MoveAllFilesOver.cs b/src/Ryujinx/Modules/Updater/Utils/MoveAllFilesOver.cs new file mode 100644 index 0000000000..9f35dbe2d4 --- /dev/null +++ b/src/Ryujinx/Modules/Updater/Utils/MoveAllFilesOver.cs @@ -0,0 +1,39 @@ +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; +using System; +using System.IO; + +namespace Ryujinx.Modules +{ + internal static partial class Updater + { + private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDialog) + { + int total = Directory.GetFiles(root, "*", SearchOption.AllDirectories).Length; + foreach (string directory in Directory.GetDirectories(root)) + { + string dirName = Path.GetFileName(directory); + + if (!Directory.Exists(Path.Combine(dest, dirName))) + { + Directory.CreateDirectory(Path.Combine(dest, dirName)); + } + + MoveAllFilesOver(directory, Path.Combine(dest, dirName), taskDialog); + } + + double count = 0; + foreach (string file in Directory.GetFiles(root)) + { + count++; + + File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true); + + Dispatcher.UIThread.InvokeAsync(() => + { + taskDialog.SetProgressBarState(GetPercentage(count, total), TaskDialogProgressState.Normal); + }); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx/Modules/Updater/Utils/RestartApplication.cs b/src/Ryujinx/Modules/Updater/Utils/RestartApplication.cs new file mode 100644 index 0000000000..f8e1437966 --- /dev/null +++ b/src/Ryujinx/Modules/Updater/Utils/RestartApplication.cs @@ -0,0 +1,68 @@ +using Avalonia.Controls; +using Ryujinx.UI.Common.Helper; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Ryujinx.Modules +{ + internal static partial class Updater + { + private static void RestartApplication(Window parent) + { + List arguments = CommandLineState.Arguments.ToList(); + string executableDirectory = AppDomain.CurrentDomain.BaseDirectory; + + if (OperatingSystem.IsMacOS()) + { + string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", "..")); + string newBundlePath = Path.Combine(_updateDir, "Ryujinx.app"); + string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh"); + string currentPid = Environment.ProcessId.ToString(); + + arguments.InsertRange(0, new List { updaterScriptPath, baseBundlePath, newBundlePath, currentPid }); + Process.Start("/bin/bash", arguments); + } + else + { + string ryuName = Path.GetFileName(Environment.ProcessPath) ?? string.Empty; + + // Migration: Start the updated binary. + // TODO: Remove this in a future update. + if (ryuName.StartsWith("Ryujinx.Ava")) + { + ryuName = ryuName.Replace(".Ava", ""); + } + + if (ryuName.EndsWith(".ryuold")) + { + ryuName = ryuName[..^7]; + } + + // Fallback if the executable could not be found. + if (ryuName.Length == 0 || !Path.Exists(Path.Combine(executableDirectory, ryuName))) + { + ryuName = OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx"; + } + + ProcessStartInfo processStart = new(ryuName) + { + UseShellExecute = true, + WorkingDirectory = executableDirectory, + }; + + foreach (string argument in arguments) + { + processStart.ArgumentList.Add(argument); + } + + Process.Start(processStart); + } + + Environment.Exit(0); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx/Modules/Updater/Utils/SendAsyncWithHeaders.cs b/src/Ryujinx/Modules/Updater/Utils/SendAsyncWithHeaders.cs new file mode 100644 index 0000000000..7e2b3bc1a0 --- /dev/null +++ b/src/Ryujinx/Modules/Updater/Utils/SendAsyncWithHeaders.cs @@ -0,0 +1,20 @@ +using System; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; + +namespace Ryujinx.Modules +{ + internal static partial class Updater + { + private static async Task SendAsyncWithHeaders(string url, RangeHeaderValue range = null) + { + using var request = new HttpRequestMessage(HttpMethod.Get, url); + if (range != null) + { + request.Headers.Range = range; + } + return await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx/Modules/Updater/Utils/ShowUpdateDialogAndExecute.cs b/src/Ryujinx/Modules/Updater/Utils/ShowUpdateDialogAndExecute.cs new file mode 100644 index 0000000000..d0dae128d9 --- /dev/null +++ b/src/Ryujinx/Modules/Updater/Utils/ShowUpdateDialogAndExecute.cs @@ -0,0 +1,33 @@ +using Avalonia.Controls; +using Ryujinx.Ava; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.UI.Common.Helper; +using System; +using System.Threading.Tasks; + +namespace Ryujinx.Modules +{ + internal static partial class Updater + { + private static async Task ShowUpdateDialogAndExecute(Window mainWindow) + { + var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog( + LocaleManager.Instance[LocaleKeys.RyujinxUpdater], + LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage], + $"{Program.Version} -> {_buildVer}"); + + if (shouldUpdate) + { + await UpdateRyujinx(mainWindow, _buildUrl); + } + else + { + _running = false; + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx/Modules/Updater/Utils/TryUpdateVersionInfo.cs b/src/Ryujinx/Modules/Updater/Utils/TryUpdateVersionInfo.cs new file mode 100644 index 0000000000..4d01dc7fc9 --- /dev/null +++ b/src/Ryujinx/Modules/Updater/Utils/TryUpdateVersionInfo.cs @@ -0,0 +1,56 @@ +using Ryujinx.Ava; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.UI.Common.Helper; +using Ryujinx.UI.Common.Models.Github; +using System; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Ryujinx.Modules +{ + internal static partial class Updater + { + private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + private static async Task TryUpdateVersionInfo(string buildInfoUrl, bool showVersionUpToDate) + { + try + { + HttpResponseMessage response = await SendAsyncWithHeaders(buildInfoUrl); + string fetchedJson = await response.Content.ReadAsStringAsync(); + var fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse); + _buildVer = fetched.Name; + + foreach (var asset in fetched.Assets) + { + if (asset.Name.StartsWith("ryujinx") && asset.Name.EndsWith(_platformExt) && asset.State == "uploaded") + { + _buildUrl = asset.BrowserDownloadUrl; + return true; + } + } + + if (_buildUrl == null && showVersionUpToDate) + { + await ContentDialogHelper.CreateUpdaterInfoDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], ""); + } + + _running = false; + return false; + } + catch (Exception exception) + { + Logger.Error?.Print(LogClass.Application, exception.Message); + await ContentDialogHelper.CreateErrorDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]); + _running = false; + return false; + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx/Modules/Updater/Utils/UpdateRyujinx.cs b/src/Ryujinx/Modules/Updater/Utils/UpdateRyujinx.cs new file mode 100644 index 0000000000..c90850c83b --- /dev/null +++ b/src/Ryujinx/Modules/Updater/Utils/UpdateRyujinx.cs @@ -0,0 +1,83 @@ +using Avalonia.Controls; +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.UI.Common.Helper; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Ryujinx.Modules +{ + internal static partial class Updater + { + private static bool _updateSuccessful; + + private static async Task UpdateRyujinx(Window parent, string downloadUrl) + { + _updateSuccessful = false; + + // Empty update dir, although it shouldn't ever have anything inside it + if (Directory.Exists(_updateDir)) + { + Directory.Delete(_updateDir, true); + } + + Directory.CreateDirectory(_updateDir); + + string updateFile = Path.Combine(_updateDir, "update.bin"); + + TaskDialog taskDialog = new() + { + Header = LocaleManager.Instance[LocaleKeys.RyujinxUpdater], + SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading], + IconSource = new SymbolIconSource { Symbol = Symbol.Download }, + ShowProgressBar = true, + XamlRoot = parent, + }; + + taskDialog.Opened += async (s, e) => + { + if (_buildSize >= 0) + { + await DoUpdateWithMultipleThreads(taskDialog, downloadUrl, updateFile); + } + else + { + DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); + } + }; + + await taskDialog.ShowAsync(true); + + if (_updateSuccessful) + { + bool shouldRestart = true; + + if (!OperatingSystem.IsMacOS()) + { + shouldRestart = await ContentDialogHelper.CreateChoiceDialog( + LocaleManager.Instance[LocaleKeys.RyujinxUpdater], + LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage], + LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]); + } + + if (shouldRestart) + { + RestartApplication(parent); + } + } + } + } +} \ No newline at end of file From 22c339f0a4c42aeacd8e9ea1e48addc5b0adee4b Mon Sep 17 00:00:00 2001 From: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com> Date: Wed, 17 Apr 2024 16:03:23 +0700 Subject: [PATCH 08/18] Newline moment --- src/Ryujinx/Modules/Updater/Utils/CanUpdate.cs | 2 +- src/Ryujinx/Modules/Updater/Utils/CleanupUpdate.cs | 2 +- src/Ryujinx/Modules/Updater/Utils/DetectPlatform.cs | 2 +- .../Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs | 2 +- src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs | 2 +- src/Ryujinx/Modules/Updater/Utils/EnumerateFilesToDelete.cs | 2 +- src/Ryujinx/Modules/Updater/Utils/ExtractTarGzipFile.cs | 2 +- src/Ryujinx/Modules/Updater/Utils/ExtractZipFile.cs | 2 +- src/Ryujinx/Modules/Updater/Utils/FetchBuildSizeInfo.cs | 2 +- src/Ryujinx/Modules/Updater/Utils/HandleVersionComparison.cs | 2 +- src/Ryujinx/Modules/Updater/Utils/InstallUpdate.cs | 2 +- src/Ryujinx/Modules/Updater/Utils/MoveAllFilesOver.cs | 2 +- src/Ryujinx/Modules/Updater/Utils/RestartApplication.cs | 2 +- src/Ryujinx/Modules/Updater/Utils/SendAsyncWithHeaders.cs | 2 +- src/Ryujinx/Modules/Updater/Utils/ShowUpdateDialogAndExecute.cs | 2 +- src/Ryujinx/Modules/Updater/Utils/TryUpdateVersionInfo.cs | 2 +- src/Ryujinx/Modules/Updater/Utils/UpdateRyujinx.cs | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Ryujinx/Modules/Updater/Utils/CanUpdate.cs b/src/Ryujinx/Modules/Updater/Utils/CanUpdate.cs index f021fb985d..5f60297d5d 100644 --- a/src/Ryujinx/Modules/Updater/Utils/CanUpdate.cs +++ b/src/Ryujinx/Modules/Updater/Utils/CanUpdate.cs @@ -67,4 +67,4 @@ namespace Ryujinx.Modules #endif } } -} \ No newline at end of file +} diff --git a/src/Ryujinx/Modules/Updater/Utils/CleanupUpdate.cs b/src/Ryujinx/Modules/Updater/Utils/CleanupUpdate.cs index bedca8efaf..e74f35161b 100644 --- a/src/Ryujinx/Modules/Updater/Utils/CleanupUpdate.cs +++ b/src/Ryujinx/Modules/Updater/Utils/CleanupUpdate.cs @@ -53,4 +53,4 @@ namespace Ryujinx.Modules } } } -} \ No newline at end of file +} diff --git a/src/Ryujinx/Modules/Updater/Utils/DetectPlatform.cs b/src/Ryujinx/Modules/Updater/Utils/DetectPlatform.cs index b08258eb20..a8019bbbc2 100644 --- a/src/Ryujinx/Modules/Updater/Utils/DetectPlatform.cs +++ b/src/Ryujinx/Modules/Updater/Utils/DetectPlatform.cs @@ -24,4 +24,4 @@ namespace Ryujinx.Modules } } } -} \ No newline at end of file +} diff --git a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs b/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs index 63c37351ec..d914cbe280 100644 --- a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs +++ b/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs @@ -120,4 +120,4 @@ namespace Ryujinx.Modules return memoryStream.ToArray(); } } -} \ No newline at end of file +} diff --git a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs b/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs index b6e6f5a7be..9fda861770 100644 --- a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs +++ b/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs @@ -75,4 +75,4 @@ namespace Ryujinx.Modules }); } } -} \ No newline at end of file +} diff --git a/src/Ryujinx/Modules/Updater/Utils/EnumerateFilesToDelete.cs b/src/Ryujinx/Modules/Updater/Utils/EnumerateFilesToDelete.cs index 00802bb0cf..128b34b8a8 100644 --- a/src/Ryujinx/Modules/Updater/Utils/EnumerateFilesToDelete.cs +++ b/src/Ryujinx/Modules/Updater/Utils/EnumerateFilesToDelete.cs @@ -44,4 +44,4 @@ namespace Ryujinx.Modules return files.Where(f => !new FileInfo(f).Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System)); } } -} \ No newline at end of file +} diff --git a/src/Ryujinx/Modules/Updater/Utils/ExtractTarGzipFile.cs b/src/Ryujinx/Modules/Updater/Utils/ExtractTarGzipFile.cs index a0f91b3544..e4053efb77 100644 --- a/src/Ryujinx/Modules/Updater/Utils/ExtractTarGzipFile.cs +++ b/src/Ryujinx/Modules/Updater/Utils/ExtractTarGzipFile.cs @@ -53,4 +53,4 @@ namespace Ryujinx.Modules } } } -} \ No newline at end of file +} diff --git a/src/Ryujinx/Modules/Updater/Utils/ExtractZipFile.cs b/src/Ryujinx/Modules/Updater/Utils/ExtractZipFile.cs index b184becad1..88bde18af6 100644 --- a/src/Ryujinx/Modules/Updater/Utils/ExtractZipFile.cs +++ b/src/Ryujinx/Modules/Updater/Utils/ExtractZipFile.cs @@ -41,4 +41,4 @@ namespace Ryujinx.Modules } } } -} \ No newline at end of file +} diff --git a/src/Ryujinx/Modules/Updater/Utils/FetchBuildSizeInfo.cs b/src/Ryujinx/Modules/Updater/Utils/FetchBuildSizeInfo.cs index 6fb48e5119..6ef894d0cd 100644 --- a/src/Ryujinx/Modules/Updater/Utils/FetchBuildSizeInfo.cs +++ b/src/Ryujinx/Modules/Updater/Utils/FetchBuildSizeInfo.cs @@ -27,4 +27,4 @@ namespace Ryujinx.Modules } } } -} \ No newline at end of file +} diff --git a/src/Ryujinx/Modules/Updater/Utils/HandleVersionComparison.cs b/src/Ryujinx/Modules/Updater/Utils/HandleVersionComparison.cs index 37dc32a8fb..6b0173d321 100644 --- a/src/Ryujinx/Modules/Updater/Utils/HandleVersionComparison.cs +++ b/src/Ryujinx/Modules/Updater/Utils/HandleVersionComparison.cs @@ -41,4 +41,4 @@ namespace Ryujinx.Modules } } } -} \ No newline at end of file +} diff --git a/src/Ryujinx/Modules/Updater/Utils/InstallUpdate.cs b/src/Ryujinx/Modules/Updater/Utils/InstallUpdate.cs index 61e4cc3355..0aac4a3bd4 100644 --- a/src/Ryujinx/Modules/Updater/Utils/InstallUpdate.cs +++ b/src/Ryujinx/Modules/Updater/Utils/InstallUpdate.cs @@ -89,4 +89,4 @@ namespace Ryujinx.Modules taskDialog.Hide(); } } -} \ No newline at end of file +} diff --git a/src/Ryujinx/Modules/Updater/Utils/MoveAllFilesOver.cs b/src/Ryujinx/Modules/Updater/Utils/MoveAllFilesOver.cs index 9f35dbe2d4..5ac4bd462d 100644 --- a/src/Ryujinx/Modules/Updater/Utils/MoveAllFilesOver.cs +++ b/src/Ryujinx/Modules/Updater/Utils/MoveAllFilesOver.cs @@ -36,4 +36,4 @@ namespace Ryujinx.Modules } } } -} \ No newline at end of file +} diff --git a/src/Ryujinx/Modules/Updater/Utils/RestartApplication.cs b/src/Ryujinx/Modules/Updater/Utils/RestartApplication.cs index f8e1437966..c9e74819e7 100644 --- a/src/Ryujinx/Modules/Updater/Utils/RestartApplication.cs +++ b/src/Ryujinx/Modules/Updater/Utils/RestartApplication.cs @@ -65,4 +65,4 @@ namespace Ryujinx.Modules Environment.Exit(0); } } -} \ No newline at end of file +} diff --git a/src/Ryujinx/Modules/Updater/Utils/SendAsyncWithHeaders.cs b/src/Ryujinx/Modules/Updater/Utils/SendAsyncWithHeaders.cs index 7e2b3bc1a0..ba2554a8b9 100644 --- a/src/Ryujinx/Modules/Updater/Utils/SendAsyncWithHeaders.cs +++ b/src/Ryujinx/Modules/Updater/Utils/SendAsyncWithHeaders.cs @@ -17,4 +17,4 @@ namespace Ryujinx.Modules return await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); } } -} \ No newline at end of file +} diff --git a/src/Ryujinx/Modules/Updater/Utils/ShowUpdateDialogAndExecute.cs b/src/Ryujinx/Modules/Updater/Utils/ShowUpdateDialogAndExecute.cs index d0dae128d9..475198fe91 100644 --- a/src/Ryujinx/Modules/Updater/Utils/ShowUpdateDialogAndExecute.cs +++ b/src/Ryujinx/Modules/Updater/Utils/ShowUpdateDialogAndExecute.cs @@ -30,4 +30,4 @@ namespace Ryujinx.Modules } } } -} \ No newline at end of file +} diff --git a/src/Ryujinx/Modules/Updater/Utils/TryUpdateVersionInfo.cs b/src/Ryujinx/Modules/Updater/Utils/TryUpdateVersionInfo.cs index 4d01dc7fc9..a661ef2c49 100644 --- a/src/Ryujinx/Modules/Updater/Utils/TryUpdateVersionInfo.cs +++ b/src/Ryujinx/Modules/Updater/Utils/TryUpdateVersionInfo.cs @@ -53,4 +53,4 @@ namespace Ryujinx.Modules } } } -} \ No newline at end of file +} diff --git a/src/Ryujinx/Modules/Updater/Utils/UpdateRyujinx.cs b/src/Ryujinx/Modules/Updater/Utils/UpdateRyujinx.cs index c90850c83b..d112ea461d 100644 --- a/src/Ryujinx/Modules/Updater/Utils/UpdateRyujinx.cs +++ b/src/Ryujinx/Modules/Updater/Utils/UpdateRyujinx.cs @@ -80,4 +80,4 @@ namespace Ryujinx.Modules } } } -} \ No newline at end of file +} From 18cdae513d07dffdb677f66917dd749216156856 Mon Sep 17 00:00:00 2001 From: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com> Date: Wed, 17 Apr 2024 16:18:09 +0700 Subject: [PATCH 09/18] Fixed imports ordering --- src/Ryujinx/Modules/Updater/Utils/GetCurrentVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ryujinx/Modules/Updater/Utils/GetCurrentVersion.cs b/src/Ryujinx/Modules/Updater/Utils/GetCurrentVersion.cs index a36bdc9c8a..485bc0dda5 100644 --- a/src/Ryujinx/Modules/Updater/Utils/GetCurrentVersion.cs +++ b/src/Ryujinx/Modules/Updater/Utils/GetCurrentVersion.cs @@ -1,7 +1,7 @@ -using System; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Common.Logging; +using System; using System.Threading; using System.Threading.Tasks; From 22e00bf42ab7f00fbf4c8519ee3776252f3fc146 Mon Sep 17 00:00:00 2001 From: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com> Date: Wed, 17 Apr 2024 16:38:16 +0700 Subject: [PATCH 10/18] Fixed CA1835 --- .../Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs | 8 +++++--- .../Modules/Updater/Utils/DoUpdateWithSingleThread.cs | 7 ++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs b/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs index d914cbe280..58d0a0923e 100644 --- a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs +++ b/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs @@ -95,18 +95,20 @@ namespace Ryujinx.Modules private static async Task DownloadFileChunk(string url, long start, long end, int index, TaskDialog taskDialog, int[] progressPercentage) { - byte[] buffer = new byte[8192]; + Memory buffer = new byte[8192]; + using var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Range = new RangeHeaderValue(start, end); HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); + using var stream = await response.Content.ReadAsStreamAsync(); using var memoryStream = new MemoryStream(); int bytesRead; long totalRead = 0; - while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0) + while ((bytesRead = await stream.ReadAsync(buffer, CancellationToken.None)) > 0) { - memoryStream.Write(buffer, 0, bytesRead); + memoryStream.Write(buffer.Slice(0, bytesRead).ToArray(), 0, bytesRead); totalRead += bytesRead; int progress = (int)((totalRead * 100) / (end - start + 1)); progressPercentage[index] = progress; diff --git a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs b/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs index 9fda861770..24121c5dcd 100644 --- a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs +++ b/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs @@ -26,6 +26,7 @@ namespace Ryujinx.Modules HttpResponseMessage response = await httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead); if (!response.IsSuccessStatusCode) { + Logger.Error?.Print(LogClass.Application, $"Failed to download file: {response.ReasonPhrase}"); throw new HttpRequestException($"Failed to download file: {response.ReasonPhrase}"); } @@ -36,12 +37,12 @@ namespace Ryujinx.Modules using Stream remoteFileStream = await response.Content.ReadAsStreamAsync(); using Stream updateFileStream = File.Open(updateFile, FileMode.Create); - byte[] buffer = new byte[32 * 1024]; + Memory buffer = new byte[32 * 1024]; int readSize; - while ((readSize = await remoteFileStream.ReadAsync(buffer, 0, buffer.Length)) > 0) + while ((readSize = await remoteFileStream.ReadAsync(buffer, CancellationToken.None)) > 0) { - updateFileStream.Write(buffer, 0, readSize); + updateFileStream.Write(buffer.Slice(0, readSize).ToArray(), 0, readSize); byteWritten += readSize; int progress = GetPercentage(byteWritten, totalBytes); From 3a1c722947908c5cef2c124a1688a21c89cf6d6e Mon Sep 17 00:00:00 2001 From: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com> Date: Wed, 17 Apr 2024 16:50:57 +0700 Subject: [PATCH 11/18] Fixed IDE1006 --- src/Ryujinx/Modules/Updater/Updater.cs | 2 +- .../Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs | 2 +- src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs | 4 ++-- src/Ryujinx/Modules/Updater/Utils/SendAsyncWithHeaders.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Ryujinx/Modules/Updater/Updater.cs b/src/Ryujinx/Modules/Updater/Updater.cs index bb4402fcd3..1ecde83c58 100644 --- a/src/Ryujinx/Modules/Updater/Updater.cs +++ b/src/Ryujinx/Modules/Updater/Updater.cs @@ -12,7 +12,7 @@ namespace Ryujinx.Modules { private const string GitHubApiUrl = "https://api.github.com"; - private static readonly HttpClient httpClient = new HttpClient + private static readonly HttpClient _httpClient = new HttpClient { DefaultRequestHeaders = { diff --git a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs b/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs index 58d0a0923e..054d775cd5 100644 --- a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs +++ b/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs @@ -99,7 +99,7 @@ namespace Ryujinx.Modules using var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Range = new RangeHeaderValue(start, end); - HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); + HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); using var stream = await response.Content.ReadAsStreamAsync(); using var memoryStream = new MemoryStream(); diff --git a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs b/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs index 24121c5dcd..5c54e16d70 100644 --- a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs +++ b/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs @@ -21,9 +21,9 @@ namespace Ryujinx.Modules private static async Task DoUpdateWithSingleThreadWorker(TaskDialog taskDialog, string downloadUrl, string updateFile) { // We do not want to timeout while downloading - httpClient.Timeout = TimeSpan.FromDays(1); + _httpClient.Timeout = TimeSpan.FromDays(1); - HttpResponseMessage response = await httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead); + HttpResponseMessage response = await _httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead); if (!response.IsSuccessStatusCode) { Logger.Error?.Print(LogClass.Application, $"Failed to download file: {response.ReasonPhrase}"); diff --git a/src/Ryujinx/Modules/Updater/Utils/SendAsyncWithHeaders.cs b/src/Ryujinx/Modules/Updater/Utils/SendAsyncWithHeaders.cs index ba2554a8b9..7228be23ae 100644 --- a/src/Ryujinx/Modules/Updater/Utils/SendAsyncWithHeaders.cs +++ b/src/Ryujinx/Modules/Updater/Utils/SendAsyncWithHeaders.cs @@ -14,7 +14,7 @@ namespace Ryujinx.Modules { request.Headers.Range = range; } - return await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); + return await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); } } } From 942f02ffbc053efeb46f2f2f46e3933212555578 Mon Sep 17 00:00:00 2001 From: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com> Date: Wed, 17 Apr 2024 17:18:45 +0700 Subject: [PATCH 12/18] Shush, IDE0057 Since Memory is initialized with a fixed size, `.Slice(0, readSize)` and `.Slice(0, bytesRead)` are necessary to ensure that only the part of the buffer that contains data is written to the file stream. So IDE0057 is not appropriate in this context. --- .../Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs | 2 ++ src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs b/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs index 054d775cd5..9df2f54d1e 100644 --- a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs +++ b/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs @@ -108,7 +108,9 @@ namespace Ryujinx.Modules while ((bytesRead = await stream.ReadAsync(buffer, CancellationToken.None)) > 0) { +#pragma warning disable IDE0057 // Disable the warning for unnecessary slicing memoryStream.Write(buffer.Slice(0, bytesRead).ToArray(), 0, bytesRead); +#pragma warning restore IDE0057 totalRead += bytesRead; int progress = (int)((totalRead * 100) / (end - start + 1)); progressPercentage[index] = progress; diff --git a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs b/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs index 5c54e16d70..2deff25f97 100644 --- a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs +++ b/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs @@ -42,7 +42,9 @@ namespace Ryujinx.Modules while ((readSize = await remoteFileStream.ReadAsync(buffer, CancellationToken.None)) > 0) { +#pragma warning disable IDE0057 // Disable the warning for unnecessary slicing updateFileStream.Write(buffer.Slice(0, readSize).ToArray(), 0, readSize); +#pragma warning restore IDE0057 byteWritten += readSize; int progress = GetPercentage(byteWritten, totalBytes); From 44d6e8aa9ca2c07b62b7bdd727f8a361519d4827 Mon Sep 17 00:00:00 2001 From: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com> Date: Wed, 17 Apr 2024 17:31:22 +0700 Subject: [PATCH 13/18] Forgot to import `Ryujinx.Common` --- src/Ryujinx/Modules/Updater/Updater.cs | 1 + src/Ryujinx/Modules/Updater/Utils/CanUpdate.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Ryujinx/Modules/Updater/Updater.cs b/src/Ryujinx/Modules/Updater/Updater.cs index 1ecde83c58..b33511e4e3 100644 --- a/src/Ryujinx/Modules/Updater/Updater.cs +++ b/src/Ryujinx/Modules/Updater/Updater.cs @@ -1,5 +1,6 @@ using Avalonia.Controls; using Avalonia.Threading; +using Ryujinx.Common; using System; using System.IO; using System.Net.Http; diff --git a/src/Ryujinx/Modules/Updater/Utils/CanUpdate.cs b/src/Ryujinx/Modules/Updater/Utils/CanUpdate.cs index 5f60297d5d..9e2102c6c4 100644 --- a/src/Ryujinx/Modules/Updater/Utils/CanUpdate.cs +++ b/src/Ryujinx/Modules/Updater/Utils/CanUpdate.cs @@ -2,6 +2,7 @@ using Avalonia.Threading; using FluentAvalonia.UI.Controls; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Common; using System; using System.Net.NetworkInformation; using System.Runtime.InteropServices; From c0c873243fba5e1bb7ee01bd1c40f3debc3a5bda Mon Sep 17 00:00:00 2001 From: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com> Date: Wed, 17 Apr 2024 20:19:19 +0700 Subject: [PATCH 14/18] Code organization --- .../DoUpdateWithMultipleThreads.cs | 2 +- .../DoUpdateWithSingleThread.cs | 24 +++---------------- .../CleanupUpdate.cs | 0 .../EnumerateFilesToDelete.cs | 2 -- .../ExtractTarGzipFile.cs | 0 .../ExtractZipFile.cs | 0 .../MoveAllFilesOver.cs | 0 .../{Utils => UpdateHelpers}/CanUpdate.cs | 0 .../FetchBuildSizeInfo.cs | 0 .../GetCurrentVersion.cs | 0 .../HandleVersionComparison.cs | 0 .../ShowUpdateDialogAndExecute.cs | 0 .../TryUpdateVersionInfo.cs | 0 .../{Utils => UpdateHelpers}/UpdateRyujinx.cs | 2 +- src/Ryujinx/Modules/Updater/Updater.cs | 2 ++ 15 files changed, 7 insertions(+), 25 deletions(-) rename src/Ryujinx/Modules/Updater/{Utils => DownloadHelpers}/DoUpdateWithMultipleThreads.cs (98%) rename src/Ryujinx/Modules/Updater/{Utils => DownloadHelpers}/DoUpdateWithSingleThread.cs (67%) rename src/Ryujinx/Modules/Updater/{Utils => FileOperations}/CleanupUpdate.cs (100%) rename src/Ryujinx/Modules/Updater/{Utils => FileOperations}/EnumerateFilesToDelete.cs (91%) rename src/Ryujinx/Modules/Updater/{Utils => FileOperations}/ExtractTarGzipFile.cs (100%) rename src/Ryujinx/Modules/Updater/{Utils => FileOperations}/ExtractZipFile.cs (100%) rename src/Ryujinx/Modules/Updater/{Utils => FileOperations}/MoveAllFilesOver.cs (100%) rename src/Ryujinx/Modules/Updater/{Utils => UpdateHelpers}/CanUpdate.cs (100%) rename src/Ryujinx/Modules/Updater/{Utils => UpdateHelpers}/FetchBuildSizeInfo.cs (100%) rename src/Ryujinx/Modules/Updater/{Utils => UpdateHelpers}/GetCurrentVersion.cs (100%) rename src/Ryujinx/Modules/Updater/{Utils => UpdateHelpers}/HandleVersionComparison.cs (100%) rename src/Ryujinx/Modules/Updater/{Utils => UpdateHelpers}/ShowUpdateDialogAndExecute.cs (100%) rename src/Ryujinx/Modules/Updater/{Utils => UpdateHelpers}/TryUpdateVersionInfo.cs (100%) rename src/Ryujinx/Modules/Updater/{Utils => UpdateHelpers}/UpdateRyujinx.cs (96%) diff --git a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs b/src/Ryujinx/Modules/Updater/DownloadHelpers/DoUpdateWithMultipleThreads.cs similarity index 98% rename from src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs rename to src/Ryujinx/Modules/Updater/DownloadHelpers/DoUpdateWithMultipleThreads.cs index 9df2f54d1e..f75139aa0a 100644 --- a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithMultipleThreads.cs +++ b/src/Ryujinx/Modules/Updater/DownloadHelpers/DoUpdateWithMultipleThreads.cs @@ -71,7 +71,7 @@ namespace Ryujinx.Modules { Logger.Warning?.Print(LogClass.Application, e.Message); Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater."); - DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); + await DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); } }); } diff --git a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs b/src/Ryujinx/Modules/Updater/DownloadHelpers/DoUpdateWithSingleThread.cs similarity index 67% rename from src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs rename to src/Ryujinx/Modules/Updater/DownloadHelpers/DoUpdateWithSingleThread.cs index 2deff25f97..5da83fff35 100644 --- a/src/Ryujinx/Modules/Updater/Utils/DoUpdateWithSingleThread.cs +++ b/src/Ryujinx/Modules/Updater/DownloadHelpers/DoUpdateWithSingleThread.cs @@ -18,7 +18,7 @@ namespace Ryujinx.Modules { internal static partial class Updater { - private static async Task DoUpdateWithSingleThreadWorker(TaskDialog taskDialog, string downloadUrl, string updateFile) + private static async Task DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile) { // We do not want to timeout while downloading _httpClient.Timeout = TimeSpan.FromDays(1); @@ -42,40 +42,22 @@ namespace Ryujinx.Modules while ((readSize = await remoteFileStream.ReadAsync(buffer, CancellationToken.None)) > 0) { -#pragma warning disable IDE0057 // Disable the warning for unnecessary slicing - updateFileStream.Write(buffer.Slice(0, readSize).ToArray(), 0, readSize); -#pragma warning restore IDE0057 + updateFileStream.Write(buffer.Span.Slice(0, readSize)); byteWritten += readSize; - int progress = GetPercentage(byteWritten, totalBytes); Dispatcher.UIThread.Post(() => { - taskDialog.SetProgressBarState(progress, TaskDialogProgressState.Normal); + taskDialog.SetProgressBarState(GetPercentage(byteWritten, totalBytes), TaskDialogProgressState.Normal); }); } await InstallUpdate(taskDialog, updateFile); } - private static int GetPercentage(long value, long total) - { - if (total == 0) - return 0; - return (int)((value * 100) / total); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static double GetPercentage(double value, double max) { return max == 0 ? 0 : value / max * 100; } - - private static void DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile) - { - Task.Run(async () => - { - await DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile); - }); - } } } diff --git a/src/Ryujinx/Modules/Updater/Utils/CleanupUpdate.cs b/src/Ryujinx/Modules/Updater/FileOperations/CleanupUpdate.cs similarity index 100% rename from src/Ryujinx/Modules/Updater/Utils/CleanupUpdate.cs rename to src/Ryujinx/Modules/Updater/FileOperations/CleanupUpdate.cs diff --git a/src/Ryujinx/Modules/Updater/Utils/EnumerateFilesToDelete.cs b/src/Ryujinx/Modules/Updater/FileOperations/EnumerateFilesToDelete.cs similarity index 91% rename from src/Ryujinx/Modules/Updater/Utils/EnumerateFilesToDelete.cs rename to src/Ryujinx/Modules/Updater/FileOperations/EnumerateFilesToDelete.cs index 128b34b8a8..c94245cca2 100644 --- a/src/Ryujinx/Modules/Updater/Utils/EnumerateFilesToDelete.cs +++ b/src/Ryujinx/Modules/Updater/FileOperations/EnumerateFilesToDelete.cs @@ -8,8 +8,6 @@ namespace Ryujinx.Modules { internal static partial class Updater { - private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory; - private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish"); private static readonly string[] _windowsDependencyDirs = Array.Empty(); // NOTE: This method should always reflect the latest build layout. diff --git a/src/Ryujinx/Modules/Updater/Utils/ExtractTarGzipFile.cs b/src/Ryujinx/Modules/Updater/FileOperations/ExtractTarGzipFile.cs similarity index 100% rename from src/Ryujinx/Modules/Updater/Utils/ExtractTarGzipFile.cs rename to src/Ryujinx/Modules/Updater/FileOperations/ExtractTarGzipFile.cs diff --git a/src/Ryujinx/Modules/Updater/Utils/ExtractZipFile.cs b/src/Ryujinx/Modules/Updater/FileOperations/ExtractZipFile.cs similarity index 100% rename from src/Ryujinx/Modules/Updater/Utils/ExtractZipFile.cs rename to src/Ryujinx/Modules/Updater/FileOperations/ExtractZipFile.cs diff --git a/src/Ryujinx/Modules/Updater/Utils/MoveAllFilesOver.cs b/src/Ryujinx/Modules/Updater/FileOperations/MoveAllFilesOver.cs similarity index 100% rename from src/Ryujinx/Modules/Updater/Utils/MoveAllFilesOver.cs rename to src/Ryujinx/Modules/Updater/FileOperations/MoveAllFilesOver.cs diff --git a/src/Ryujinx/Modules/Updater/Utils/CanUpdate.cs b/src/Ryujinx/Modules/Updater/UpdateHelpers/CanUpdate.cs similarity index 100% rename from src/Ryujinx/Modules/Updater/Utils/CanUpdate.cs rename to src/Ryujinx/Modules/Updater/UpdateHelpers/CanUpdate.cs diff --git a/src/Ryujinx/Modules/Updater/Utils/FetchBuildSizeInfo.cs b/src/Ryujinx/Modules/Updater/UpdateHelpers/FetchBuildSizeInfo.cs similarity index 100% rename from src/Ryujinx/Modules/Updater/Utils/FetchBuildSizeInfo.cs rename to src/Ryujinx/Modules/Updater/UpdateHelpers/FetchBuildSizeInfo.cs diff --git a/src/Ryujinx/Modules/Updater/Utils/GetCurrentVersion.cs b/src/Ryujinx/Modules/Updater/UpdateHelpers/GetCurrentVersion.cs similarity index 100% rename from src/Ryujinx/Modules/Updater/Utils/GetCurrentVersion.cs rename to src/Ryujinx/Modules/Updater/UpdateHelpers/GetCurrentVersion.cs diff --git a/src/Ryujinx/Modules/Updater/Utils/HandleVersionComparison.cs b/src/Ryujinx/Modules/Updater/UpdateHelpers/HandleVersionComparison.cs similarity index 100% rename from src/Ryujinx/Modules/Updater/Utils/HandleVersionComparison.cs rename to src/Ryujinx/Modules/Updater/UpdateHelpers/HandleVersionComparison.cs diff --git a/src/Ryujinx/Modules/Updater/Utils/ShowUpdateDialogAndExecute.cs b/src/Ryujinx/Modules/Updater/UpdateHelpers/ShowUpdateDialogAndExecute.cs similarity index 100% rename from src/Ryujinx/Modules/Updater/Utils/ShowUpdateDialogAndExecute.cs rename to src/Ryujinx/Modules/Updater/UpdateHelpers/ShowUpdateDialogAndExecute.cs diff --git a/src/Ryujinx/Modules/Updater/Utils/TryUpdateVersionInfo.cs b/src/Ryujinx/Modules/Updater/UpdateHelpers/TryUpdateVersionInfo.cs similarity index 100% rename from src/Ryujinx/Modules/Updater/Utils/TryUpdateVersionInfo.cs rename to src/Ryujinx/Modules/Updater/UpdateHelpers/TryUpdateVersionInfo.cs diff --git a/src/Ryujinx/Modules/Updater/Utils/UpdateRyujinx.cs b/src/Ryujinx/Modules/Updater/UpdateHelpers/UpdateRyujinx.cs similarity index 96% rename from src/Ryujinx/Modules/Updater/Utils/UpdateRyujinx.cs rename to src/Ryujinx/Modules/Updater/UpdateHelpers/UpdateRyujinx.cs index d112ea461d..b69e3aa9ca 100644 --- a/src/Ryujinx/Modules/Updater/Utils/UpdateRyujinx.cs +++ b/src/Ryujinx/Modules/Updater/UpdateHelpers/UpdateRyujinx.cs @@ -55,7 +55,7 @@ namespace Ryujinx.Modules } else { - DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); + await DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); } }; diff --git a/src/Ryujinx/Modules/Updater/Updater.cs b/src/Ryujinx/Modules/Updater/Updater.cs index b33511e4e3..cbe6e0aba4 100644 --- a/src/Ryujinx/Modules/Updater/Updater.cs +++ b/src/Ryujinx/Modules/Updater/Updater.cs @@ -21,7 +21,9 @@ namespace Ryujinx.Modules } }; + 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 static bool _running; From a29f1675931816512e94cffa5254ad9079d5c38a Mon Sep 17 00:00:00 2001 From: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com> Date: Wed, 17 Apr 2024 20:34:27 +0700 Subject: [PATCH 15/18] OK nevermind found a way to fix IDE0057 --- .../DoUpdateWithMultipleThreads.cs | 16 ++++++++++------ .../DownloadHelpers/DoUpdateWithSingleThread.cs | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Ryujinx/Modules/Updater/DownloadHelpers/DoUpdateWithMultipleThreads.cs b/src/Ryujinx/Modules/Updater/DownloadHelpers/DoUpdateWithMultipleThreads.cs index f75139aa0a..2599901930 100644 --- a/src/Ryujinx/Modules/Updater/DownloadHelpers/DoUpdateWithMultipleThreads.cs +++ b/src/Ryujinx/Modules/Updater/DownloadHelpers/DoUpdateWithMultipleThreads.cs @@ -105,20 +105,24 @@ namespace Ryujinx.Modules using var memoryStream = new MemoryStream(); int bytesRead; long totalRead = 0; + int lastReportedProgress = -1; while ((bytesRead = await stream.ReadAsync(buffer, CancellationToken.None)) > 0) { -#pragma warning disable IDE0057 // Disable the warning for unnecessary slicing - memoryStream.Write(buffer.Slice(0, bytesRead).ToArray(), 0, bytesRead); -#pragma warning restore IDE0057 + memoryStream.Write(buffer.Span[..bytesRead]); totalRead += bytesRead; int progress = (int)((totalRead * 100) / (end - start + 1)); progressPercentage[index] = progress; - Dispatcher.UIThread.Post(() => + // Throttle UI updates to only fire when there is a change in progress percentage + if (progress != lastReportedProgress) { - taskDialog.SetProgressBarState(progressPercentage.Sum() / ConnectionCount, TaskDialogProgressState.Normal); - }); + lastReportedProgress = progress; + Dispatcher.UIThread.Post(() => + { + taskDialog.SetProgressBarState(progressPercentage.Sum() / ConnectionCount, TaskDialogProgressState.Normal); + }); + } } return memoryStream.ToArray(); diff --git a/src/Ryujinx/Modules/Updater/DownloadHelpers/DoUpdateWithSingleThread.cs b/src/Ryujinx/Modules/Updater/DownloadHelpers/DoUpdateWithSingleThread.cs index 5da83fff35..7afa1503ad 100644 --- a/src/Ryujinx/Modules/Updater/DownloadHelpers/DoUpdateWithSingleThread.cs +++ b/src/Ryujinx/Modules/Updater/DownloadHelpers/DoUpdateWithSingleThread.cs @@ -42,7 +42,7 @@ namespace Ryujinx.Modules while ((readSize = await remoteFileStream.ReadAsync(buffer, CancellationToken.None)) > 0) { - updateFileStream.Write(buffer.Span.Slice(0, readSize)); + updateFileStream.Write(buffer.Span[..readSize]); byteWritten += readSize; Dispatcher.UIThread.Post(() => From c63053adf27b227488c2283faa9f139318319021 Mon Sep 17 00:00:00 2001 From: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com> Date: Thu, 18 Apr 2024 13:16:01 +0700 Subject: [PATCH 16/18] Remove temporary code --- src/Ryujinx/Modules/Updater/UpdateHelpers/CanUpdate.cs | 5 +++-- .../Modules/Updater/UpdateHelpers/GetCurrentVersion.cs | 4 ++-- src/Ryujinx/Modules/Updater/Updater.cs | 3 +-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Ryujinx/Modules/Updater/UpdateHelpers/CanUpdate.cs b/src/Ryujinx/Modules/Updater/UpdateHelpers/CanUpdate.cs index 9e2102c6c4..07a22b3243 100644 --- a/src/Ryujinx/Modules/Updater/UpdateHelpers/CanUpdate.cs +++ b/src/Ryujinx/Modules/Updater/UpdateHelpers/CanUpdate.cs @@ -1,5 +1,6 @@ using Avalonia.Threading; using FluentAvalonia.UI.Controls; +using Ryujinx.Ava; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Common; @@ -28,7 +29,7 @@ namespace Ryujinx.Modules return false; } - /*if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid) + if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid) { if (showWarnings) { @@ -40,7 +41,7 @@ namespace Ryujinx.Modules } return false; - }*/ // Temporary commented out, will revert back + } return true; #else diff --git a/src/Ryujinx/Modules/Updater/UpdateHelpers/GetCurrentVersion.cs b/src/Ryujinx/Modules/Updater/UpdateHelpers/GetCurrentVersion.cs index 485bc0dda5..aa929a45df 100644 --- a/src/Ryujinx/Modules/Updater/UpdateHelpers/GetCurrentVersion.cs +++ b/src/Ryujinx/Modules/Updater/UpdateHelpers/GetCurrentVersion.cs @@ -1,3 +1,4 @@ +using Ryujinx.Ava; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Common.Logging; @@ -15,8 +16,7 @@ namespace Ryujinx.Modules { try { - //return Version.Parse(Program.Version); - return Version.Parse("1.1.0"); // Temporary code, will revert back + return Version.Parse(Program.Version); } catch { diff --git a/src/Ryujinx/Modules/Updater/Updater.cs b/src/Ryujinx/Modules/Updater/Updater.cs index cbe6e0aba4..b9263b2210 100644 --- a/src/Ryujinx/Modules/Updater/Updater.cs +++ b/src/Ryujinx/Modules/Updater/Updater.cs @@ -44,8 +44,7 @@ namespace Ryujinx.Modules return; } - //string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest"; - string buildInfoUrl = $"{GitHubApiUrl}/repos/Ryujinx/release-channel-master/releases/latest"; // Temporary code, will revert back + string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest"; if (!await TryUpdateVersionInfo(buildInfoUrl, showVersionUpToDate)) { return; From 1f49d59e6b98a52cd4d502cc6d4d587e47096839 Mon Sep 17 00:00:00 2001 From: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com> Date: Fri, 5 Jul 2024 13:56:43 +0700 Subject: [PATCH 17/18] add cache deletion for closed PRs --- .github/workflows/checks.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 2bef2d8e00..ab45b6d5fa 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -11,6 +11,10 @@ on: - '!*.md' - '.github/workflows/*.yml' + pull_request_target: + branches: [ master ] + types: [closed] + permissions: pull-requests: write checks: write @@ -72,3 +76,29 @@ jobs: uses: ./.github/workflows/build.yml needs: format secrets: inherit + + cleanup: + if: github.event.pull_request.merged == false + runs-on: ubuntu-latest + steps: + - name: Setup gh extension + run: | + gh extension install actions/gh-actions-cache + + - name: Cleanup + run: | + echo "Fetching list of cache keys" + cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 ) + + ## Setting this to not fail the workflow while deleting cache keys. + set +e + echo "Deleting caches..." + for cacheKey in $cacheKeysForPR + do + gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm + done + echo "Done" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge From 3baca6b0fe9b9e59b131a22292dd908bfad93565 Mon Sep 17 00:00:00 2001 From: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com> Date: Fri, 5 Jul 2024 13:58:30 +0700 Subject: [PATCH 18/18] Update checks.yml --- .github/workflows/checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index ab45b6d5fa..b29cf23d28 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -99,6 +99,6 @@ jobs: done echo "Done" env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ github.token }} REPO: ${{ github.repository }} BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge