From 7e58b21f3d1b0ef69121b66e91488e1529ee5719 Mon Sep 17 00:00:00 2001 From: TSRBerry <20988865+TSRBerry@users.noreply.github.com> Date: Sat, 13 Jan 2024 11:45:38 +0100 Subject: [PATCH] Fix Amiibo regression and some minor code improvements (#6107) * Remove redundant code and fix small issues * Log amiibo exceptions * Add more checks when getting Amiibo data * Fall back to online data if local file is inaccessible * Make dotnet format happy --- .../UI/ViewModels/AmiiboWindowViewModel.cs | 86 ++++++++++---- src/Ryujinx/Ui/Windows/AmiiboWindow.cs | 110 ++++++++++++------ 2 files changed, 138 insertions(+), 58 deletions(-) diff --git a/src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs index c41e8bcc2..0e0d858a4 100644 --- a/src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs +++ b/src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs @@ -17,6 +17,7 @@ using System.IO; using System.Linq; using System.Net.Http; using System.Text; +using System.Text.Json; using System.Threading.Tasks; namespace Ryujinx.Ava.UI.ViewModels @@ -188,17 +189,25 @@ namespace Ryujinx.Ava.UI.ViewModels _httpClient.Dispose(); } - private bool TryGetAmiiboJson(string json, out AmiiboJson amiiboJson) + private static bool TryGetAmiiboJson(string json, out AmiiboJson amiiboJson) { + if (string.IsNullOrEmpty(json)) + { + amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson); + + return false; + } + try { - amiiboJson = JsonHelper.Deserialize(json, _serializerContext.AmiiboJson); + amiiboJson = JsonHelper.Deserialize(json, _serializerContext.AmiiboJson); return true; } - catch + catch (JsonException exception) { - amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson); + Logger.Error?.Print(LogClass.Application, $"Unable to deserialize amiibo data: {exception}"); + amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson); return false; } @@ -208,27 +217,41 @@ namespace Ryujinx.Ava.UI.ViewModels { bool localIsValid = false; bool remoteIsValid = false; - AmiiboJson amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson); + AmiiboJson amiiboJson = new(); try { - localIsValid = TryGetAmiiboJson(File.ReadAllText(_amiiboJsonPath), out amiiboJson); + try + { + if (File.Exists(_amiiboJsonPath)) + { + localIsValid = TryGetAmiiboJson(await File.ReadAllTextAsync(_amiiboJsonPath), out amiiboJson); + } + } + catch (Exception exception) + { + Logger.Warning?.Print(LogClass.Application, $"Unable to read data from '{_amiiboJsonPath}': {exception}"); + } if (!localIsValid || await NeedsUpdate(amiiboJson.LastUpdated)) { remoteIsValid = TryGetAmiiboJson(await DownloadAmiiboJson(), out amiiboJson); } } - catch + catch (Exception exception) { if (!(localIsValid || remoteIsValid)) { + Logger.Error?.Print(LogClass.Application, $"Couldn't get valid amiibo data: {exception}"); + // Neither local or remote files are valid JSON, close window. ShowInfoDialog(); Close(); } else if (!remoteIsValid) { + Logger.Warning?.Print(LogClass.Application, $"Couldn't update amiibo data: {exception}"); + // Only the local file is valid, the local one should be used // but the user should be warned. ShowInfoDialog(); @@ -388,11 +411,18 @@ namespace Ryujinx.Ava.UI.ViewModels private async Task NeedsUpdate(DateTime oldLastModified) { - HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://amiibo.ryujinx.org/")); - - if (response.IsSuccessStatusCode) + try { - return response.Content.Headers.LastModified != oldLastModified; + HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://amiibo.ryujinx.org/")); + + if (response.IsSuccessStatusCode) + { + return response.Content.Headers.LastModified != oldLastModified; + } + } + catch (HttpRequestException exception) + { + Logger.Error?.Print(LogClass.Application, $"Unable to check for amiibo data updates: {exception}"); } return false; @@ -400,21 +430,33 @@ namespace Ryujinx.Ava.UI.ViewModels private async Task DownloadAmiiboJson() { - HttpResponseMessage response = await _httpClient.GetAsync("https://amiibo.ryujinx.org/"); - - if (response.IsSuccessStatusCode) + try { - string amiiboJsonString = await response.Content.ReadAsStringAsync(); + HttpResponseMessage response = await _httpClient.GetAsync("https://amiibo.ryujinx.org/"); - using (FileStream amiiboJsonStream = File.Create(_amiiboJsonPath, 4096, FileOptions.WriteThrough)) + if (response.IsSuccessStatusCode) { - amiiboJsonStream.Write(Encoding.UTF8.GetBytes(amiiboJsonString)); + string amiiboJsonString = await response.Content.ReadAsStringAsync(); + + try + { + using FileStream dlcJsonStream = File.Create(_amiiboJsonPath, 4096, FileOptions.WriteThrough); + dlcJsonStream.Write(Encoding.UTF8.GetBytes(amiiboJsonString)); + } + catch (Exception exception) + { + Logger.Warning?.Print(LogClass.Application, $"Couldn't write amiibo data to file '{_amiiboJsonPath}: {exception}'"); + } + + return amiiboJsonString; } - return amiiboJsonString; + Logger.Error?.Print(LogClass.Application, $"Failed to download amiibo data. Response status code: {response.StatusCode}"); + } + catch (HttpRequestException exception) + { + Logger.Error?.Print(LogClass.Application, $"Failed to request amiibo data: {exception}"); } - - Logger.Error?.Print(LogClass.Application, $"Failed to download amiibo data. Response status code: {response.StatusCode}"); await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle], LocaleManager.Instance[LocaleKeys.DialogAmiiboApiFailFetchMessage], @@ -422,9 +464,7 @@ namespace Ryujinx.Ava.UI.ViewModels "", LocaleManager.Instance[LocaleKeys.RyujinxInfo]); - Close(); - - return DefaultJson; + return null; } private void Close() diff --git a/src/Ryujinx/Ui/Windows/AmiiboWindow.cs b/src/Ryujinx/Ui/Windows/AmiiboWindow.cs index 2673f9121..a2a5bce6f 100644 --- a/src/Ryujinx/Ui/Windows/AmiiboWindow.cs +++ b/src/Ryujinx/Ui/Windows/AmiiboWindow.cs @@ -1,3 +1,4 @@ +using Gdk; using Gtk; using Ryujinx.Common; using Ryujinx.Common.Configuration; @@ -13,7 +14,9 @@ using System.Linq; using System.Net.Http; using System.Reflection; using System.Text; +using System.Text.Json; using System.Threading.Tasks; +using Window = Gtk.Window; namespace Ryujinx.Ui.Windows { @@ -49,11 +52,11 @@ namespace Ryujinx.Ui.Windows public AmiiboWindow() : base($"Ryujinx {Program.Version} - Amiibo") { - Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png"); + Icon = new Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png"); InitializeComponent(); - _httpClient = new HttpClient() + _httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(30), }; @@ -64,7 +67,7 @@ namespace Ryujinx.Ui.Windows _amiiboList = new List(); _amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png"); - _amiiboImage.Pixbuf = new Gdk.Pixbuf(_amiiboLogoBytes); + _amiiboImage.Pixbuf = new Pixbuf(_amiiboLogoBytes); _scanButton.Sensitive = false; _randomUuidCheckBox.Sensitive = false; @@ -72,17 +75,25 @@ namespace Ryujinx.Ui.Windows _ = LoadContentAsync(); } - private bool TryGetAmiiboJson(string json, out AmiiboJson amiiboJson) + private static bool TryGetAmiiboJson(string json, out AmiiboJson amiiboJson) { + if (string.IsNullOrEmpty(json)) + { + amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson); + + return false; + } + try { - amiiboJson = JsonHelper.Deserialize(json, _serializerContext.AmiiboJson); + amiiboJson = JsonHelper.Deserialize(json, _serializerContext.AmiiboJson); return true; } - catch + catch (JsonException exception) { - amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson); + Logger.Error?.Print(LogClass.Application, $"Unable to deserialize amiibo data: {exception}"); + amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson); return false; } @@ -92,27 +103,41 @@ namespace Ryujinx.Ui.Windows { bool localIsValid = false; bool remoteIsValid = false; - AmiiboJson amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson); + AmiiboJson amiiboJson = new(); try { - localIsValid = TryGetAmiiboJson(File.ReadAllText(_amiiboJsonPath), out amiiboJson); + try + { + if (File.Exists(_amiiboJsonPath)) + { + localIsValid = TryGetAmiiboJson(await File.ReadAllTextAsync(_amiiboJsonPath), out amiiboJson); + } + } + catch (Exception exception) + { + Logger.Warning?.Print(LogClass.Application, $"Unable to read data from '{_amiiboJsonPath}': {exception}"); + } if (!localIsValid || await NeedsUpdate(amiiboJson.LastUpdated)) { remoteIsValid = TryGetAmiiboJson(await DownloadAmiiboJson(), out amiiboJson); } } - catch + catch (Exception exception) { if (!(localIsValid || remoteIsValid)) { + Logger.Error?.Print(LogClass.Application, $"Couldn't get valid amiibo data: {exception}"); + // Neither local or remote files are valid JSON, close window. ShowInfoDialog(); Close(); } else if (!remoteIsValid) { + Logger.Warning?.Print(LogClass.Application, $"Couldn't update amiibo data: {exception}"); + // Only the local file is valid, the local one should be used // but the user should be warned. ShowInfoDialog(); @@ -196,11 +221,18 @@ namespace Ryujinx.Ui.Windows private async Task NeedsUpdate(DateTime oldLastModified) { - HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://amiibo.ryujinx.org/")); - - if (response.IsSuccessStatusCode) + try { - return response.Content.Headers.LastModified != oldLastModified; + HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://amiibo.ryujinx.org/")); + + if (response.IsSuccessStatusCode) + { + return response.Content.Headers.LastModified != oldLastModified; + } + } + catch (HttpRequestException exception) + { + Logger.Error?.Print(LogClass.Application, $"Unable to check for amiibo data updates: {exception}"); } return false; @@ -208,29 +240,37 @@ namespace Ryujinx.Ui.Windows private async Task DownloadAmiiboJson() { - HttpResponseMessage response = await _httpClient.GetAsync("https://amiibo.ryujinx.org/"); - - if (response.IsSuccessStatusCode) + try { - string amiiboJsonString = await response.Content.ReadAsStringAsync(); + HttpResponseMessage response = await _httpClient.GetAsync("https://amiibo.ryujinx.org/"); - using (FileStream dlcJsonStream = File.Create(_amiiboJsonPath, 4096, FileOptions.WriteThrough)) + if (response.IsSuccessStatusCode) { - dlcJsonStream.Write(Encoding.UTF8.GetBytes(amiiboJsonString)); + string amiiboJsonString = await response.Content.ReadAsStringAsync(); + + try + { + using FileStream dlcJsonStream = File.Create(_amiiboJsonPath, 4096, FileOptions.WriteThrough); + dlcJsonStream.Write(Encoding.UTF8.GetBytes(amiiboJsonString)); + } + catch (Exception exception) + { + Logger.Warning?.Print(LogClass.Application, $"Couldn't write amiibo data to file '{_amiiboJsonPath}: {exception}'"); + } + + return amiiboJsonString; } - return amiiboJsonString; - } - else - { Logger.Error?.Print(LogClass.Application, $"Failed to download amiibo data. Response status code: {response.StatusCode}"); - - GtkDialog.CreateInfoDialog($"Amiibo API", "An error occured while fetching information from the API."); - - Close(); + } + catch (HttpRequestException exception) + { + Logger.Error?.Print(LogClass.Application, $"Failed to request amiibo data: {exception}"); } - return DefaultJson; + GtkDialog.CreateInfoDialog("Amiibo API", "An error occured while fetching information from the API."); + + return null; } private async Task UpdateAmiiboPreview(string imageUrl) @@ -240,7 +280,7 @@ namespace Ryujinx.Ui.Windows if (response.IsSuccessStatusCode) { byte[] amiiboPreviewBytes = await response.Content.ReadAsByteArrayAsync(); - Gdk.Pixbuf amiiboPreview = new(amiiboPreviewBytes); + Pixbuf amiiboPreview = new(amiiboPreviewBytes); float ratio = Math.Min((float)_amiiboImage.AllocatedWidth / amiiboPreview.Width, (float)_amiiboImage.AllocatedHeight / amiiboPreview.Height); @@ -248,7 +288,7 @@ namespace Ryujinx.Ui.Windows int resizeHeight = (int)(amiiboPreview.Height * ratio); int resizeWidth = (int)(amiiboPreview.Width * ratio); - _amiiboImage.Pixbuf = amiiboPreview.ScaleSimple(resizeWidth, resizeHeight, Gdk.InterpType.Bilinear); + _amiiboImage.Pixbuf = amiiboPreview.ScaleSimple(resizeWidth, resizeHeight, InterpType.Bilinear); } else { @@ -258,7 +298,7 @@ namespace Ryujinx.Ui.Windows private static void ShowInfoDialog() { - GtkDialog.CreateInfoDialog($"Amiibo API", "Unable to connect to Amiibo API server. The service may be down or you may need to verify your internet connection is online."); + GtkDialog.CreateInfoDialog("Amiibo API", "Unable to connect to Amiibo API server. The service may be down or you may need to verify your internet connection is online."); } // @@ -314,7 +354,7 @@ namespace Ryujinx.Ui.Windows { AmiiboId = _amiiboCharsComboBox.ActiveId; - _amiiboImage.Pixbuf = new Gdk.Pixbuf(_amiiboLogoBytes); + _amiiboImage.Pixbuf = new Pixbuf(_amiiboLogoBytes); string imageUrl = _amiiboList.Find(amiibo => amiibo.Head + amiibo.Tail == _amiiboCharsComboBox.ActiveId).Image; @@ -354,7 +394,7 @@ namespace Ryujinx.Ui.Windows private void ShowAllCheckBox_Clicked(object sender, EventArgs e) { - _amiiboImage.Pixbuf = new Gdk.Pixbuf(_amiiboLogoBytes); + _amiiboImage.Pixbuf = new Pixbuf(_amiiboLogoBytes); _amiiboSeriesComboBox.Changed -= SeriesComboBox_Changed; _amiiboCharsComboBox.Changed -= CharacterComboBox_Changed; @@ -365,7 +405,7 @@ namespace Ryujinx.Ui.Windows _scanButton.Sensitive = false; _randomUuidCheckBox.Sensitive = false; - new Task(() => ParseAmiiboData()).Start(); + new Task(ParseAmiiboData).Start(); } private void ScanButton_Pressed(object sender, EventArgs args)