Add title update autoloading

This commit is contained in:
Jimmy Reichley 2024-08-19 00:53:00 -04:00
parent 3d7ede533f
commit 6bdf194ebc
No known key found for this signature in database
GPG key ID: 67715DC5A329803C
2 changed files with 224 additions and 249 deletions

View file

@ -44,9 +44,9 @@ namespace Ryujinx.UI.App.Common
public event EventHandler<ApplicationAddedEventArgs> ApplicationAdded; public event EventHandler<ApplicationAddedEventArgs> ApplicationAdded;
public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated; public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
public IObservableCache<ApplicationData, string> Applications; public readonly IObservableCache<ApplicationData, (ulong Id, string Path)> Applications;
public IObservableCache<(TitleUpdateModel TitleUpdate, bool IsSelected), TitleUpdateModel> TitleUpdates; public readonly IObservableCache<(TitleUpdateModel TitleUpdate, bool IsSelected), TitleUpdateModel> TitleUpdates;
public IObservableCache<(DownloadableContentModel Dlc, bool IsEnabled), DownloadableContentModel> DownloadableContents; public readonly IObservableCache<(DownloadableContentModel Dlc, bool IsEnabled), DownloadableContentModel> DownloadableContents;
private readonly byte[] _nspIcon; private readonly byte[] _nspIcon;
private readonly byte[] _xciIcon; private readonly byte[] _xciIcon;
@ -57,7 +57,7 @@ namespace Ryujinx.UI.App.Common
private readonly VirtualFileSystem _virtualFileSystem; private readonly VirtualFileSystem _virtualFileSystem;
private readonly IntegrityCheckLevel _checkLevel; private readonly IntegrityCheckLevel _checkLevel;
private CancellationTokenSource _cancellationToken; private CancellationTokenSource _cancellationToken;
private readonly SourceCache<ApplicationData, string> _applications = new(it => it.Path); private readonly SourceCache<ApplicationData, (ulong Id, string Path)> _applications = new(it => (it.Id, it.Path));
private readonly SourceCache<(TitleUpdateModel TitleUpdate, bool IsSelected), TitleUpdateModel> _titleUpdates = new(it => it.TitleUpdate); private readonly SourceCache<(TitleUpdateModel TitleUpdate, bool IsSelected), TitleUpdateModel> _titleUpdates = new(it => it.TitleUpdate);
private readonly SourceCache<(DownloadableContentModel Dlc, bool IsEnabled), DownloadableContentModel> _downloadableContents = new(it => it.Dlc); private readonly SourceCache<(DownloadableContentModel Dlc, bool IsEnabled), DownloadableContentModel> _downloadableContents = new(it => it.Dlc);
@ -742,8 +742,12 @@ namespace Ryujinx.UI.App.Common
foreach (var application in applications) foreach (var application in applications)
{ {
it.AddOrUpdate(application); it.AddOrUpdate(application);
LoadTitleUpdatesForApplication(application);
LoadDlcForApplication(application); LoadDlcForApplication(application);
if (LoadTitleUpdatesForApplication(application))
{
// Trigger a reload of the version data
RefreshApplicationInfo(application.IdBase);
}
} }
}); });
@ -779,117 +783,6 @@ namespace Ryujinx.UI.App.Common
} }
} }
private void LoadTitleUpdatesForApplication(ApplicationData application)
{
_titleUpdates.Edit(it =>
{
var savedUpdates =
TitleUpdatesHelper.LoadTitleUpdatesJson(_virtualFileSystem, application.IdBase);
it.AddOrUpdate(savedUpdates);
var selectedUpdate = savedUpdates.FirstOrOptional(update => update.IsSelected);
if (TryGetTitleUpdatesFromFile(application.Path, out var bundledUpdates))
{
var savedUpdateLookup = savedUpdates.Select(update => update.Item1).ToHashSet();
bool addedNewUpdate = false;
foreach (var update in bundledUpdates)
{
if (!savedUpdateLookup.Contains(update))
{
addedNewUpdate = true;
it.AddOrUpdate((update, false));
}
}
if (addedNewUpdate)
{
var gameUpdates = it.Items.Where(update => update.TitleUpdate.TitleIdBase == application.IdBase).ToList();
TitleUpdatesHelper.SaveTitleUpdatesJson(_virtualFileSystem, application.IdBase, gameUpdates);
}
}
});
}
private void LoadDlcForApplication(ApplicationData application)
{
_downloadableContents.Edit(it =>
{
var savedDlc =
DownloadableContentsHelper.LoadDownloadableContentsJson(_virtualFileSystem, application.IdBase);
it.AddOrUpdate(savedDlc);
if (TryGetDownloadableContentFromFile(application.Path, out var bundledDlc))
{
var savedDlcLookup = savedDlc.Select(dlc => dlc.Item1).ToHashSet();
bool addedNewDlc = false;
foreach (var dlc in bundledDlc)
{
if (!savedDlcLookup.Contains(dlc))
{
addedNewDlc = true;
it.AddOrUpdate((dlc, true));
}
}
if (addedNewDlc)
{
var gameDlcs = it.Items.Where(dlc => dlc.Dlc.TitleIdBase == application.IdBase).ToList();
DownloadableContentsHelper.SaveDownloadableContentsJson(_virtualFileSystem, application.IdBase,
gameDlcs);
}
}
});
}
public void LoadTitleUpdates()
{
return;
}
public void LoadDownloadableContents()
{
_downloadableContents.Edit(it =>
{
it.Clear();
foreach (ApplicationData application in Applications.Items)
{
var savedDlc = DownloadableContentsHelper.LoadDownloadableContentsJson(_virtualFileSystem, application.IdBase);
it.AddOrUpdate(savedDlc);
if (TryGetDownloadableContentFromFile(application.Path, out var bundledDlc))
{
var savedDlcLookup = savedDlc.Select(dlc => dlc.Item1).ToHashSet();
bool addedNewDlc = false;
foreach (var dlc in bundledDlc)
{
if (!savedDlcLookup.Contains(dlc))
{
addedNewDlc = true;
it.AddOrUpdate((dlc, true));
}
}
if (addedNewDlc)
{
var gameDlcs = it.Items.Where(dlc => dlc.Dlc.TitleIdBase == application.IdBase).ToList();
DownloadableContentsHelper.SaveDownloadableContentsJson(_virtualFileSystem, application.IdBase, gameDlcs);
}
}
}
});
}
private void SaveDownloadableContentsForGame(ulong titleIdBase)
{
var dlcs = DownloadableContents.Items.Where(dlc => dlc.Dlc.TitleIdBase == titleIdBase).ToList();
DownloadableContentsHelper.SaveDownloadableContentsJson(_virtualFileSystem, titleIdBase, dlcs);
}
public void SaveDownloadableContentsForGame(ApplicationData application, List<(DownloadableContentModel, bool IsEnabled)> dlcs) public void SaveDownloadableContentsForGame(ApplicationData application, List<(DownloadableContentModel, bool IsEnabled)> dlcs)
{ {
_downloadableContents.Edit(it => _downloadableContents.Edit(it =>
@ -901,12 +794,6 @@ namespace Ryujinx.UI.App.Common
}); });
} }
private void SaveTitleUpdatesForGame(ulong titleIdBase)
{
var updates = TitleUpdates.Items.Where(update => update.TitleUpdate.TitleIdBase == titleIdBase).ToList();
TitleUpdatesHelper.SaveTitleUpdatesJson(_virtualFileSystem, titleIdBase, updates);
}
public void SaveTitleUpdatesForGame(ApplicationData application, List<(TitleUpdateModel, bool IsSelected)> updates) public void SaveTitleUpdatesForGame(ApplicationData application, List<(TitleUpdateModel, bool IsSelected)> updates)
{ {
_titleUpdates.Edit(it => _titleUpdates.Edit(it =>
@ -937,7 +824,7 @@ namespace Ryujinx.UI.App.Common
if (!Directory.Exists(appDir)) if (!Directory.Exists(appDir))
{ {
Logger.Warning?.Print(LogClass.Application, Logger.Warning?.Print(LogClass.Application,
$"The specified game directory \"{appDir}\" does not exist."); $"The specified autoload directory \"{appDir}\" does not exist.");
continue; continue;
} }
@ -1021,111 +908,113 @@ namespace Ryujinx.UI.App.Common
public int AutoLoadTitleUpdates(List<string> appDirs) public int AutoLoadTitleUpdates(List<string> appDirs)
{ {
return 0; _cancellationToken = new CancellationTokenSource();
// _cancellationToken = new CancellationTokenSource();
// _titleUpdates.Clear(); List<string> updatePaths = new();
// int numUpdatesLoaded = 0;
// // Builds the applications list with paths to found applications
// List<string> applicationPaths = new(); try
// {
// try foreach (string appDir in appDirs)
// { {
// foreach (string appDir in appDirs) if (_cancellationToken.Token.IsCancellationRequested)
// { {
// if (_cancellationToken.Token.IsCancellationRequested) return numUpdatesLoaded;
// { }
// return;
// } if (!Directory.Exists(appDir))
// {
// if (!Directory.Exists(appDir)) Logger.Warning?.Print(LogClass.Application,
// { $"The specified autoload directory \"{appDir}\" does not exist.");
// Logger.Warning?.Print(LogClass.Application,
// $"The specified game directory \"{appDir}\" does not exist."); continue;
// }
// continue;
// } try
// {
// try EnumerationOptions options = new()
// { {
// EnumerationOptions options = new() RecurseSubdirectories = true,
// { IgnoreInaccessible = false,
// RecurseSubdirectories = true, };
// IgnoreInaccessible = false,
// }; IEnumerable<string> files = Directory.EnumerateFiles(appDir, "*", options).Where(
// file =>
// IEnumerable<string> files = Directory.EnumerateFiles(appDir, "*", options) {
// .Where(file => return
// { (Path.GetExtension(file).ToLower() is ".nsp" &&
// return ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value);
// (Path.GetExtension(file).ToLower() is ".nsp" && });
// ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value) ||
// (Path.GetExtension(file).ToLower() is ".xci" && foreach (string app in files)
// ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value); {
// }); if (_cancellationToken.Token.IsCancellationRequested)
// {
// foreach (string app in files) return numUpdatesLoaded;
// { }
// if (_cancellationToken.Token.IsCancellationRequested)
// { var fileInfo = new FileInfo(app);
// return;
// } try
// {
// var fileInfo = new FileInfo(app); var fullPath = fileInfo.ResolveLinkTarget(true)?.FullName ?? fileInfo.FullName;
//
// try updatePaths.Add(fullPath);
// { }
// var fullPath = fileInfo.ResolveLinkTarget(true)?.FullName ?? catch (IOException exception)
// fileInfo.FullName; {
// Logger.Warning?.Print(LogClass.Application,
// applicationPaths.Add(fullPath); $"Failed to resolve the full path to file: \"{app}\" Error: {exception}");
// } }
// catch (IOException exception) }
// { }
// Logger.Warning?.Print(LogClass.Application, catch (UnauthorizedAccessException)
// $"Failed to resolve the full path to file: \"{app}\" Error: {exception}"); {
// } Logger.Warning?.Print(LogClass.Application,
// } $"Failed to get access to directory: \"{appDir}\"");
// } }
// catch (UnauthorizedAccessException) }
// {
// Logger.Warning?.Print(LogClass.Application, var appIdLookup = Applications.Items.Select(it => it.IdBase).ToHashSet();
// $"Failed to get access to directory: \"{appDir}\"");
// } foreach (string updatePath in updatePaths)
// } {
// if (_cancellationToken.Token.IsCancellationRequested)
// // Loops through applications list, creating a struct and then firing an event containing the struct for each application {
// foreach (string applicationPath in applicationPaths) return numUpdatesLoaded;
// { }
// if (_cancellationToken.Token.IsCancellationRequested)
// { if (TryGetTitleUpdatesFromFile(updatePath, out var foundUpdates))
// return; {
// } foreach (var update in foundUpdates.Where(it => appIdLookup.Contains(it.TitleIdBase)))
// {
// if (TryGetTitleUpdatesFromFile(applicationPath, out List<TitleUpdateModel> titleUpdates)) if (!_titleUpdates.Lookup(update).HasValue)
// { {
// foreach (var titleUpdate in titleUpdates) var currentlySelected = TitleUpdates.Items.FirstOrOptional(it =>
// { it.TitleUpdate.TitleIdBase == update.TitleIdBase && it.IsSelected);
// OnTitleUpdateAdded(new TitleUpdateAddedEventArgs()
// { var shouldSelect = !currentlySelected.HasValue ||
// TitleUpdate = titleUpdate, currentlySelected.Value.TitleUpdate.Version < update.Version;
// }); _titleUpdates.AddOrUpdate((update, shouldSelect));
// } SaveTitleUpdatesForGame(update.TitleIdBase);
// numUpdatesLoaded++;
// _titleUpdates.Edit(it =>
// { if (shouldSelect)
// foreach (var titleUpdate in titleUpdates) {
// { RefreshApplicationInfo(update.TitleIdBase);
// it.AddOrUpdate((titleUpdate, false)); }
// } }
// }); }
// } }
// } }
// } }
// finally finally
// { {
// _cancellationToken.Dispose(); _cancellationToken.Dispose();
// _cancellationToken = null; _cancellationToken = null;
// } }
return numUpdatesLoaded;
} }
protected void OnApplicationAdded(ApplicationAddedEventArgs e) protected void OnApplicationAdded(ApplicationAddedEventArgs e)
@ -1465,5 +1354,108 @@ namespace Ryujinx.UI.App.Common
return null; return null;
} }
private void LoadDlcForApplication(ApplicationData application)
{
_downloadableContents.Edit(it =>
{
var savedDlc =
DownloadableContentsHelper.LoadDownloadableContentsJson(_virtualFileSystem, application.IdBase);
it.AddOrUpdate(savedDlc);
if (TryGetDownloadableContentFromFile(application.Path, out var bundledDlc))
{
var savedDlcLookup = savedDlc.Select(dlc => dlc.Item1).ToHashSet();
bool addedNewDlc = false;
foreach (var dlc in bundledDlc)
{
if (!savedDlcLookup.Contains(dlc))
{
addedNewDlc = true;
it.AddOrUpdate((dlc, true));
}
}
if (addedNewDlc)
{
var gameDlcs = it.Items.Where(dlc => dlc.Dlc.TitleIdBase == application.IdBase).ToList();
DownloadableContentsHelper.SaveDownloadableContentsJson(_virtualFileSystem, application.IdBase,
gameDlcs);
}
}
});
}
private bool LoadTitleUpdatesForApplication(ApplicationData application)
{
var modifiedVersion = false;
_titleUpdates.Edit(it =>
{
var savedUpdates =
TitleUpdatesHelper.LoadTitleUpdatesJson(_virtualFileSystem, application.IdBase);
it.AddOrUpdate(savedUpdates);
var selectedUpdate = savedUpdates.FirstOrOptional(update => update.IsSelected);
if (TryGetTitleUpdatesFromFile(application.Path, out var bundledUpdates))
{
var savedUpdateLookup = savedUpdates.Select(update => update.Item1).ToHashSet();
bool addedNewUpdate = false;
foreach (var update in bundledUpdates.OrderByDescending(bundled => bundled.Version))
{
if (!savedUpdateLookup.Contains(update))
{
bool shouldSelect = false;
if (!selectedUpdate.HasValue || selectedUpdate.Value.Item1.Version < update.Version)
{
shouldSelect = true;
selectedUpdate = Optional<(TitleUpdateModel, bool IsSelected)>.Create((update, true));
}
modifiedVersion = modifiedVersion || shouldSelect;
it.AddOrUpdate((update, shouldSelect));
addedNewUpdate = true;
}
}
if (addedNewUpdate)
{
var gameUpdates = it.Items.Where(update => update.TitleUpdate.TitleIdBase == application.IdBase).ToList();
TitleUpdatesHelper.SaveTitleUpdatesJson(_virtualFileSystem, application.IdBase, gameUpdates);
}
}
});
return modifiedVersion;
}
private void SaveDownloadableContentsForGame(ulong titleIdBase)
{
var dlcs = DownloadableContents.Items.Where(dlc => dlc.Dlc.TitleIdBase == titleIdBase).ToList();
DownloadableContentsHelper.SaveDownloadableContentsJson(_virtualFileSystem, titleIdBase, dlcs);
}
private void SaveTitleUpdatesForGame(ulong titleIdBase)
{
var updates = TitleUpdates.Items.Where(update => update.TitleUpdate.TitleIdBase == titleIdBase).ToList();
TitleUpdatesHelper.SaveTitleUpdatesJson(_virtualFileSystem, titleIdBase, updates);
}
private void RefreshApplicationInfo(ulong appIdBase)
{
var application = Applications.Items.First(it => it.IdBase == appIdBase);
if (!TryGetApplicationsFromFile(application.Path, out List<ApplicationData> applications))
{
return;
}
var newApplication = applications.First(it => it.IdBase == appIdBase);
_applications.AddOrUpdate(newApplication);
}
} }
} }

