mirror of
				https://github.com/Ryujinx/Ryujinx.git
				synced 2025-11-04 01:03:43 +00:00 
			
		
		
		
	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
					
				@ -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,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user