Skip to content

Commit

Permalink
Automatically remove invalid dlc and updates as part of auto load (#42)
Browse files Browse the repository at this point in the history
* 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

* Fixed missing french message for AutoloadDlcAddedMessage
  • Loading branch information
amurgshere authored Oct 27, 2024
1 parent 6be8838 commit 7038a90
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 44 deletions.
85 changes: 67 additions & 18 deletions src/Ryujinx.UI.Common/App/ApplicationLibrary.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using DynamicData;
using DynamicData.Kernel;
using Gommon;
using LibHac;
using LibHac.Common;
using LibHac.Fs;
Expand Down Expand Up @@ -801,17 +802,31 @@ public void SaveTitleUpdatesForGame(ApplicationData application, List<(TitleUpda

// 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;
Expand Down Expand Up @@ -900,17 +915,37 @@ public int AutoLoadDownloadableContents(List<string> appDirs)
// 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;
Expand Down Expand Up @@ -979,27 +1014,21 @@ public int AutoLoadTitleUpdates(List<string> appDirs)
{
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);
}
}
}
}
}

titleIdsToSave.ForEach(titleId => SaveTitleUpdatesForGame(titleId));
titleIdsToRefresh.ForEach(titleId => RefreshApplicationInfo(titleId));
}
finally
{
Expand All @@ -1010,6 +1039,24 @@ public int AutoLoadTitleUpdates(List<string> appDirs)
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);
Expand Down Expand Up @@ -1394,8 +1441,8 @@ private bool LoadTitleUpdatesForApplication(ApplicationData application)
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))
Expand All @@ -1404,17 +1451,19 @@ private bool LoadTitleUpdatesForApplication(ApplicationData application)
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);
Expand Down
4 changes: 3 additions & 1 deletion src/Ryujinx/Assets/Locales/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -733,8 +734,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",
Expand Down
4 changes: 3 additions & 1 deletion src/Ryujinx/Assets/Locales/fr_FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
"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",
Expand Down Expand Up @@ -733,8 +734,9 @@
"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)",
"AutoloadDlcAndUpdateAddedMessage": "{0} nouveau(x) contenu(s) téléchargeable(s) et {1} nouvelle(s) mise(s) à jour ajouté(s)",
"AutoloadUpdateRemovedMessage": "{0} mises à jour manquantes supprimées",
"ModWindowHeading": "{0} Mod(s)",
"UserProfilesEditProfile": "Éditer la sélection",
"Cancel": "Annuler",
Expand Down
22 changes: 16 additions & 6 deletions src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,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;
Expand Down Expand Up @@ -1280,7 +1281,7 @@ private void RendererHost_Created(object sender, EventArgs e)
_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
{
Expand All @@ -1291,14 +1292,17 @@ private async Task LoadContentFromFolder(LocaleKeys localeMessageKey, Func<List<
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);
});
}
Expand Down Expand Up @@ -1554,12 +1558,18 @@ public async Task OpenFile()

public async Task LoadDlcFromFolder()
{
await LoadContentFromFolder(LocaleKeys.AutoloadDlcAddedMessage, ApplicationLibrary.AutoLoadDownloadableContents);
await LoadContentFromFolder(
LocaleKeys.AutoloadDlcAddedMessage,
LocaleKeys.AutoloadDlcRemovedMessage,
ApplicationLibrary.AutoLoadDownloadableContents);
}

public async Task LoadTitleUpdatesFromFolder()
{
await LoadContentFromFolder(LocaleKeys.AutoloadUpdateAddedMessage, ApplicationLibrary.AutoLoadTitleUpdates);
await LoadContentFromFolder(
LocaleKeys.AutoloadUpdateAddedMessage,
LocaleKeys.AutoloadUpdateRemovedMessage,
ApplicationLibrary.AutoLoadTitleUpdates);
}

public async Task OpenFolder()
Expand Down
2 changes: 1 addition & 1 deletion src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
2 changes: 1 addition & 1 deletion src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@
<Separator Height="1" />
<StackPanel
Orientation="Vertical"
Spacing="2">
Spacing="5">
<TextBlock
Classes="h1"
Text="{locale:Locale SettingsTabSystemHacks}" />
Expand Down
7 changes: 5 additions & 2 deletions src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,18 @@
</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"
Orientation="Vertical"
Spacing="10">
<ListBox
Name="AutoloadDirsList"
MinHeight="120"
MinHeight="100"
ItemsSource="{Binding AutoloadDirectories}">
<ListBox.Styles>
<Style Selector="ListBoxItem">
Expand Down
28 changes: 14 additions & 14 deletions src/Ryujinx/UI/Windows/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -633,10 +633,10 @@ private void ReloadGameList()
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;
Expand All @@ -648,19 +648,19 @@ private void ReloadGameList()
applicationLibraryThread.Start();
}

private void ShowNewContentAddedDialog(int numDlcAdded, int numUpdatesAdded)
private void ShowNewContentAddedDialog(int numDlcAdded, int numDlcRemoved, int numUpdatesAdded, int numUpdatesRemoved)
{
string msg = numDlcAdded > 0 && numUpdatesAdded > 0
? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAndUpdateAddedMessage], numDlcAdded, numUpdatesAdded)
: numDlcAdded > 0
? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAddedMessage], numDlcAdded)
: numUpdatesAdded > 0
? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateAddedMessage], numUpdatesAdded)
: null;

if (msg is null)
return;
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
};

string msg = String.Join("\r\n", messages);

if (String.IsNullOrWhiteSpace(msg))
return;

Dispatcher.UIThread.InvokeAsync(async () =>
{
Expand Down

0 comments on commit 7038a90

Please sign in to comment.