View file

@ -638,22 +638,14 @@ namespace Ryujinx.Ava.UI.Windows
Thread applicationLibraryThread = new(() => Thread applicationLibraryThread = new(() =>
{ {
ApplicationLibrary.DesiredLanguage = ConfigurationState.Instance.System.Language; ApplicationLibrary.DesiredLanguage = ConfigurationState.Instance.System.Language;
TimeIt("games", () => ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs));
TimeIt("updates", () => ApplicationLibrary.LoadTitleUpdates()); ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs);
TimeIt("DLC", () => ApplicationLibrary.LoadDownloadableContents());
var autoloadDirs = ConfigurationState.Instance.UI.AutoloadDirs.Value; var autoloadDirs = ConfigurationState.Instance.UI.AutoloadDirs.Value;
if (autoloadDirs.Count > 0) if (autoloadDirs.Count > 0)
{ {
var updatesLoaded = 0; var updatesLoaded = ApplicationLibrary.AutoLoadTitleUpdates(autoloadDirs);
TimeIt("auto updates", var dlcLoaded = ApplicationLibrary.AutoLoadDownloadableContents(autoloadDirs);
() => updatesLoaded =
ApplicationLibrary.AutoLoadTitleUpdates(autoloadDirs));
var dlcLoaded = 0;
TimeIt("auto dlc",
() => dlcLoaded =
ApplicationLibrary.AutoLoadDownloadableContents(autoloadDirs));
ShowNewContentAddedDialog(dlcLoaded, updatesLoaded); ShowNewContentAddedDialog(dlcLoaded, updatesLoaded);
} }
@ -667,15 +659,6 @@ namespace Ryujinx.Ava.UI.Windows
applicationLibraryThread.Start(); applicationLibraryThread.Start();
} }
private static void TimeIt(string tag, Action act)
{
var watch = System.Diagnostics.Stopwatch.StartNew();
act();
watch.Stop();
var elapsedMs = watch.ElapsedMilliseconds;
Console.WriteLine("[{0}] {1} ms", tag, elapsedMs);
}
private Task ShowNewContentAddedDialog(int numDlcAdded, int numUpdatesAdded) private Task ShowNewContentAddedDialog(int numDlcAdded, int numUpdatesAdded)
{ {
var msg = ""; var msg = "";