0
0
Fork 0
mirror of https://github.com/ryujinx-mirror/ryujinx.git synced 2024-12-22 19:45:46 +00:00

Automatically remove invalid dlc and updates as part of auto load

Fixed some minor label spacing issues in options dialog
Removal of unused variable in input view model
This commit is contained in:
Aaron Murgatroyd 2024-10-24 00:35:51 +10:00
parent 57d8f5b4b5
commit ce24341d1d
8 changed files with 117 additions and 53 deletions

View file

@ -802,17 +802,31 @@ namespace Ryujinx.UI.App.Common
// Searches the provided directories for DLC NSP files that are _valid for the currently detected games in the
// library_, and then enables those DLC.
public int AutoLoadDownloadableContents(List<string> appDirs)
public int AutoLoadDownloadableContents(List<string> appDirs, out int numDlcRemoved)
{
_cancellationToken = new CancellationTokenSource();
List<string> dlcPaths = new();
int newDlcLoaded = 0;
numDlcRemoved = 0;
try
{
// Remove any downloadable content which can no longer be located on disk
Logger.Notice.Print(LogClass.Application, $"Removing non-existing Title DLCs");
var dlcToRemove = _downloadableContents.Items
.Where(dlc => !File.Exists(dlc.Dlc.ContainerPath))
.ToList();
dlcToRemove.ForEach(dlc =>
Logger.Warning?.Print(LogClass.Application, $"Title DLC removed: {dlc.Dlc.ContainerPath}")
);
numDlcRemoved += dlcToRemove.Distinct().Count();
_downloadableContents.RemoveKeys(dlcToRemove.Select(dlc => dlc.Dlc));
foreach (string appDir in appDirs)
{
Logger.Notice.Print(LogClass.Application, $"Auto loading DLC from: {appDir}");
if (_cancellationToken.Token.IsCancellationRequested)
{
return newDlcLoaded;
@ -901,17 +915,37 @@ namespace Ryujinx.UI.App.Common
// Searches the provided directories for update NSP files that are _valid for the currently detected games in the
// library_, and then applies those updates. If a newly-detected update is a newer version than the currently
// selected update (or if no update is currently selected), then that update will be selected.
public int AutoLoadTitleUpdates(List<string> appDirs)
public int AutoLoadTitleUpdates(List<string> appDirs, out int numUpdatesRemoved)
{
_cancellationToken = new CancellationTokenSource();
List<string> updatePaths = new();
int numUpdatesLoaded = 0;
numUpdatesRemoved = 0;
try
{
var titleIdsToSave = new HashSet<ulong>();
var titleIdsToRefresh = new HashSet<ulong>();
// Remove any updates which can no longer be located on disk
Logger.Notice.Print(LogClass.Application, $"Removing non-existing Title Updates");
var updatesToRemove = _titleUpdates.Items
.Where(it => !File.Exists(it.TitleUpdate.Path))
.ToList();
numUpdatesRemoved += updatesToRemove.Select(it => it.TitleUpdate).Distinct().Count();
updatesToRemove.ForEach(ti =>
Logger.Warning?.Print(LogClass.Application, $"Title update removed: {ti.TitleUpdate.Path}")
);
_titleUpdates.RemoveKeys(updatesToRemove.Select(it => it.TitleUpdate));
titleIdsToSave.UnionWith(updatesToRemove.Select(it => it.TitleUpdate.TitleIdBase));
titleIdsToRefresh.UnionWith(updatesToRemove.Where(it => it.IsSelected).Select(update => update.TitleUpdate.TitleIdBase));
foreach (string appDir in appDirs)
{
Logger.Notice.Print(LogClass.Application, $"Auto loading updates from: {appDir}");
if (_cancellationToken.Token.IsCancellationRequested)
{
return numUpdatesLoaded;
@ -980,27 +1014,24 @@ namespace Ryujinx.UI.App.Common
{
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));
if (currentlySelected.HasValue && shouldSelect)
_titleUpdates.AddOrUpdate((currentlySelected.Value.TitleUpdate, false));
SaveTitleUpdatesForGame(update.TitleIdBase);
bool shouldSelect = AddAndAutoSelectUpdate(update);
titleIdsToSave.Add(update.TitleIdBase);
numUpdatesLoaded++;
if (shouldSelect)
{
RefreshApplicationInfo(update.TitleIdBase);
titleIdsToRefresh.Add(update.TitleIdBase);
}
}
}
}
}
foreach (var titleId in titleIdsToSave)
SaveTitleUpdatesForGame(titleId);
foreach (var titleId in titleIdsToRefresh)
RefreshApplicationInfo(titleId);
}
finally
{
@ -1011,6 +1042,24 @@ namespace Ryujinx.UI.App.Common
return numUpdatesLoaded;
}
private bool AddAndAutoSelectUpdate(TitleUpdateModel update)
{
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));
if (currentlySelected.HasValue && shouldSelect)
{
_titleUpdates.AddOrUpdate((currentlySelected.Value.TitleUpdate, false));
}
return shouldSelect;
}
protected void OnApplicationCountUpdated(ApplicationCountUpdatedEventArgs e)
{
ApplicationCountUpdated?.Invoke(null, e);
@ -1395,8 +1444,8 @@ namespace Ryujinx.UI.App.Common
if (TryGetTitleUpdatesFromFile(application.Path, out var bundledUpdates))
{
var savedUpdateLookup = savedUpdates.Select(update => update.Item1).ToHashSet();
bool updatesChanged = false;
bool addedNewUpdate = false;
foreach (var update in bundledUpdates.OrderByDescending(bundled => bundled.Version))
{
if (!savedUpdateLookup.Contains(update))
@ -1405,17 +1454,19 @@ namespace Ryujinx.UI.App.Common
if (!selectedUpdate.HasValue || selectedUpdate.Value.Item1.Version < update.Version)
{
shouldSelect = true;
selectedUpdate = Optional<(TitleUpdateModel, bool IsSelected)>.Create((update, true));
if (selectedUpdate.HasValue)
_titleUpdates.AddOrUpdate((selectedUpdate.Value.Item1, false));
selectedUpdate = DynamicData.Kernel.Optional<(TitleUpdateModel, bool IsSelected)>.Create((update, true));
}
modifiedVersion = modifiedVersion || shouldSelect;
it.AddOrUpdate((update, shouldSelect));
addedNewUpdate = true;
updatesChanged = true;
}
}
if (addedNewUpdate)
if (updatesChanged)
{
var gameUpdates = it.Items.Where(update => update.TitleUpdate.TitleIdBase == application.IdBase).ToList();
TitleUpdatesHelper.SaveTitleUpdatesJson(_virtualFileSystem, application.IdBase, gameUpdates);

View file

@ -106,6 +106,7 @@
"SettingsTabGeneralHideCursorAlways": "Always",
"SettingsTabGeneralGameDirectories": "Game Directories",
"SettingsTabGeneralAutoloadDirectories": "Autoload DLC/Updates Directories",
"SettingsTabGeneralAutoloadNote": "DLC and Updates which refer to missing files will be unloaded automatically",
"SettingsTabGeneralAdd": "Add",
"SettingsTabGeneralRemove": "Remove",
"SettingsTabSystem": "System",
@ -725,8 +726,9 @@
"DlcWindowHeading": "{0} Downloadable Content(s)",
"DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed",
"AutoloadUpdateAddedMessage": "{0} new update(s) added",
"AutoloadDlcAndUpdateAddedMessage": "{0} new downloadable content(s) and {1} new update(s) added",
"AutoloadUpdateRemovedMessage": "{0} missing update(s) removed",
"ModWindowHeading": "{0} Mod(s)",
"UserProfilesEditProfile": "Edit Selected",
"Cancel": "Cancel",

View file

@ -102,6 +102,8 @@
"SettingsTabGeneralHideCursorOnIdle": "Masquer le curseur si inactif",
"SettingsTabGeneralHideCursorAlways": "Toujours",
"SettingsTabGeneralGameDirectories": "Dossiers des jeux",
"SettingsTabGeneralAutoloadDirectories": "Dossiers des mises à jour/DLC",
"SettingsTabGeneralAutoloadNote": "Les DLC et les mises à jour faisant référence aux fichiers manquants seront automatiquement déchargés.",
"SettingsTabGeneralAdd": "Ajouter",
"SettingsTabGeneralRemove": "Retirer",
"SettingsTabSystem": "Système",
@ -708,6 +710,11 @@
"CheatWindowHeading": "Cheats disponibles pour {0} [{1}]",
"BuildId": "BuildId:",
"DlcWindowHeading": "{0} Contenu(s) téléchargeable(s)",
"DlcWindowDlcAddedMessage": "{0} nouveau(x) contenu(s) téléchargeable(s) ajouté(s)",
"AutoloadDlcAddedMessage": "{0} nouveau(x) contenu(s) téléchargeable(s) ajouté(s)",
"AutoloadDlcRemovedMessage": "{0} contenu(s) téléchargeable(s) manquant(s) supprimé(s)",
"AutoloadUpdateAddedMessage": "{0} nouvelle(s) mise(s) à jour ajoutée(s)",
"AutoloadUpdateRemovedMessage": "{0} mises à jour manquantes supprimées",
"ModWindowHeading": "{0} Mod(s)",
"UserProfilesEditProfile": "Éditer la sélection",
"Cancel": "Annuler",

View file

@ -52,6 +52,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public class MainWindowViewModel : BaseModel
{
private const int HotKeyPressDelayMs = 500;
private delegate int LoadContentFromFolderDelegate(List<string> dirs, out int numRemoved);
private ObservableCollectionExtended<ApplicationData> _applications;
private string _aspectStatusText;
@ -1259,7 +1260,7 @@ namespace Ryujinx.Ava.UI.ViewModels
_rendererWaitEvent.Set();
}
private async Task LoadContentFromFolder(LocaleKeys localeMessageKey, Func<List<string>, int> onDirsSelected)
private async Task LoadContentFromFolder(LocaleKeys localeMessageAddedKey, LocaleKeys localeMessageRemovedKey, LoadContentFromFolderDelegate onDirsSelected)
{
var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
@ -1270,14 +1271,17 @@ namespace Ryujinx.Ava.UI.ViewModels
if (result.Count > 0)
{
var dirs = result.Select(it => it.Path.LocalPath).ToList();
var numAdded = onDirsSelected(dirs);
var numAdded = onDirsSelected(dirs, out int numRemoved);
var msg = string.Format(LocaleManager.Instance[localeMessageKey], numAdded);
var msg = String.Join("\r\n", new string[] {
string.Format(LocaleManager.Instance[localeMessageRemovedKey], numRemoved),
string.Format(LocaleManager.Instance[localeMessageAddedKey], numAdded)
});
await Dispatcher.UIThread.InvokeAsync(async () =>
{
await ContentDialogHelper.ShowTextDialog(
LocaleManager.Instance[numAdded > 0 ? LocaleKeys.RyujinxConfirm : LocaleKeys.RyujinxInfo],
LocaleManager.Instance[numAdded > 0 || numRemoved > 0 ? LocaleKeys.RyujinxConfirm : LocaleKeys.RyujinxInfo],
msg, "", "", "", LocaleManager.Instance[LocaleKeys.InputDialogOk], (int)Symbol.Checkmark);
});
}
@ -1533,14 +1537,18 @@ namespace Ryujinx.Ava.UI.ViewModels
public async Task LoadDlcFromFolder()
{
await LoadContentFromFolder(LocaleKeys.AutoloadDlcAddedMessage,
dirs => ApplicationLibrary.AutoLoadDownloadableContents(dirs));
await LoadContentFromFolder(
LocaleKeys.AutoloadDlcAddedMessage,
LocaleKeys.AutoloadDlcRemovedMessage,
ApplicationLibrary.AutoLoadDownloadableContents);
}
public async Task LoadTitleUpdatesFromFolder()
{
await LoadContentFromFolder(LocaleKeys.AutoloadUpdateAddedMessage,
dirs => ApplicationLibrary.AutoLoadTitleUpdates(dirs));
await LoadContentFromFolder(
LocaleKeys.AutoloadUpdateAddedMessage,
LocaleKeys.AutoloadUpdateRemovedMessage,
ApplicationLibrary.AutoLoadTitleUpdates);
}
public async Task OpenFolder()

View file

@ -52,7 +52,7 @@
</CheckBox>
</StackPanel>
<Separator Height="1" />
<StackPanel Orientation="Vertical" Spacing="2">
<StackPanel Orientation="Vertical" Spacing="5">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabLoggingDeveloperOptions}" />
<TextBlock Foreground="{DynamicResource SecondaryTextColor}" Text="{locale:Locale SettingsTabLoggingDeveloperOptionsNote}" />
</StackPanel>

View file

@ -195,7 +195,7 @@
<Separator Height="1" />
<StackPanel
Orientation="Vertical"
Spacing="2">
Spacing="5">
<TextBlock
Classes="h1"
Text="{locale:Locale SettingsTabSystemHacks}" />

View file

@ -129,7 +129,10 @@
</Grid>
</StackPanel>
<Separator Height="1" />
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralAutoloadDirectories}" />
<StackPanel Orientation="Vertical" Spacing="5">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralAutoloadDirectories}" />
<TextBlock Foreground="{DynamicResource SecondaryTextColor}" Text="{locale:Locale SettingsTabGeneralAutoloadNote}" />
</StackPanel>
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
@ -137,7 +140,7 @@
Spacing="10">
<ListBox
Name="AutoloadDirsList"
MinHeight="120"
MinHeight="100"
ItemsSource="{Binding AutoloadDirectories}">
<ListBox.Styles>
<Style Selector="ListBoxItem">

