mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2024-11-28 04:24:02 +00:00
Add title update autoloading
This commit is contained in:
parent
3d7ede533f
commit
6bdf194ebc
2 changed files with 224 additions and 249 deletions
|
@ -44,9 +44,9 @@ namespace Ryujinx.UI.App.Common
|
|||
public event EventHandler<ApplicationAddedEventArgs> ApplicationAdded;
|
||||
public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
|
||||
|
||||
public IObservableCache<ApplicationData, string> Applications;
|
||||
public IObservableCache<(TitleUpdateModel TitleUpdate, bool IsSelected), TitleUpdateModel> TitleUpdates;
|
||||
public IObservableCache<(DownloadableContentModel Dlc, bool IsEnabled), DownloadableContentModel> DownloadableContents;
|
||||
public readonly IObservableCache<ApplicationData, (ulong Id, string Path)> Applications;
|
||||
public readonly IObservableCache<(TitleUpdateModel TitleUpdate, bool IsSelected), TitleUpdateModel> TitleUpdates;
|
||||
public readonly IObservableCache<(DownloadableContentModel Dlc, bool IsEnabled), DownloadableContentModel> DownloadableContents;
|
||||
|
||||
private readonly byte[] _nspIcon;
|
||||
private readonly byte[] _xciIcon;
|
||||
|
@ -57,7 +57,7 @@ namespace Ryujinx.UI.App.Common
|
|||
private readonly VirtualFileSystem _virtualFileSystem;
|
||||
private readonly IntegrityCheckLevel _checkLevel;
|
||||
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<(DownloadableContentModel Dlc, bool IsEnabled), DownloadableContentModel> _downloadableContents = new(it => it.Dlc);
|
||||
|
||||
|
@ -742,8 +742,12 @@ namespace Ryujinx.UI.App.Common
|
|||
foreach (var application in applications)
|
||||
{
|
||||
it.AddOrUpdate(application);
|
||||
LoadTitleUpdatesForApplication(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)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
_titleUpdates.Edit(it =>
|
||||
|
@ -937,7 +824,7 @@ namespace Ryujinx.UI.App.Common
|
|||
if (!Directory.Exists(appDir))
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application,
|
||||
$"The specified game directory \"{appDir}\" does not exist.");
|
||||
$"The specified autoload directory \"{appDir}\" does not exist.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
@ -1021,111 +908,113 @@ namespace Ryujinx.UI.App.Common
|
|||
|
||||
public int AutoLoadTitleUpdates(List<string> appDirs)
|
||||
{
|
||||
return 0;
|
||||
// _cancellationToken = new CancellationTokenSource();
|
||||
// _titleUpdates.Clear();
|
||||
//
|
||||
// // Builds the applications list with paths to found applications
|
||||
// List<string> applicationPaths = new();
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// foreach (string appDir in appDirs)
|
||||
// {
|
||||
// if (_cancellationToken.Token.IsCancellationRequested)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (!Directory.Exists(appDir))
|
||||
// {
|
||||
// Logger.Warning?.Print(LogClass.Application,
|
||||
// $"The specified game directory \"{appDir}\" does not exist.");
|
||||
//
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// EnumerationOptions options = new()
|
||||
// {
|
||||
// RecurseSubdirectories = true,
|
||||
// IgnoreInaccessible = false,
|
||||
// };
|
||||
//
|
||||
// IEnumerable<string> files = Directory.EnumerateFiles(appDir, "*", options)
|
||||
// .Where(file =>
|
||||
// {
|
||||
// return
|
||||
// (Path.GetExtension(file).ToLower() is ".nsp" &&
|
||||
// ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value) ||
|
||||
// (Path.GetExtension(file).ToLower() is ".xci" &&
|
||||
// ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value);
|
||||
// });
|
||||
//
|
||||
// foreach (string app in files)
|
||||
// {
|
||||
// if (_cancellationToken.Token.IsCancellationRequested)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// var fileInfo = new FileInfo(app);
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// var fullPath = fileInfo.ResolveLinkTarget(true)?.FullName ??
|
||||
// fileInfo.FullName;
|
||||
//
|
||||
// applicationPaths.Add(fullPath);
|
||||
// }
|
||||
// catch (IOException exception)
|
||||
// {
|
||||
// Logger.Warning?.Print(LogClass.Application,
|
||||
// $"Failed to resolve the full path to file: \"{app}\" Error: {exception}");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// catch (UnauthorizedAccessException)
|
||||
// {
|
||||
// Logger.Warning?.Print(LogClass.Application,
|
||||
// $"Failed to get access to directory: \"{appDir}\"");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Loops through applications list, creating a struct and then firing an event containing the struct for each application
|
||||
// foreach (string applicationPath in applicationPaths)
|
||||
// {
|
||||
// if (_cancellationToken.Token.IsCancellationRequested)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (TryGetTitleUpdatesFromFile(applicationPath, out List<TitleUpdateModel> titleUpdates))
|
||||
// {
|
||||
// foreach (var titleUpdate in titleUpdates)
|
||||
// {
|
||||
// OnTitleUpdateAdded(new TitleUpdateAddedEventArgs()
|
||||
// {
|
||||
// TitleUpdate = titleUpdate,
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// _titleUpdates.Edit(it =>
|
||||
// {
|
||||
// foreach (var titleUpdate in titleUpdates)
|
||||
// {
|
||||
// it.AddOrUpdate((titleUpdate, false));
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// _cancellationToken.Dispose();
|
||||
// _cancellationToken = null;
|
||||
// }
|
||||
_cancellationToken = new CancellationTokenSource();
|
||||
|
||||
List<string> updatePaths = new();
|
||||
int numUpdatesLoaded = 0;
|
||||
|
||||
try
|
||||
{
|
||||
foreach (string appDir in appDirs)
|
||||
{
|
||||
if (_cancellationToken.Token.IsCancellationRequested)
|
||||
{
|
||||
return numUpdatesLoaded;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(appDir))
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application,
|
||||
$"The specified autoload directory \"{appDir}\" does not exist.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
EnumerationOptions options = new()
|
||||
{
|
||||
RecurseSubdirectories = true,
|
||||
IgnoreInaccessible = false,
|
||||
};
|
||||
|
||||
IEnumerable<string> files = Directory.EnumerateFiles(appDir, "*", options).Where(
|
||||
file =>
|
||||
{
|
||||
return
|
||||
(Path.GetExtension(file).ToLower() is ".nsp" &&
|
||||
ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value);
|
||||
});
|
||||
|
||||
foreach (string app in files)
|
||||
{
|
||||
if (_cancellationToken.Token.IsCancellationRequested)
|
||||
{
|
||||
return numUpdatesLoaded;
|
||||
}
|
||||
|
||||
var fileInfo = new FileInfo(app);
|
||||
|
||||
try
|
||||
{
|
||||
var fullPath = fileInfo.ResolveLinkTarget(true)?.FullName ?? fileInfo.FullName;
|
||||
|
||||
updatePaths.Add(fullPath);
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application,
|
||||
$"Failed to resolve the full path to file: \"{app}\" Error: {exception}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application,
|
||||
$"Failed to get access to directory: \"{appDir}\"");
|
||||
}
|
||||
}
|
||||
|
||||
var appIdLookup = Applications.Items.Select(it => it.IdBase).ToHashSet();
|
||||
|
||||
foreach (string updatePath in updatePaths)
|
||||
{
|
||||
if (_cancellationToken.Token.IsCancellationRequested)
|
||||
{
|
||||
return numUpdatesLoaded;
|
||||
}
|
||||
|
||||
if (TryGetTitleUpdatesFromFile(updatePath, out var foundUpdates))
|
||||
{
|
||||
foreach (var update in foundUpdates.Where(it => appIdLookup.Contains(it.TitleIdBase)))
|
||||
{
|
||||
if (!_titleUpdates.Lookup(update).HasValue)
|
||||
{
|
||||
var currentlySelected = TitleUpdates.Items.FirstOrOptional(it =>
|
||||
it.TitleUpdate.TitleIdBase == update.TitleIdBase && it.IsSelected);
|
||||
|
||||
var shouldSelect = !currentlySelected.HasValue ||
|
||||
currentlySelected.Value.TitleUpdate.Version < update.Version;
|
||||
_titleUpdates.AddOrUpdate((update, shouldSelect));
|
||||
SaveTitleUpdatesForGame(update.TitleIdBase);
|
||||
numUpdatesLoaded++;
|
||||
|
||||
if (shouldSelect)
|
||||
{
|
||||
RefreshApplicationInfo(update.TitleIdBase);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_cancellationToken.Dispose();
|
||||
_cancellationToken = null;
|
||||
}
|
||||
|
||||
return numUpdatesLoaded;
|
||||
}
|
||||
|
||||
protected void OnApplicationAdded(ApplicationAddedEventArgs e)
|
||||
|
@ -1465,5 +1354,108 @@ namespace Ryujinx.UI.App.Common
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -638,22 +638,14 @@ namespace Ryujinx.Ava.UI.Windows
|
|||
Thread applicationLibraryThread = new(() =>
|
||||
{
|
||||
ApplicationLibrary.DesiredLanguage = ConfigurationState.Instance.System.Language;
|
||||
TimeIt("games", () => ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs));
|
||||
TimeIt("updates", () => ApplicationLibrary.LoadTitleUpdates());
|
||||
TimeIt("DLC", () => ApplicationLibrary.LoadDownloadableContents());
|
||||
|
||||
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs);
|
||||
|
||||
var autoloadDirs = ConfigurationState.Instance.UI.AutoloadDirs.Value;
|
||||
if (autoloadDirs.Count > 0)
|
||||
{
|
||||
var updatesLoaded = 0;
|
||||
TimeIt("auto updates",
|
||||
() => updatesLoaded =
|
||||
ApplicationLibrary.AutoLoadTitleUpdates(autoloadDirs));
|
||||
|
||||
var dlcLoaded = 0;
|
||||
TimeIt("auto dlc",
|
||||
() => dlcLoaded =
|
||||
ApplicationLibrary.AutoLoadDownloadableContents(autoloadDirs));
|
||||
var updatesLoaded = ApplicationLibrary.AutoLoadTitleUpdates(autoloadDirs);
|
||||
var dlcLoaded = ApplicationLibrary.AutoLoadDownloadableContents(autoloadDirs);
|
||||
|
||||
ShowNewContentAddedDialog(dlcLoaded, updatesLoaded);
|
||||
}
|
||||
|
@ -667,15 +659,6 @@ namespace Ryujinx.Ava.UI.Windows
|
|||
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)
|
||||
{
|
||||
var msg = "";
|
||||
|
|
Loading…
Reference in a new issue