using Bugsnag; using ErsatzTV.Application.MediaCards; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Extensions; using ErsatzTV.Core.Interfaces.Emby; using ErsatzTV.Core.Interfaces.Jellyfin; using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Plex; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Search; using ErsatzTV.Core.Search; using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Extensions; using Microsoft.EntityFrameworkCore; using static ErsatzTV.Application.MediaCards.Mapper; namespace ErsatzTV.Application.Search; public class QuerySearchIndexEpisodesHandler : IRequestHandler { private readonly IClient _client; private readonly IDbContextFactory _dbContextFactory; private readonly IEmbyPathReplacementService _embyPathReplacementService; private readonly IFallbackMetadataProvider _fallbackMetadataProvider; private readonly IJellyfinPathReplacementService _jellyfinPathReplacementService; private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IPlexPathReplacementService _plexPathReplacementService; private readonly ISearchIndex _searchIndex; private readonly ITelevisionRepository _televisionRepository; public QuerySearchIndexEpisodesHandler( IClient client, ISearchIndex searchIndex, ITelevisionRepository televisionRepository, IMediaSourceRepository mediaSourceRepository, IPlexPathReplacementService plexPathReplacementService, IJellyfinPathReplacementService jellyfinPathReplacementService, IEmbyPathReplacementService embyPathReplacementService, IFallbackMetadataProvider fallbackMetadataProvider, IDbContextFactory dbContextFactory) { _client = client; _searchIndex = searchIndex; _televisionRepository = televisionRepository; _mediaSourceRepository = mediaSourceRepository; _plexPathReplacementService = plexPathReplacementService; _jellyfinPathReplacementService = jellyfinPathReplacementService; _embyPathReplacementService = embyPathReplacementService; _fallbackMetadataProvider = fallbackMetadataProvider; _dbContextFactory = dbContextFactory; } public async Task Handle( QuerySearchIndexEpisodes request, CancellationToken cancellationToken) { SearchResult searchResult = await _searchIndex.Search( _client, request.Query, (request.PageNumber - 1) * request.PageSize, request.PageSize); Option maybeJellyfin = await _mediaSourceRepository.GetAllJellyfin() .Map(list => list.HeadOrNone()); Option maybeEmby = await _mediaSourceRepository.GetAllEmby() .Map(list => list.HeadOrNone()); var episodeIds = searchResult.Items.Map(i => i.Id).ToList(); List episodes = await _televisionRepository.GetEpisodesForCards(episodeIds); // try to load fallback metadata for episodes that have none // this handles an edge case of trashed items with no saved metadata var missingEpisodes = episodeIds.Except(episodes.Map(e => e.EpisodeId)).ToList(); await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); foreach (int missingEpisodeId in missingEpisodes) { Option maybeEpisode = await dbContext.Episodes .AsNoTracking() .Include(e => e.MediaVersions) .ThenInclude(e => e.MediaFiles) .Include(e => e.Season) .ThenInclude(s => s.SeasonMetadata) .ThenInclude(sm => sm.Artwork) .Include(e => e.Season) .ThenInclude(s => s.Show) .ThenInclude(s => s.ShowMetadata) .ThenInclude(sm => sm.Artwork) .SelectOneAsync(e => e.Id, e => e.Id == missingEpisodeId); foreach (Episode episode in maybeEpisode) { foreach (EpisodeMetadata headMetadata in _fallbackMetadataProvider.GetFallbackMetadata(episode) .HeadOrNone()) { headMetadata.Episode = episode; episode.EpisodeMetadata = new List { headMetadata }; episodes.Add(headMetadata); } } } var items = new List(); foreach (EpisodeMetadata episodeMetadata in episodes) { string localPath = await episodeMetadata.Episode.GetLocalPath( _plexPathReplacementService, _jellyfinPathReplacementService, _embyPathReplacementService, false); items.Add(ProjectToViewModel(episodeMetadata, maybeJellyfin, maybeEmby, true, localPath)); } return new TelevisionEpisodeCardResultsViewModel(searchResult.TotalCount, items, searchResult.PageMap); } }