UI: Fix applications times (#4294)

* Fix applications times

* Add spaces

* Fix TimeString formatting
This commit is contained in:
Ac_K 2023-01-16 00:11:16 +01:00 committed by GitHub
parent 065c4e520d
commit 64263c5218
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -38,7 +38,7 @@ namespace Ryujinx.Ui.App.Common
private readonly byte[] _nroIcon; private readonly byte[] _nroIcon;
private readonly byte[] _nsoIcon; private readonly byte[] _nsoIcon;
private VirtualFileSystem _virtualFileSystem; private readonly VirtualFileSystem _virtualFileSystem;
private Language _desiredTitleLanguage; private Language _desiredTitleLanguage;
private CancellationTokenSource _cancellationToken; private CancellationTokenSource _cancellationToken;
@ -53,7 +53,7 @@ namespace Ryujinx.Ui.App.Common
_nsoIcon = GetResourceBytes("Ryujinx.Ui.Common.Resources.Icon_NSO.png"); _nsoIcon = GetResourceBytes("Ryujinx.Ui.Common.Resources.Icon_NSO.png");
} }
private byte[] GetResourceBytes(string resourceName) private static byte[] GetResourceBytes(string resourceName)
{ {
Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName); Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName);
byte[] resourceByteArray = new byte[resourceStream.Length]; byte[] resourceByteArray = new byte[resourceStream.Length];
@ -68,9 +68,9 @@ namespace Ryujinx.Ui.App.Common
_cancellationToken?.Cancel(); _cancellationToken?.Cancel();
} }
public void ReadControlData(IFileSystem controlFs, Span<byte> outProperty) public static void ReadControlData(IFileSystem controlFs, Span<byte> outProperty)
{ {
using var controlFile = new UniqueRef<IFile>(); using UniqueRef<IFile> controlFile = new();
controlFs.OpenFile(ref controlFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); controlFs.OpenFile(ref controlFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure(); controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure();
@ -86,7 +86,7 @@ namespace Ryujinx.Ui.App.Common
_cancellationToken = new CancellationTokenSource(); _cancellationToken = new CancellationTokenSource();
// Builds the applications list with paths to found applications // Builds the applications list with paths to found applications
List<string> applications = new List<string>(); List<string> applications = new();
try try
{ {
@ -143,14 +143,14 @@ namespace Ryujinx.Ui.App.Common
string version = "0"; string version = "0";
byte[] applicationIcon = null; byte[] applicationIcon = null;
BlitStruct<ApplicationControlProperty> controlHolder = new BlitStruct<ApplicationControlProperty>(1); BlitStruct<ApplicationControlProperty> controlHolder = new(1);
try try
{ {
string extension = Path.GetExtension(applicationPath).ToLower(); string extension = Path.GetExtension(applicationPath).ToLower();
using (FileStream file = new FileStream(applicationPath, FileMode.Open, FileAccess.Read)) using FileStream file = new(applicationPath, FileMode.Open, FileAccess.Read);
{
if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci") if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
{ {
try try
@ -161,7 +161,7 @@ namespace Ryujinx.Ui.App.Common
if (extension == ".xci") if (extension == ".xci")
{ {
Xci xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage()); Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
pfs = xci.OpenPartition(XciPartitionType.Secure); pfs = xci.OpenPartition(XciPartitionType.Secure);
} }
@ -176,11 +176,11 @@ namespace Ryujinx.Ui.App.Common
{ {
if (Path.GetExtension(fileEntry.FullPath).ToLower() == ".nca") if (Path.GetExtension(fileEntry.FullPath).ToLower() == ".nca")
{ {
using var ncaFile = new UniqueRef<IFile>(); using UniqueRef<IFile> ncaFile = new();
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = new Nca(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage()); Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
// Some main NCAs don't have a data partition, so check if the partition exists before opening it // Some main NCAs don't have a data partition, so check if the partition exists before opening it
@ -209,13 +209,13 @@ namespace Ryujinx.Ui.App.Common
{ {
applicationIcon = _nspIcon; applicationIcon = _nspIcon;
using var npdmFile = new UniqueRef<IFile>(); using UniqueRef<IFile> npdmFile = new();
Result result = pfs.OpenFile(ref npdmFile.Ref(), "/main.npdm".ToU8Span(), OpenMode.Read); Result result = pfs.OpenFile(ref npdmFile.Ref(), "/main.npdm".ToU8Span(), OpenMode.Read);
if (ResultFs.PathNotFound.Includes(result)) if (ResultFs.PathNotFound.Includes(result))
{ {
Npdm npdm = new Npdm(npdmFile.Get.AsStream()); Npdm npdm = new(npdmFile.Get.AsStream());
titleName = npdm.TitleName; titleName = npdm.TitleName;
titleId = npdm.Aci0.TitleId.ToString("x16"); titleId = npdm.Aci0.TitleId.ToString("x16");
@ -239,16 +239,15 @@ namespace Ryujinx.Ui.App.Common
// Read the icon from the ControlFS and store it as a byte array // Read the icon from the ControlFS and store it as a byte array
try try
{ {
using var icon = new UniqueRef<IFile>(); using UniqueRef<IFile> icon = new();
controlFs.OpenFile(ref icon.Ref(), $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure(); controlFs.OpenFile(ref icon.Ref(), $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
using (MemoryStream stream = new MemoryStream()) using MemoryStream stream = new();
{
icon.Get.AsStream().CopyTo(stream); icon.Get.AsStream().CopyTo(stream);
applicationIcon = stream.ToArray(); applicationIcon = stream.ToArray();
} }
}
catch (HorizonResultException) catch (HorizonResultException)
{ {
foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*")) foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
@ -262,11 +261,11 @@ namespace Ryujinx.Ui.App.Common
controlFs.OpenFile(ref icon.Ref(), entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); controlFs.OpenFile(ref icon.Ref(), entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
using (MemoryStream stream = new MemoryStream()) using MemoryStream stream = new();
{
icon.Get.AsStream().CopyTo(stream); icon.Get.AsStream().CopyTo(stream);
applicationIcon = stream.ToArray(); applicationIcon = stream.ToArray();
}
if (applicationIcon != null) if (applicationIcon != null)
{ {
@ -274,10 +273,7 @@ namespace Ryujinx.Ui.App.Common
} }
} }
if (applicationIcon == null) applicationIcon ??= extension == ".xci" ? _xciIcon : _nspIcon;
{
applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon;
}
} }
} }
} }
@ -304,7 +300,7 @@ namespace Ryujinx.Ui.App.Common
} }
else if (extension == ".nro") else if (extension == ".nro")
{ {
BinaryReader reader = new BinaryReader(file); BinaryReader reader = new(file);
byte[] Read(long position, int size) byte[] Read(long position, int size)
{ {
@ -356,7 +352,7 @@ namespace Ryujinx.Ui.App.Common
{ {
try try
{ {
Nca nca = new Nca(_virtualFileSystem.KeySet, new FileStream(applicationPath, FileMode.Open, FileAccess.Read).AsStorage()); Nca nca = new(_virtualFileSystem.KeySet, new FileStream(applicationPath, FileMode.Open, FileAccess.Read).AsStorage());
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
if (nca.Header.ContentType != NcaContentType.Program || (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())) if (nca.Header.ContentType != NcaContentType.Program || (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()))
@ -389,7 +385,6 @@ namespace Ryujinx.Ui.App.Common
titleName = Path.GetFileNameWithoutExtension(applicationPath); titleName = Path.GetFileNameWithoutExtension(applicationPath);
} }
} }
}
catch (IOException exception) catch (IOException exception)
{ {
Logger.Warning?.Print(LogClass.Application, exception.Message); Logger.Warning?.Print(LogClass.Application, exception.Message);
@ -404,14 +399,21 @@ namespace Ryujinx.Ui.App.Common
appMetadata.Title = titleName; appMetadata.Title = titleName;
}); });
if (appMetadata.LastPlayed != "Never" && !DateTime.TryParse(appMetadata.LastPlayed, out _)) if (appMetadata.LastPlayed != "Never")
{
if (!DateTime.TryParse(appMetadata.LastPlayed, out _))
{ {
Logger.Warning?.Print(LogClass.Application, $"Last played datetime \"{appMetadata.LastPlayed}\" is invalid for current system culture, skipping (did current culture change?)"); Logger.Warning?.Print(LogClass.Application, $"Last played datetime \"{appMetadata.LastPlayed}\" is invalid for current system culture, skipping (did current culture change?)");
appMetadata.LastPlayed = "Never"; appMetadata.LastPlayed = "Never";
} }
else
{
appMetadata.LastPlayed = appMetadata.LastPlayed[..^3];
}
}
ApplicationData data = new ApplicationData ApplicationData data = new()
{ {
Favorite = appMetadata.Favorite, Favorite = appMetadata.Favorite,
Icon = applicationIcon, Icon = applicationIcon,
@ -419,7 +421,7 @@ namespace Ryujinx.Ui.App.Common
TitleId = titleId, TitleId = titleId,
Developer = developer, Developer = developer,
Version = version, Version = version,
TimePlayed = ConvertSecondsToReadableString(appMetadata.TimePlayed), TimePlayed = ConvertSecondsToFormattedString(appMetadata.TimePlayed),
TimePlayedNum = appMetadata.TimePlayed, TimePlayedNum = appMetadata.TimePlayed,
LastPlayed = appMetadata.LastPlayed, LastPlayed = appMetadata.LastPlayed,
FileExtension = Path.GetExtension(applicationPath).ToUpper().Remove(0, 1), FileExtension = Path.GetExtension(applicationPath).ToUpper().Remove(0, 1),
@ -488,11 +490,10 @@ namespace Ryujinx.Ui.App.Common
appMetadata = new ApplicationMetadata(); appMetadata = new ApplicationMetadata();
using (FileStream stream = File.Create(metadataFile, 4096, FileOptions.WriteThrough)) using FileStream stream = File.Create(metadataFile, 4096, FileOptions.WriteThrough);
{
JsonHelper.Serialize(stream, appMetadata, true); JsonHelper.Serialize(stream, appMetadata, true);
} }
}
try try
{ {
@ -509,11 +510,10 @@ namespace Ryujinx.Ui.App.Common
{ {
modifyFunction(appMetadata); modifyFunction(appMetadata);
using (FileStream stream = File.Create(metadataFile, 4096, FileOptions.WriteThrough)) using FileStream stream = File.Create(metadataFile, 4096, FileOptions.WriteThrough);
{
JsonHelper.Serialize(stream, appMetadata, true); JsonHelper.Serialize(stream, appMetadata, true);
} }
}
return appMetadata; return appMetadata;
} }
@ -529,8 +529,8 @@ namespace Ryujinx.Ui.App.Common
{ {
string extension = Path.GetExtension(applicationPath).ToLower(); string extension = Path.GetExtension(applicationPath).ToLower();
using (FileStream file = new FileStream(applicationPath, FileMode.Open, FileAccess.Read)) using FileStream file = new(applicationPath, FileMode.Open, FileAccess.Read);
{
if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci") if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
{ {
try try
@ -574,12 +574,11 @@ namespace Ryujinx.Ui.App.Common
controlFs.OpenFile(ref icon.Ref(), $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure(); controlFs.OpenFile(ref icon.Ref(), $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
using (MemoryStream stream = new MemoryStream()) using MemoryStream stream = new();
{
icon.Get.AsStream().CopyTo(stream); icon.Get.AsStream().CopyTo(stream);
applicationIcon = stream.ToArray(); applicationIcon = stream.ToArray();
} }
}
catch (HorizonResultException) catch (HorizonResultException)
{ {
foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*")) foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
@ -593,7 +592,7 @@ namespace Ryujinx.Ui.App.Common
controlFs.OpenFile(ref icon.Ref(), entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); controlFs.OpenFile(ref icon.Ref(), entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
using (MemoryStream stream = new MemoryStream()) using (MemoryStream stream = new())
{ {
icon.Get.AsStream().CopyTo(stream); icon.Get.AsStream().CopyTo(stream);
applicationIcon = stream.ToArray(); applicationIcon = stream.ToArray();
@ -605,29 +604,21 @@ namespace Ryujinx.Ui.App.Common
} }
} }
if (applicationIcon == null) applicationIcon ??= extension == ".xci" ? _xciIcon : _nspIcon;
{
applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon;
}
} }
} }
} }
catch (MissingKeyException) catch (MissingKeyException)
{ {
applicationIcon = extension == ".xci" applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon;
? _xciIcon
: _nspIcon;
} }
catch (InvalidDataException) catch (InvalidDataException)
{ {
applicationIcon = extension == ".xci" applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon;
? _xciIcon
: _nspIcon;
} }
catch (Exception exception) catch (Exception exception)
{ {
Logger.Warning?.Print(LogClass.Application, Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. File: '{applicationPath}' Error: {exception}");
$"The file encountered was not of a valid type. File: '{applicationPath}' Error: {exception}");
} }
} }
else if (extension == ".nro") else if (extension == ".nro")
@ -664,8 +655,7 @@ namespace Ryujinx.Ui.App.Common
} }
catch catch
{ {
Logger.Warning?.Print(LogClass.Application, Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}");
$"The file encountered was not of a valid type. Errored File: {applicationPath}");
} }
} }
else if (extension == ".nca") else if (extension == ".nca")
@ -679,42 +669,37 @@ namespace Ryujinx.Ui.App.Common
} }
} }
} }
}
catch(Exception) catch(Exception)
{ {
Logger.Warning?.Print(LogClass.Application, Logger.Warning?.Print(LogClass.Application, $"Could not retrieve a valid icon for the app. Default icon will be used. Errored File: {applicationPath}");
$"Could not retrieve a valid icon for the app. Default icon will be used. Errored File: {applicationPath}");
} }
return applicationIcon ?? _ncaIcon; return applicationIcon ?? _ncaIcon;
} }
private string ConvertSecondsToReadableString(double seconds) private static string ConvertSecondsToFormattedString(double seconds)
{ {
const int secondsPerMinute = 60; System.TimeSpan time = System.TimeSpan.FromSeconds(seconds);
const int secondsPerHour = secondsPerMinute * 60;
const int secondsPerDay = secondsPerHour * 24;
string readableString; string timeString;
if (time.Days != 0)
if (seconds < secondsPerMinute)
{ {
readableString = $"{seconds} seconds"; timeString = $"{time.Days}d {time.Hours:D2}h {time.Minutes:D2}m";
} }
else if (seconds < secondsPerHour) else if (time.Hours != 0)
{ {
readableString = $"{Math.Round(seconds / secondsPerMinute, 0, MidpointRounding.AwayFromZero)} minutes"; timeString = $"{time.Hours:D2}h {time.Minutes:D2}m";
} }
else if (seconds < secondsPerDay) else if (time.Minutes != 0)
{ {
readableString = $"{Math.Round(seconds / secondsPerHour, 1, MidpointRounding.AwayFromZero)} hours"; timeString = $"{time.Minutes:D2}m";
} }
else else
{ {
readableString = $"{Math.Round(seconds / secondsPerDay, 1, MidpointRounding.AwayFromZero)} days"; timeString = "Never";
} }
return readableString; return timeString;
} }
private void GetGameInformation(ref ApplicationControlProperty controlData, out string titleName, out string titleId, out string publisher, out string version) private void GetGameInformation(ref ApplicationControlProperty controlData, out string titleName, out string titleId, out string publisher, out string version)
@ -797,8 +782,7 @@ namespace Ryujinx.Ui.App.Common
} }
catch (InvalidDataException) catch (InvalidDataException)
{ {
Logger.Warning?.Print(LogClass.Application, Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {updatePath}");
$"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {updatePath}");
} }
catch (MissingKeyException exception) catch (MissingKeyException exception)
{ {