using System.Collections.Generic; using System.Data; using System.Linq; using System.Threading.Tasks; using Dapper; 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 MediaSourceRepository : IMediaSourceRepository { private readonly IDbConnection _dbConnection; private readonly IDbContextFactory _dbContextFactory; public MediaSourceRepository( IDbContextFactory dbContextFactory, IDbConnection dbConnection) { _dbContextFactory = dbContextFactory; _dbConnection = dbConnection; } public async Task Add(LocalMediaSource localMediaSource) { await using TvContext context = _dbContextFactory.CreateDbContext(); await context.LocalMediaSources.AddAsync(localMediaSource); await context.SaveChangesAsync(); return localMediaSource; } public async Task Add(PlexMediaSource plexMediaSource) { await using TvContext context = _dbContextFactory.CreateDbContext(); await context.PlexMediaSources.AddAsync(plexMediaSource); await context.SaveChangesAsync(); return plexMediaSource; } public async Task> GetAll() { await using TvContext context = _dbContextFactory.CreateDbContext(); List all = await context.MediaSources.ToListAsync(); foreach (PlexMediaSource plex in all.OfType()) { await context.Entry(plex).Collection(p => p.Connections).LoadAsync(); } return all; } public Task> GetAllPlex() { using TvContext context = _dbContextFactory.CreateDbContext(); return context.PlexMediaSources .Include(p => p.Connections) .ToListAsync(); } public Task> GetPlexLibraries(int plexMediaSourceId) { using TvContext context = _dbContextFactory.CreateDbContext(); return context.PlexLibraries .Filter(l => l.MediaSourceId == plexMediaSourceId) .ToListAsync(); } public Task> GetPlexPathReplacements(int plexMediaSourceId) { using TvContext context = _dbContextFactory.CreateDbContext(); return context.PlexPathReplacements .Filter(r => r.PlexMediaSourceId == plexMediaSourceId) .ToListAsync(); } public Task> GetPlexLibrary(int plexLibraryId) { using TvContext context = _dbContextFactory.CreateDbContext(); return context.PlexLibraries .Include(l => l.Paths) .OrderBy(l => l.Id) // https://github.com/dotnet/efcore/issues/22579 .SingleOrDefaultAsync(l => l.Id == plexLibraryId) .Map(Optional); } public Task> Get(int id) { using TvContext context = _dbContextFactory.CreateDbContext(); return context.MediaSources .OrderBy(s => s.Id) // https://github.com/dotnet/efcore/issues/22579 .SingleOrDefaultAsync(s => s.Id == id) .Map(Optional); } public Task> GetPlex(int id) { using TvContext context = _dbContextFactory.CreateDbContext(); return context.PlexMediaSources .Include(p => p.Connections) .Include(p => p.Libraries) .Include(p => p.PathReplacements) .OrderBy(s => s.Id) // https://github.com/dotnet/efcore/issues/22579 .SingleOrDefaultAsync(p => p.Id == id) .Map(Optional); } public async Task> GetPlexByLibraryId(int plexLibraryId) { int? id = await _dbConnection.QuerySingleAsync( @"SELECT L.MediaSourceId FROM Library L INNER JOIN PlexLibrary PL on L.Id = PL.Id WHERE L.Id = @PlexLibraryId", new { PlexLibraryId = plexLibraryId }); await using TvContext context = _dbContextFactory.CreateDbContext(); return await context.PlexMediaSources .Include(p => p.Connections) .Include(p => p.Libraries) .OrderBy(p => p.Id) .SingleOrDefaultAsync(p => p.Id == id) .Map(Optional); } public Task> GetPlexPathReplacementsByLibraryId(int plexLibraryPathId) { using TvContext context = _dbContextFactory.CreateDbContext(); return context.PlexPathReplacements .FromSqlRaw( @"select ppr.* from LibraryPath lp inner join PlexLibrary pl ON pl.Id = lp.LibraryId inner join Library l ON l.Id = pl.Id inner join PlexPathReplacement ppr on ppr.PlexMediaSourceId = l.MediaSourceId where lp.Id = {0}", plexLibraryPathId) .ToListAsync(); } public Task CountMediaItems(int id) { using TvContext context = _dbContextFactory.CreateDbContext(); return context.MediaItems .CountAsync(i => i.LibraryPath.Library.MediaSourceId == id); } public async Task Update(LocalMediaSource localMediaSource) { await using TvContext context = _dbContextFactory.CreateDbContext(); context.LocalMediaSources.Update(localMediaSource); await context.SaveChangesAsync(); } public async Task Update( PlexMediaSource plexMediaSource, List toAdd, List toDelete) { await _dbConnection.ExecuteAsync( @"UPDATE PlexMediaSource SET ProductVersion = @ProductVersion, ServerName = @ServerName WHERE Id = @Id", new { plexMediaSource.ProductVersion, plexMediaSource.ServerName, plexMediaSource.Id }); await using TvContext dbContext = _dbContextFactory.CreateDbContext(); foreach (PlexConnection add in toAdd) { add.PlexMediaSourceId = plexMediaSource.Id; dbContext.Entry(add).State = EntityState.Added; } foreach (PlexConnection delete in toDelete) { dbContext.Entry(delete).State = EntityState.Deleted; } await dbContext.SaveChangesAsync(); PlexMediaSource pms = await dbContext.PlexMediaSources.FindAsync(plexMediaSource.Id); await dbContext.Entry(pms).Collection(x => x.Connections).LoadAsync(); if (plexMediaSource.Connections.Any() && plexMediaSource.Connections.All(c => !c.IsActive)) { plexMediaSource.Connections.Head().IsActive = true; await dbContext.SaveChangesAsync(); } } public async Task UpdateLibraries( int plexMediaSourceId, List toAdd, List toDelete) { await using TvContext dbContext = _dbContextFactory.CreateDbContext(); foreach (PlexLibrary add in toAdd) { add.MediaSourceId = plexMediaSourceId; dbContext.Entry(add).State = EntityState.Added; } foreach (PlexLibrary delete in toDelete) { dbContext.Entry(delete).State = EntityState.Deleted; } await dbContext.SaveChangesAsync(); return Unit.Default; } public async Task UpdatePathReplacements( int plexMediaSourceId, List toAdd, List toUpdate, List toDelete) { foreach (PlexPathReplacement add in toAdd) { await _dbConnection.ExecuteAsync( @"INSERT INTO PlexPathReplacement (PlexPath, LocalPath, PlexMediaSourceId) VALUES (@PlexPath, @LocalPath, @PlexMediaSourceId)", new { add.PlexPath, add.LocalPath, PlexMediaSourceId = plexMediaSourceId }); } foreach (PlexPathReplacement update in toUpdate) { await _dbConnection.ExecuteAsync( @"UPDATE PlexPathReplacement SET PlexPath = @PlexPath, LocalPath = @LocalPath WHERE Id = @Id", new { update.PlexPath, update.LocalPath, update.Id }); } foreach (PlexPathReplacement delete in toDelete) { await _dbConnection.ExecuteAsync( @"DELETE FROM PlexPathReplacement WHERE Id = @Id", new { delete.Id }); } return Unit.Default; } public async Task Update(PlexLibrary plexMediaSourceLibrary) { await using TvContext context = _dbContextFactory.CreateDbContext(); context.PlexLibraries.Update(plexMediaSourceLibrary); await context.SaveChangesAsync(); } public async Task Delete(int mediaSourceId) { await using TvContext context = _dbContextFactory.CreateDbContext(); MediaSource mediaSource = await context.MediaSources.FindAsync(mediaSourceId); context.MediaSources.Remove(mediaSource); await context.SaveChangesAsync(); } public async Task DeleteAllPlex() { await using TvContext context = _dbContextFactory.CreateDbContext(); List allMediaSources = await context.PlexMediaSources.ToListAsync(); context.PlexMediaSources.RemoveRange(allMediaSources); List allPlexLibraries = await context.PlexLibraries.ToListAsync(); context.PlexLibraries.RemoveRange(allPlexLibraries); await context.SaveChangesAsync(); return Unit.Default; } public async Task> DisablePlexLibrarySync(List libraryIds) { await _dbConnection.ExecuteAsync( "UPDATE PlexLibrary SET ShouldSyncItems = 0 WHERE Id IN @ids", new { ids = libraryIds }); await _dbConnection.ExecuteAsync( "UPDATE Library SET LastScan = null WHERE Id IN @ids", new { ids = libraryIds }); List movieIds = await _dbConnection.QueryAsync( @"SELECT m.Id FROM MediaItem m INNER JOIN PlexMovie pm ON pm.Id = m.Id INNER JOIN LibraryPath lp ON lp.Id = m.LibraryPathId INNER JOIN Library l ON l.Id = lp.LibraryId WHERE l.Id IN @ids", new { ids = libraryIds }).Map(result => result.ToList()); await _dbConnection.ExecuteAsync( @"DELETE FROM MediaItem WHERE Id IN (SELECT m.Id FROM MediaItem m INNER JOIN PlexMovie pm ON pm.Id = m.Id INNER JOIN LibraryPath lp ON lp.Id = m.LibraryPathId INNER JOIN Library l ON l.Id = lp.LibraryId WHERE l.Id IN @ids)", new { ids = libraryIds }); await _dbConnection.ExecuteAsync( @"DELETE FROM MediaItem WHERE Id IN (SELECT m.Id FROM MediaItem m INNER JOIN PlexEpisode pe ON pe.Id = m.Id INNER JOIN LibraryPath lp ON lp.Id = m.LibraryPathId INNER JOIN Library l ON l.Id = lp.LibraryId WHERE l.Id IN @ids)", new { ids = libraryIds }); await _dbConnection.ExecuteAsync( @"DELETE FROM MediaItem WHERE Id IN (SELECT m.Id FROM MediaItem m INNER JOIN PlexSeason ps ON ps.Id = m.Id INNER JOIN LibraryPath lp ON lp.Id = m.LibraryPathId INNER JOIN Library l ON l.Id = lp.LibraryId WHERE l.Id IN @ids)", new { ids = libraryIds }); List showIds = await _dbConnection.QueryAsync( @"SELECT m.Id FROM MediaItem m INNER JOIN PlexShow ps ON ps.Id = m.Id INNER JOIN LibraryPath lp ON lp.Id = m.LibraryPathId INNER JOIN Library l ON l.Id = lp.LibraryId WHERE l.Id IN @ids", new { ids = libraryIds }).Map(result => result.ToList()); await _dbConnection.ExecuteAsync( @"DELETE FROM MediaItem WHERE Id IN (SELECT m.Id FROM MediaItem m INNER JOIN PlexShow ps ON ps.Id = m.Id INNER JOIN LibraryPath lp ON lp.Id = m.LibraryPathId INNER JOIN Library l ON l.Id = lp.LibraryId WHERE l.Id IN @ids)", new { ids = libraryIds }); return movieIds.Append(showIds).ToList(); } public Task EnablePlexLibrarySync(IEnumerable libraryIds) => _dbConnection.ExecuteAsync( "UPDATE PlexLibrary SET ShouldSyncItems = 1 WHERE Id IN @ids", new { ids = libraryIds }); } }