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
This commit is contained in:
TSRBerry 2024-01-13 11:45:38 +01:00 committed by GitHub
parent 4fbc978e73
commit 7e58b21f3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 138 additions and 58 deletions

View File

@ -17,6 +17,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.ViewModels namespace Ryujinx.Ava.UI.ViewModels
@ -188,17 +189,25 @@ namespace Ryujinx.Ava.UI.ViewModels
_httpClient.Dispose(); _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 try
{ {
amiiboJson = JsonHelper.Deserialize<AmiiboJson>(json, _serializerContext.AmiiboJson); amiiboJson = JsonHelper.Deserialize(json, _serializerContext.AmiiboJson);
return true; return true;
} }
catch catch (JsonException exception)
{ {
amiiboJson = JsonHelper.Deserialize<AmiiboJson>(DefaultJson, _serializerContext.AmiiboJson); Logger.Error?.Print(LogClass.Application, $"Unable to deserialize amiibo data: {exception}");
amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson);
return false; return false;
} }
@ -208,27 +217,41 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
bool localIsValid = false; bool localIsValid = false;
bool remoteIsValid = false; bool remoteIsValid = false;
AmiiboJson amiiboJson = JsonHelper.Deserialize<AmiiboJson>(DefaultJson, _serializerContext.AmiiboJson); AmiiboJson amiiboJson = new();
try 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)) if (!localIsValid || await NeedsUpdate(amiiboJson.LastUpdated))
{ {
remoteIsValid = TryGetAmiiboJson(await DownloadAmiiboJson(), out amiiboJson); remoteIsValid = TryGetAmiiboJson(await DownloadAmiiboJson(), out amiiboJson);
} }
} }
catch catch (Exception exception)
{ {
if (!(localIsValid || remoteIsValid)) 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. // Neither local or remote files are valid JSON, close window.
ShowInfoDialog(); ShowInfoDialog();
Close(); Close();
} }
else if (!remoteIsValid) 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 // Only the local file is valid, the local one should be used
// but the user should be warned. // but the user should be warned.
ShowInfoDialog(); ShowInfoDialog();
@ -388,11 +411,18 @@ namespace Ryujinx.Ava.UI.ViewModels
private async Task<bool> NeedsUpdate(DateTime oldLastModified) private async Task<bool> NeedsUpdate(DateTime oldLastModified)
{ {
HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://amiibo.ryujinx.org/")); try
if (response.IsSuccessStatusCode)
{ {
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; return false;
@ -400,21 +430,33 @@ namespace Ryujinx.Ava.UI.ViewModels
private async Task<string> DownloadAmiiboJson() private async Task<string> DownloadAmiiboJson()
{ {
HttpResponseMessage response = await _httpClient.GetAsync("https://amiibo.ryujinx.org/"); try
if (response.IsSuccessStatusCode)
{ {
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], await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle],
LocaleManager.Instance[LocaleKeys.DialogAmiiboApiFailFetchMessage], LocaleManager.Instance[LocaleKeys.DialogAmiiboApiFailFetchMessage],
@ -422,9 +464,7 @@ namespace Ryujinx.Ava.UI.ViewModels
"", "",
LocaleManager.Instance[LocaleKeys.RyujinxInfo]); LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
Close(); return null;
return DefaultJson;
} }
private void Close() private void Close()

View File

@ -1,3 +1,4 @@
using Gdk;
using Gtk; using Gtk;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
@ -13,7 +14,9 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using Window = Gtk.Window;
namespace Ryujinx.Ui.Windows namespace Ryujinx.Ui.Windows
{ {
@ -49,11 +52,11 @@ namespace Ryujinx.Ui.Windows
public AmiiboWindow() : base($"Ryujinx {Program.Version} - Amiibo") 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(); InitializeComponent();
_httpClient = new HttpClient() _httpClient = new HttpClient
{ {
Timeout = TimeSpan.FromSeconds(30), Timeout = TimeSpan.FromSeconds(30),
}; };
@ -64,7 +67,7 @@ namespace Ryujinx.Ui.Windows
_amiiboList = new List<AmiiboApi>(); _amiiboList = new List<AmiiboApi>();
_amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png"); _amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png");
_amiiboImage.Pixbuf = new Gdk.Pixbuf(_amiiboLogoBytes); _amiiboImage.Pixbuf = new Pixbuf(_amiiboLogoBytes);
_scanButton.Sensitive = false; _scanButton.Sensitive = false;
_randomUuidCheckBox.Sensitive = false; _randomUuidCheckBox.Sensitive = false;
@ -72,17 +75,25 @@ namespace Ryujinx.Ui.Windows
_ = LoadContentAsync(); _ = 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 try
{ {
amiiboJson = JsonHelper.Deserialize<AmiiboJson>(json, _serializerContext.AmiiboJson); amiiboJson = JsonHelper.Deserialize(json, _serializerContext.AmiiboJson);
return true; return true;
} }
catch catch (JsonException exception)
{ {
amiiboJson = JsonHelper.Deserialize<AmiiboJson>(DefaultJson, _serializerContext.AmiiboJson); Logger.Error?.Print(LogClass.Application, $"Unable to deserialize amiibo data: {exception}");
amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson);
return false; return false;
} }
@ -92,27 +103,41 @@ namespace Ryujinx.Ui.Windows
{ {
bool localIsValid = false; bool localIsValid = false;
bool remoteIsValid = false; bool remoteIsValid = false;
AmiiboJson amiiboJson = JsonHelper.Deserialize<AmiiboJson>(DefaultJson, _serializerContext.AmiiboJson); AmiiboJson amiiboJson = new();
try 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)) if (!localIsValid || await NeedsUpdate(amiiboJson.LastUpdated))
{ {
remoteIsValid = TryGetAmiiboJson(await DownloadAmiiboJson(), out amiiboJson); remoteIsValid = TryGetAmiiboJson(await DownloadAmiiboJson(), out amiiboJson);
} }
} }
catch catch (Exception exception)
{ {
if (!(localIsValid || remoteIsValid)) 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. // Neither local or remote files are valid JSON, close window.
ShowInfoDialog(); ShowInfoDialog();
Close(); Close();
} }
else if (!remoteIsValid) 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 // Only the local file is valid, the local one should be used
// but the user should be warned. // but the user should be warned.
ShowInfoDialog(); ShowInfoDialog();
@ -196,11 +221,18 @@ namespace Ryujinx.Ui.Windows
private async Task<bool> NeedsUpdate(DateTime oldLastModified) private async Task<bool> NeedsUpdate(DateTime oldLastModified)
{ {
HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://amiibo.ryujinx.org/")); try
if (response.IsSuccessStatusCode)
{ {
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; return false;
@ -208,29 +240,37 @@ namespace Ryujinx.Ui.Windows
private async Task<string> DownloadAmiiboJson() private async Task<string> DownloadAmiiboJson()
{ {
HttpResponseMessage response = await _httpClient.GetAsync("https://amiibo.ryujinx.org/"); try
if (response.IsSuccessStatusCode)
{ {
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}"); 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."); catch (HttpRequestException exception)
{
Close(); 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) private async Task UpdateAmiiboPreview(string imageUrl)
@ -240,7 +280,7 @@ namespace Ryujinx.Ui.Windows
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
byte[] amiiboPreviewBytes = await response.Content.ReadAsByteArrayAsync(); 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 ratio = Math.Min((float)_amiiboImage.AllocatedWidth / amiiboPreview.Width,
(float)_amiiboImage.AllocatedHeight / amiiboPreview.Height); (float)_amiiboImage.AllocatedHeight / amiiboPreview.Height);
@ -248,7 +288,7 @@ namespace Ryujinx.Ui.Windows
int resizeHeight = (int)(amiiboPreview.Height * ratio); int resizeHeight = (int)(amiiboPreview.Height * ratio);
int resizeWidth = (int)(amiiboPreview.Width * 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 else
{ {
@ -258,7 +298,7 @@ namespace Ryujinx.Ui.Windows
private static void ShowInfoDialog() 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; 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; 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) private void ShowAllCheckBox_Clicked(object sender, EventArgs e)
{ {
_amiiboImage.Pixbuf = new Gdk.Pixbuf(_amiiboLogoBytes); _amiiboImage.Pixbuf = new Pixbuf(_amiiboLogoBytes);
_amiiboSeriesComboBox.Changed -= SeriesComboBox_Changed; _amiiboSeriesComboBox.Changed -= SeriesComboBox_Changed;
_amiiboCharsComboBox.Changed -= CharacterComboBox_Changed; _amiiboCharsComboBox.Changed -= CharacterComboBox_Changed;
@ -365,7 +405,7 @@ namespace Ryujinx.Ui.Windows
_scanButton.Sensitive = false; _scanButton.Sensitive = false;
_randomUuidCheckBox.Sensitive = false; _randomUuidCheckBox.Sensitive = false;
new Task(() => ParseAmiiboData()).Start(); new Task(ParseAmiiboData).Start();
} }
private void ScanButton_Pressed(object sender, EventArgs args) private void ScanButton_Pressed(object sender, EventArgs args)