refactor dbcontext lifetime (#258)

* refactor create playout handler

* refactor get all playouts handler

* refactor delete playout handler

* remove dead code

* ignore unnamed artists for collections

* more repository cleanup

* more schedule items refactoring

* more playout refactoring

* refactor playout builder

* refactor ffmpeg profiles

* more ffmpeg profile refactoring

* rework resolutions

* refactor media collections

* refactor config elements

* update changelog

* more cleanup
This commit is contained in:
Jason Dove
2021-06-13 20:19:10 -05:00
committed by GitHub
parent 4172074ac4
commit 0fb5bfde58
208 changed files with 1532 additions and 16934 deletions
@@ -22,14 +22,6 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
_dbConnection = dbConnection;
}
public async Task<Channel> Add(Channel channel)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
await dbContext.Channels.AddAsync(channel);
await dbContext.SaveChangesAsync();
return channel;
}
public async Task<Option<Channel>> Get(int id)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
@@ -124,11 +116,6 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
await dbContext.SaveChangesAsync();
}
public Task<int> CountPlayouts(int channelId) =>
_dbConnection.QuerySingleAsync<int>(
@"SELECT COUNT(*) FROM Playout WHERE ChannelId = @ChannelId",
new { ChannelId = channelId });
public async Task<Unit> RemoveWatermark(Channel channel)
{
if (channel.Watermark != null)
@@ -3,6 +3,7 @@ using System.Linq;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Infrastructure.Extensions;
using LanguageExt;
using Microsoft.EntityFrameworkCore;
using static LanguageExt.Prelude;
@@ -11,36 +12,56 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
{
public class ConfigElementRepository : IConfigElementRepository
{
private readonly TvContext _dbContext;
private readonly IDbContextFactory<TvContext> _dbContextFactory;
public ConfigElementRepository(TvContext dbContext) => _dbContext = dbContext;
public ConfigElementRepository(IDbContextFactory<TvContext> dbContextFactory) =>
_dbContextFactory = dbContextFactory;
public async Task<ConfigElement> Add(ConfigElement configElement)
public async Task<Unit> Upsert<T>(ConfigElementKey configElementKey, T value)
{
await _dbContext.ConfigElements.AddAsync(configElement);
await _dbContext.SaveChangesAsync();
return configElement;
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
Option<ConfigElement> maybeElement = await dbContext.ConfigElements
.SelectOneAsync(c => c.Key, c => c.Key == configElementKey.Key);
await maybeElement.Match(
async element =>
{
element.Value = value.ToString();
await dbContext.SaveChangesAsync();
},
async () =>
{
var configElement = new ConfigElement
{
Key = configElementKey.Key,
Value = value.ToString()
};
await dbContext.ConfigElements.AddAsync(configElement);
await dbContext.SaveChangesAsync();
});
return Unit.Default;
}
public Task<Option<ConfigElement>> Get(ConfigElementKey key) =>
_dbContext.ConfigElements
public async Task<Option<ConfigElement>> Get(ConfigElementKey key)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
return await dbContext.ConfigElements
.OrderBy(ce => ce.Key)
.SingleOrDefaultAsync(ce => ce.Key == key.Key)
.Map(Optional);
}
public Task<Option<T>> GetValue<T>(ConfigElementKey key) =>
Get(key).MapT(ce => (T) Convert.ChangeType(ce.Value, typeof(T)));
public Task Update(ConfigElement configElement)
public async Task Delete(ConfigElement configElement)
{
_dbContext.ConfigElements.Update(configElement);
return _dbContext.SaveChangesAsync();
}
public Task Delete(ConfigElement configElement)
{
_dbContext.ConfigElements.Remove(configElement);
return _dbContext.SaveChangesAsync();
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
dbContext.ConfigElements.Remove(configElement);
await dbContext.SaveChangesAsync();
}
}
}
@@ -1,9 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
@@ -11,45 +8,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
{
public class FFmpegProfileRepository : IFFmpegProfileRepository
{
private readonly TvContext _dbContext;
private readonly IDbContextFactory<TvContext> _dbContextFactory;
public FFmpegProfileRepository(IDbContextFactory<TvContext> dbContextFactory, TvContext dbContext)
{
public FFmpegProfileRepository(IDbContextFactory<TvContext> dbContextFactory) =>
_dbContextFactory = dbContextFactory;
_dbContext = dbContext;
}
public async Task<FFmpegProfile> Add(FFmpegProfile ffmpegProfile)
{
await _dbContext.FFmpegProfiles.AddAsync(ffmpegProfile);
await _dbContext.SaveChangesAsync();
return ffmpegProfile;
}
public async Task<Option<FFmpegProfile>> Get(int id) =>
await _dbContext.FFmpegProfiles
.Include(p => p.Resolution)
.OrderBy(p => p.Id)
.SingleOrDefaultAsync(p => p.Id == id);
public Task<List<FFmpegProfile>> GetAll() =>
_dbContext.FFmpegProfiles
.Include(p => p.Resolution)
.ToListAsync();
public Task Update(FFmpegProfile ffmpegProfile)
{
_dbContext.FFmpegProfiles.Update(ffmpegProfile);
return _dbContext.SaveChangesAsync();
}
public async Task Delete(int ffmpegProfileId)
{
FFmpegProfile ffmpegProfile = await _dbContext.FFmpegProfiles.FindAsync(ffmpegProfileId);
_dbContext.FFmpegProfiles.Remove(ffmpegProfile);
await _dbContext.SaveChangesAsync();
}
public async Task<FFmpegProfile> Copy(int ffmpegProfileId, string name)
{
@@ -14,203 +14,40 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
public class MediaCollectionRepository : IMediaCollectionRepository
{
private readonly IDbConnection _dbConnection;
private readonly TvContext _dbContext;
private readonly IDbContextFactory<TvContext> _dbContextFactory;
public MediaCollectionRepository(
TvContext dbContext,
IDbContextFactory<TvContext> dbContextFactory,
IDbConnection dbConnection)
{
_dbContext = dbContext;
_dbContextFactory = dbContextFactory;
_dbConnection = dbConnection;
}
public async Task<Collection> Add(Collection collection)
{
await _dbContext.Collections.AddAsync(collection);
await _dbContext.SaveChangesAsync();
return collection;
}
public async Task<bool> AddMediaItem(int collectionId, int mediaItemId)
{
var modified = false;
Option<Collection> maybeCollection = await _dbContext.Collections
.Include(c => c.MediaItems)
.OrderBy(c => c.Id)
.SingleOrDefaultAsync(c => c.Id == collectionId)
.Map(Optional);
await maybeCollection.IfSomeAsync(
async collection =>
{
if (collection.MediaItems.All(i => i.Id != mediaItemId))
{
Option<MediaItem> maybeMediaItem = await _dbContext.MediaItems
.OrderBy(i => i.Id)
.SingleOrDefaultAsync(i => i.Id == mediaItemId)
.Map(Optional);
await maybeMediaItem.IfSomeAsync(
async mediaItem =>
{
collection.MediaItems.Add(mediaItem);
modified = await _dbContext.SaveChangesAsync() > 0;
});
}
});
return modified;
}
public async Task<bool> AddMediaItems(int collectionId, List<int> mediaItemIds)
{
var modified = false;
Option<Collection> maybeCollection = await _dbContext.Collections
.Include(c => c.MediaItems)
.OrderBy(c => c.Id)
.SingleOrDefaultAsync(c => c.Id == collectionId)
.Map(Optional);
await maybeCollection.IfSomeAsync(
async collection =>
{
var toAdd = mediaItemIds.Filter(i => collection.MediaItems.All(i2 => i2.Id != i)).ToList();
if (toAdd.Any())
{
List<MediaItem> items = await _dbContext.MediaItems
.Filter(mi => toAdd.Contains(mi.Id))
.ToListAsync();
collection.MediaItems.AddRange(items);
modified = await _dbContext.SaveChangesAsync() > 0;
}
});
return modified;
}
public Task<Option<Collection>> Get(int id) =>
_dbContext.Collections
.Include(c => c.CollectionItems)
.OrderBy(c => c.Id)
.SingleOrDefaultAsync(c => c.Id == id)
.Map(Optional);
public Task<Option<Collection>> GetCollectionWithItems(int id) =>
_dbContext.Collections
.Include(c => c.MediaItems)
.ThenInclude(i => i.LibraryPath)
.Include(c => c.MediaItems)
.ThenInclude(i => (i as Movie).MovieMetadata)
.Include(c => c.MediaItems)
.ThenInclude(i => (i as MusicVideo).MusicVideoMetadata)
.Include(c => c.MediaItems)
.ThenInclude(i => (i as Show).ShowMetadata)
.Include(c => c.MediaItems)
.ThenInclude(i => (i as Season).Show)
.ThenInclude(s => s.ShowMetadata)
.Include(c => c.MediaItems)
.ThenInclude(i => (i as Episode).EpisodeMetadata)
.Include(c => c.MediaItems)
.ThenInclude(i => (i as Episode).Season)
.ThenInclude(s => s.Show)
.ThenInclude(s => s.ShowMetadata)
.OrderBy(c => c.Id)
.SingleOrDefaultAsync(c => c.Id == id)
.Map(Optional);
public Task<Option<Collection>> GetCollectionWithItemsUntracked(int id) =>
_dbContext.Collections
.AsNoTracking()
.Include(c => c.CollectionItems)
.Include(c => c.MediaItems)
.ThenInclude(i => i.LibraryPath)
.Include(c => c.MediaItems)
.ThenInclude(i => (i as Movie).MovieMetadata)
.ThenInclude(mm => mm.Artwork)
.Include(c => c.MediaItems)
.ThenInclude(i => (i as Artist).ArtistMetadata)
.ThenInclude(mvm => mvm.Artwork)
.Include(c => c.MediaItems)
.ThenInclude(i => (i as MusicVideo).MusicVideoMetadata)
.ThenInclude(mvm => mvm.Artwork)
.Include(c => c.MediaItems)
.ThenInclude(i => (i as MusicVideo).Artist)
.ThenInclude(a => a.ArtistMetadata)
.Include(c => c.MediaItems)
.ThenInclude(i => (i as Show).ShowMetadata)
.ThenInclude(sm => sm.Artwork)
.Include(c => c.MediaItems)
.ThenInclude(i => (i as Season).SeasonMetadata)
.ThenInclude(sm => sm.Artwork)
.Include(c => c.MediaItems)
.ThenInclude(i => (i as Season).Show)
.ThenInclude(s => s.ShowMetadata)
.Include(c => c.MediaItems)
.ThenInclude(i => (i as Episode).EpisodeMetadata)
.ThenInclude(em => em.Artwork)
.Include(c => c.MediaItems)
.ThenInclude(i => (i as Episode).EpisodeMetadata)
.ThenInclude(em => em.Directors)
.Include(c => c.MediaItems)
.ThenInclude(i => (i as Episode).EpisodeMetadata)
.ThenInclude(em => em.Writers)
.Include(c => c.MediaItems)
.ThenInclude(i => (i as Episode).Season)
.ThenInclude(s => s.Show)
.ThenInclude(s => s.ShowMetadata)
.Include(c => c.MediaItems)
.ThenInclude(i => (i as Episode).Season)
.ThenInclude(s => s.SeasonMetadata)
.OrderBy(c => c.Id)
.SingleOrDefaultAsync(c => c.Id == id)
.Map(Optional);
public Task<Option<Collection>> GetCollectionWithCollectionItemsUntracked(int id) =>
_dbContext.Collections
.Include(c => c.CollectionItems)
.OrderBy(c => c.Id)
.SingleOrDefaultAsync(c => c.Id == id)
.Map(Optional);
public Task<List<Collection>> GetAll() =>
_dbContext.Collections.ToListAsync();
public Task<int> CountAllCollections() =>
_dbConnection.QuerySingleAsync<int>(@"SELECT COUNT (*) FROM Collection");
public async Task<List<Collection>> GetPagedCollections(int pageNumber, int pageSize)
public async Task<Option<Collection>> GetCollectionWithCollectionItemsUntracked(int id)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
return await dbContext.Collections.FromSqlRaw(
@"SELECT * FROM Collection
ORDER BY Name
LIMIT {0} OFFSET {1}",
pageSize,
pageNumber * pageSize)
.AsNoTracking()
.ToListAsync();
return await dbContext.Collections
.Include(c => c.CollectionItems)
.OrderBy(c => c.Id)
.SingleOrDefaultAsync(c => c.Id == id)
.Map(Optional);
}
public Task<Option<List<MediaItem>>> GetItems(int id) =>
Get(id).MapT(GetItemsForCollection).Bind(x => x.Sequence());
public Task<bool> Update(Collection collection)
public async Task<List<MediaItem>> GetItems(int collectionId)
{
_dbContext.Collections.Update(collection);
return _dbContext.SaveChangesAsync().Map(result => result > 0);
}
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
public async Task Delete(int collectionId)
{
Collection mediaCollection = await _dbContext.Collections.FindAsync(collectionId);
_dbContext.Collections.Remove(mediaCollection);
await _dbContext.SaveChangesAsync();
var result = new List<MediaItem>();
result.AddRange(await GetMovieItems(dbContext, collectionId));
result.AddRange(await GetShowItems(dbContext, collectionId));
result.AddRange(await GetSeasonItems(dbContext, collectionId));
result.AddRange(await GetEpisodeItems(dbContext, collectionId));
result.AddRange(await GetArtistItems(dbContext, collectionId));
result.AddRange(await GetMusicVideoItems(dbContext, collectionId));
return result.Distinct().ToList();
}
public Task<List<int>> PlayoutIdsUsingCollection(int collectionId) =>
@@ -228,45 +65,31 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
@"SELECT IFNULL(MIN(UseCustomPlaybackOrder), 0) FROM Collection WHERE Id = @CollectionId",
new { CollectionId = collectionId });
private async Task<List<MediaItem>> GetItemsForCollection(Collection collection)
{
var result = new List<MediaItem>();
result.AddRange(await GetMovieItems(collection));
result.AddRange(await GetShowItems(collection));
result.AddRange(await GetSeasonItems(collection));
result.AddRange(await GetEpisodeItems(collection));
result.AddRange(await GetArtistItems(collection));
result.AddRange(await GetMusicVideoItems(collection));
return result.Distinct().ToList();
}
private async Task<List<Movie>> GetMovieItems(Collection collection)
private async Task<List<Movie>> GetMovieItems(TvContext dbContext, int collectionId)
{
IEnumerable<int> ids = await _dbConnection.QueryAsync<int>(
@"SELECT m.Id FROM CollectionItem ci
INNER JOIN Movie m ON m.Id = ci.MediaItemId
WHERE ci.CollectionId = @CollectionId",
new { CollectionId = collection.Id });
new { CollectionId = collectionId });
return await _dbContext.Movies
return await dbContext.Movies
.Include(m => m.MovieMetadata)
.Include(m => m.MediaVersions)
.Filter(m => ids.Contains(m.Id))
.ToListAsync();
}
private async Task<List<MusicVideo>> GetArtistItems(Collection collection)
private async Task<List<MusicVideo>> GetArtistItems(TvContext dbContext, int collectionId)
{
IEnumerable<int> ids = await _dbConnection.QueryAsync<int>(
@"SELECT MusicVideo.Id FROM CollectionItem ci
INNER JOIN Artist on Artist.Id = ci.MediaItemId
INNER JOIN MusicVideo on Artist.Id = MusicVideo.ArtistId
WHERE ci.CollectionId = @CollectionId",
new { CollectionId = collection.Id });
new { CollectionId = collectionId });
return await _dbContext.MusicVideos
return await dbContext.MusicVideos
.Include(m => m.Artist)
.ThenInclude(a => a.ArtistMetadata)
.Include(m => m.MusicVideoMetadata)
@@ -276,15 +99,15 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
}
private async Task<List<MusicVideo>> GetMusicVideoItems(Collection collection)
private async Task<List<MusicVideo>> GetMusicVideoItems(TvContext dbContext, int collectionId)
{
IEnumerable<int> ids = await _dbConnection.QueryAsync<int>(
@"SELECT m.Id FROM CollectionItem ci
INNER JOIN MusicVideo m ON m.Id = ci.MediaItemId
WHERE ci.CollectionId = @CollectionId",
new { CollectionId = collection.Id });
new { CollectionId = collectionId });
return await _dbContext.MusicVideos
return await dbContext.MusicVideos
.Include(m => m.Artist)
.ThenInclude(a => a.ArtistMetadata)
.Include(m => m.MusicVideoMetadata)
@@ -293,7 +116,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.ToListAsync();
}
private async Task<List<Episode>> GetShowItems(Collection collection)
private async Task<List<Episode>> GetShowItems(TvContext dbContext, int collectionId)
{
IEnumerable<int> ids = await _dbConnection.QueryAsync<int>(
@"SELECT Episode.Id FROM CollectionItem ci
@@ -301,9 +124,9 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
INNER JOIN Season ON Season.ShowId = Show.Id
INNER JOIN Episode ON Episode.SeasonId = Season.Id
WHERE ci.CollectionId = @CollectionId",
new { CollectionId = collection.Id });
new { CollectionId = collectionId });
return await _dbContext.Episodes
return await dbContext.Episodes
.Include(e => e.EpisodeMetadata)
.Include(e => e.MediaVersions)
.Include(e => e.Season)
@@ -313,16 +136,16 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.ToListAsync();
}
private async Task<List<Episode>> GetSeasonItems(Collection collection)
private async Task<List<Episode>> GetSeasonItems(TvContext dbContext, int collectionId)
{
IEnumerable<int> ids = await _dbConnection.QueryAsync<int>(
@"SELECT Episode.Id FROM CollectionItem ci
INNER JOIN Season ON Season.Id = ci.MediaItemId
INNER JOIN Episode ON Episode.SeasonId = Season.Id
WHERE ci.CollectionId = @CollectionId",
new { CollectionId = collection.Id });
new { CollectionId = collectionId });
return await _dbContext.Episodes
return await dbContext.Episodes
.Include(e => e.EpisodeMetadata)
.Include(e => e.MediaVersions)
.Include(e => e.Season)
@@ -332,15 +155,15 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.ToListAsync();
}
private async Task<List<Episode>> GetEpisodeItems(Collection collection)
private async Task<List<Episode>> GetEpisodeItems(TvContext dbContext, int collectionId)
{
IEnumerable<int> ids = await _dbConnection.QueryAsync<int>(
@"SELECT Episode.Id FROM CollectionItem ci
INNER JOIN Episode ON Episode.Id = ci.MediaItemId
WHERE ci.CollectionId = @CollectionId",
new { CollectionId = collection.Id });
new { CollectionId = collectionId });
return await _dbContext.Episodes
return await dbContext.Episodes
.Include(e => e.EpisodeMetadata)
.Include(e => e.MediaVersions)
.Include(e => e.Season)
@@ -3,47 +3,16 @@ 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 MediaItemRepository : IMediaItemRepository
{
private readonly IDbConnection _dbConnection;
private readonly IDbContextFactory<TvContext> _dbContextFactory;
public MediaItemRepository(IDbContextFactory<TvContext> dbContextFactory, IDbConnection dbConnection)
{
_dbContextFactory = dbContextFactory;
_dbConnection = dbConnection;
}
public async Task<Option<MediaItem>> Get(int id)
{
await using TvContext context = _dbContextFactory.CreateDbContext();
return await context.MediaItems
.Include(i => i.LibraryPath)
.OrderBy(i => i.Id)
.SingleOrDefaultAsync(i => i.Id == id)
.Map(Optional);
}
public async Task<List<MediaItem>> GetAll()
{
await using TvContext context = _dbContextFactory.CreateDbContext();
return await context.MediaItems.ToListAsync();
}
public async Task<bool> Update(MediaItem mediaItem)
{
await using TvContext context = _dbContextFactory.CreateDbContext();
context.MediaItems.Update(mediaItem);
return await context.SaveChangesAsync() > 0;
}
public MediaItemRepository(IDbConnection dbConnection) => _dbConnection = dbConnection;
public Task<List<string>> GetAllLanguageCodes() =>
_dbConnection.QueryAsync<string>(
@@ -24,14 +24,6 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
_dbConnection = dbConnection;
}
public async Task<LocalMediaSource> Add(LocalMediaSource localMediaSource)
{
await using TvContext context = _dbContextFactory.CreateDbContext();
await context.LocalMediaSources.AddAsync(localMediaSource);
await context.SaveChangesAsync();
return localMediaSource;
}
public async Task<PlexMediaSource> Add(PlexMediaSource plexMediaSource)
{
await using TvContext context = _dbContextFactory.CreateDbContext();
@@ -40,18 +32,6 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
return plexMediaSource;
}
public async Task<List<MediaSource>> GetAll()
{
await using TvContext context = _dbContextFactory.CreateDbContext();
List<MediaSource> all = await context.MediaSources.ToListAsync();
foreach (PlexMediaSource plex in all.OfType<PlexMediaSource>())
{
await context.Entry(plex).Collection(p => p.Connections).LoadAsync();
}
return all;
}
public Task<List<PlexMediaSource>> GetAllPlex()
{
using TvContext context = _dbContextFactory.CreateDbContext();
@@ -87,15 +67,6 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.Map(Optional);
}
public Task<Option<MediaSource>> 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<Option<PlexMediaSource>> GetPlex(int id)
{
using TvContext context = _dbContextFactory.CreateDbContext();
@@ -140,20 +111,6 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.ToListAsync();
}
public Task<int> 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<PlexConnection> sortedConnections,
@@ -328,21 +285,6 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
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<List<int>> DeleteAllPlex()
{
await using TvContext context = _dbContextFactory.CreateDbContext();
@@ -139,25 +139,6 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
async () => await AddPlexMovie(context, library, item));
}
public Task<int> GetMovieCount() =>
_dbConnection.QuerySingleAsync<int>(@"SELECT COUNT(DISTINCT MovieId) FROM MovieMetadata");
public async Task<List<MovieMetadata>> GetPagedMovies(int pageNumber, int pageSize)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
return await dbContext.MovieMetadata.FromSqlRaw(
@"SELECT * FROM MovieMetadata WHERE Id IN
(SELECT Id FROM MovieMetadata GROUP BY MovieId, MetadataKind HAVING MetadataKind = MAX(MetadataKind))
ORDER BY SortTitle
LIMIT {0} OFFSET {1}",
pageSize,
(pageNumber - 1) * pageSize)
.AsNoTracking()
.Include(mm => mm.Artwork)
.OrderBy(mm => mm.SortTitle)
.ToListAsync();
}
public async Task<List<MovieMetadata>> GetMoviesForCards(List<int> ids)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
@@ -131,23 +131,6 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.ToListAsync();
}
public async Task<Option<MusicVideo>> GetMusicVideo(int musicVideoId)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
return await dbContext.MusicVideos
.Include(m => m.MusicVideoMetadata)
.ThenInclude(m => m.Artwork)
.Include(m => m.MusicVideoMetadata)
.ThenInclude(m => m.Genres)
.Include(m => m.MusicVideoMetadata)
.ThenInclude(m => m.Tags)
.Include(m => m.MusicVideoMetadata)
.ThenInclude(m => m.Studios)
.OrderBy(m => m.Id)
.SingleOrDefaultAsync(m => m.Id == musicVideoId)
.Map(Optional);
}
public Task<IEnumerable<string>> FindOrphanPaths(LibraryPath libraryPath) =>
_dbConnection.QueryAsync<string>(
@"SELECT MF.Path
@@ -1,144 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
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 PlayoutRepository : IPlayoutRepository
{
private readonly TvContext _dbContext;
private readonly IDbContextFactory<TvContext> _dbContextFactory;
public PlayoutRepository(TvContext dbContext, IDbContextFactory<TvContext> dbContextFactory)
{
_dbContext = dbContext;
_dbContextFactory = dbContextFactory;
}
public async Task<Playout> Add(Playout playout)
{
await _dbContext.Playouts.AddAsync(playout);
await _dbContext.SaveChangesAsync();
return playout;
}
public Task<Option<Playout>> Get(int id) =>
_dbContext.Playouts
.OrderBy(p => p.Id)
.SingleOrDefaultAsync(p => p.Id == id)
.Map(Optional);
public async Task<Option<Playout>> GetFull(int id) =>
await _dbContext.Playouts
.Include(p => p.Channel)
.Include(p => p.Items)
.Include(p => p.ProgramScheduleAnchors)
.ThenInclude(a => a.MediaItem)
.Include(p => p.ProgramSchedule)
.ThenInclude(ps => ps.Items)
.ThenInclude(psi => psi.Collection)
.Include(p => p.ProgramSchedule)
.ThenInclude(ps => ps.Items)
.ThenInclude(psi => psi.MediaItem)
.OrderBy(p => p.Id) // https://github.com/dotnet/efcore/issues/22579#issuecomment-694772289
.SingleOrDefaultAsync(p => p.Id == id);
public Task<Option<PlayoutItem>> GetPlayoutItem(int channelId, DateTimeOffset now) =>
_dbContext.PlayoutItems
.Where(pi => pi.Playout.ChannelId == channelId)
.Where(pi => pi.Start <= now.UtcDateTime && pi.Finish > now.UtcDateTime)
.Include(i => i.MediaItem)
.ThenInclude(mi => (mi as Episode).MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.Include(i => i.MediaItem)
.ThenInclude(mi => (mi as Episode).MediaVersions)
.ThenInclude(mv => mv.Streams)
.Include(i => i.MediaItem)
.ThenInclude(mi => (mi as Movie).MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.Include(i => i.MediaItem)
.ThenInclude(mi => (mi as Movie).MediaVersions)
.ThenInclude(mv => mv.Streams)
.Include(i => i.MediaItem)
.ThenInclude(mi => (mi as MusicVideo).MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.Include(i => i.MediaItem)
.ThenInclude(mi => (mi as MusicVideo).MediaVersions)
.ThenInclude(mv => mv.Streams)
.AsNoTracking()
.OrderBy(pi => pi.Start)
.SingleOrDefaultAsync()
.Map(Optional);
public Task<Option<DateTimeOffset>> GetNextItemStart(int channelId, DateTimeOffset now) =>
_dbContext.PlayoutItems
.Where(pi => pi.Playout.ChannelId == channelId)
.Where(pi => pi.Start > now.UtcDateTime)
.OrderBy(pi => pi.Start)
.FirstOrDefaultAsync()
.Map(Optional)
.MapT(pi => pi.StartOffset);
public async Task<List<PlayoutItem>> GetPlayoutItems(int playoutId)
{
await using TvContext context = _dbContextFactory.CreateDbContext();
return await context.PlayoutItems
.AsNoTracking()
.Include(i => i.MediaItem)
.ThenInclude(mi => (mi as Movie).MovieMetadata)
.ThenInclude(mm => mm.Artwork)
.Include(i => i.MediaItem)
.ThenInclude(mi => (mi as Movie).MediaVersions)
.Include(i => i.MediaItem)
.ThenInclude(mi => (mi as MusicVideo).MusicVideoMetadata)
.ThenInclude(mm => mm.Artwork)
.Include(i => i.MediaItem)
.ThenInclude(mi => (mi as MusicVideo).MediaVersions)
.Include(i => i.MediaItem)
.ThenInclude(mi => (mi as MusicVideo).Artist)
.ThenInclude(mm => mm.ArtistMetadata)
.Include(i => i.MediaItem)
.ThenInclude(mi => (mi as Episode).EpisodeMetadata)
.ThenInclude(em => em.Artwork)
.Include(i => i.MediaItem)
.ThenInclude(mi => (mi as Episode).MediaVersions)
.Include(i => i.MediaItem)
.ThenInclude(mi => (mi as Episode).Season)
.ThenInclude(s => s.SeasonMetadata)
.Include(i => i.MediaItem)
.ThenInclude(mi => (mi as Episode).Season.Show)
.ThenInclude(s => s.ShowMetadata)
.Filter(i => i.PlayoutId == playoutId)
.ToListAsync();
}
public async Task<List<Playout>> GetAll()
{
await using TvContext context = _dbContextFactory.CreateDbContext();
return await context.Playouts
.AsNoTracking()
.Include(p => p.Channel)
.Include(p => p.ProgramSchedule)
.ToListAsync();
}
public Task Update(Playout playout)
{
_dbContext.Playouts.Update(playout);
return _dbContext.SaveChangesAsync();
}
public async Task Delete(int playoutId)
{
Playout playout = await _dbContext.Playouts.FindAsync(playoutId);
_dbContext.Playouts.Remove(playout);
await _dbContext.SaveChangesAsync();
}
}
}
@@ -1,87 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
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 ProgramScheduleRepository : IProgramScheduleRepository
{
private readonly TvContext _dbContext;
public ProgramScheduleRepository(TvContext dbContext) => _dbContext = dbContext;
public async Task<ProgramSchedule> Add(ProgramSchedule programSchedule)
{
await _dbContext.ProgramSchedules.AddAsync(programSchedule);
await _dbContext.SaveChangesAsync();
return programSchedule;
}
public Task<Option<ProgramSchedule>> Get(int id) =>
_dbContext.ProgramSchedules
.OrderBy(s => s.Id)
.SingleOrDefaultAsync(s => s.Id == id)
.Map(Optional);
public async Task<Option<ProgramSchedule>> GetWithPlayouts(int id) =>
await _dbContext.ProgramSchedules
.Include(ps => ps.Items)
.Include(ps => ps.Playouts)
.OrderBy(ps => ps.Id)
.SingleOrDefaultAsync(ps => ps.Id == id);
public Task<List<ProgramSchedule>> GetAll() =>
_dbContext.ProgramSchedules.ToListAsync();
public async Task Update(ProgramSchedule programSchedule)
{
_dbContext.ProgramSchedules.Update(programSchedule);
await _dbContext.SaveChangesAsync();
await _dbContext.Entry(programSchedule).Collection(s => s.Items).Query().Include(i => i.Collection)
.LoadAsync();
await _dbContext.Entry(programSchedule).Collection(s => s.Playouts).LoadAsync();
}
public async Task Delete(int programScheduleId)
{
ProgramSchedule programSchedule = await _dbContext.ProgramSchedules.FindAsync(programScheduleId);
_dbContext.ProgramSchedules.Remove(programSchedule);
await _dbContext.SaveChangesAsync();
}
public async Task<Option<List<ProgramScheduleItem>>> GetItems(int programScheduleId)
{
Option<ProgramSchedule> maybeSchedule = await Get(programScheduleId);
return await maybeSchedule.Map(
async programSchedule =>
{
await _dbContext.Entry(programSchedule).Collection(s => s.Items).LoadAsync();
await _dbContext.Entry(programSchedule).Collection(s => s.Items).Query()
.Include(i => i.Collection)
.Include(i => i.MediaItem)
.ThenInclude(i => (i as Movie).MovieMetadata)
.ThenInclude(mm => mm.Artwork)
.Include(i => i.MediaItem)
.ThenInclude(i => (i as Season).SeasonMetadata)
.ThenInclude(sm => sm.Artwork)
.Include(i => i.MediaItem)
.ThenInclude(i => (i as Season).Show)
.ThenInclude(s => s.ShowMetadata)
.ThenInclude(sm => sm.Artwork)
.Include(i => i.MediaItem)
.ThenInclude(i => (i as Show).ShowMetadata)
.ThenInclude(sm => sm.Artwork)
.Include(i => i.MediaItem)
.ThenInclude(i => (i as Artist).ArtistMetadata)
.ThenInclude(am => am.Artwork)
.LoadAsync();
return programSchedule.Items;
}).Sequence();
}
}
}
@@ -1,27 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
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 ResolutionRepository : IResolutionRepository
{
private readonly TvContext _dbContext;
public ResolutionRepository(TvContext dbContext) => _dbContext = dbContext;
public Task<Option<Resolution>> Get(int id) =>
_dbContext.Resolutions
.OrderBy(r => r.Id)
.SingleOrDefaultAsync(r => r.Id == id)
.Map(Optional);
public Task<List<Resolution>> GetAll() =>
_dbContext.Resolutions.ToListAsync();
}
}
@@ -162,19 +162,6 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.ToListAsync();
}
public async Task<Option<Episode>> GetEpisode(int episodeId)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
return await dbContext.Episodes
.AsNoTracking()
.Include(e => e.Season)
.Include(e => e.EpisodeMetadata)
.ThenInclude(em => em.Artwork)
.OrderBy(s => s.Id)
.SingleOrDefaultAsync(s => s.Id == episodeId)
.Map(Optional);
}
public async Task<int> GetEpisodeCount(int seasonId)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
@@ -58,6 +58,7 @@ namespace ErsatzTV.Infrastructure.Data
public DbSet<Collection> Collections { get; set; }
public DbSet<CollectionItem> CollectionItems { get; set; }
public DbSet<ProgramSchedule> ProgramSchedules { get; set; }
public DbSet<ProgramScheduleItem> ProgramScheduleItems { get; set; }
public DbSet<Playout> Playouts { get; set; }
public DbSet<PlayoutItem> PlayoutItems { get; set; }
public DbSet<PlayoutProgramScheduleAnchor> PlayoutProgramScheduleItemAnchors { get; set; }
@@ -0,0 +1,18 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
using LanguageExt;
namespace ErsatzTV.Infrastructure.Extensions
{
public static class ConfigElementQueryableExtensions
{
public static Task<Option<T>> GetValue<T>(
this IQueryable<ConfigElement> configElements,
ConfigElementKey key) =>
configElements
.SelectOneAsync(ce => ce.Key, ce => ce.Key == key.Key)
.MapT(ce => (T) Convert.ChangeType(ce.Value, typeof(T)));
}
}
@@ -0,0 +1,23 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
using LanguageExt;
using Microsoft.EntityFrameworkCore;
using static LanguageExt.Prelude;
namespace ErsatzTV.Infrastructure.Extensions
{
public static class PlayoutItemQueryableExtensions
{
public static Task<Option<PlayoutItem>> ForChannelAndTime(
this IQueryable<PlayoutItem> dbSet,
int channelId,
DateTimeOffset time) =>
dbSet.Filter(pi => pi.Playout.ChannelId == channelId)
.Filter(pi => pi.Start <= time.UtcDateTime && pi.Finish > time.UtcDateTime)
.OrderBy(pi => pi.Start)
.FirstOrDefaultAsync()
.Map(Optional);
}
}
@@ -0,0 +1,18 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using LanguageExt;
using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Infrastructure.Extensions
{
public static class QueryableExtensions
{
public static async Task<Option<T>> SelectOneAsync<T, TKey>(
this IQueryable<T> enumerable,
Expression<Func<T, TKey>> keySelector,
Expression<Func<T, bool>> predicate) where T : class =>
await enumerable.OrderBy(keySelector).FirstOrDefaultAsync(predicate);
}
}