View file

@ -647,10 +647,10 @@ namespace Ryujinx.Ava.UI.Windows
var autoloadDirs = ConfigurationState.Instance.UI.AutoloadDirs.Value;
if (autoloadDirs.Count > 0)
{
var updatesLoaded = ApplicationLibrary.AutoLoadTitleUpdates(autoloadDirs);
var dlcLoaded = ApplicationLibrary.AutoLoadDownloadableContents(autoloadDirs);
var updatesLoaded = ApplicationLibrary.AutoLoadTitleUpdates(autoloadDirs, out int updatesRemoved);
var dlcLoaded = ApplicationLibrary.AutoLoadDownloadableContents(autoloadDirs, out int dlcRemoved);
ShowNewContentAddedDialog(dlcLoaded, updatesLoaded);
ShowNewContentAddedDialog(dlcLoaded, dlcRemoved, updatesLoaded, updatesRemoved);
}
_isLoading = false;
@ -662,28 +662,21 @@ namespace Ryujinx.Ava.UI.Windows
applicationLibraryThread.Start();
}
private Task ShowNewContentAddedDialog(int numDlcAdded, int numUpdatesAdded)
private void ShowNewContentAddedDialog(int numDlcAdded, int numDlcRemoved, int numUpdatesAdded, int numUpdatesRemoved)
{
var msg = "";
string[] messages = {
numDlcRemoved > 0 ? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcRemovedMessage], numDlcRemoved): null,
numDlcAdded > 0 ? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAddedMessage], numDlcAdded): null,
numUpdatesRemoved > 0 ? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateRemovedMessage], numUpdatesRemoved): null,
numUpdatesAdded > 0 ? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateAddedMessage], numUpdatesAdded) : null
};
if (numDlcAdded > 0 && numUpdatesAdded > 0)
{
msg = string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAndUpdateAddedMessage], numDlcAdded, numUpdatesAdded);
}
else if (numDlcAdded > 0)
{
msg = string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAddedMessage], numDlcAdded);
}
else if (numUpdatesAdded > 0)
{
msg = string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateAddedMessage], numUpdatesAdded);
}
else
{
return Task.CompletedTask;
}
string msg = String.Join("\r\n", messages);
return Dispatcher.UIThread.InvokeAsync(async () =>
if (String.IsNullOrWhiteSpace(msg))
return;
Dispatcher.UIThread.InvokeAsync(async () =>
{
await ContentDialogHelper.ShowTextDialog(LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle],
msg, "", "", "", LocaleManager.Instance[LocaleKeys.InputDialogOk], (int)Symbol.Checkmark);