using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Repositories; using LanguageExt; using Microsoft.EntityFrameworkCore; using static LanguageExt.Prelude; namespace ErsatzTV.Infrastructure.Data.Repositories { public class TelevisionRepository : ITelevisionRepository { private readonly TvContext _dbContext; public TelevisionRepository(TvContext dbContext) => _dbContext = dbContext; public async Task Update(TelevisionShow show) { _dbContext.TelevisionShows.Update(show); return await _dbContext.SaveChangesAsync() > 0; } public async Task Update(TelevisionSeason season) { _dbContext.TelevisionSeasons.Update(season); return await _dbContext.SaveChangesAsync() > 0; } public async Task Update(TelevisionEpisodeMediaItem episode) { _dbContext.TelevisionEpisodeMediaItems.Update(episode); return await _dbContext.SaveChangesAsync() > 0; } public Task> GetAllShows() => _dbContext.TelevisionShows .AsNoTracking() .Include(s => s.Metadata) .ToListAsync(); public Task> GetShow(int televisionShowId) => _dbContext.TelevisionShows .AsNoTracking() .Filter(s => s.Id == televisionShowId) .Include(s => s.Metadata) .SingleOrDefaultAsync() .Map(Optional); public Task GetShowCount() => _dbContext.TelevisionShows .AsNoTracking() .CountAsync(); public Task> GetPagedShows(int pageNumber, int pageSize) => _dbContext.TelevisionShows .AsNoTracking() .Include(s => s.Metadata) .OrderBy(s => s.Metadata == null ? string.Empty : s.Metadata.SortTitle) .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .ToListAsync(); public Task> GetAllSeasons() => _dbContext.TelevisionSeasons .AsNoTracking() .Include(s => s.TelevisionShow) .ThenInclude(s => s.Metadata) .ToListAsync(); public Task> GetSeason(int televisionSeasonId) => _dbContext.TelevisionSeasons .AsNoTracking() .Include(s => s.TelevisionShow) .ThenInclude(s => s.Metadata) .SingleOrDefaultAsync(s => s.Id == televisionSeasonId) .Map(Optional); public Task GetSeasonCount(int televisionShowId) => _dbContext.TelevisionSeasons .AsNoTracking() .Where(s => s.TelevisionShowId == televisionShowId) .CountAsync(); public Task> GetPagedSeasons(int televisionShowId, int pageNumber, int pageSize) => _dbContext.TelevisionSeasons .AsNoTracking() .Where(s => s.TelevisionShowId == televisionShowId) .Include(s => s.TelevisionShow) .ThenInclude(s => s.Metadata) .OrderBy(s => s.Number) .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .ToListAsync(); public Task> GetEpisode(int televisionEpisodeId) => _dbContext.TelevisionEpisodeMediaItems .AsNoTracking() .Include(s => s.Season) .Include(s => s.Metadata) .SingleOrDefaultAsync(s => s.Id == televisionEpisodeId) .Map(Optional); public Task GetEpisodeCount(int televisionSeasonId) => _dbContext.TelevisionEpisodeMediaItems .AsNoTracking() .Where(e => e.SeasonId == televisionSeasonId) .CountAsync(); public Task> GetPagedEpisodes( int televisionSeasonId, int pageNumber, int pageSize) => _dbContext.TelevisionEpisodeMediaItems .AsNoTracking() .Include(e => e.Metadata) .Include(e => e.Season) .ThenInclude(s => s.TelevisionShow) .ThenInclude(s => s.Metadata) .Where(e => e.SeasonId == televisionSeasonId) .OrderBy(s => s.Metadata.Episode) .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .ToListAsync(); public async Task> GetShowByPath(int mediaSourceId, string path) { Option maybeShowId = await _dbContext.LocalTelevisionShowSources .SingleOrDefaultAsync(s => s.MediaSourceId == mediaSourceId && s.Path == path) .Map(Optional) .MapT(s => s.TelevisionShowId); return await maybeShowId.Match>>( async id => await _dbContext.TelevisionShows .Include(s => s.Metadata) .Include(s => s.Sources) .SingleOrDefaultAsync(s => s.Id == id), () => Task.FromResult(Option.None)); } public async Task> GetShowByMetadata(TelevisionShowMetadata metadata) { Option maybeShow = await _dbContext.TelevisionShows .Include(s => s.Metadata) .Where(s => s.Metadata.Title == metadata.Title && s.Metadata.Year == metadata.Year) .SingleOrDefaultAsync() .Map(Optional); await maybeShow.IfSomeAsync( async show => { await _dbContext.Entry(show).Reference(s => s.Metadata).LoadAsync(); await _dbContext.Entry(show).Collection(s => s.Sources).LoadAsync(); }); return maybeShow; } public async Task> AddShow( int localMediaSourceId, string showFolder, TelevisionShowMetadata metadata) { try { var show = new TelevisionShow { Sources = new List(), Metadata = metadata, Seasons = new List() }; show.Sources.Add( new LocalTelevisionShowSource { MediaSourceId = localMediaSourceId, Path = showFolder, TelevisionShow = show }); await _dbContext.TelevisionShows.AddAsync(show); await _dbContext.SaveChangesAsync(); return show; } catch (Exception ex) { return BaseError.New(ex.Message); } } public async Task> GetOrAddSeason( TelevisionShow show, string path, int seasonNumber) { Option maybeExisting = await _dbContext.TelevisionSeasons .SingleOrDefaultAsync(i => i.Path == path); return await maybeExisting.Match( season => Right(season).AsTask(), () => AddSeason(show, path, seasonNumber)); } public async Task> GetOrAddEpisode( TelevisionSeason season, int mediaSourceId, string path) { Option maybeExisting = await _dbContext.TelevisionEpisodeMediaItems .Include(i => i.Metadata) .Include(i => i.Statistics) .SingleOrDefaultAsync(i => i.Path == path); return await maybeExisting.Match( episode => Right(episode).AsTask(), () => AddEpisode(season, mediaSourceId, path)); } public Task DeleteMissingSources(int localMediaSourceId, List allFolders) => _dbContext.LocalTelevisionShowSources .Where(s => s.MediaSourceId == localMediaSourceId && !allFolders.Contains(s.Path)) .ToListAsync() .Bind( list => { _dbContext.LocalTelevisionShowSources.RemoveRange(list); return _dbContext.SaveChangesAsync(); }) .ToUnit(); public Task DeleteEmptyShows() => _dbContext.TelevisionShows .Where(s => s.Sources.Count == 0) .ToListAsync() .Bind( list => { _dbContext.TelevisionShows.RemoveRange(list); return _dbContext.SaveChangesAsync(); }) .ToUnit(); public async Task> GetShowItems(int televisionShowId) { // TODO: would be nice to get the media items in one go, but ef... List showItemIds = await _dbContext.GenericIntegerIds.FromSqlRaw( @"select tmi.Id from TelevisionEpisodes tmi inner join TelevisionSeasons tsn on tsn.Id = tmi.SeasonId inner join TelevisionShows ts on ts.Id = tsn.TelevisionShowId where ts.Id = {0}", televisionShowId) .Select(i => i.Id) .ToListAsync(); return await _dbContext.TelevisionEpisodeMediaItems .AsNoTracking() .Include(e => e.Metadata) .Where(mi => showItemIds.Contains(mi.Id)) .ToListAsync(); } public async Task> GetSeasonItems(int televisionSeasonId) { // TODO: would be nice to get the media items in one go, but ef... List seasonItemIds = await _dbContext.GenericIntegerIds.FromSqlRaw( @"select tmi.Id from TelevisionEpisodes tmi inner join TelevisionSeasons tsn on tsn.Id = tmi.SeasonId where tsn.Id = {0}", televisionSeasonId) .Select(i => i.Id) .ToListAsync(); return await _dbContext.TelevisionEpisodeMediaItems .AsNoTracking() .Include(e => e.Metadata) .Where(mi => seasonItemIds.Contains(mi.Id)) .ToListAsync(); } private async Task> AddSeason( TelevisionShow show, string path, int seasonNumber) { try { var season = new TelevisionSeason { TelevisionShowId = show.Id, Path = path, Number = seasonNumber, Episodes = new List() }; await _dbContext.TelevisionSeasons.AddAsync(season); await _dbContext.SaveChangesAsync(); return season; } catch (Exception ex) { return BaseError.New(ex.Message); } } private async Task> AddEpisode( TelevisionSeason season, int mediaSourceId, string path) { try { var episode = new TelevisionEpisodeMediaItem { MediaSourceId = mediaSourceId, SeasonId = season.Id, Path = path }; await _dbContext.TelevisionEpisodeMediaItems.AddAsync(episode); await _dbContext.SaveChangesAsync(); return episode; } catch (Exception ex) { return BaseError.New(ex.Message); } } } }