Initial commit

This commit is contained in:
Jason Dove
2021-02-08 21:13:53 -06:00
commit 4d52e115b5
493 changed files with 31399 additions and 0 deletions
@@ -0,0 +1,20 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class ChannelConfiguration : IEntityTypeConfiguration<Channel>
{
public void Configure(EntityTypeBuilder<Channel> builder)
{
builder.HasIndex(c => c.Number)
.IsUnique();
builder.HasMany(c => c.Playouts) // TODO: is this correct, or should we have one to one?
.WithOne(p => p.Channel)
.HasForeignKey(p => p.ChannelId)
.OnDelete(DeleteBehavior.Cascade);
}
}
}
@@ -0,0 +1,13 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class ConfigElementConfiguration : IEntityTypeConfiguration<ConfigElement>
{
public void Configure(EntityTypeBuilder<ConfigElement> builder) =>
builder.HasIndex(ce => ce.Key)
.IsUnique();
}
}
@@ -0,0 +1,12 @@
using ErsatzTV.Core.AggregateModels;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class GenericIntegerIdConfiguration : IEntityTypeConfiguration<GenericIntegerId>
{
public void Configure(EntityTypeBuilder<GenericIntegerId> builder) =>
builder.HasNoKey();
}
}
@@ -0,0 +1,12 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class LocalMediaSourceConfiguration : IEntityTypeConfiguration<LocalMediaSource>
{
public void Configure(EntityTypeBuilder<LocalMediaSource> builder) =>
builder.ToTable("LocalMediaSources");
}
}
@@ -0,0 +1,12 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class MediaCollectionConfiguration : IEntityTypeConfiguration<MediaCollection>
{
public void Configure(EntityTypeBuilder<MediaCollection> builder) =>
builder.HasIndex(c => c.Name).IsUnique();
}
}
@@ -0,0 +1,12 @@
using ErsatzTV.Core.AggregateModels;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class MediaCollectionSummaryConfiguration : IEntityTypeConfiguration<MediaCollectionSummary>
{
public void Configure(EntityTypeBuilder<MediaCollectionSummary> builder) =>
builder.HasNoKey();
}
}
@@ -0,0 +1,12 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class MediaItemConfiguration : IEntityTypeConfiguration<MediaItem>
{
public void Configure(EntityTypeBuilder<MediaItem> builder) =>
builder.OwnsOne(c => c.Metadata).WithOwner();
}
}
@@ -0,0 +1,12 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class MediaSourceConfiguration : IEntityTypeConfiguration<MediaSource>
{
public void Configure(EntityTypeBuilder<MediaSource> builder) =>
builder.HasIndex(ms => ms.Name).IsUnique();
}
}
@@ -0,0 +1,24 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class PlayoutConfiguration : IEntityTypeConfiguration<Playout>
{
public void Configure(EntityTypeBuilder<Playout> builder)
{
builder.HasMany(p => p.Items)
.WithOne(pi => pi.Playout)
.HasForeignKey(pi => pi.PlayoutId)
.OnDelete(DeleteBehavior.Cascade);
builder.OwnsOne(c => c.Anchor);
builder.HasMany(p => p.ProgramScheduleAnchors)
.WithOne(a => a.Playout)
.HasForeignKey(a => a.PlayoutId)
.OnDelete(DeleteBehavior.Cascade);
}
}
}
@@ -0,0 +1,16 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class PlayoutProgramScheduleAnchorConfiguration : IEntityTypeConfiguration<PlayoutProgramScheduleAnchor>
{
public void Configure(EntityTypeBuilder<PlayoutProgramScheduleAnchor> builder)
{
builder.HasKey(a => new { a.PlayoutId, a.ProgramScheduleId, ContentGroupId = a.MediaCollectionId });
builder.OwnsOne(a => a.EnumeratorState);
}
}
}
@@ -0,0 +1,11 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class PlexMediaSourceConfiguration : IEntityTypeConfiguration<PlexMediaSource>
{
public void Configure(EntityTypeBuilder<PlexMediaSource> builder) => builder.ToTable("PlexMediaSources");
}
}
@@ -0,0 +1,12 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class PlexMediaSourceConnectionConfiguration : IEntityTypeConfiguration<PlexMediaSourceConnection>
{
public void Configure(EntityTypeBuilder<PlexMediaSourceConnection> builder) =>
builder.ToTable("PlexMediaSourceConnections");
}
}
@@ -0,0 +1,12 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class PlexMediaSourceLibraryConfiguration : IEntityTypeConfiguration<PlexMediaSourceLibrary>
{
public void Configure(EntityTypeBuilder<PlexMediaSourceLibrary> builder) =>
builder.ToTable("PlexMediaSourceLibraries");
}
}
@@ -0,0 +1,24 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class ProgramScheduleConfiguration : IEntityTypeConfiguration<ProgramSchedule>
{
public void Configure(EntityTypeBuilder<ProgramSchedule> builder)
{
builder.HasIndex(ps => ps.Name)
.IsUnique();
builder.HasMany(ps => ps.Items)
.WithOne(i => i.ProgramSchedule)
.HasForeignKey(i => i.ProgramScheduleId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasMany(ps => ps.Playouts)
.WithOne(p => p.ProgramSchedule)
.HasForeignKey(p => p.ProgramScheduleId);
}
}
}
@@ -0,0 +1,12 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class ProgramScheduleItemConfiguration : IEntityTypeConfiguration<ProgramScheduleItem>
{
public void Configure(EntityTypeBuilder<ProgramScheduleItem> builder) =>
builder.ToTable("ProgramScheduleItems");
}
}
@@ -0,0 +1,12 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class ProgramScheduleItemDurationConfiguration : IEntityTypeConfiguration<ProgramScheduleItemDuration>
{
public void Configure(EntityTypeBuilder<ProgramScheduleItemDuration> builder) =>
builder.ToTable("ProgramScheduleDurationItems");
}
}
@@ -0,0 +1,12 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class ProgramScheduleItemFloodConfiguration : IEntityTypeConfiguration<ProgramScheduleItemFlood>
{
public void Configure(EntityTypeBuilder<ProgramScheduleItemFlood> builder) =>
builder.ToTable("ProgramScheduleFloodItems");
}
}
@@ -0,0 +1,12 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class ProgramScheduleItemMultipleConfiguration : IEntityTypeConfiguration<ProgramScheduleItemMultiple>
{
public void Configure(EntityTypeBuilder<ProgramScheduleItemMultiple> builder) =>
builder.ToTable("ProgramScheduleMultipleItems");
}
}
@@ -0,0 +1,12 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class ProgramScheduleItemOneConfiguration : IEntityTypeConfiguration<ProgramScheduleItemOne>
{
public void Configure(EntityTypeBuilder<ProgramScheduleItemOne> builder) =>
builder.ToTable("ProgramScheduleOneItems");
}
}
@@ -0,0 +1,17 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class SimpleMediaCollectionConfiguration : IEntityTypeConfiguration<SimpleMediaCollection>
{
public void Configure(EntityTypeBuilder<SimpleMediaCollection> builder)
{
builder.ToTable("SimpleMediaCollections");
builder.HasMany(cg => cg.Items)
.WithMany(c => c.SimpleMediaCollections);
}
}
}
@@ -0,0 +1,17 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class TelevisionMediaCollectionConfiguration : IEntityTypeConfiguration<TelevisionMediaCollection>
{
public void Configure(EntityTypeBuilder<TelevisionMediaCollection> builder)
{
builder.ToTable("TelevisionMediaCollections");
builder.HasIndex(c => new { c.ShowTitle, c.SeasonNumber })
.IsUnique();
}
}
}
@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using ErsatzTV.Core.Domain;
using LanguageExt;
namespace ErsatzTV.Infrastructure.Data
{
public static class DbInitializer
{
public static Unit Initialize(TvContext context)
{
if (context.Resolutions.Any())
{
return Unit.Default;
}
var resolutions = new List<Resolution>
{
new() { Id = 1, Name = "720x480", Width = 720, Height = 480 },
new() { Id = 2, Name = "1280x720", Width = 1280, Height = 720 },
new() { Id = 3, Name = "1920x1080", Width = 1920, Height = 1080 },
new() { Id = 4, Name = "3840x2160", Width = 3840, Height = 2160 }
};
context.Resolutions.AddRange(resolutions);
context.SaveChanges();
var resolutionConfig = new ConfigElement
{
Key = ConfigElementKey.FFmpegDefaultResolutionId.Key,
Value = "3" // 1920x1080
};
context.ConfigElements.Add(resolutionConfig);
context.SaveChanges();
var defaultProfile = FFmpegProfile.New("1920x1080 x264 ac3", resolutions[2]);
context.FFmpegProfiles.Add(defaultProfile);
context.SaveChanges();
var profileConfig = new ConfigElement
{
Key = ConfigElementKey.FFmpegDefaultProfileId.Key,
Value = defaultProfile.Id.ToString()
};
context.ConfigElements.Add(profileConfig);
context.SaveChanges();
var defaultChannel = new Channel(Guid.NewGuid())
{
Number = 1,
Name = "ErsatzTV",
FFmpegProfile = defaultProfile,
StreamingMode = StreamingMode.TransportStream
};
context.Channels.Add(defaultChannel);
context.SaveChanges();
// TODO: clean this up
// var mediaSource = new LocalMediaSource
// {
// Name = "Default"
// };
// context.MediaSources.Add(mediaSource);
// context.SaveChanges();
// TODO: create looping static image that mentions configuring via web
return Unit.Default;
}
}
}
@@ -0,0 +1,56 @@
using System.Collections.Generic;
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 ChannelRepository : IChannelRepository
{
private readonly TvContext _dbContext;
public ChannelRepository(TvContext dbContext) => _dbContext = dbContext;
public async Task<Channel> Add(Channel channel)
{
await _dbContext.Channels.AddAsync(channel);
await _dbContext.SaveChangesAsync();
return channel;
}
public Task<Option<Channel>> Get(int id) =>
_dbContext.Channels.SingleOrDefaultAsync(c => c.Id == id).Map(Optional);
public Task<Option<Channel>> GetByNumber(int number) =>
_dbContext.Channels
.Include(c => c.FFmpegProfile)
.ThenInclude(p => p.Resolution)
.SingleOrDefaultAsync(c => c.Number == number)
.Map(Optional);
public Task<List<Channel>> GetAll() => _dbContext.Channels.ToListAsync();
public Task<List<Channel>> GetAllForGuide() =>
_dbContext.Channels
.Include(c => c.Playouts)
.ThenInclude(p => p.Items)
.ThenInclude(i => i.MediaItem)
.ToListAsync();
public async Task Update(Channel channel)
{
_dbContext.Channels.Update(channel);
await _dbContext.SaveChangesAsync();
}
public async Task Delete(int channelId)
{
Channel channel = await _dbContext.Channels.FindAsync(channelId);
_dbContext.Channels.Remove(channel);
await _dbContext.SaveChangesAsync();
}
}
}
@@ -0,0 +1,41 @@
using System;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Infrastructure.Data.Repositories
{
public class ConfigElementRepository : IConfigElementRepository
{
private readonly TvContext _dbContext;
public ConfigElementRepository(TvContext dbContext) => _dbContext = dbContext;
public async Task<ConfigElement> Add(ConfigElement configElement)
{
await _dbContext.ConfigElements.AddAsync(configElement);
await _dbContext.SaveChangesAsync();
return configElement;
}
public async Task<Option<ConfigElement>> Get(ConfigElementKey key) =>
await _dbContext.ConfigElements.SingleOrDefaultAsync(ce => ce.Key == key.Key);
public Task<Option<T>> GetValue<T>(ConfigElementKey key) =>
Get(key).MapT(ce => (T) Convert.ChangeType(ce.Value, typeof(T)));
public async Task Update(ConfigElement configElement)
{
_dbContext.ConfigElements.Update(configElement);
await _dbContext.SaveChangesAsync();
}
public async Task Delete(ConfigElement configElement)
{
_dbContext.ConfigElements.Remove(configElement);
await _dbContext.SaveChangesAsync();
}
}
}
@@ -0,0 +1,46 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Infrastructure.Data.Repositories
{
public class FFmpegProfileRepository : IFFmpegProfileRepository
{
private readonly TvContext _dbContext;
public FFmpegProfileRepository(TvContext dbContext) => _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)
.SingleOrDefaultAsync(p => p.Id == id);
public Task<List<FFmpegProfile>> GetAll() =>
_dbContext.FFmpegProfiles
.Include(p => p.Resolution)
.ToListAsync();
public async Task Update(FFmpegProfile ffmpegProfile)
{
_dbContext.FFmpegProfiles.Update(ffmpegProfile);
await _dbContext.SaveChangesAsync();
}
public async Task Delete(int ffmpegProfileId)
{
FFmpegProfile ffmpegProfile = await _dbContext.FFmpegProfiles.FindAsync(ffmpegProfileId);
_dbContext.FFmpegProfiles.Remove(ffmpegProfile);
await _dbContext.SaveChangesAsync();
}
}
}
@@ -0,0 +1,142 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ErsatzTV.Core.AggregateModels;
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 MediaCollectionRepository : IMediaCollectionRepository
{
private readonly TvContext _dbContext;
public MediaCollectionRepository(TvContext dbContext) => _dbContext = dbContext;
public async Task<SimpleMediaCollection> Add(SimpleMediaCollection collection)
{
await _dbContext.SimpleMediaCollections.AddAsync(collection);
await _dbContext.SaveChangesAsync();
return collection;
}
public Task<Option<MediaCollection>> Get(int id) =>
_dbContext.MediaCollections.SingleOrDefaultAsync(c => c.Id == id).Map(Optional);
public Task<Option<SimpleMediaCollection>> GetSimpleMediaCollection(int id) =>
Get(id).Map(c => c.OfType<SimpleMediaCollection>().HeadOrNone());
public Task<Option<TelevisionMediaCollection>> GetTelevisionMediaCollection(int id) =>
Get(id).Map(c => c.OfType<TelevisionMediaCollection>().HeadOrNone());
public Task<List<SimpleMediaCollection>> GetSimpleMediaCollections() =>
_dbContext.SimpleMediaCollections.ToListAsync();
public Task<List<MediaCollection>> GetAll() =>
_dbContext.MediaCollections.ToListAsync();
public Task<List<MediaCollectionSummary>> GetSummaries(string searchString) =>
_dbContext.MediaCollectionSummaries.FromSqlRaw(
@"SELECT mc.Id, mc.Name, Count(mismc.ItemsId) AS ItemCount, true AS IsSimple
FROM MediaCollections mc
INNER JOIN SimpleMediaCollections smc ON smc.Id = mc.Id
LEFT OUTER JOIN MediaItemSimpleMediaCollection mismc ON mismc.SimpleMediaCollectionsId = mc.Id
WHERE mc.Name LIKE {0}
GROUP BY mc.Id, mc.Name
UNION ALL
SELECT mc.Id, mc.Name, Count(mi.Id) AS ItemCount, false AS IsSimple
FROM MediaCollections mc
INNER JOIN TelevisionMediaCollections tmc ON tmc.Id = mc.Id
LEFT OUTER JOIN MediaItems mi ON (tmc.SeasonNumber IS NULL OR mi.Metadata_SeasonNumber = tmc.SeasonNumber)
AND mi.Metadata_Title = tmc.ShowTitle
WHERE mc.Name LIKE {0}
GROUP BY mc.Id, mc.Name",
$"%{searchString}%").ToListAsync();
public Task<Option<List<MediaItem>>> GetItems(int id) =>
Get(id).MapT(
collection => collection switch
{
SimpleMediaCollection s => SimpleItems(s),
TelevisionMediaCollection t => TelevisionItems(t)
}).Bind(x => x.Sequence());
public Task<Option<List<MediaItem>>> GetSimpleMediaCollectionItems(int id) =>
GetSimpleMediaCollection(id).MapT(SimpleItems).Bind(x => x.Sequence());
public Task<Option<List<MediaItem>>> GetTelevisionMediaCollectionItems(int id) =>
GetTelevisionMediaCollection(id).MapT(TelevisionItems).Bind(x => x.Sequence());
public Task Update(SimpleMediaCollection collection)
{
_dbContext.SimpleMediaCollections.Update(collection);
return _dbContext.SaveChangesAsync();
}
public async Task InsertOrIgnore(TelevisionMediaCollection collection)
{
if (!_dbContext.TelevisionMediaCollections.Any(
existing => existing.ShowTitle == collection.ShowTitle &&
existing.SeasonNumber == collection.SeasonNumber))
{
await _dbContext.TelevisionMediaCollections.AddAsync(collection);
await _dbContext.SaveChangesAsync();
}
}
public Task<Unit> ReplaceItems(int collectionId, List<MediaItem> mediaItems) =>
GetSimpleMediaCollection(collectionId).IfSomeAsync(
async c =>
{
await SimpleItems(c);
c.Items.Clear();
foreach (MediaItem mediaItem in mediaItems)
{
c.Items.Add(mediaItem);
}
_dbContext.SimpleMediaCollections.Update(c);
await _dbContext.SaveChangesAsync();
});
public async Task Delete(int mediaCollectionId)
{
MediaCollection mediaCollection = await _dbContext.MediaCollections.FindAsync(mediaCollectionId);
_dbContext.MediaCollections.Remove(mediaCollection);
await _dbContext.SaveChangesAsync();
}
public async Task DeleteEmptyTelevisionCollections()
{
List<int> ids = await _dbContext.GenericIntegerIds.FromSqlRaw(
@"SELECT mc.Id FROM MediaCollections mc
INNER JOIN TelevisionMediaCollections t on mc.Id = t.Id
WHERE NOT EXISTS
(SELECT 1 FROM MediaItems mi WHERE t.ShowTitle = mi.Metadata_Title AND (t.SeasonNumber IS NULL OR t.SeasonNumber = mi.Metadata_SeasonNumber))")
.Map(i => i.Id)
.ToListAsync();
List<MediaCollection> toDelete =
await _dbContext.MediaCollections.Where(mc => ids.Contains(mc.Id)).ToListAsync();
_dbContext.MediaCollections.RemoveRange(toDelete);
await _dbContext.SaveChangesAsync();
}
private async Task<List<MediaItem>> SimpleItems(SimpleMediaCollection collection)
{
await _dbContext.Entry(collection).Collection(c => c.Items).LoadAsync();
return collection.Items.ToList();
}
private Task<List<MediaItem>> TelevisionItems(TelevisionMediaCollection collection) =>
_dbContext.MediaItems
.Filter(c => c.Metadata.Title == collection.ShowTitle)
.Filter(c => collection.SeasonNumber == null || c.Metadata.SeasonNumber == collection.SeasonNumber)
.ToListAsync();
}
}
@@ -0,0 +1,53 @@
using System.Collections.Generic;
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 MediaItemRepository : IMediaItemRepository
{
private readonly TvContext _dbContext;
public MediaItemRepository(TvContext dbContext) => _dbContext = dbContext;
public async Task<int> Add(MediaItem mediaItem)
{
await _dbContext.MediaItems.AddAsync(mediaItem);
await _dbContext.SaveChangesAsync();
return mediaItem.Id;
}
public Task<Option<MediaItem>> Get(int id) =>
_dbContext.MediaItems.SingleOrDefaultAsync(i => i.Id == id).Map(Optional);
public Task<List<MediaItem>> GetAll() => _dbContext.MediaItems.ToListAsync();
public Task<List<MediaItem>> GetAll(MediaType mediaType) =>
_dbContext.MediaItems
.Include(i => i.Source)
.Filter(i => i.Metadata.MediaType == mediaType)
.ToListAsync();
public Task<List<MediaItem>> GetAllByMediaSourceId(int mediaSourceId) =>
_dbContext.MediaItems
.Filter(i => i.MediaSourceId == mediaSourceId)
.ToListAsync();
public async Task Update(MediaItem mediaItem)
{
_dbContext.MediaItems.Update(mediaItem);
await _dbContext.SaveChangesAsync();
}
public async Task Delete(int mediaItemId)
{
MediaItem mediaItem = await _dbContext.MediaItems.FindAsync(mediaItemId);
_dbContext.MediaItems.Remove(mediaItem);
await _dbContext.SaveChangesAsync();
}
}
}
@@ -0,0 +1,76 @@
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 MediaSourceRepository : IMediaSourceRepository
{
private readonly TvContext _dbContext;
public MediaSourceRepository(TvContext dbContext) => _dbContext = dbContext;
public async Task<LocalMediaSource> Add(LocalMediaSource localMediaSource)
{
await _dbContext.LocalMediaSources.AddAsync(localMediaSource);
await _dbContext.SaveChangesAsync();
return localMediaSource;
}
public async Task<PlexMediaSource> Add(PlexMediaSource plexMediaSource)
{
await _dbContext.PlexMediaSources.AddAsync(plexMediaSource);
await _dbContext.SaveChangesAsync();
return plexMediaSource;
}
public async Task<List<MediaSource>> GetAll()
{
List<MediaSource> all = await _dbContext.MediaSources.ToListAsync();
foreach (PlexMediaSource plex in all.OfType<PlexMediaSource>())
{
await _dbContext.Entry(plex).Collection(p => p.Connections).LoadAsync();
}
return all;
}
public Task<List<PlexMediaSource>> GetAllPlex() =>
_dbContext.PlexMediaSources
.Include(p => p.Connections)
.ToListAsync();
public Task<Option<MediaSource>> Get(int id) =>
_dbContext.MediaSources
.SingleOrDefaultAsync(s => s.Id == id)
.Map(Optional);
public Task<Option<PlexMediaSource>> GetPlex(int id) =>
_dbContext.PlexMediaSources
.Include(p => p.Connections)
.Include(p => p.Libraries)
.SingleOrDefaultAsync(p => p.Id == id)
.Map(Optional);
public Task<int> CountMediaItems(int id) =>
_dbContext.MediaItems.CountAsync(i => i.MediaSourceId == id);
public async Task Update(PlexMediaSource plexMediaSource)
{
_dbContext.PlexMediaSources.Update(plexMediaSource);
await _dbContext.SaveChangesAsync();
}
public async Task Delete(int id)
{
MediaSource mediaSource = await _dbContext.MediaSources.FindAsync(id);
_dbContext.MediaSources.Remove(mediaSource);
await _dbContext.SaveChangesAsync();
}
}
}
@@ -0,0 +1,99 @@
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.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using static LanguageExt.Prelude;
namespace ErsatzTV.Infrastructure.Data.Repositories
{
public class PlayoutRepository : IPlayoutRepository
{
private readonly TvContext _dbContext;
public PlayoutRepository(TvContext dbContext) => _dbContext = dbContext;
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.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)
.Include(p => p.ProgramSchedule)
.ThenInclude(ps => ps.Items)
.ThenInclude(psi => psi.MediaCollection)
.OrderBy(p => p.Id) // https://github.com/dotnet/efcore/issues/22579#issuecomment-694772289
.SingleOrDefaultAsync(p => p.Id == id);
public async Task<Option<PlayoutItem>> GetPlayoutItem(int channelId, DateTimeOffset now)
{
var p1 = new SqliteParameter("channelId", channelId);
var p2 = new SqliteParameter("now", now);
return await _dbContext.PlayoutItems
.FromSqlRaw(
"select i.* from playoutitems i inner join playouts p on i.playoutid = p.id where p.channelid = @channelId and i.start <= @now and i.finish > @now",
p1,
p2)
.Include(i => i.MediaItem)
.AsNoTracking()
.SingleOrDefaultAsync();
}
public Task<List<PlayoutItem>> GetPlayoutItems(int playoutId) =>
_dbContext.PlayoutItems
.Include(i => i.MediaItem)
.ThenInclude(mi => mi.Metadata)
.Filter(i => i.PlayoutId == playoutId)
.ToListAsync();
public Task<List<int>> GetPlayoutIdsForMediaItems(Seq<MediaItem> mediaItems)
{
var ids = string.Join(", ", mediaItems.Map(mi => mi.Id));
return _dbContext.Playouts.FromSqlRaw(
@"SELECT DISTINCT p.* FROM Playouts p
INNER JOIN ProgramScheduleItems psi on psi.ProgramScheduleId = p.ProgramScheduleId
INNER JOIN SimpleMediaCollections smc on smc.Id = psi.MediaCollectionId
INNER JOIN MediaItemSimpleMediaCollection mismc on mismc.SimpleMediaCollectionsId = smc.Id
WHERE mismc.ItemsId in ({0})
UNION
SELECT DISTINCT p.* FROM Playouts p
INNER JOIN ProgramScheduleItems psi on psi.ProgramScheduleId = p.ProgramScheduleId
INNER JOIN TelevisionMediaCollections tmc on tmc.Id = psi.MediaCollectionId
INNER JOIN MediaItems mi on mi.Metadata_Title = tmc.ShowTitle and (tmc.SeasonNumber is null or tmc.SeasonNumber = mi.Metadata_SeasonNumber)
WHERE mi.Id in ({0})",
ids).Select(p => p.Id).ToListAsync();
}
public Task<List<Playout>> GetAll() =>
_dbContext.Playouts
.Include(p => p.Channel)
.Include(p => p.ProgramSchedule)
.ToListAsync();
public async Task Update(Playout playout)
{
_dbContext.Playouts.Update(playout);
await _dbContext.SaveChangesAsync();
}
public async Task Delete(int playoutId)
{
Playout playout = await _dbContext.Playouts.FindAsync(playoutId);
_dbContext.Playouts.Remove(playout);
await _dbContext.SaveChangesAsync();
}
}
}
@@ -0,0 +1,65 @@
using System.Collections.Generic;
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.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)
.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.MediaCollection)
.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.MediaCollection).LoadAsync();
return programSchedule.Items;
}).Sequence();
}
}
}
@@ -0,0 +1,23 @@
using System.Collections.Generic;
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.SingleOrDefaultAsync(r => r.Id == id).Map(Optional);
public Task<List<Resolution>> GetAll() =>
_dbContext.Resolutions.ToListAsync();
}
}
+45
View File
@@ -0,0 +1,45 @@
using ErsatzTV.Core.AggregateModels;
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.Infrastructure.Data
{
public class TvContext : DbContext
{
private readonly ILoggerFactory _loggerFactory;
public TvContext(DbContextOptions<TvContext> options, ILoggerFactory loggerFactory)
: base(options) =>
_loggerFactory = loggerFactory;
public DbSet<ConfigElement> ConfigElements { get; set; }
public DbSet<Channel> Channels { get; set; }
public DbSet<MediaSource> MediaSources { get; set; }
public DbSet<LocalMediaSource> LocalMediaSources { get; set; }
public DbSet<PlexMediaSource> PlexMediaSources { get; set; }
public DbSet<MediaItem> MediaItems { get; set; }
public DbSet<MediaCollection> MediaCollections { get; set; }
public DbSet<SimpleMediaCollection> SimpleMediaCollections { get; set; }
public DbSet<TelevisionMediaCollection> TelevisionMediaCollections { get; set; }
public DbSet<ProgramSchedule> ProgramSchedules { get; set; }
public DbSet<Playout> Playouts { get; set; }
public DbSet<PlayoutItem> PlayoutItems { get; set; }
public DbSet<PlayoutProgramScheduleAnchor> PlayoutProgramScheduleItemAnchors { get; set; }
public DbSet<FFmpegProfile> FFmpegProfiles { get; set; }
public DbSet<Resolution> Resolutions { get; set; }
// support raw sql queries
public DbSet<MediaCollectionSummary> MediaCollectionSummaries { get; set; }
public DbSet<GenericIntegerId> GenericIntegerIds { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
optionsBuilder.UseLoggerFactory(_loggerFactory);
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ApplyConfigurationsFromAssembly(typeof(TvContext).Assembly);
}
}
}