parent
a6bf885340
commit
2f0e0daa3c
8 changed files with 203 additions and 50 deletions
|
|
@ -8,7 +8,7 @@ namespace LANCommander.Server.Services.Abstractions
|
|||
{
|
||||
string Name { get; }
|
||||
MediaType[] SupportedMediaTypes { get; }
|
||||
Task<IEnumerable<MediaGrabberResult>> SearchAsync(MediaType type, string keywords);
|
||||
Task<IEnumerable<MediaGrabberResult>> SearchAsync(MediaType type, string keywords, int page = 0);
|
||||
Task<MediaGrabberDownload> DownloadAsync(MediaGrabberResult result);
|
||||
|
||||
Task<MediaGrabberDownload> DownloadAsync(MediaGrabberResult result, IProgress<MediaDownloadProgress>? progress)
|
||||
|
|
@ -16,16 +16,24 @@ namespace LANCommander.Server.Services.Abstractions
|
|||
|
||||
IEnumerable<string> GetGrabberNames() => [Name];
|
||||
|
||||
/// <summary>
|
||||
/// Whether this grabber can return additional pages of results for a given game/group.
|
||||
/// Grabbers that return their full result set in a single request should leave this false.
|
||||
/// </summary>
|
||||
bool SupportsPaging => false;
|
||||
|
||||
IEnumerable<string> GetPagingGrabberNames() => SupportsPaging ? [Name] : [];
|
||||
|
||||
async IAsyncEnumerable<IEnumerable<MediaGrabberResult>> SearchStreamAsync(
|
||||
MediaType type, string keywords,
|
||||
MediaType type, string keywords, int page,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
yield return await SearchAsync(type, keywords);
|
||||
yield return await SearchAsync(type, keywords, page);
|
||||
}
|
||||
|
||||
IAsyncEnumerable<IEnumerable<MediaGrabberResult>> SearchStreamAsync(
|
||||
MediaType type, string keywords, string? grabberName,
|
||||
MediaType type, string keywords, string? grabberName, int page,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
=> SearchStreamAsync(type, keywords, cancellationToken);
|
||||
=> SearchStreamAsync(type, keywords, page, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,11 +22,11 @@ namespace LANCommander.Server.Services.MediaGrabbers
|
|||
_grabbers = new List<IMediaGrabberService> { hq, steam, steamGridDb, youtube };
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<MediaGrabberResult>> SearchAsync(MediaType type, string keywords)
|
||||
public async Task<IEnumerable<MediaGrabberResult>> SearchAsync(MediaType type, string keywords, int page = 0)
|
||||
{
|
||||
var results = new List<MediaGrabberResult>();
|
||||
|
||||
await foreach (var batch in SearchStreamAsync(type, keywords))
|
||||
await foreach (var batch in SearchStreamAsync(type, keywords, page))
|
||||
results.AddRange(batch);
|
||||
|
||||
return results;
|
||||
|
|
@ -34,16 +34,18 @@ namespace LANCommander.Server.Services.MediaGrabbers
|
|||
|
||||
public IEnumerable<string> GetGrabberNames() => _grabbers.Select(g => g.Name);
|
||||
|
||||
public IEnumerable<string> GetPagingGrabberNames() => _grabbers.Where(g => g.SupportsPaging).Select(g => g.Name);
|
||||
|
||||
public async IAsyncEnumerable<IEnumerable<MediaGrabberResult>> SearchStreamAsync(
|
||||
MediaType type, string keywords,
|
||||
MediaType type, string keywords, int page,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
await foreach (var batch in SearchStreamAsync(type, keywords, null, cancellationToken))
|
||||
await foreach (var batch in SearchStreamAsync(type, keywords, null, page, cancellationToken))
|
||||
yield return batch;
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<IEnumerable<MediaGrabberResult>> SearchStreamAsync(
|
||||
MediaType type, string keywords, string? grabberName,
|
||||
MediaType type, string keywords, string? grabberName, int page,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
var applicable = _grabbers.Where(g => g.SupportedMediaTypes.Contains(type)).ToList();
|
||||
|
|
@ -60,7 +62,7 @@ namespace LANCommander.Server.Services.MediaGrabbers
|
|||
{
|
||||
try
|
||||
{
|
||||
var results = (await grabber.SearchAsync(type, keywords)).ToList();
|
||||
var results = (await grabber.SearchAsync(type, keywords, page)).ToList();
|
||||
|
||||
foreach (var result in results)
|
||||
result.GrabberName = grabber.Name;
|
||||
|
|
|
|||
|
|
@ -36,8 +36,13 @@ public class HqMediaGrabber(
|
|||
{ MediaType.Manual, HqModels.MediaType.Manual },
|
||||
};
|
||||
|
||||
public async Task<IEnumerable<MediaGrabberResult>> SearchAsync(MediaType type, string keywords)
|
||||
public async Task<IEnumerable<MediaGrabberResult>> SearchAsync(MediaType type, string keywords, int page = 0)
|
||||
{
|
||||
// HQ returns all matching media for a game in a single request,
|
||||
// so there are no further pages to load.
|
||||
if (page > 0)
|
||||
return [];
|
||||
|
||||
if (!settingsProvider.CurrentValue.Server.HQ.IsAuthenticated)
|
||||
return [];
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ namespace LANCommander.Server.Services.MediaGrabbers
|
|||
|
||||
public string Name => "SteamGridDB";
|
||||
|
||||
public bool SupportsPaging => true;
|
||||
|
||||
public MediaType[] SupportedMediaTypes =>
|
||||
[
|
||||
MediaType.Icon,
|
||||
|
|
@ -34,7 +36,7 @@ namespace LANCommander.Server.Services.MediaGrabbers
|
|||
_settingsProvider = settingsProvider;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<MediaGrabberResult>> SearchAsync(MediaType type, string keywords)
|
||||
public async Task<IEnumerable<MediaGrabberResult>> SearchAsync(MediaType type, string keywords, int page = 0)
|
||||
{
|
||||
var results = new List<MediaGrabberResult>();
|
||||
|
||||
|
|
@ -52,23 +54,23 @@ namespace LANCommander.Server.Services.MediaGrabbers
|
|||
switch (type)
|
||||
{
|
||||
case MediaType.Icon:
|
||||
results.AddRange(await GetIconsAsync(SteamGridDb, game));
|
||||
results.AddRange(await GetIconsAsync(SteamGridDb, game, page));
|
||||
break;
|
||||
|
||||
case MediaType.Cover:
|
||||
results.AddRange(await GetCoversAsync(SteamGridDb, game));
|
||||
results.AddRange(await GetCoversAsync(SteamGridDb, game, page));
|
||||
break;
|
||||
|
||||
case MediaType.Background:
|
||||
results.AddRange(await GetBackgroundsAsync(SteamGridDb, game));
|
||||
results.AddRange(await GetBackgroundsAsync(SteamGridDb, game, page));
|
||||
break;
|
||||
|
||||
case MediaType.Logo:
|
||||
results.AddRange(await GetLogosAsync(SteamGridDb, game));
|
||||
results.AddRange(await GetLogosAsync(SteamGridDb, game, page));
|
||||
break;
|
||||
|
||||
case MediaType.Grid:
|
||||
results.AddRange(await GetGridsAsync(SteamGridDb, game));
|
||||
results.AddRange(await GetGridsAsync(SteamGridDb, game, page));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -97,11 +99,11 @@ namespace LANCommander.Server.Services.MediaGrabbers
|
|||
};
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<MediaGrabberResult>> GetIconsAsync(SteamGridDb SteamGridDb, SteamGridDbGame game)
|
||||
private async Task<IEnumerable<MediaGrabberResult>> GetIconsAsync(SteamGridDb SteamGridDb, SteamGridDbGame game, int page)
|
||||
{
|
||||
var results = new List<MediaGrabberResult>();
|
||||
|
||||
var icons = await SteamGridDb.GetIconsByGameIdAsync(game.Id, types: SteamGridDbTypes.Static);
|
||||
var icons = await SteamGridDb.GetIconsByGameIdAsync(game.Id, types: SteamGridDbTypes.Static, page: page);
|
||||
|
||||
results.AddRange(icons.Where(i => SupportedFormats.Contains(i.Format)).Select(i => new MediaGrabberResult()
|
||||
{
|
||||
|
|
@ -113,7 +115,7 @@ namespace LANCommander.Server.Services.MediaGrabbers
|
|||
MimeType = GetMimeType(i.Format)
|
||||
}));
|
||||
|
||||
var animatedIcons = await SteamGridDb.GetIconsByGameIdAsync(game.Id, types: SteamGridDbTypes.Animated);
|
||||
var animatedIcons = await SteamGridDb.GetIconsByGameIdAsync(game.Id, types: SteamGridDbTypes.Animated, page: page);
|
||||
|
||||
results.AddRange(animatedIcons.Where(i => i.Format == SteamGridDbFormats.Png).Select(i => new MediaGrabberResult()
|
||||
{
|
||||
|
|
@ -128,11 +130,11 @@ namespace LANCommander.Server.Services.MediaGrabbers
|
|||
return results;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<MediaGrabberResult>> GetCoversAsync(SteamGridDb SteamGridDb, SteamGridDbGame game)
|
||||
private async Task<IEnumerable<MediaGrabberResult>> GetCoversAsync(SteamGridDb SteamGridDb, SteamGridDbGame game, int page)
|
||||
{
|
||||
var results = new List<MediaGrabberResult>();
|
||||
|
||||
var covers = await SteamGridDb.GetGridsByGameIdAsync(game.Id, types: SteamGridDbTypes.Static);
|
||||
var covers = await SteamGridDb.GetGridsByGameIdAsync(game.Id, types: SteamGridDbTypes.Static, page: page);
|
||||
|
||||
results.AddRange(covers.Where(b => SupportedFormats.Contains(b.Format)).Select(b => new MediaGrabberResult()
|
||||
{
|
||||
|
|
@ -144,7 +146,7 @@ namespace LANCommander.Server.Services.MediaGrabbers
|
|||
MimeType = GetMimeType(b.Format)
|
||||
}));
|
||||
|
||||
var animatedCovers = await SteamGridDb.GetGridsByGameIdAsync(game.Id, types: SteamGridDbTypes.Animated);
|
||||
var animatedCovers = await SteamGridDb.GetGridsByGameIdAsync(game.Id, types: SteamGridDbTypes.Animated, page: page);
|
||||
|
||||
results.AddRange(animatedCovers.Where(b => b.Format == SteamGridDbFormats.Png).Select(b => new MediaGrabberResult()
|
||||
{
|
||||
|
|
@ -159,11 +161,11 @@ namespace LANCommander.Server.Services.MediaGrabbers
|
|||
return results;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<MediaGrabberResult>> GetBackgroundsAsync(SteamGridDb SteamGridDb, SteamGridDbGame game)
|
||||
private async Task<IEnumerable<MediaGrabberResult>> GetBackgroundsAsync(SteamGridDb SteamGridDb, SteamGridDbGame game, int page)
|
||||
{
|
||||
var results = new List<MediaGrabberResult>();
|
||||
|
||||
var backgrounds = await SteamGridDb.GetHeroesByGameIdAsync(game.Id, types: SteamGridDbTypes.Static);
|
||||
var backgrounds = await SteamGridDb.GetHeroesByGameIdAsync(game.Id, types: SteamGridDbTypes.Static, page: page);
|
||||
|
||||
results.AddRange(backgrounds.Where(b => SupportedFormats.Contains(b.Format)).Select(b => new MediaGrabberResult()
|
||||
{
|
||||
|
|
@ -175,7 +177,7 @@ namespace LANCommander.Server.Services.MediaGrabbers
|
|||
MimeType = GetMimeType(b.Format)
|
||||
}));
|
||||
|
||||
var animatedBackgrounds = await SteamGridDb.GetHeroesByGameIdAsync(game.Id, types: SteamGridDbTypes.Animated);
|
||||
var animatedBackgrounds = await SteamGridDb.GetHeroesByGameIdAsync(game.Id, types: SteamGridDbTypes.Animated, page: page);
|
||||
|
||||
results.AddRange(animatedBackgrounds.Where(b => b.Format == SteamGridDbFormats.Png).Select(b => new MediaGrabberResult()
|
||||
{
|
||||
|
|
@ -190,11 +192,11 @@ namespace LANCommander.Server.Services.MediaGrabbers
|
|||
return results;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<MediaGrabberResult>> GetLogosAsync(SteamGridDb SteamGridDb, SteamGridDbGame game)
|
||||
private async Task<IEnumerable<MediaGrabberResult>> GetLogosAsync(SteamGridDb SteamGridDb, SteamGridDbGame game, int page)
|
||||
{
|
||||
var results = new List<MediaGrabberResult>();
|
||||
|
||||
var logos = await SteamGridDb.GetLogosByGameIdAsync(game.Id, types: SteamGridDbTypes.Static);
|
||||
var logos = await SteamGridDb.GetLogosByGameIdAsync(game.Id, types: SteamGridDbTypes.Static, page: page);
|
||||
|
||||
results.AddRange(logos.Where(b => SupportedFormats.Contains(b.Format)).Select(b => new MediaGrabberResult()
|
||||
{
|
||||
|
|
@ -206,7 +208,7 @@ namespace LANCommander.Server.Services.MediaGrabbers
|
|||
MimeType = GetMimeType(b.Format)
|
||||
}));
|
||||
|
||||
var animatedLogos = await SteamGridDb.GetLogosByGameIdAsync(game.Id, types: SteamGridDbTypes.Animated);
|
||||
var animatedLogos = await SteamGridDb.GetLogosByGameIdAsync(game.Id, types: SteamGridDbTypes.Animated, page: page);
|
||||
|
||||
results.AddRange(animatedLogos.Where(b => b.Format == SteamGridDbFormats.Png).Select(b => new MediaGrabberResult()
|
||||
{
|
||||
|
|
@ -221,14 +223,15 @@ namespace LANCommander.Server.Services.MediaGrabbers
|
|||
return results;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<MediaGrabberResult>> GetGridsAsync(SteamGridDb SteamGridDb, SteamGridDbGame game)
|
||||
private async Task<IEnumerable<MediaGrabberResult>> GetGridsAsync(SteamGridDb SteamGridDb, SteamGridDbGame game, int page)
|
||||
{
|
||||
var results = new List<MediaGrabberResult>();
|
||||
|
||||
var grids = await SteamGridDb.GetGridsByGameIdAsync(
|
||||
game.Id,
|
||||
dimensions: SteamGridDbDimensions.W460H215 | SteamGridDbDimensions.W920H430,
|
||||
types: SteamGridDbTypes.Static);
|
||||
types: SteamGridDbTypes.Static,
|
||||
page: page);
|
||||
|
||||
results.AddRange(grids.Where(g => SupportedFormats.Contains(g.Format)).Select(g => new MediaGrabberResult()
|
||||
{
|
||||
|
|
@ -243,7 +246,8 @@ namespace LANCommander.Server.Services.MediaGrabbers
|
|||
var animatedGrids = await SteamGridDb.GetGridsByGameIdAsync(
|
||||
game.Id,
|
||||
dimensions: SteamGridDbDimensions.W460H215 | SteamGridDbDimensions.W920H430,
|
||||
types: SteamGridDbTypes.Animated);
|
||||
types: SteamGridDbTypes.Animated,
|
||||
page: page);
|
||||
|
||||
results.AddRange(animatedGrids.Where(g => g.Format == SteamGridDbFormats.Png).Select(g => new MediaGrabberResult()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -28,8 +28,13 @@ namespace LANCommander.Server.Services.MediaGrabbers
|
|||
SteamClient = new SteamClient();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<MediaGrabberResult>> SearchAsync(MediaType type, string keywords)
|
||||
public async Task<IEnumerable<MediaGrabberResult>> SearchAsync(MediaType type, string keywords, int page = 0)
|
||||
{
|
||||
// Steam returns the full set of assets for a game in a single request,
|
||||
// so there are no further pages to load.
|
||||
if (page > 0)
|
||||
return Enumerable.Empty<MediaGrabberResult>();
|
||||
|
||||
return type switch
|
||||
{
|
||||
MediaType.Cover => await GetCoversAsync(keywords),
|
||||
|
|
|
|||
|
|
@ -13,15 +13,19 @@ namespace LANCommander.Server.Services.MediaGrabbers
|
|||
{
|
||||
public string Name => "YouTube";
|
||||
|
||||
public bool SupportsPaging => true;
|
||||
|
||||
public MediaType[] SupportedMediaTypes => [MediaType.Video];
|
||||
|
||||
public async Task<IEnumerable<MediaGrabberResult>> SearchAsync(MediaType type, string keywords)
|
||||
private const int PageSize = 20;
|
||||
|
||||
public async Task<IEnumerable<MediaGrabberResult>> SearchAsync(MediaType type, string keywords, int page = 0)
|
||||
{
|
||||
var youtube = new YoutubeClient();
|
||||
var results = new List<MediaGrabberResult>();
|
||||
var group = $"{keywords.Trim()}: Trailers & Gameplay";
|
||||
|
||||
foreach (var video in (await youtube.Search.GetVideosAsync(keywords + " trailer gameplay")).Take(20))
|
||||
foreach (var video in (await youtube.Search.GetVideosAsync(keywords + " trailer gameplay")).Skip(page * PageSize).Take(PageSize))
|
||||
{
|
||||
var thumbnail = video.Thumbnails.GetWithHighestResolution();
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,11 @@
|
|||
}
|
||||
|
||||
.media-grabber-group-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
|
@ -28,6 +33,12 @@
|
|||
padding: 24px 0;
|
||||
}
|
||||
|
||||
.media-grabber-load-more {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
// ── Video preview ────────────────────────────────────────────
|
||||
.media-grabber-preview {
|
||||
position: relative;
|
||||
|
|
|
|||
|
|
@ -57,8 +57,16 @@
|
|||
{
|
||||
<div class="media-grabber-group">
|
||||
<div class="media-grabber-group-header">
|
||||
<h2>@grabberGroup.Group</h2>
|
||||
<span class="media-grabber-group-source">via @grabberGroup.GrabberName</span>
|
||||
<div class="media-grabber-group-title">
|
||||
<h2>@grabberGroup.Group</h2>
|
||||
<span class="media-grabber-group-source">via @grabberGroup.GrabberName</span>
|
||||
</div>
|
||||
@if (MultiSelect && grabberGroup.Results.Any())
|
||||
{
|
||||
<Button Size="ButtonSize.Small" OnClick="() => ToggleSelectAll(grabberGroup)">
|
||||
@(AllSelected(grabberGroup) ? "Deselect all" : "Select all")
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (Type == MediaType.Video)
|
||||
|
|
@ -90,6 +98,18 @@
|
|||
{
|
||||
<ImagePicker Size="Size" MultiSelect="MultiSelect" SelectedValues="_selectedKeys" SelectedValuesChanged="OnMultiImageSelectionChanged" Images="@grabberGroup.Results.ToDictionary(r => r.UniqueKey, r => r.ThumbnailUrl)" ValueChanged="OnImageSelected" OnPicked="OnImagePicked" />
|
||||
}
|
||||
|
||||
@if (CanLoadMore(grabberGroup))
|
||||
{
|
||||
<div class="media-grabber-load-more">
|
||||
<Button Size="ButtonSize.Small"
|
||||
Loading="@(_loadingGroupId == GroupId(grabberGroup))"
|
||||
Disabled="@(_loadingGroupId != null)"
|
||||
OnClick="() => LoadMoreGroup(grabberGroup)">
|
||||
Load more
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
@ -135,6 +155,12 @@
|
|||
CancellationTokenSource _searchCts;
|
||||
List<MediaGrabberResult> _allResults = new();
|
||||
List<GroupedResults> _groupedResults = new();
|
||||
HashSet<string> _seenKeys = new();
|
||||
|
||||
HashSet<string> _pagingProviders = new();
|
||||
Dictionary<string, int> _groupPages = new();
|
||||
HashSet<string> _exhaustedGroups = new();
|
||||
string _loadingGroupId;
|
||||
|
||||
string _selectedProvider = string.Empty;
|
||||
string[] _availableProviders = [];
|
||||
|
|
@ -147,6 +173,7 @@
|
|||
|
||||
_availableProviders = new[] { "All" }.Concat(MediaGrabberService.GetGrabberNames()).ToArray();
|
||||
_selectedProvider = _availableProviders.First();
|
||||
_pagingProviders = MediaGrabberService.GetPagingGrabberNames().ToHashSet();
|
||||
|
||||
await GetResults(Type, Search);
|
||||
}
|
||||
|
|
@ -171,7 +198,11 @@
|
|||
_preview = null;
|
||||
_allResults.Clear();
|
||||
_groupedResults.Clear();
|
||||
_seenKeys.Clear();
|
||||
_selectedKeys.Clear();
|
||||
_groupPages.Clear();
|
||||
_exhaustedGroups.Clear();
|
||||
_loadingGroupId = null;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
if (String.IsNullOrWhiteSpace(search))
|
||||
|
|
@ -179,18 +210,7 @@
|
|||
|
||||
var grabberName = _selectedProvider == "All" ? null : _selectedProvider;
|
||||
|
||||
await foreach (var batch in MediaGrabberService.SearchStreamAsync(type, search, grabberName, token))
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
foreach (var result in batch)
|
||||
result.UniqueKey = $"{result.GrabberName}:{result.Id}";
|
||||
|
||||
_allResults.AddRange(batch);
|
||||
RebuildGroups();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
await StreamResults(type, search, grabberName, 0, null, token);
|
||||
|
||||
Loading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
|
@ -205,6 +225,79 @@
|
|||
}
|
||||
}
|
||||
|
||||
private string GroupId(GroupedResults group) => $"{group.GrabberName}|{group.Group}";
|
||||
|
||||
private bool CanLoadMore(GroupedResults group) =>
|
||||
!Loading
|
||||
&& group.Results.Any()
|
||||
&& _pagingProviders.Contains(group.GrabberName)
|
||||
&& !_exhaustedGroups.Contains(GroupId(group));
|
||||
|
||||
private async Task LoadMoreGroup(GroupedResults group)
|
||||
{
|
||||
if (Loading || _loadingGroupId != null || String.IsNullOrWhiteSpace(Search))
|
||||
return;
|
||||
|
||||
var groupId = GroupId(group);
|
||||
_searchCts ??= new CancellationTokenSource();
|
||||
var token = _searchCts.Token;
|
||||
|
||||
try
|
||||
{
|
||||
_loadingGroupId = groupId;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
var nextPage = _groupPages.GetValueOrDefault(groupId) + 1;
|
||||
_groupPages[groupId] = nextPage;
|
||||
|
||||
// Only query the provider this group came from, and keep only this game's results.
|
||||
var added = await StreamResults(Type, Search, group.GrabberName, nextPage, group.Group, token);
|
||||
|
||||
if (added == 0)
|
||||
_exhaustedGroups.Add(groupId);
|
||||
|
||||
_loadingGroupId = null;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
_loadingGroupId = null;
|
||||
MessageService.Error("An unknown error occurred when trying to get more media results");
|
||||
Logger.LogError(ex, "An unknown error occurred when trying to get more media results");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> StreamResults(MediaType type, string search, string? grabberName, int page, string? onlyGroup, CancellationToken token)
|
||||
{
|
||||
var added = 0;
|
||||
|
||||
await foreach (var batch in MediaGrabberService.SearchStreamAsync(type, search, grabberName, page, token))
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
foreach (var result in batch)
|
||||
{
|
||||
if (onlyGroup != null && result.Group != onlyGroup)
|
||||
continue;
|
||||
|
||||
result.UniqueKey = $"{result.GrabberName}:{result.Id}";
|
||||
|
||||
if (!_seenKeys.Add(result.UniqueKey))
|
||||
continue;
|
||||
|
||||
_allResults.Add(result);
|
||||
added++;
|
||||
}
|
||||
|
||||
RebuildGroups();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
private void RebuildGroups()
|
||||
{
|
||||
_groupedResults = _allResults
|
||||
|
|
@ -229,6 +322,27 @@
|
|||
}
|
||||
}
|
||||
|
||||
private bool AllSelected(GroupedResults group) =>
|
||||
group.Results.Count > 0 && group.Results.All(r => _selectedKeys.Contains(r.UniqueKey));
|
||||
|
||||
private async Task ToggleSelectAll(GroupedResults group)
|
||||
{
|
||||
var keys = group.Results.Select(r => r.UniqueKey).ToList();
|
||||
|
||||
if (keys.All(_selectedKeys.Contains))
|
||||
{
|
||||
foreach (var key in keys)
|
||||
_selectedKeys.Remove(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var key in keys)
|
||||
_selectedKeys.Add(key);
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task OnResultPicked(MediaGrabberResult result)
|
||||
{
|
||||
Media = result;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue