Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically remove invalid dlc and updates as part of auto load #42

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -802,17 +803,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 @@ -901,17 +916,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 @@ -980,27 +1015,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 @@ -1011,6 +1040,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 @@ -1395,8 +1442,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 @@ -1405,17 +1452,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 @@ -731,8 +732,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 @@ -731,8 +732,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)",
GreemDev marked this conversation as resolved.
Show resolved Hide resolved
"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
1 change: 0 additions & 1 deletion src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ public class InputViewModel : BaseModel, IDisposable

private PlayerIndex _playerId;
private int _controller;
private readonly int _controllerNumber;
private string _controllerImage;
private int _device;
private object _configViewModel;
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 @@ -51,6 +51,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 @@ -1262,7 +1263,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 @@ -1273,14 +1274,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 @@ -1536,12 +1540,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 @@ -640,10 +640,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 @@ -655,19 +655,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
Loading