Defer errors when processing multiple queries (#660)
This commit is contained in:
parent
e4bfb708dc
commit
3a19ecb343
@ -1,10 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Gress;
|
||||
using YoutubeDownloader.Core.Utils;
|
||||
using YoutubeExplode;
|
||||
using YoutubeExplode.Channels;
|
||||
@ -122,33 +120,4 @@ public class QueryResolver(IReadOnlyList<Cookie>? initialCookies = null)
|
||||
?? await TryResolveChannelAsync(query, cancellationToken)
|
||||
?? await ResolveSearchAsync(query, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<QueryResult> ResolveAsync(
|
||||
IReadOnlyList<string> queries,
|
||||
IProgress<Percentage>? progress = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (queries.Count == 1)
|
||||
return await ResolveAsync(queries.Single(), cancellationToken);
|
||||
|
||||
var videos = new List<IVideo>();
|
||||
var videoIds = new HashSet<VideoId>();
|
||||
|
||||
var completed = 0;
|
||||
foreach (var query in queries)
|
||||
{
|
||||
var result = await ResolveAsync(query, cancellationToken);
|
||||
|
||||
foreach (var video in result.Videos)
|
||||
{
|
||||
if (videoIds.Add(video.Id))
|
||||
videos.Add(video);
|
||||
}
|
||||
|
||||
progress?.Report(Percentage.FromFraction(1.0 * ++completed / queries.Count));
|
||||
}
|
||||
|
||||
return new QueryResult(QueryResultKind.Aggregate, $"{queries.Count} queries", videos);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using YoutubeExplode.Videos;
|
||||
|
||||
namespace YoutubeDownloader.Core.Resolving;
|
||||
|
||||
public record QueryResult(QueryResultKind Kind, string Title, IReadOnlyList<IVideo> Videos);
|
||||
public record QueryResult(QueryResultKind Kind, string Title, IReadOnlyList<IVideo> Videos)
|
||||
{
|
||||
public static QueryResult Aggregate(IReadOnlyList<QueryResult> results)
|
||||
{
|
||||
if (!results.Any())
|
||||
throw new ArgumentException("Cannot aggregate empty results.", nameof(results));
|
||||
|
||||
return new QueryResult(
|
||||
// Single query -> inherit kind, multiple queries -> aggregate
|
||||
results.Count == 1
|
||||
? results.Single().Kind
|
||||
: QueryResultKind.Aggregate,
|
||||
// Single query -> inherit title, multiple queries -> aggregate
|
||||
results.Count == 1
|
||||
? results.Single().Title
|
||||
: $"{results.Count} queries",
|
||||
// Combine all videos, deduplicate by ID
|
||||
results.SelectMany(q => q.Videos).DistinctBy(v => v.Id).ToArray()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@ -21,6 +22,7 @@ namespace YoutubeDownloader.ViewModels.Components;
|
||||
public partial class DashboardViewModel : ViewModelBase
|
||||
{
|
||||
private readonly ViewModelManager _viewModelManager;
|
||||
private readonly SnackbarManager _snackbarManager;
|
||||
private readonly DialogManager _dialogManager;
|
||||
private readonly SettingsService _settingsService;
|
||||
|
||||
@ -28,24 +30,15 @@ public partial class DashboardViewModel : ViewModelBase
|
||||
private readonly ResizableSemaphore _downloadSemaphore = new();
|
||||
private readonly AutoResetProgressMuxer _progressMuxer;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(IsProgressIndeterminate))]
|
||||
[NotifyCanExecuteChangedFor(nameof(ProcessQueryCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(ShowAuthSetupCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(ShowSettingsCommand))]
|
||||
public partial bool IsBusy { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(ProcessQueryCommand))]
|
||||
public partial string? Query { get; set; }
|
||||
|
||||
public DashboardViewModel(
|
||||
ViewModelManager viewModelManager,
|
||||
SnackbarManager snackbarManager,
|
||||
DialogManager dialogManager,
|
||||
SettingsService settingsService
|
||||
)
|
||||
{
|
||||
_viewModelManager = viewModelManager;
|
||||
_snackbarManager = snackbarManager;
|
||||
_dialogManager = dialogManager;
|
||||
_settingsService = settingsService;
|
||||
|
||||
@ -67,12 +60,23 @@ public partial class DashboardViewModel : ViewModelBase
|
||||
);
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(IsProgressIndeterminate))]
|
||||
[NotifyCanExecuteChangedFor(nameof(ProcessQueryCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(ShowAuthSetupCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(ShowSettingsCommand))]
|
||||
public partial bool IsBusy { get; set; }
|
||||
|
||||
public ProgressContainer<Percentage> Progress { get; } = new();
|
||||
|
||||
public ObservableCollection<DownloadViewModel> Downloads { get; } = [];
|
||||
|
||||
public bool IsProgressIndeterminate => IsBusy && Progress.Current.Fraction is <= 0 or >= 1;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(ProcessQueryCommand))]
|
||||
public partial string? Query { get; set; }
|
||||
|
||||
public ObservableCollection<DownloadViewModel> Downloads { get; } = [];
|
||||
|
||||
private bool CanShowAuthSetup() => !IsBusy;
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanShowAuthSetup))]
|
||||
@ -179,18 +183,41 @@ public partial class DashboardViewModel : ViewModelBase
|
||||
var resolver = new QueryResolver(_settingsService.LastAuthCookies);
|
||||
var downloader = new VideoDownloader(_settingsService.LastAuthCookies);
|
||||
|
||||
var result = await resolver.ResolveAsync(
|
||||
Query.Split(
|
||||
"\n",
|
||||
// Split queries by newlines
|
||||
var queries = Query.Split(
|
||||
'\n',
|
||||
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries
|
||||
),
|
||||
progress
|
||||
);
|
||||
|
||||
// Single video
|
||||
if (result.Videos.Count == 1)
|
||||
// Process individual queries
|
||||
var queryResults = new List<QueryResult>();
|
||||
foreach (var (i, query) in queries.Index())
|
||||
{
|
||||
var video = result.Videos.Single();
|
||||
try
|
||||
{
|
||||
queryResults.Add(await resolver.ResolveAsync(query));
|
||||
}
|
||||
// If it's not the only query in the list, don't interrupt the process
|
||||
// and report the error via an async notification instead of a sync dialog.
|
||||
// https://github.com/Tyrrrz/YoutubeDownloader/issues/563
|
||||
catch (YoutubeExplodeException ex)
|
||||
when (ex is VideoUnavailableException or PlaylistUnavailableException
|
||||
&& queries.Length > 1
|
||||
)
|
||||
{
|
||||
_snackbarManager.Notify(ex.Message);
|
||||
}
|
||||
|
||||
progress.Report(Percentage.FromFraction((i + 1.0) / queries.Length));
|
||||
}
|
||||
|
||||
// Aggregate results
|
||||
var queryResult = QueryResult.Aggregate(queryResults);
|
||||
|
||||
// Single video result
|
||||
if (queryResult.Videos.Count == 1)
|
||||
{
|
||||
var video = queryResult.Videos.Single();
|
||||
|
||||
var downloadOptions = await downloader.GetDownloadOptionsAsync(
|
||||
video.Id,
|
||||
@ -209,14 +236,14 @@ public partial class DashboardViewModel : ViewModelBase
|
||||
Query = "";
|
||||
}
|
||||
// Multiple videos
|
||||
else if (result.Videos.Count > 1)
|
||||
else if (queryResult.Videos.Count > 1)
|
||||
{
|
||||
var downloads = await _dialogManager.ShowDialogAsync(
|
||||
_viewModelManager.CreateDownloadMultipleSetupViewModel(
|
||||
result.Title,
|
||||
result.Videos,
|
||||
queryResult.Title,
|
||||
queryResult.Videos,
|
||||
// Pre-select videos if they come from a single query and not from search
|
||||
result.Kind
|
||||
queryResult.Kind
|
||||
is not QueryResultKind.Search
|
||||
and not QueryResultKind.Aggregate
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user