diff --git a/OF DL.Cli/Program.cs b/OF DL.Cli/Program.cs index 731abb4..3bcb864 100644 --- a/OF DL.Cli/Program.cs +++ b/OF DL.Cli/Program.cs @@ -294,8 +294,9 @@ public class Program(IServiceProvider serviceProvider) } else if (config.NonInteractiveMode && !string.IsNullOrEmpty(config.NonInteractiveModeListName)) { - Dictionary selectedUsers = + ListUserSelectionResult listSelectionResult = await orchestrationService.GetUsersForListAsync(config.NonInteractiveModeListName, users, lists); + Dictionary selectedUsers = listSelectionResult.SelectedUsers; hasSelectedUsersKVP = new KeyValuePair>(true, selectedUsers); } else diff --git a/OF DL.Core/Models/Downloads/CreatorDownloadResult.cs b/OF DL.Core/Models/Downloads/CreatorDownloadResult.cs index d686e1b..d376764 100644 --- a/OF DL.Core/Models/Downloads/CreatorDownloadResult.cs +++ b/OF DL.Core/Models/Downloads/CreatorDownloadResult.cs @@ -27,3 +27,10 @@ public class UserListResult public string? IgnoredListError { get; set; } } + +public class ListUserSelectionResult +{ + public Dictionary SelectedUsers { get; set; } = new(); + + public List UnavailableUsernames { get; set; } = []; +} diff --git a/OF DL.Core/Services/DownloadOrchestrationService.cs b/OF DL.Core/Services/DownloadOrchestrationService.cs index 51f5f79..3f28fb0 100644 --- a/OF DL.Core/Services/DownloadOrchestrationService.cs +++ b/OF DL.Core/Services/DownloadOrchestrationService.cs @@ -110,13 +110,26 @@ public class DownloadOrchestrationService( /// All available users. /// Known lists keyed by name. /// The users that belong to the list. - public async Task> GetUsersForListAsync( + public async Task GetUsersForListAsync( string listName, Dictionary allUsers, Dictionary lists) { + ListUserSelectionResult result = new(); long listId = lists[listName]; List listUsernames = await apiService.GetListUsers($"/lists/{listId}/users") ?? []; - return allUsers.Where(x => listUsernames.Contains(x.Key)) + HashSet listUsernamesSet = listUsernames.ToHashSet(StringComparer.OrdinalIgnoreCase); + HashSet allUsernamesSet = allUsers.Keys.ToHashSet(StringComparer.OrdinalIgnoreCase); + + result.SelectedUsers = allUsers + .Where(x => listUsernamesSet.Contains(x.Key)) .ToDictionary(x => x.Key, x => x.Value); + + result.UnavailableUsernames = listUsernames + .Where(username => !allUsernamesSet.Contains(username)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .OrderBy(username => username, StringComparer.OrdinalIgnoreCase) + .ToList(); + + return result; } /// diff --git a/OF DL.Core/Services/IDownloadOrchestrationService.cs b/OF DL.Core/Services/IDownloadOrchestrationService.cs index 8c4dc7c..46f46ea 100644 --- a/OF DL.Core/Services/IDownloadOrchestrationService.cs +++ b/OF DL.Core/Services/IDownloadOrchestrationService.cs @@ -17,7 +17,7 @@ public interface IDownloadOrchestrationService /// /// Get users for a specific list by name. /// - Task> GetUsersForListAsync( + Task GetUsersForListAsync( string listName, Dictionary allUsers, Dictionary lists); /// diff --git a/OF DL.Gui/ViewModels/MainWindowViewModel.cs b/OF DL.Gui/ViewModels/MainWindowViewModel.cs index c782990..9429a1e 100644 --- a/OF DL.Gui/ViewModels/MainWindowViewModel.cs +++ b/OF DL.Gui/ViewModels/MainWindowViewModel.cs @@ -371,10 +371,23 @@ public partial class MainWindowViewModel( [NotifyCanExecuteChangedFor(nameof(SubmitSinglePostOrMessageCommand))] [NotifyCanExecuteChangedFor(nameof(DownloadSelectedCommand))] [NotifyCanExecuteChangedFor(nameof(DownloadPurchasedTabCommand))] + [NotifyCanExecuteChangedFor(nameof(SelectUsersFromListCommand))] private bool _isDownloadSelectionWarningModalOpen; [ObservableProperty] private string _downloadSelectionWarningMessage = string.Empty; + [ObservableProperty] + [NotifyCanExecuteChangedFor(nameof(OpenSinglePostOrMessageModalCommand))] + [NotifyCanExecuteChangedFor(nameof(SubmitSinglePostOrMessageCommand))] + [NotifyCanExecuteChangedFor(nameof(DownloadSelectedCommand))] + [NotifyCanExecuteChangedFor(nameof(DownloadPurchasedTabCommand))] + [NotifyCanExecuteChangedFor(nameof(SelectUsersFromListCommand))] + private bool _isListSelectionWarningModalOpen; + + [ObservableProperty] private string _listSelectionWarningMessage = string.Empty; + + public ObservableCollection ListSelectionUnavailableUsers { get; } = []; + [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SubmitSinglePostOrMessageCommand))] private string _singlePostOrMessageUrl = string.Empty; @@ -827,20 +840,24 @@ public partial class MainWindowViewModel( user.IsSelected = false; } - Dictionary listUsers = await downloadOrchestrationService.GetUsersForListAsync( + ListUserSelectionResult listSelectionResult = await downloadOrchestrationService.GetUsersForListAsync( SelectedListName, _allUsers, _allLists); - HashSet selectedUsernames = listUsers.Keys.ToHashSet(StringComparer.Ordinal); + HashSet selectedUsernames = listSelectionResult.SelectedUsers.Keys + .ToHashSet(StringComparer.OrdinalIgnoreCase); foreach (SelectableUserViewModel user in AvailableUsers) { user.IsSelected = selectedUsernames.Contains(user.Username); } + ShowListSelectionWarningIfNeeded(listSelectionResult.UnavailableUsernames); + OnPropertyChanged(nameof(SelectedUsersSummary)); OnPropertyChanged(nameof(AllUsersSelected)); DownloadSelectedCommand.NotifyCanExecuteChanged(); + SelectUsersFromListCommand.NotifyCanExecuteChanged(); } finally { @@ -935,6 +952,13 @@ public partial class MainWindowViewModel( EditConfig(); } + [RelayCommand] + private void CloseListSelectionWarning() + { + IsListSelectionWarningModalOpen = false; + ListSelectionUnavailableUsers.Clear(); + } + [RelayCommand(CanExecute = nameof(CanStopWork))] private void StopWork() { @@ -1075,6 +1099,7 @@ public partial class MainWindowViewModel( private bool CanApplySelectedList() => CurrentScreen == AppScreen.UserSelection && !string.IsNullOrWhiteSpace(SelectedListName) && + !IsListSelectionWarningModalOpen && !IsDownloading; private bool CanDownloadSelected() => @@ -1083,6 +1108,7 @@ public partial class MainWindowViewModel( !IsDownloadSelectionWarningModalOpen && !IsMissingCdmWarningModalOpen && !IsShowScrapeSizeWarningModalOpen && + !IsListSelectionWarningModalOpen && !IsDownloading; private bool CanDownloadPurchasedTab() => @@ -1091,6 +1117,7 @@ public partial class MainWindowViewModel( !IsDownloadSelectionWarningModalOpen && !IsMissingCdmWarningModalOpen && !IsShowScrapeSizeWarningModalOpen && + !IsListSelectionWarningModalOpen && !IsDownloading; private bool CanOpenSinglePostOrMessageModal() => @@ -1099,6 +1126,7 @@ public partial class MainWindowViewModel( !IsDownloadSelectionWarningModalOpen && !IsMissingCdmWarningModalOpen && !IsShowScrapeSizeWarningModalOpen && + !IsListSelectionWarningModalOpen && !IsSinglePostOrMessageModalOpen; private bool CanSubmitSinglePostOrMessage() => @@ -1106,6 +1134,7 @@ public partial class MainWindowViewModel( !IsDownloadSelectionWarningModalOpen && !IsMissingCdmWarningModalOpen && !IsShowScrapeSizeWarningModalOpen && + !IsListSelectionWarningModalOpen && !IsDownloading && !string.IsNullOrWhiteSpace(SinglePostOrMessageUrl); @@ -1195,7 +1224,8 @@ public partial class MainWindowViewModel( partial void OnSelectedListNameChanged(string? value) { SelectUsersFromListCommand.NotifyCanExecuteChanged(); - if (_isApplyingListSelection || IsDownloading || CurrentScreen != AppScreen.UserSelection || + if (_isApplyingListSelection || IsDownloading || IsListSelectionWarningModalOpen || + CurrentScreen != AppScreen.UserSelection || string.IsNullOrWhiteSpace(value)) { return; @@ -1533,6 +1563,42 @@ public partial class MainWindowViewModel( return confirmed; } + private void ShowListSelectionWarningIfNeeded(IReadOnlyCollection unavailableUsers) + { + if (unavailableUsers.Count == 0) + { + return; + } + + ListSelectionUnavailableUsers.Clear(); + foreach (string username in unavailableUsers.OrderBy(name => name, StringComparer.OrdinalIgnoreCase)) + { + ListSelectionUnavailableUsers.Add(username); + } + + List recommendations = []; + if (!configService.CurrentConfig.IncludeExpiredSubscriptions) + { + recommendations.Add("Include Expired Subscriptions"); + } + + if (!configService.CurrentConfig.IncludeRestrictedSubscriptions) + { + recommendations.Add("Include Restricted Subscriptions"); + } + + string recommendationText = recommendations.Count > 0 + ? $"Recommendation:\nEnable {string.Join(" and ", recommendations.Select(option => $"\"{option}\""))} in the subscriptions section of the configuration page. After doing so, select \"Refresh\" from the file menu." + : "These users may be unavailable or inaccessible with your current account."; + + ListSelectionWarningMessage = + $"{unavailableUsers.Count} user(s) from \"{SelectedListName}\" could not be selected.\n\n" + + $"{recommendationText}\n\n" + + "Users that could not be selected:"; + + IsListSelectionWarningModalOpen = true; + } + private static bool TryParseSinglePostOrMessageUrl( string url, out SingleDownloadRequest request, diff --git a/OF DL.Gui/Views/MainWindow.axaml b/OF DL.Gui/Views/MainWindow.axaml index ba0d7c2..d9bb626 100644 --- a/OF DL.Gui/Views/MainWindow.axaml +++ b/OF DL.Gui/Views/MainWindow.axaml @@ -1573,6 +1573,58 @@ + + + + + + + + + + + + + + + + + + +