using System.Runtime.CompilerServices; using Dapper; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Infrastructure.Extensions; using Microsoft.EntityFrameworkCore; namespace ErsatzTV.Infrastructure.Data.Repositories; public class SearchRepository(IDbContextFactory dbContextFactory) : ISearchRepository { public async Task> GetItemToIndex(int id, CancellationToken cancellationToken) { await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); var baseItem = await dbContext.MediaItems .AsNoTracking() .TagWithCallSite() .SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken); if (baseItem is null) { return Option.None; } switch (baseItem) { case Movie: return await dbContext.Movies .AsNoTracking() .TagWithCallSite() .IncludeForSearch() .AsSplitQuery() .SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken); case Episode: return await dbContext.Episodes .AsNoTracking() .TagWithCallSite() .IncludeForSearch() .AsSplitQuery() .SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken); case Season: return await dbContext.Seasons .AsNoTracking() .TagWithCallSite() .IncludeForSearch() .AsSplitQuery() .SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken); case Show: return await dbContext.Shows .AsNoTracking() .TagWithCallSite() .IncludeForSearch() .AsSplitQuery() .SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken); case MusicVideo: return await dbContext.MusicVideos .AsNoTracking() .TagWithCallSite() .IncludeForSearch() .AsSplitQuery() .SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken); case Artist: return await dbContext.Artists .AsNoTracking() .TagWithCallSite() .IncludeForSearch() .AsSplitQuery() .SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken); case OtherVideo: return await dbContext.OtherVideos .AsNoTracking() .TagWithCallSite() .IncludeForSearch() .AsSplitQuery() .SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken); case Song: return await dbContext.Songs .AsNoTracking() .TagWithCallSite() .IncludeForSearch() .AsSplitQuery() .SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken); case Image: return await dbContext.Images .AsNoTracking() .TagWithCallSite() .IncludeForSearch() .AsSplitQuery() .SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken); case RemoteStream: return await dbContext.RemoteStreams .AsNoTracking() .TagWithCallSite() .IncludeForSearch() .AsSplitQuery() .SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken); } return Option.None; } public async Task> GetLanguagesForShow(Show show) { await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(); return await dbContext.Connection.QueryAsync( @"SELECT DISTINCT Language FROM MediaStream INNER JOIN MediaVersion MV ON MediaStream.MediaVersionId = MV.Id INNER JOIN Episode E ON MV.EpisodeId = E.Id INNER JOIN Season S ON E.SeasonId = S.Id WHERE MediaStreamKind = 2 AND S.ShowId = @ShowId", new { ShowId = show.Id }).Map(result => result.ToList()); } public async Task> GetSubLanguagesForShow(Show show) { await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(); return await dbContext.Connection.QueryAsync( @"SELECT DISTINCT Language FROM MediaStream INNER JOIN MediaVersion MV ON MediaStream.MediaVersionId = MV.Id INNER JOIN Episode E ON MV.EpisodeId = E.Id INNER JOIN Season S ON E.SeasonId = S.Id WHERE MediaStreamKind = 3 AND S.ShowId = @ShowId", new { ShowId = show.Id }).Map(result => result.ToList()); } public async Task> GetLanguagesForSeason(Season season) { await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(); return await dbContext.Connection.QueryAsync( @"SELECT DISTINCT Language FROM MediaStream INNER JOIN MediaVersion MV ON MediaStream.MediaVersionId = MV.Id INNER JOIN Episode E ON MV.EpisodeId = E.Id WHERE MediaStreamKind = 2 AND E.SeasonId = @SeasonId", new { SeasonId = season.Id }).Map(result => result.ToList()); } public async Task> GetSubLanguagesForSeason(Season season) { await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(); return await dbContext.Connection.QueryAsync( @"SELECT DISTINCT Language FROM MediaStream INNER JOIN MediaVersion MV ON MediaStream.MediaVersionId = MV.Id INNER JOIN Episode E ON MV.EpisodeId = E.Id WHERE MediaStreamKind = 3 AND E.SeasonId = @SeasonId", new { SeasonId = season.Id }).Map(result => result.ToList()); } public async Task> GetLanguagesForArtist(Artist artist) { await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(); return await dbContext.Connection.QueryAsync( @"SELECT DISTINCT Language FROM MediaStream INNER JOIN MediaVersion V ON MediaStream.MediaVersionId = V.Id INNER JOIN MusicVideo MV ON V.MusicVideoId = MV.Id INNER JOIN Artist A on MV.ArtistId = A.Id WHERE MediaStreamKind = 2 AND A.Id = @ArtistId", new { ArtistId = artist.Id }).Map(result => result.ToList()); } public async Task> GetSubLanguagesForArtist(Artist artist) { await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(); return await dbContext.Connection.QueryAsync( @"SELECT DISTINCT Language FROM MediaStream INNER JOIN MediaVersion V ON MediaStream.MediaVersionId = V.Id INNER JOIN MusicVideo MV ON V.MusicVideoId = MV.Id INNER JOIN Artist A on MV.ArtistId = A.Id WHERE MediaStreamKind = 3 AND A.Id = @ArtistId", new { ArtistId = artist.Id }).Map(result => result.ToList()); } public async IAsyncEnumerable GetAllMediaItems( [EnumeratorCancellation] CancellationToken cancellationToken = default) { await foreach (var item in GetAllMovies(cancellationToken)) { yield return item; } await foreach (var item in GetAllShows(cancellationToken)) { yield return item; } await foreach (var item in GetAllSeasons(cancellationToken)) { yield return item; } await foreach (var item in GetAllEpisodes(cancellationToken)) { yield return item; } await foreach (var item in GetAllMusicVideos(cancellationToken)) { yield return item; } await foreach (var item in GetAllArtists(cancellationToken)) { yield return item; } await foreach (var item in GetAllOtherVideos(cancellationToken)) { yield return item; } await foreach (var item in GetAllSongs(cancellationToken)) { yield return item; } await foreach (var item in GetAllImages(cancellationToken)) { yield return item; } await foreach (var item in GetAllRemoteStreams(cancellationToken)) { yield return item; } } private async IAsyncEnumerable GetAllMovies([EnumeratorCancellation] CancellationToken cancellationToken) { await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); ConfiguredCancelableAsyncEnumerable movies = dbContext.Movies .AsNoTracking() .TagWithCallSite() .IncludeForSearch() .AsSplitQuery() .AsAsyncEnumerable() .WithCancellation(cancellationToken); await foreach (var movie in movies) { yield return movie; } } private async IAsyncEnumerable GetAllShows([EnumeratorCancellation] CancellationToken cancellationToken) { await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); ConfiguredCancelableAsyncEnumerable shows = dbContext.Shows .AsNoTracking() .TagWithCallSite() .IncludeForSearch() .AsSplitQuery() .AsAsyncEnumerable() .WithCancellation(cancellationToken); await foreach (var movie in shows) { yield return movie; } } private async IAsyncEnumerable GetAllSeasons([EnumeratorCancellation] CancellationToken cancellationToken) { await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); ConfiguredCancelableAsyncEnumerable seasons = dbContext.Seasons .AsNoTracking() .TagWithCallSite() .IncludeForSearch() .AsSplitQuery() .AsAsyncEnumerable() .WithCancellation(cancellationToken); await foreach (var movie in seasons) { yield return movie; } } private async IAsyncEnumerable GetAllEpisodes([EnumeratorCancellation] CancellationToken cancellationToken) { await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); ConfiguredCancelableAsyncEnumerable episodes = dbContext.Episodes .AsNoTracking() .TagWithCallSite() .IncludeForSearch() .AsSplitQuery() .AsAsyncEnumerable() .WithCancellation(cancellationToken); await foreach (var movie in episodes) { yield return movie; } } private async IAsyncEnumerable GetAllMusicVideos( [EnumeratorCancellation] CancellationToken cancellationToken) { await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); ConfiguredCancelableAsyncEnumerable musicVideos = dbContext.MusicVideos .AsNoTracking() .TagWithCallSite() .IncludeForSearch() .AsSplitQuery() .AsAsyncEnumerable() .WithCancellation(cancellationToken); await foreach (var movie in musicVideos) { yield return movie; } } private async IAsyncEnumerable GetAllArtists([EnumeratorCancellation] CancellationToken cancellationToken) { await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); ConfiguredCancelableAsyncEnumerable artists = dbContext.Artists .AsNoTracking() .TagWithCallSite() .IncludeForSearch() .AsSplitQuery() .AsAsyncEnumerable() .WithCancellation(cancellationToken); await foreach (var movie in artists) { yield return movie; } } private async IAsyncEnumerable GetAllOtherVideos( [EnumeratorCancellation] CancellationToken cancellationToken) { await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); ConfiguredCancelableAsyncEnumerable otherVideos = dbContext.OtherVideos .AsNoTracking() .TagWithCallSite() .IncludeForSearch() .AsSplitQuery() .AsAsyncEnumerable() .WithCancellation(cancellationToken); await foreach (var movie in otherVideos) { yield return movie; } } private async IAsyncEnumerable GetAllSongs([EnumeratorCancellation] CancellationToken cancellationToken) { await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); ConfiguredCancelableAsyncEnumerable songs = dbContext.Songs .AsNoTracking() .TagWithCallSite() .IncludeForSearch() .AsSplitQuery() .AsAsyncEnumerable() .WithCancellation(cancellationToken); await foreach (var movie in songs) { yield return movie; } } private async IAsyncEnumerable GetAllImages([EnumeratorCancellation] CancellationToken cancellationToken) { await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); ConfiguredCancelableAsyncEnumerable images = dbContext.Images .AsNoTracking() .TagWithCallSite() .IncludeForSearch() .AsSplitQuery() .AsAsyncEnumerable() .WithCancellation(cancellationToken); await foreach (var movie in images) { yield return movie; } } private async IAsyncEnumerable GetAllRemoteStreams( [EnumeratorCancellation] CancellationToken cancellationToken) { await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); ConfiguredCancelableAsyncEnumerable remoteStreams = dbContext.RemoteStreams .AsNoTracking() .TagWithCallSite() .IncludeForSearch() .AsSplitQuery() .AsAsyncEnumerable() .WithCancellation(cancellationToken); await foreach (var movie in remoteStreams) { yield return movie; } } }