Changed LastPlayed field from string to nullable DateTime (#4861)
* Changed LastPlayed field from string to nullable DateTime Added ApplicationData.LastPlayedString property Added NullableDateTimeConverter for the DateTime->string conversion in Avalonia * Added migration from string-based last_played to DateTime-based last_played_utc * Updated comment style * Added MarkupExtension to NullableDateTimeConverter and changed its usage Cleaned up leftover usings * Missed one comment
This commit is contained in:
parent
5cbdfbc7a4
commit
531da8a1c0
9 changed files with 113 additions and 43 deletions
|
@ -671,7 +671,7 @@ namespace Ryujinx.Ava
|
|||
|
||||
_viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata =>
|
||||
{
|
||||
appMetadata.LastPlayed = DateTime.UtcNow.ToString();
|
||||
appMetadata.LastPlayed = DateTime.UtcNow;
|
||||
});
|
||||
|
||||
return true;
|
||||
|
|
|
@ -129,7 +129,7 @@
|
|||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding LastPlayed}"
|
||||
Text="{Binding LastPlayed, Converter={helpers:NullableDateTimeConverter}}"
|
||||
TextAlignment="Right"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
|
|
38
src/Ryujinx.Ava/UI/Helpers/NullableDateTimeConverter.cs
Normal file
38
src/Ryujinx.Ava/UI/Helpers/NullableDateTimeConverter.cs
Normal file
|
@ -0,0 +1,38 @@
|
|||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
internal class NullableDateTimeConverter : MarkupExtension, IValueConverter
|
||||
{
|
||||
private static readonly NullableDateTimeConverter _instance = new();
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return LocaleManager.Instance[LocaleKeys.Never];
|
||||
}
|
||||
|
||||
if (value is DateTime dateTime)
|
||||
{
|
||||
return dateTime.ToLocalTime().ToString(culture);
|
||||
}
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override object ProvideValue(IServiceProvider serviceProvider)
|
||||
{
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -14,20 +13,20 @@ namespace Ryujinx.Ava.UI.Models.Generic
|
|||
|
||||
public int Compare(ApplicationData x, ApplicationData y)
|
||||
{
|
||||
string aValue = x.LastPlayed;
|
||||
string bValue = y.LastPlayed;
|
||||
var aValue = x.LastPlayed;
|
||||
var bValue = y.LastPlayed;
|
||||
|
||||
if (aValue == LocaleManager.Instance[LocaleKeys.Never])
|
||||
if (!aValue.HasValue)
|
||||
{
|
||||
aValue = DateTime.UnixEpoch.ToString();
|
||||
aValue = DateTime.UnixEpoch;
|
||||
}
|
||||
|
||||
if (bValue == LocaleManager.Instance[LocaleKeys.Never])
|
||||
if (!bValue.HasValue)
|
||||
{
|
||||
bValue = DateTime.UnixEpoch.ToString();
|
||||
bValue = DateTime.UnixEpoch;
|
||||
}
|
||||
|
||||
return (IsAscending ? 1 : -1) * DateTime.Compare(DateTime.Parse(bValue), DateTime.Parse(aValue));
|
||||
return (IsAscending ? 1 : -1) * DateTime.Compare(bValue.Value, aValue.Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1524,10 +1524,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
{
|
||||
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
|
||||
{
|
||||
if (DateTime.TryParse(appMetadata.LastPlayed, out DateTime lastPlayedDateTime))
|
||||
if (appMetadata.LastPlayed.HasValue)
|
||||
{
|
||||
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
|
||||
|
||||
double sessionTimePlayed = DateTime.UtcNow.Subtract(appMetadata.LastPlayed.Value).TotalSeconds;
|
||||
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -10,27 +10,44 @@ using LibHac.Tools.FsSystem.NcaUtils;
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Ui.App.Common
|
||||
{
|
||||
public class ApplicationData
|
||||
{
|
||||
public bool Favorite { get; set; }
|
||||
public byte[] Icon { get; set; }
|
||||
public string TitleName { get; set; }
|
||||
public string TitleId { get; set; }
|
||||
public string Developer { get; set; }
|
||||
public string Version { get; set; }
|
||||
public string TimePlayed { get; set; }
|
||||
public double TimePlayedNum { get; set; }
|
||||
public string LastPlayed { get; set; }
|
||||
public string FileExtension { get; set; }
|
||||
public string FileSize { get; set; }
|
||||
public double FileSizeBytes { get; set; }
|
||||
public string Path { get; set; }
|
||||
public bool Favorite { get; set; }
|
||||
public byte[] Icon { get; set; }
|
||||
public string TitleName { get; set; }
|
||||
public string TitleId { get; set; }
|
||||
public string Developer { get; set; }
|
||||
public string Version { get; set; }
|
||||
public string TimePlayed { get; set; }
|
||||
public double TimePlayedNum { get; set; }
|
||||
public DateTime? LastPlayed { get; set; }
|
||||
public string FileExtension { get; set; }
|
||||
public string FileSize { get; set; }
|
||||
public double FileSizeBytes { get; set; }
|
||||
public string Path { get; set; }
|
||||
public BlitStruct<ApplicationControlProperty> ControlHolder { get; set; }
|
||||
|
||||
|
||||
[JsonIgnore]
|
||||
public string LastPlayedString
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!LastPlayed.HasValue)
|
||||
{
|
||||
// TODO: maybe put localized string here instead of just "Never"
|
||||
return "Never";
|
||||
}
|
||||
|
||||
return LastPlayed.Value.ToLocalTime().ToString(CultureInfo.CurrentCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetApplicationBuildId(VirtualFileSystem virtualFileSystem, string titleFilePath)
|
||||
{
|
||||
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
|
||||
|
|
|
@ -414,21 +414,28 @@ namespace Ryujinx.Ui.App.Common
|
|||
ApplicationMetadata appMetadata = LoadAndSaveMetaData(titleId, appMetadata =>
|
||||
{
|
||||
appMetadata.Title = titleName;
|
||||
});
|
||||
|
||||
if (appMetadata.LastPlayed != "Never")
|
||||
{
|
||||
if (!DateTime.TryParse(appMetadata.LastPlayed, out _))
|
||||
if (appMetadata.LastPlayedOld == default || appMetadata.LastPlayed.HasValue)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Last played datetime \"{appMetadata.LastPlayed}\" is invalid for current system culture, skipping (did current culture change?)");
|
||||
// Don't do the migration if last_played doesn't exist or last_played_utc already has a value.
|
||||
return;
|
||||
}
|
||||
|
||||
appMetadata.LastPlayed = "Never";
|
||||
// Migrate from string-based last_played to DateTime-based last_played_utc.
|
||||
if (DateTime.TryParse(appMetadata.LastPlayedOld, out DateTime lastPlayedOldParsed))
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, $"last_played found: \"{appMetadata.LastPlayedOld}\", migrating to last_played_utc");
|
||||
appMetadata.LastPlayed = lastPlayedOldParsed;
|
||||
|
||||
// Migration successful: deleting last_played from the metadata file.
|
||||
appMetadata.LastPlayedOld = default;
|
||||
}
|
||||
else
|
||||
{
|
||||
appMetadata.LastPlayed = appMetadata.LastPlayed[..^3];
|
||||
// Migration failed: emitting warning but leaving the unparsable value in the metadata file so the user can fix it.
|
||||
Logger.Warning?.Print(LogClass.Application, $"Last played string \"{appMetadata.LastPlayedOld}\" is invalid for current system culture, skipping (did current culture change?)");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ApplicationData data = new()
|
||||
{
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
namespace Ryujinx.Ui.App.Common
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Ui.App.Common
|
||||
{
|
||||
public class ApplicationMetadata
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public bool Favorite { get; set; }
|
||||
public double TimePlayed { get; set; }
|
||||
public string LastPlayed { get; set; } = "Never";
|
||||
|
||||
[JsonPropertyName("last_played_utc")]
|
||||
public DateTime? LastPlayed { get; set; } = null;
|
||||
|
||||
[JsonPropertyName("last_played")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public string LastPlayedOld { get; set; }
|
||||
}
|
||||
}
|
|
@ -876,7 +876,7 @@ namespace Ryujinx.Ui
|
|||
|
||||
_applicationLibrary.LoadAndSaveMetaData(_emulationContext.Processes.ActiveApplication.ProgramIdText, appMetadata =>
|
||||
{
|
||||
appMetadata.LastPlayed = DateTime.UtcNow.ToString();
|
||||
appMetadata.LastPlayed = DateTime.UtcNow;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1019,10 +1019,11 @@ namespace Ryujinx.Ui
|
|||
{
|
||||
_applicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
|
||||
{
|
||||
DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed);
|
||||
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
|
||||
|
||||
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
|
||||
if (appMetadata.LastPlayed.HasValue)
|
||||
{
|
||||
double sessionTimePlayed = DateTime.UtcNow.Subtract(appMetadata.LastPlayed.Value).TotalSeconds;
|
||||
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1089,7 +1090,7 @@ namespace Ryujinx.Ui
|
|||
args.AppData.Developer,
|
||||
args.AppData.Version,
|
||||
args.AppData.TimePlayed,
|
||||
args.AppData.LastPlayed,
|
||||
args.AppData.LastPlayedString,
|
||||
args.AppData.FileExtension,
|
||||
args.AppData.FileSize,
|
||||
args.AppData.Path,
|
||||
|
|
Reference in a new issue