Database redesign (#31)
* starting database redesign * set season and episode numbers * use datetimes in db (utc); update movie metadata * get movie cards from new table * copy show/episode metadata * remove old movie metadata type * rename new movie metadata type * code cleanup * start to remove old television classes * remove old television tables from database * fix playout building * fix collection views * fix show/season views * clean up movie metadata table * fix scanner tests * add libraries ui * code cleanup * fix movie scanning/metadata * add library scan button to ui * delete library path from ui * temp disable movie scanning * remove orphan media items and prevent duplicate paths * attach artwork to metadata * fix split show/season display * fix television artwork * store year distinct from release date * fix collections ui * code cleanup * add library paths from ui * fix adding to collections from ui * fix schedule items loading * schedule editing works again * remove some todos * more cleanup * fix unit tests * fix episode sorting * fix deleting show library paths * remove unused class * fix playout list in ui * fix log viewer * start to use version/file instead of statistics * clean up old columns * fix playout display (time zone) * fix playback * fix channel guide time zone * cascade more deletes * fix compiler warnings * fix adding new seasons * use artwork for channel logo * clean cache folder on startup (move channel logos, delete everything else) * log database migration * update homepage docs for libraries * fix adding new channel with logo * fix episode numbers in epg
This commit is contained in:
@@ -8,6 +8,8 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Channel> builder)
|
||||
{
|
||||
builder.ToTable("Channel");
|
||||
|
||||
builder.HasIndex(c => c.Number)
|
||||
.IsUnique();
|
||||
|
||||
@@ -15,6 +17,10 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
.WithOne(p => p.Channel)
|
||||
.HasForeignKey(p => p.ChannelId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasMany(c => c.Artwork)
|
||||
.WithOne()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class CollectionConfiguration : IEntityTypeConfiguration<Collection>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Collection> builder)
|
||||
{
|
||||
builder.ToTable("Collection");
|
||||
|
||||
builder.HasMany(c => c.MediaItems)
|
||||
.WithMany(m => m.Collections)
|
||||
.UsingEntity<CollectionItem>(
|
||||
j => j.HasOne(ci => ci.MediaItem)
|
||||
.WithMany(mi => mi.CollectionItems)
|
||||
.HasForeignKey(ci => ci.MediaItemId)
|
||||
.OnDelete(DeleteBehavior.Cascade),
|
||||
j => j.HasOne(ci => ci.Collection)
|
||||
.WithMany(c => c.CollectionItems)
|
||||
.HasForeignKey(ci => ci.CollectionId)
|
||||
.OnDelete(DeleteBehavior.Cascade),
|
||||
j => j.HasKey(ci => new { ci.CollectionId, ci.MediaItemId }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,12 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class ConfigElementConfiguration : IEntityTypeConfiguration<ConfigElement>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ConfigElement> builder) =>
|
||||
public void Configure(EntityTypeBuilder<ConfigElement> builder)
|
||||
{
|
||||
builder.ToTable("ConfigElement");
|
||||
|
||||
builder.HasIndex(ce => ce.Key)
|
||||
.IsUnique();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class FFmpegProfileConfiguration : IEntityTypeConfiguration<FFmpegProfile>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<FFmpegProfile> builder) => builder.ToTable("FFmpegProfile");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class LibraryConfiguration : IEntityTypeConfiguration<Library>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Library> builder) =>
|
||||
builder.ToTable("Library");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class LibraryPathConfiguration : IEntityTypeConfiguration<LibraryPath>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<LibraryPath> builder)
|
||||
{
|
||||
builder.ToTable("LibraryPath");
|
||||
|
||||
builder.HasMany(p => p.MediaItems)
|
||||
.WithOne(i => i.LibraryPath)
|
||||
.HasForeignKey(i => i.LibraryPathId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class LocalLibraryConfiguration : IEntityTypeConfiguration<LocalLibrary>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<LocalLibrary> builder) =>
|
||||
builder.ToTable("LocalLibrary");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class PlexLibraryConfiguration : IEntityTypeConfiguration<PlexLibrary>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PlexLibrary> builder) =>
|
||||
builder.ToTable("PlexLibrary");
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
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,23 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class EpisodeConfiguration : IEntityTypeConfiguration<Episode>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Episode> builder)
|
||||
{
|
||||
builder.ToTable("Episode");
|
||||
|
||||
builder.HasMany(e => e.EpisodeMetadata)
|
||||
.WithOne(m => m.Episode)
|
||||
.HasForeignKey(m => m.EpisodeId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasMany(e => e.MediaVersions)
|
||||
.WithOne()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class MediaFileConfiguration : IEntityTypeConfiguration<MediaFile>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<MediaFile> builder)
|
||||
{
|
||||
builder.ToTable("MediaFile");
|
||||
|
||||
builder.HasIndex(f => f.Path)
|
||||
.IsUnique();
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-5
@@ -6,10 +6,6 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class MediaItemConfiguration : IEntityTypeConfiguration<MediaItem>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<MediaItem> builder)
|
||||
{
|
||||
builder.ToTable("MediaItems");
|
||||
builder.OwnsOne(c => c.Statistics).WithOwner();
|
||||
}
|
||||
public void Configure(EntityTypeBuilder<MediaItem> builder) => builder.ToTable("MediaItem");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class MediaVersionConfiguration : IEntityTypeConfiguration<MediaVersion>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<MediaVersion> builder)
|
||||
{
|
||||
builder.ToTable("MediaVersion");
|
||||
|
||||
builder.HasMany(v => v.MediaFiles)
|
||||
.WithOne(f => f.MediaVersion)
|
||||
.HasForeignKey(f => f.MediaVersionId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class MovieConfiguration : IEntityTypeConfiguration<Movie>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Movie> builder)
|
||||
{
|
||||
builder.ToTable("Movie");
|
||||
|
||||
builder.HasMany(m => m.MovieMetadata)
|
||||
.WithOne(m => m.Movie)
|
||||
.HasForeignKey(m => m.MovieId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasMany(m => m.MediaVersions)
|
||||
.WithOne()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class PlexMovieMediaItemConfiguration : IEntityTypeConfiguration<PlexMovie>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PlexMovie> builder) => builder.ToTable("PlexMovie");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class SeasonConfiguration : IEntityTypeConfiguration<Season>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Season> builder)
|
||||
{
|
||||
builder.ToTable("Season");
|
||||
|
||||
builder.HasMany(s => s.Episodes)
|
||||
.WithOne(e => e.Season)
|
||||
.HasForeignKey(e => e.SeasonId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasMany(s => s.SeasonMetadata)
|
||||
.WithOne(s => s.Season)
|
||||
.HasForeignKey(s => s.SeasonId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class ShowConfiguration : IEntityTypeConfiguration<Show>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Show> builder)
|
||||
{
|
||||
builder.ToTable("Show");
|
||||
|
||||
builder.HasMany(s => s.Seasons)
|
||||
.WithOne(s => s.Show)
|
||||
.HasForeignKey(s => s.ShowId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasMany(s => s.ShowMetadata)
|
||||
.WithOne(s => s.Show)
|
||||
.HasForeignKey(s => s.ShowId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -7,6 +7,6 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
public class LocalMediaSourceConfiguration : IEntityTypeConfiguration<LocalMediaSource>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<LocalMediaSource> builder) =>
|
||||
builder.ToTable("LocalMediaSources");
|
||||
builder.ToTable("LocalMediaSource");
|
||||
}
|
||||
}
|
||||
+9
-2
@@ -6,7 +6,14 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class MediaSourceConfiguration : IEntityTypeConfiguration<MediaSource>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<MediaSource> builder) =>
|
||||
builder.HasIndex(ms => ms.Name).IsUnique();
|
||||
public void Configure(EntityTypeBuilder<MediaSource> builder)
|
||||
{
|
||||
builder.ToTable("MediaSource");
|
||||
|
||||
builder.HasMany(s => s.Libraries)
|
||||
.WithOne(l => l.MediaSource)
|
||||
.HasForeignKey(l => l.MediaSourceId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class PlexConnectionConfiguration : IEntityTypeConfiguration<PlexConnection>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PlexConnection> builder) =>
|
||||
builder.ToTable("PlexConnection");
|
||||
}
|
||||
}
|
||||
+9
-1
@@ -6,6 +6,14 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class PlexMediaSourceConfiguration : IEntityTypeConfiguration<PlexMediaSource>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PlexMediaSource> builder) => builder.ToTable("PlexMediaSources");
|
||||
public void Configure(EntityTypeBuilder<PlexMediaSource> builder)
|
||||
{
|
||||
builder.ToTable("PlexMediaSource");
|
||||
|
||||
builder.HasMany(s => s.Connections)
|
||||
.WithOne(c => c.PlexMediaSource)
|
||||
.HasForeignKey(c => c.PlexMediaSourceId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class ArtworkConfiguration : IEntityTypeConfiguration<Artwork>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Artwork> builder) => builder.ToTable("Artwork");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class EpisodeMetadataConfiguration : IEntityTypeConfiguration<EpisodeMetadata>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<EpisodeMetadata> builder)
|
||||
{
|
||||
builder.ToTable("EpisodeMetadata");
|
||||
|
||||
builder.HasMany(em => em.Artwork)
|
||||
.WithOne()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -6,7 +6,13 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class MovieMetadataConfiguration : IEntityTypeConfiguration<MovieMetadata>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<MovieMetadata> builder) =>
|
||||
public void Configure(EntityTypeBuilder<MovieMetadata> builder)
|
||||
{
|
||||
builder.ToTable("MovieMetadata");
|
||||
|
||||
builder.HasMany(mm => mm.Artwork)
|
||||
.WithOne()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class SeasonMetadataConfiguration : IEntityTypeConfiguration<SeasonMetadata>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<SeasonMetadata> builder)
|
||||
{
|
||||
builder.ToTable("SeasonMetadata");
|
||||
|
||||
builder.HasMany(em => em.Artwork)
|
||||
.WithOne()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class ShowMetadataConfiguration : IEntityTypeConfiguration<ShowMetadata>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ShowMetadata> builder)
|
||||
{
|
||||
builder.ToTable("ShowMetadata");
|
||||
|
||||
builder.HasMany(sm => sm.Artwork)
|
||||
.WithOne()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class MovieMediaItemConfiguration : IEntityTypeConfiguration<MovieMediaItem>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<MovieMediaItem> builder)
|
||||
{
|
||||
builder.ToTable("Movies");
|
||||
|
||||
builder.HasOne(i => i.Metadata)
|
||||
.WithOne(m => m.Movie)
|
||||
.HasForeignKey<MovieMetadata>(m => m.MovieId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Playout> builder)
|
||||
{
|
||||
builder.ToTable("Playout");
|
||||
|
||||
builder.HasMany(p => p.Items)
|
||||
.WithOne(pi => pi.Playout)
|
||||
.HasForeignKey(pi => pi.PlayoutId)
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class PlayoutItemConfiguration : IEntityTypeConfiguration<PlayoutItem>
|
||||
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PlayoutItem> builder)
|
||||
{
|
||||
builder.ToTable("PlayoutItem");
|
||||
|
||||
builder.HasOne(pi => pi.MediaItem)
|
||||
.WithMany()
|
||||
.HasForeignKey(pi => pi.MediaItemId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
-1
@@ -6,8 +6,24 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class PlayoutProgramScheduleAnchorConfiguration : IEntityTypeConfiguration<PlayoutProgramScheduleAnchor>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PlayoutProgramScheduleAnchor> builder) =>
|
||||
public void Configure(EntityTypeBuilder<PlayoutProgramScheduleAnchor> builder)
|
||||
{
|
||||
builder.ToTable("PlayoutProgramScheduleAnchor");
|
||||
|
||||
builder.OwnsOne(a => a.EnumeratorState)
|
||||
.WithOwner();
|
||||
|
||||
builder.HasOne(i => i.Collection)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.CollectionId)
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired(false);
|
||||
|
||||
builder.HasOne(i => i.MediaItem)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.MediaItemId)
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ProgramSchedule> builder)
|
||||
{
|
||||
builder.ToTable("ProgramSchedule");
|
||||
|
||||
builder.HasIndex(ps => ps.Name)
|
||||
.IsUnique();
|
||||
|
||||
@@ -18,7 +20,8 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
|
||||
builder.HasMany(ps => ps.Playouts)
|
||||
.WithOne(p => p.ProgramSchedule)
|
||||
.HasForeignKey(p => p.ProgramScheduleId);
|
||||
.HasForeignKey(p => p.ProgramScheduleId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,21 +8,18 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ProgramScheduleItem> builder)
|
||||
{
|
||||
builder.ToTable("ProgramScheduleItems");
|
||||
builder.ToTable("ProgramScheduleItem");
|
||||
|
||||
builder.HasOne(i => i.MediaCollection)
|
||||
builder.HasOne(i => i.Collection)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.MediaCollectionId)
|
||||
.HasForeignKey(i => i.CollectionId)
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired(false);
|
||||
|
||||
builder.HasOne(i => i.TelevisionShow)
|
||||
builder.HasOne(i => i.MediaItem)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.TelevisionShowId)
|
||||
.IsRequired(false);
|
||||
|
||||
builder.HasOne(i => i.TelevisionSeason)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.TelevisionSeasonId)
|
||||
.HasForeignKey(i => i.MediaItemId)
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired(false);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -7,6 +7,6 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
public class ProgramScheduleItemDurationConfiguration : IEntityTypeConfiguration<ProgramScheduleItemDuration>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ProgramScheduleItemDuration> builder) =>
|
||||
builder.ToTable("ProgramScheduleDurationItems");
|
||||
builder.ToTable("ProgramScheduleDurationItem");
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -7,6 +7,6 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
public class ProgramScheduleItemFloodConfiguration : IEntityTypeConfiguration<ProgramScheduleItemFlood>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ProgramScheduleItemFlood> builder) =>
|
||||
builder.ToTable("ProgramScheduleFloodItems");
|
||||
builder.ToTable("ProgramScheduleFloodItem");
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -7,6 +7,6 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
public class ProgramScheduleItemMultipleConfiguration : IEntityTypeConfiguration<ProgramScheduleItemMultiple>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ProgramScheduleItemMultiple> builder) =>
|
||||
builder.ToTable("ProgramScheduleMultipleItems");
|
||||
builder.ToTable("ProgramScheduleMultipleItem");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
public class ProgramScheduleItemOneConfiguration : IEntityTypeConfiguration<ProgramScheduleItemOne>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ProgramScheduleItemOne> builder) =>
|
||||
builder.ToTable("ProgramScheduleOneItems");
|
||||
builder.ToTable("ProgramScheduleOneItem");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class ResolutionConfiguration : IEntityTypeConfiguration<Resolution>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Resolution> builder) => builder.ToTable("Resolution");
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
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(c => c.Movies)
|
||||
.WithMany(m => m.SimpleMediaCollections)
|
||||
.UsingEntity(join => join.ToTable("SimpleMediaCollectionMovies"));
|
||||
|
||||
builder.HasMany(c => c.TelevisionShows)
|
||||
.WithMany(s => s.SimpleMediaCollections)
|
||||
.UsingEntity(join => join.ToTable("SimpleMediaCollectionShows"));
|
||||
|
||||
builder.HasMany(c => c.TelevisionSeasons)
|
||||
.WithMany(s => s.SimpleMediaCollections)
|
||||
.UsingEntity(join => join.ToTable("SimpleMediaCollectionSeasons"));
|
||||
|
||||
builder.HasMany(c => c.TelevisionEpisodes)
|
||||
.WithMany(e => e.SimpleMediaCollections)
|
||||
.UsingEntity(join => join.ToTable("SimpleMediaCollectionEpisodes"));
|
||||
}
|
||||
}
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class TelevisionEpisodeMediaItemConfiguration : IEntityTypeConfiguration<TelevisionEpisodeMediaItem>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TelevisionEpisodeMediaItem> builder)
|
||||
{
|
||||
builder.ToTable("TelevisionEpisodes");
|
||||
|
||||
builder.HasOne(i => i.Metadata)
|
||||
.WithOne(m => m.TelevisionEpisode)
|
||||
.HasForeignKey<TelevisionEpisodeMetadata>(m => m.TelevisionEpisodeId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class TelevisionEpisodeMetadataConfiguration : IEntityTypeConfiguration<TelevisionEpisodeMetadata>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TelevisionEpisodeMetadata> builder) =>
|
||||
builder.ToTable("TelevisionEpisodeMetadata");
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class TelevisionSeasonConfiguration : IEntityTypeConfiguration<TelevisionSeason>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TelevisionSeason> builder)
|
||||
{
|
||||
builder.ToTable("TelevisionSeasons");
|
||||
|
||||
builder.HasMany(season => season.Episodes)
|
||||
.WithOne(episode => episode.Season)
|
||||
.HasForeignKey(episode => episode.SeasonId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class TelevisionShowConfiguration : IEntityTypeConfiguration<TelevisionShow>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TelevisionShow> builder)
|
||||
{
|
||||
builder.ToTable("TelevisionShows");
|
||||
|
||||
builder.HasOne(show => show.Metadata)
|
||||
.WithOne(metadata => metadata.TelevisionShow)
|
||||
.HasForeignKey<TelevisionShowMetadata>(metadata => metadata.TelevisionShowId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasMany(show => show.Seasons)
|
||||
.WithOne(season => season.TelevisionShow);
|
||||
|
||||
builder.HasMany(show => show.Sources)
|
||||
.WithOne(source => source.TelevisionShow)
|
||||
.HasForeignKey(source => source.TelevisionShowId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class TelevisionShowMetadataConfiguration : IEntityTypeConfiguration<TelevisionShowMetadata>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TelevisionShowMetadata> builder) =>
|
||||
builder.ToTable("TelevisionShowMetadata");
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
@@ -22,27 +23,39 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
}
|
||||
|
||||
public Task<Option<Channel>> Get(int id) =>
|
||||
_dbContext.Channels.SingleOrDefaultAsync(c => c.Id == id).Map(Optional);
|
||||
_dbContext.Channels
|
||||
.Include(c => c.Artwork)
|
||||
.OrderBy(c => c.Id)
|
||||
.SingleOrDefaultAsync(c => c.Id == id)
|
||||
.Map(Optional);
|
||||
|
||||
public Task<Option<Channel>> GetByNumber(int number) =>
|
||||
_dbContext.Channels
|
||||
.Include(c => c.FFmpegProfile)
|
||||
.ThenInclude(p => p.Resolution)
|
||||
.OrderBy(c => c.Number)
|
||||
.SingleOrDefaultAsync(c => c.Number == number)
|
||||
.Map(Optional);
|
||||
|
||||
public Task<List<Channel>> GetAll() => _dbContext.Channels.ToListAsync();
|
||||
public Task<List<Channel>> GetAll() =>
|
||||
_dbContext.Channels
|
||||
.Include(c => c.Artwork)
|
||||
.ToListAsync();
|
||||
|
||||
public Task<List<Channel>> GetAllForGuide() =>
|
||||
_dbContext.Channels
|
||||
.Include(c => c.Playouts)
|
||||
.ThenInclude(p => p.Items)
|
||||
.ThenInclude(i => i.MediaItem)
|
||||
.ThenInclude(i => (i as TelevisionEpisodeMediaItem).Metadata)
|
||||
.ThenInclude(i => (i as Episode).EpisodeMetadata)
|
||||
.Include(c => c.Playouts)
|
||||
.ThenInclude(p => p.Items)
|
||||
.ThenInclude(i => i.MediaItem)
|
||||
.ThenInclude(i => (i as MovieMediaItem).Metadata)
|
||||
.ThenInclude(i => (i as Episode).Season)
|
||||
.Include(c => c.Playouts)
|
||||
.ThenInclude(p => p.Items)
|
||||
.ThenInclude(i => i.MediaItem)
|
||||
.ThenInclude(i => (i as Movie).MovieMetadata)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task Update(Channel channel)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
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
|
||||
{
|
||||
@@ -20,8 +22,11 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
return configElement;
|
||||
}
|
||||
|
||||
public async Task<Option<ConfigElement>> Get(ConfigElementKey key) =>
|
||||
await _dbContext.ConfigElements.SingleOrDefaultAsync(ce => ce.Key == key.Key);
|
||||
public Task<Option<ConfigElement>> Get(ConfigElementKey key) =>
|
||||
_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)));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
@@ -23,6 +24,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
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() =>
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
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 LibraryRepository : ILibraryRepository
|
||||
{
|
||||
private readonly IDbConnection _dbConnection;
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
|
||||
public LibraryRepository(IDbContextFactory<TvContext> dbContextFactory, IDbConnection dbConnection)
|
||||
{
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_dbConnection = dbConnection;
|
||||
}
|
||||
|
||||
public async Task<LibraryPath> Add(LibraryPath libraryPath)
|
||||
{
|
||||
await using TvContext context = _dbContextFactory.CreateDbContext();
|
||||
await context.LibraryPaths.AddAsync(libraryPath);
|
||||
await context.SaveChangesAsync();
|
||||
return libraryPath;
|
||||
}
|
||||
|
||||
public Task<Option<Library>> Get(int libraryId)
|
||||
{
|
||||
using TvContext context = _dbContextFactory.CreateDbContext();
|
||||
return context.Libraries
|
||||
.Include(l => l.Paths)
|
||||
.OrderBy(l => l.Id)
|
||||
.SingleOrDefaultAsync(l => l.Id == libraryId)
|
||||
.Map(Optional);
|
||||
}
|
||||
|
||||
public Task<Option<LocalLibrary>> GetLocal(int libraryId)
|
||||
{
|
||||
using TvContext context = _dbContextFactory.CreateDbContext();
|
||||
return context.LocalLibraries
|
||||
.OrderBy(l => l.Id)
|
||||
.SingleOrDefaultAsync(l => l.Id == libraryId)
|
||||
.Map(Optional);
|
||||
}
|
||||
|
||||
public Task<List<LocalLibrary>> GetAllLocal()
|
||||
{
|
||||
using TvContext context = _dbContextFactory.CreateDbContext();
|
||||
return context.LocalLibraries.ToListAsync();
|
||||
}
|
||||
|
||||
public Task<Unit> UpdateLastScan(Library library) => _dbConnection.ExecuteAsync(
|
||||
"UPDATE Library SET LastScan = @LastScan WHERE Id = @Id",
|
||||
new { library.LastScan, library.Id }).ToUnit();
|
||||
|
||||
public Task<List<LibraryPath>> GetLocalPaths(int libraryId)
|
||||
{
|
||||
using TvContext context = _dbContextFactory.CreateDbContext();
|
||||
return context.LocalLibraries
|
||||
.Include(l => l.Paths)
|
||||
.OrderBy(l => l.Id)
|
||||
.SingleOrDefaultAsync(l => l.Id == libraryId)
|
||||
.Map(Optional)
|
||||
.Match(l => l.Paths, () => new List<LibraryPath>());
|
||||
}
|
||||
|
||||
public Task<Option<LibraryPath>> GetPath(int libraryPathId)
|
||||
{
|
||||
using TvContext context = _dbContextFactory.CreateDbContext();
|
||||
return context.LibraryPaths
|
||||
.OrderBy(lp => lp.Id)
|
||||
.SingleOrDefaultAsync(lp => lp.Id == libraryPathId)
|
||||
.Map(Optional);
|
||||
}
|
||||
|
||||
public Task<int> CountMediaItemsByPath(int libraryPathId) =>
|
||||
_dbConnection.QuerySingleAsync<int>(
|
||||
@"SELECT COUNT(*) FROM MediaItem WHERE LibraryPathId = @LibraryPathId",
|
||||
new { LibraryPathId = libraryPathId });
|
||||
|
||||
public async Task DeleteLocalPath(int libraryPathId)
|
||||
{
|
||||
await using TvContext context = _dbContextFactory.CreateDbContext();
|
||||
LibraryPath libraryPath = await context.LibraryPaths.FindAsync(libraryPathId);
|
||||
context.LibraryPaths.Remove(libraryPath);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -23,174 +22,191 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
_dbConnection = dbConnection;
|
||||
}
|
||||
|
||||
public async Task<SimpleMediaCollection> Add(SimpleMediaCollection collection)
|
||||
public async Task<Collection> Add(Collection collection)
|
||||
{
|
||||
await _dbContext.SimpleMediaCollections.AddAsync(collection);
|
||||
await _dbContext.Collections.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) =>
|
||||
_dbContext.SimpleMediaCollections
|
||||
public async Task<Unit> AddMediaItem(int collectionId, int mediaItemId)
|
||||
{
|
||||
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);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return Unit.Default;
|
||||
}
|
||||
|
||||
public Task<Option<Collection>> Get(int id) =>
|
||||
_dbContext.Collections
|
||||
.OrderBy(c => c.Id)
|
||||
.SingleOrDefaultAsync(c => c.Id == id)
|
||||
.Map(Optional);
|
||||
|
||||
public Task<Option<SimpleMediaCollection>> GetSimpleMediaCollectionWithItems(int id) =>
|
||||
_dbContext.SimpleMediaCollections
|
||||
.Include(s => s.Movies)
|
||||
.ThenInclude(m => m.Source)
|
||||
.Include(s => s.TelevisionShows)
|
||||
.ThenInclude(s => s.Metadata)
|
||||
.Include(s => s.TelevisionSeasons)
|
||||
.Include(s => s.TelevisionEpisodes)
|
||||
.ThenInclude(s => s.Metadata)
|
||||
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 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<SimpleMediaCollection>> GetSimpleMediaCollectionWithItemsUntracked(int id) =>
|
||||
_dbContext.SimpleMediaCollections
|
||||
public Task<Option<Collection>> GetCollectionWithItemsUntracked(int id) =>
|
||||
_dbContext.Collections
|
||||
.AsNoTracking()
|
||||
.Include(s => s.Movies)
|
||||
.ThenInclude(i => i.Source)
|
||||
.Include(s => s.Movies)
|
||||
.ThenInclude(m => m.Metadata)
|
||||
.Include(s => s.TelevisionShows)
|
||||
.ThenInclude(s => s.Metadata)
|
||||
.Include(s => s.TelevisionSeasons)
|
||||
.ThenInclude(s => s.TelevisionShow)
|
||||
.ThenInclude(s => s.Metadata)
|
||||
.Include(s => s.TelevisionEpisodes)
|
||||
.ThenInclude(s => s.Metadata)
|
||||
.Include(s => s.TelevisionEpisodes)
|
||||
.ThenInclude(e => e.Season)
|
||||
.ThenInclude(s => s.TelevisionShow)
|
||||
.ThenInclude(s => s.Metadata)
|
||||
.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 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).Season)
|
||||
.ThenInclude(s => s.Show)
|
||||
.ThenInclude(s => s.ShowMetadata)
|
||||
.OrderBy(c => c.Id)
|
||||
.SingleOrDefaultAsync(c => c.Id == id)
|
||||
.Map(Optional);
|
||||
|
||||
public Task<List<SimpleMediaCollection>> GetSimpleMediaCollections() =>
|
||||
_dbContext.SimpleMediaCollections.ToListAsync();
|
||||
|
||||
public Task<List<MediaCollection>> GetAll() =>
|
||||
_dbContext.MediaCollections.ToListAsync();
|
||||
public Task<List<Collection>> GetAll() =>
|
||||
_dbContext.Collections.ToListAsync();
|
||||
|
||||
public Task<Option<List<MediaItem>>> GetItems(int id) =>
|
||||
Get(id).MapT(
|
||||
collection => collection switch
|
||||
{
|
||||
SimpleMediaCollection s => SimpleItems(s),
|
||||
_ => throw new NotSupportedException($"Unsupported collection type {collection.GetType().Name}")
|
||||
}).Bind(x => x.Sequence());
|
||||
Get(id).MapT(GetItemsForCollection).Bind(x => x.Sequence());
|
||||
|
||||
public Task<Option<List<MediaItem>>> GetSimpleMediaCollectionItems(int id) =>
|
||||
GetSimpleMediaCollection(id).MapT(SimpleItems).Bind(x => x.Sequence());
|
||||
|
||||
public Task Update(SimpleMediaCollection collection)
|
||||
public Task Update(Collection collection)
|
||||
{
|
||||
_dbContext.SimpleMediaCollections.Update(collection);
|
||||
_dbContext.Collections.Update(collection);
|
||||
return _dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task Delete(int mediaCollectionId)
|
||||
public async Task Delete(int collectionId)
|
||||
{
|
||||
MediaCollection mediaCollection = await _dbContext.MediaCollections.FindAsync(mediaCollectionId);
|
||||
_dbContext.MediaCollections.Remove(mediaCollection);
|
||||
Collection mediaCollection = await _dbContext.Collections.FindAsync(collectionId);
|
||||
_dbContext.Collections.Remove(mediaCollection);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private async Task<List<MediaItem>> SimpleItems(SimpleMediaCollection collection)
|
||||
private async Task<List<MediaItem>> GetItemsForCollection(Collection collection)
|
||||
{
|
||||
var result = new List<MediaItem>();
|
||||
|
||||
await _dbContext.Entry(collection).Collection(c => c.Movies).LoadAsync();
|
||||
result.AddRange(collection.Movies);
|
||||
|
||||
result.AddRange(await GetTelevisionShowItems(collection));
|
||||
result.AddRange(await GetTelevisionSeasonItems(collection));
|
||||
result.AddRange(await GetTelevisionEpisodeItems(collection));
|
||||
result.AddRange(await GetMovieItems(collection));
|
||||
result.AddRange(await GetShowItems(collection));
|
||||
result.AddRange(await GetSeasonItems(collection));
|
||||
result.AddRange(await GetEpisodeItems(collection));
|
||||
|
||||
return result.Distinct().ToList();
|
||||
}
|
||||
|
||||
private async Task<List<TelevisionEpisodeMediaItem>> GetTelevisionShowItems(SimpleMediaCollection collection)
|
||||
private async Task<List<Movie>> GetMovieItems(Collection collection)
|
||||
{
|
||||
var parameters = new { CollectionId = collection.Id };
|
||||
return await _dbConnection
|
||||
.QueryAsync<TelevisionEpisodeMediaItem, MediaItemStatistics, TelevisionEpisodeMetadata,
|
||||
TelevisionEpisodeMediaItem>(
|
||||
@"select tmi.Id, tmi.SeasonId, mi.MediaSourceId, mi.LastWriteTime, mi.Path, mi.Poster, mi.PosterLastWriteTime,
|
||||
mi.Statistics_AudioCodec as AudioCodec, mi.Statistics_DisplayAspectRatio as DisplayAspectRatio, mi.Statistics_Duration as Duration, mi.Statistics_Height as Height, mi.Statistics_LastWriteTime as LastWriteTime, mi.Statistics_SampleAspectRatio as SampleAspectRatio,
|
||||
mi.Statistics_VideoCodec as VideoCodec, mi.Statistics_VideoScanType as VideoScanType, mi.Statistics_Width as Width,
|
||||
tem.TelevisionEpisodeId, tem.Id, tem.Season, tem.Episode, tem.Plot, tem.Aired, tem.Source, tem.LastWriteTime, tem.Title, tem.SortTitle
|
||||
from TelevisionEpisodes tmi
|
||||
inner join MediaItems mi on tmi.Id = mi.Id
|
||||
inner join TelevisionEpisodeMetadata tem on tem.TelevisionEpisodeId = tmi.Id
|
||||
inner join TelevisionSeasons tsn on tsn.Id = tmi.SeasonId
|
||||
inner join TelevisionShows ts on ts.Id = tsn.TelevisionShowId
|
||||
inner join SimpleMediaCollectionShows s on s.TelevisionShowsId = ts.Id
|
||||
where s.SimpleMediaCollectionsId = @CollectionId",
|
||||
(episode, statistics, metadata) =>
|
||||
{
|
||||
episode.Statistics = statistics;
|
||||
episode.Metadata = metadata;
|
||||
return episode;
|
||||
},
|
||||
parameters,
|
||||
splitOn: "AudioCodec,TelevisionEpisodeId").Map(result => result.ToList());
|
||||
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 });
|
||||
|
||||
return await _dbContext.Movies
|
||||
.Include(m => m.MovieMetadata)
|
||||
.Include(m => m.MediaVersions)
|
||||
.Filter(m => ids.Contains(m.Id))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
private async Task<List<TelevisionEpisodeMediaItem>> GetTelevisionSeasonItems(SimpleMediaCollection collection)
|
||||
private async Task<List<Episode>> GetShowItems(Collection collection)
|
||||
{
|
||||
var parameters = new { CollectionId = collection.Id };
|
||||
return await _dbConnection
|
||||
.QueryAsync<TelevisionEpisodeMediaItem, MediaItemStatistics, TelevisionEpisodeMetadata,
|
||||
TelevisionEpisodeMediaItem>(
|
||||
@"select tmi.Id, tmi.SeasonId, mi.MediaSourceId, mi.LastWriteTime, mi.Path, mi.Poster, mi.PosterLastWriteTime,
|
||||
mi.Statistics_AudioCodec as AudioCodec, mi.Statistics_DisplayAspectRatio as DisplayAspectRatio, mi.Statistics_Duration as Duration, mi.Statistics_Height as Height, mi.Statistics_LastWriteTime as LastWriteTime, mi.Statistics_SampleAspectRatio as SampleAspectRatio,
|
||||
mi.Statistics_VideoCodec as VideoCodec, mi.Statistics_VideoScanType as VideoScanType, mi.Statistics_Width as Width,
|
||||
tem.TelevisionEpisodeId, tem.Id, tem.Season, tem.Episode, tem.Plot, tem.Aired, tem.Source, tem.LastWriteTime, tem.Title, tem.SortTitle
|
||||
from TelevisionEpisodes tmi
|
||||
inner join MediaItems mi on tmi.Id = mi.Id
|
||||
inner join TelevisionEpisodeMetadata tem on tem.TelevisionEpisodeId = tmi.Id
|
||||
inner join TelevisionSeasons tsn on tsn.Id = tmi.SeasonId
|
||||
inner join SimpleMediaCollectionSeasons s on s.TelevisionSeasonsId = tsn.Id
|
||||
where s.SimpleMediaCollectionsId = @CollectionId",
|
||||
(episode, statistics, metadata) =>
|
||||
{
|
||||
episode.Statistics = statistics;
|
||||
episode.Metadata = metadata;
|
||||
return episode;
|
||||
},
|
||||
parameters,
|
||||
splitOn: "AudioCodec,TelevisionEpisodeId").Map(result => result.ToList());
|
||||
IEnumerable<int> ids = await _dbConnection.QueryAsync<int>(
|
||||
@"SELECT Episode.Id FROM CollectionItem ci
|
||||
INNER JOIN Show ON Show.Id = ci.MediaItemId
|
||||
INNER JOIN Season ON Season.ShowId = Show.Id
|
||||
INNER JOIN Episode ON Episode.SeasonId = Season.Id
|
||||
WHERE ci.CollectionId = @CollectionId",
|
||||
new { CollectionId = collection.Id });
|
||||
|
||||
return await _dbContext.Episodes
|
||||
.Include(e => e.EpisodeMetadata)
|
||||
.Include(e => e.MediaVersions)
|
||||
.Filter(e => ids.Contains(e.Id))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
private async Task<List<TelevisionEpisodeMediaItem>> GetTelevisionEpisodeItems(SimpleMediaCollection collection)
|
||||
private async Task<List<Episode>> GetSeasonItems(Collection collection)
|
||||
{
|
||||
var parameters = new { CollectionId = collection.Id };
|
||||
return await _dbConnection
|
||||
.QueryAsync<TelevisionEpisodeMediaItem, MediaItemStatistics, TelevisionEpisodeMetadata,
|
||||
TelevisionEpisodeMediaItem>(
|
||||
@"select tmi.Id, tmi.SeasonId, mi.MediaSourceId, mi.LastWriteTime, mi.Path, mi.Poster, mi.PosterLastWriteTime,
|
||||
mi.Statistics_AudioCodec as AudioCodec, mi.Statistics_DisplayAspectRatio as DisplayAspectRatio, mi.Statistics_Duration as Duration, mi.Statistics_Height as Height, mi.Statistics_LastWriteTime as LastWriteTime, mi.Statistics_SampleAspectRatio as SampleAspectRatio,
|
||||
mi.Statistics_VideoCodec as VideoCodec, mi.Statistics_VideoScanType as VideoScanType, mi.Statistics_Width as Width,
|
||||
tem.TelevisionEpisodeId, tem.Id, tem.Season, tem.Episode, tem.Plot, tem.Aired, tem.Source, tem.LastWriteTime, tem.Title, tem.SortTitle
|
||||
from TelevisionEpisodes tmi
|
||||
inner join MediaItems mi on tmi.Id = mi.Id
|
||||
inner join TelevisionEpisodeMetadata tem on tem.TelevisionEpisodeId = tmi.Id
|
||||
inner join SimpleMediaCollectionEpisodes s on s.TelevisionEpisodesId = tmi.Id
|
||||
where s.SimpleMediaCollectionsId = @CollectionId",
|
||||
(episode, statistics, metadata) =>
|
||||
{
|
||||
episode.Statistics = statistics;
|
||||
episode.Metadata = metadata;
|
||||
return episode;
|
||||
},
|
||||
parameters,
|
||||
splitOn: "AudioCodec,TelevisionEpisodeId").Map(result => result.ToList());
|
||||
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 });
|
||||
|
||||
return await _dbContext.Episodes
|
||||
.Include(e => e.EpisodeMetadata)
|
||||
.Include(e => e.MediaVersions)
|
||||
.Filter(e => ids.Contains(e.Id))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
private async Task<List<Episode>> GetEpisodeItems(Collection collection)
|
||||
{
|
||||
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 });
|
||||
|
||||
return await _dbContext.Episodes
|
||||
.Include(e => e.EpisodeMetadata)
|
||||
.Include(e => e.MediaVersions)
|
||||
.Filter(e => ids.Contains(e.Id))
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,32 +17,33 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
|
||||
public Task<Option<MediaItem>> Get(int id) =>
|
||||
_dbContext.MediaItems
|
||||
.Include(i => i.Source)
|
||||
.Include(i => i.LibraryPath)
|
||||
.OrderBy(i => i.Id)
|
||||
.SingleOrDefaultAsync(i => i.Id == id)
|
||||
.Map(Optional);
|
||||
|
||||
public Task<List<MediaItem>> GetAll() => _dbContext.MediaItems.ToListAsync();
|
||||
|
||||
public Task<List<MediaItem>> Search(string searchString)
|
||||
{
|
||||
IQueryable<TelevisionEpisodeMediaItem> episodeData =
|
||||
from c in _dbContext.TelevisionEpisodeMediaItems.Include(c => c.Source) select c;
|
||||
|
||||
if (!string.IsNullOrEmpty(searchString))
|
||||
{
|
||||
episodeData = episodeData.Where(c => EF.Functions.Like(c.Metadata.Title, $"%{searchString}%"));
|
||||
}
|
||||
|
||||
IQueryable<MovieMediaItem> movieData =
|
||||
from c in _dbContext.MovieMediaItems.Include(c => c.Source) select c;
|
||||
|
||||
if (!string.IsNullOrEmpty(searchString))
|
||||
{
|
||||
movieData = movieData.Where(c => EF.Functions.Like(c.Metadata.Title, $"%{searchString}%"));
|
||||
}
|
||||
|
||||
return episodeData.OfType<MediaItem>().Concat(movieData.OfType<MediaItem>()).ToListAsync();
|
||||
}
|
||||
public Task<List<MediaItem>> Search(string searchString) =>
|
||||
// TODO: fix this when we need to search
|
||||
// IQueryable<TelevisionEpisodeMediaItem> episodeData =
|
||||
// from c in _dbContext.TelevisionEpisodeMediaItems.Include(c => c.LibraryPath) select c;
|
||||
//
|
||||
// if (!string.IsNullOrEmpty(searchString))
|
||||
// {
|
||||
// episodeData = episodeData.Where(c => EF.Functions.Like(c.Metadata.Title, $"%{searchString}%"));
|
||||
// }
|
||||
//
|
||||
// IQueryable<Movie> movieData =
|
||||
// from c in _dbContext.Movies.Include(c => c.LibraryPath) select c;
|
||||
//
|
||||
// // if (!string.IsNullOrEmpty(searchString))
|
||||
// // {
|
||||
// // movieData = movieData.Where(c => EF.Functions.Like(c.Metadata.Title, $"%{searchString}%"));
|
||||
// // }
|
||||
//
|
||||
// return episodeData.OfType<MediaItem>().Concat(movieData.OfType<MediaItem>()).ToListAsync();
|
||||
new List<MediaItem>().AsTask();
|
||||
|
||||
public async Task<bool> Update(MediaItem mediaItem)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
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;
|
||||
@@ -11,70 +13,134 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
{
|
||||
public class MediaSourceRepository : IMediaSourceRepository
|
||||
{
|
||||
private readonly TvContext _dbContext;
|
||||
private readonly IDbConnection _dbConnection;
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
|
||||
public MediaSourceRepository(TvContext dbContext) => _dbContext = dbContext;
|
||||
public MediaSourceRepository(
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
IDbConnection dbConnection)
|
||||
{
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_dbConnection = dbConnection;
|
||||
}
|
||||
|
||||
public async Task<LocalMediaSource> Add(LocalMediaSource localMediaSource)
|
||||
{
|
||||
await _dbContext.LocalMediaSources.AddAsync(localMediaSource);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
await using TvContext context = _dbContextFactory.CreateDbContext();
|
||||
await context.LocalMediaSources.AddAsync(localMediaSource);
|
||||
await context.SaveChangesAsync();
|
||||
return localMediaSource;
|
||||
}
|
||||
|
||||
public async Task<PlexMediaSource> Add(PlexMediaSource plexMediaSource)
|
||||
{
|
||||
await _dbContext.PlexMediaSources.AddAsync(plexMediaSource);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
await using TvContext context = _dbContextFactory.CreateDbContext();
|
||||
await context.PlexMediaSources.AddAsync(plexMediaSource);
|
||||
await context.SaveChangesAsync();
|
||||
return plexMediaSource;
|
||||
}
|
||||
|
||||
public async Task<List<MediaSource>> GetAll()
|
||||
{
|
||||
List<MediaSource> all = await _dbContext.MediaSources.ToListAsync();
|
||||
await using TvContext context = _dbContextFactory.CreateDbContext();
|
||||
List<MediaSource> all = await context.MediaSources.ToListAsync();
|
||||
foreach (PlexMediaSource plex in all.OfType<PlexMediaSource>())
|
||||
{
|
||||
await _dbContext.Entry(plex).Collection(p => p.Connections).LoadAsync();
|
||||
await context.Entry(plex).Collection(p => p.Connections).LoadAsync();
|
||||
}
|
||||
|
||||
return all;
|
||||
}
|
||||
|
||||
public Task<List<PlexMediaSource>> GetAllPlex() =>
|
||||
_dbContext.PlexMediaSources
|
||||
public Task<List<PlexMediaSource>> GetAllPlex()
|
||||
{
|
||||
using TvContext context = _dbContextFactory.CreateDbContext();
|
||||
return context.PlexMediaSources
|
||||
.Include(p => p.Connections)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public Task<Option<MediaSource>> Get(int id) =>
|
||||
_dbContext.MediaSources
|
||||
public Task<List<PlexLibrary>> GetPlexLibraries(int plexMediaSourceId)
|
||||
{
|
||||
using TvContext context = _dbContextFactory.CreateDbContext();
|
||||
return context.PlexLibraries
|
||||
.Filter(l => l.MediaSourceId == plexMediaSourceId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public Task<Option<PlexLibrary>> GetPlexLibrary(int plexLibraryId)
|
||||
{
|
||||
using TvContext context = _dbContextFactory.CreateDbContext();
|
||||
return context.PlexLibraries
|
||||
.OrderBy(l => l.Id) // https://github.com/dotnet/efcore/issues/22579
|
||||
.SingleOrDefaultAsync(l => l.Id == plexLibraryId)
|
||||
.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) =>
|
||||
_dbContext.PlexMediaSources
|
||||
public Task<Option<PlexMediaSource>> GetPlex(int id)
|
||||
{
|
||||
using TvContext context = _dbContextFactory.CreateDbContext();
|
||||
return context.PlexMediaSources
|
||||
.Include(p => p.Connections)
|
||||
.Include(p => p.Libraries)
|
||||
.OrderBy(s => s.Id) // https://github.com/dotnet/efcore/issues/22579
|
||||
.SingleOrDefaultAsync(p => p.Id == id)
|
||||
.Map(Optional);
|
||||
}
|
||||
|
||||
public Task<int> CountMediaItems(int id) =>
|
||||
_dbContext.MediaItems.CountAsync(i => i.MediaSourceId == id);
|
||||
public Task<int> CountMediaItems(int id)
|
||||
{
|
||||
using TvContext context = _dbContextFactory.CreateDbContext();
|
||||
return context.MediaItems
|
||||
.CountAsync(i => i.LibraryPath.Library.MediaSourceId == id);
|
||||
}
|
||||
|
||||
public Task Update(LocalMediaSource localMediaSource) =>
|
||||
_dbContext.LocalMediaSources.Update(localMediaSource)
|
||||
.AsTask()
|
||||
.Bind(_ => _dbContext.SaveChangesAsync());
|
||||
public async Task Update(LocalMediaSource localMediaSource)
|
||||
{
|
||||
await using TvContext context = _dbContextFactory.CreateDbContext();
|
||||
context.LocalMediaSources.Update(localMediaSource);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public Task Update(PlexMediaSource plexMediaSource) =>
|
||||
_dbContext.PlexMediaSources.Update(plexMediaSource)
|
||||
.AsTask()
|
||||
.Bind(_ => _dbContext.SaveChangesAsync());
|
||||
public async Task Update(PlexMediaSource plexMediaSource)
|
||||
{
|
||||
await using TvContext context = _dbContextFactory.CreateDbContext();
|
||||
context.PlexMediaSources.Update(plexMediaSource);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task Update(PlexLibrary plexMediaSourceLibrary)
|
||||
{
|
||||
await using TvContext context = _dbContextFactory.CreateDbContext();
|
||||
context.PlexLibraries.Update(plexMediaSourceLibrary);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task Delete(int id)
|
||||
{
|
||||
MediaSource mediaSource = await _dbContext.MediaSources.FindAsync(id);
|
||||
_dbContext.MediaSources.Remove(mediaSource);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
await using TvContext context = _dbContextFactory.CreateDbContext();
|
||||
MediaSource mediaSource = await context.MediaSources.FindAsync(id);
|
||||
context.MediaSources.Remove(mediaSource);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public Task DisablePlexLibrarySync(IEnumerable<int> libraryIds) =>
|
||||
_dbConnection.ExecuteAsync(
|
||||
"UPDATE PlexMediaSourceLibraries SET ShouldSyncItems = 0 WHERE Id in @ids",
|
||||
new { ids = libraryIds });
|
||||
|
||||
public Task EnablePlexLibrarySync(IEnumerable<int> libraryIds) =>
|
||||
_dbConnection.ExecuteAsync(
|
||||
"UPDATE PlexMediaSourceLibraries SET ShouldSyncItems = 1 WHERE Id in @ids",
|
||||
new { ids = libraryIds });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Dapper;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
@@ -13,54 +15,102 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
{
|
||||
public class MovieRepository : IMovieRepository
|
||||
{
|
||||
private readonly IDbConnection _dbConnection;
|
||||
private readonly TvContext _dbContext;
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
|
||||
public MovieRepository(TvContext dbContext) => _dbContext = dbContext;
|
||||
|
||||
public Task<Option<MovieMediaItem>> GetMovie(int movieId) =>
|
||||
_dbContext.MovieMediaItems
|
||||
.Include(m => m.Metadata)
|
||||
.SingleOrDefaultAsync(m => m.Id == movieId)
|
||||
.Map(Optional);
|
||||
|
||||
public async Task<Either<BaseError, MovieMediaItem>> GetOrAdd(int mediaSourceId, string path)
|
||||
public MovieRepository(
|
||||
TvContext dbContext,
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
IDbConnection dbConnection)
|
||||
{
|
||||
Option<MovieMediaItem> maybeExisting = await _dbContext.MovieMediaItems
|
||||
.Include(i => i.Metadata)
|
||||
.SingleOrDefaultAsync(i => i.Path == path);
|
||||
|
||||
return await maybeExisting.Match(
|
||||
mediaItem => Right<BaseError, MovieMediaItem>(mediaItem).AsTask(),
|
||||
async () => await AddMovie(mediaSourceId, path));
|
||||
_dbContext = dbContext;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_dbConnection = dbConnection;
|
||||
}
|
||||
|
||||
public async Task<bool> Update(MovieMediaItem movie)
|
||||
public async Task<Option<Movie>> GetMovie(int movieId)
|
||||
{
|
||||
_dbContext.MovieMediaItems.Update(movie);
|
||||
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
|
||||
return await dbContext.Movies
|
||||
.Include(m => m.MovieMetadata)
|
||||
.ThenInclude(m => m.Artwork)
|
||||
.OrderBy(m => m.Id)
|
||||
.SingleOrDefaultAsync(m => m.Id == movieId)
|
||||
.Map(Optional);
|
||||
}
|
||||
|
||||
public async Task<Either<BaseError, Movie>> GetOrAdd(LibraryPath libraryPath, string path)
|
||||
{
|
||||
Option<Movie> maybeExisting = await _dbContext.Movies
|
||||
.Include(i => i.MovieMetadata)
|
||||
.ThenInclude(mm => mm.Artwork)
|
||||
.Include(i => i.LibraryPath)
|
||||
.Include(i => i.MediaVersions)
|
||||
.ThenInclude(mv => mv.MediaFiles)
|
||||
.OrderBy(i => i.MediaVersions.First().MediaFiles.First().Path)
|
||||
.SingleOrDefaultAsync(i => i.MediaVersions.First().MediaFiles.First().Path == path);
|
||||
|
||||
return await maybeExisting.Match(
|
||||
mediaItem => Right<BaseError, Movie>(mediaItem).AsTask(),
|
||||
async () => await AddMovie(libraryPath.Id, path));
|
||||
}
|
||||
|
||||
public async Task<Either<BaseError, PlexMovie>> GetOrAdd(PlexLibrary library, PlexMovie item)
|
||||
{
|
||||
Option<PlexMovie> maybeExisting = await _dbContext.PlexMovieMediaItems
|
||||
.Include(i => i.MovieMetadata)
|
||||
.Include(i => i.Part)
|
||||
.OrderBy(i => i.Key)
|
||||
.SingleOrDefaultAsync(i => i.Key == item.Key);
|
||||
|
||||
return await maybeExisting.Match(
|
||||
plexMovie => Right<BaseError, PlexMovie>(plexMovie).AsTask(),
|
||||
async () => await AddPlexMovie(library, item));
|
||||
}
|
||||
|
||||
public async Task<bool> Update(Movie movie)
|
||||
{
|
||||
_dbContext.Movies.Update(movie);
|
||||
return await _dbContext.SaveChangesAsync() > 0;
|
||||
}
|
||||
|
||||
public Task<int> GetMovieCount() =>
|
||||
_dbContext.MovieMediaItems
|
||||
.AsNoTracking()
|
||||
.CountAsync();
|
||||
_dbConnection.QuerySingleAsync<int>(@"SELECT COUNT(DISTINCT MovieId) FROM MovieMetadata");
|
||||
|
||||
public Task<List<MovieMediaItem>> GetPagedMovies(int pageNumber, int pageSize) =>
|
||||
_dbContext.MovieMediaItems
|
||||
.Include(s => s.Metadata)
|
||||
.OrderBy(s => s.Metadata.SortTitle)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.AsNoTracking()
|
||||
public Task<List<MovieMetadata>> GetPagedMovies(int pageNumber, int pageSize) =>
|
||||
_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)
|
||||
.Include(mm => mm.Artwork)
|
||||
.OrderBy(mm => mm.SortTitle)
|
||||
.ToListAsync();
|
||||
|
||||
private async Task<Either<BaseError, MovieMediaItem>> AddMovie(int mediaSourceId, string path)
|
||||
private async Task<Either<BaseError, Movie>> AddMovie(int libraryPathId, string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var movie = new MovieMediaItem { MediaSourceId = mediaSourceId, Path = path };
|
||||
await _dbContext.MovieMediaItems.AddAsync(movie);
|
||||
var movie = new Movie
|
||||
{
|
||||
LibraryPathId = libraryPathId,
|
||||
MediaVersions = new List<MediaVersion>
|
||||
{
|
||||
new()
|
||||
{
|
||||
MediaFiles = new List<MediaFile>
|
||||
{
|
||||
new() { Path = path }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
await _dbContext.Movies.AddAsync(movie);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
await _dbContext.Entry(movie).Reference(m => m.LibraryPath).LoadAsync();
|
||||
return movie;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -68,5 +118,24 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
return BaseError.New(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Either<BaseError, PlexMovie>> AddPlexMovie(
|
||||
PlexLibrary library,
|
||||
PlexMovie item)
|
||||
{
|
||||
try
|
||||
{
|
||||
item.LibraryPathId = library.Paths.Head().Id;
|
||||
|
||||
await _dbContext.PlexMovieMediaItems.AddAsync(item);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
await _dbContext.Entry(item).Reference(i => i.LibraryPath).LoadAsync();
|
||||
return item;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BaseError.New(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ 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;
|
||||
|
||||
@@ -25,63 +24,57 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
}
|
||||
|
||||
public Task<Option<Playout>> Get(int id) =>
|
||||
_dbContext.Playouts.SingleOrDefaultAsync(p => p.Id == id).Map(Optional);
|
||||
_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.MediaCollection)
|
||||
.ThenInclude(psi => psi.Collection)
|
||||
.Include(p => p.ProgramSchedule)
|
||||
.ThenInclude(ps => ps.Items)
|
||||
.ThenInclude(psi => psi.TelevisionShow)
|
||||
.ThenInclude(psi => psi.MediaItem)
|
||||
.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)
|
||||
public async Task<Option<PlayoutItem>> GetPlayoutItem(int channelId, DateTimeOffset now) =>
|
||||
await _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 Movie).MediaVersions)
|
||||
.ThenInclude(mv => mv.MediaFiles)
|
||||
.AsNoTracking()
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public Task<List<PlayoutItem>> GetPlayoutItems(int playoutId) =>
|
||||
_dbContext.PlayoutItems
|
||||
.Include(i => i.MediaItem)
|
||||
.ThenInclude(m => (m as MovieMediaItem).Metadata)
|
||||
.ThenInclude(mi => (mi as Movie).MovieMetadata)
|
||||
.ThenInclude(mm => mm.Artwork)
|
||||
.Include(i => i.MediaItem)
|
||||
.ThenInclude(m => (m as TelevisionEpisodeMediaItem).Metadata)
|
||||
.ThenInclude(mi => (mi as Movie).MediaVersions)
|
||||
.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)
|
||||
.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)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
@@ -22,12 +23,16 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
}
|
||||
|
||||
public Task<Option<ProgramSchedule>> Get(int id) =>
|
||||
_dbContext.ProgramSchedules.SingleOrDefaultAsync(s => s.Id == id).Map(Optional);
|
||||
_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() =>
|
||||
@@ -37,7 +42,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
{
|
||||
_dbContext.ProgramSchedules.Update(programSchedule);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
await _dbContext.Entry(programSchedule).Collection(s => s.Items).Query().Include(i => i.MediaCollection)
|
||||
await _dbContext.Entry(programSchedule).Collection(s => s.Items).Query().Include(i => i.Collection)
|
||||
.LoadAsync();
|
||||
await _dbContext.Entry(programSchedule).Collection(s => s.Playouts).LoadAsync();
|
||||
}
|
||||
@@ -57,12 +62,20 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
{
|
||||
await _dbContext.Entry(programSchedule).Collection(s => s.Items).LoadAsync();
|
||||
await _dbContext.Entry(programSchedule).Collection(s => s.Items).Query()
|
||||
.Include(i => i.MediaCollection)
|
||||
.Include(i => i.TelevisionShow)
|
||||
.ThenInclude(s => s.Metadata)
|
||||
.Include(i => i.TelevisionSeason)
|
||||
.ThenInclude(s => s.TelevisionShow)
|
||||
.ThenInclude(s => s.Metadata)
|
||||
.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)
|
||||
.LoadAsync();
|
||||
return programSchedule.Items;
|
||||
}).Sequence();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
@@ -15,7 +16,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
public ResolutionRepository(TvContext dbContext) => _dbContext = dbContext;
|
||||
|
||||
public Task<Option<Resolution>> Get(int id) =>
|
||||
_dbContext.Resolutions.SingleOrDefaultAsync(r => r.Id == id).Map(Optional);
|
||||
_dbContext.Resolutions
|
||||
.OrderBy(r => r.Id)
|
||||
.SingleOrDefaultAsync(r => r.Id == id)
|
||||
.Map(Optional);
|
||||
|
||||
public Task<List<Resolution>> GetAll() =>
|
||||
_dbContext.Resolutions.ToListAsync();
|
||||
|
||||
@@ -24,170 +24,172 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
_dbConnection = dbConnection;
|
||||
}
|
||||
|
||||
public async Task<bool> Update(TelevisionShow show)
|
||||
public async Task<bool> Update(Show show)
|
||||
{
|
||||
_dbContext.TelevisionShows.Update(show);
|
||||
_dbContext.Shows.Update(show);
|
||||
return await _dbContext.SaveChangesAsync() > 0;
|
||||
}
|
||||
|
||||
public async Task<bool> Update(TelevisionSeason season)
|
||||
public async Task<bool> Update(Season season)
|
||||
{
|
||||
_dbContext.TelevisionSeasons.Update(season);
|
||||
_dbContext.Seasons.Update(season);
|
||||
return await _dbContext.SaveChangesAsync() > 0;
|
||||
}
|
||||
|
||||
public async Task<bool> Update(TelevisionEpisodeMediaItem episode)
|
||||
public async Task<bool> Update(Episode episode)
|
||||
{
|
||||
_dbContext.TelevisionEpisodeMediaItems.Update(episode);
|
||||
_dbContext.Episodes.Update(episode);
|
||||
return await _dbContext.SaveChangesAsync() > 0;
|
||||
}
|
||||
|
||||
public Task<List<TelevisionShow>> GetAllShows() =>
|
||||
_dbContext.TelevisionShows
|
||||
public Task<List<Show>> GetAllShows() =>
|
||||
_dbContext.Shows
|
||||
.AsNoTracking()
|
||||
.Include(s => s.Metadata)
|
||||
.Include(s => s.ShowMetadata)
|
||||
.ThenInclude(sm => sm.Artwork)
|
||||
.ToListAsync();
|
||||
|
||||
public Task<Option<TelevisionShow>> GetShow(int televisionShowId) =>
|
||||
_dbContext.TelevisionShows
|
||||
public Task<Option<Show>> GetShow(int showId) =>
|
||||
_dbContext.Shows
|
||||
.AsNoTracking()
|
||||
.Filter(s => s.Id == televisionShowId)
|
||||
.Include(s => s.Metadata)
|
||||
.Filter(s => s.Id == showId)
|
||||
.Include(s => s.ShowMetadata)
|
||||
.ThenInclude(sm => sm.Artwork)
|
||||
.OrderBy(s => s.Id)
|
||||
.SingleOrDefaultAsync()
|
||||
.Map(Optional);
|
||||
|
||||
public Task<int> GetShowCount() =>
|
||||
_dbContext.TelevisionShows
|
||||
_dbContext.ShowMetadata
|
||||
.AsNoTracking()
|
||||
.GroupBy(sm => new { sm.Title, sm.Year })
|
||||
.CountAsync();
|
||||
|
||||
public Task<List<TelevisionShow>> GetPagedShows(int pageNumber, int pageSize) =>
|
||||
_dbContext.TelevisionShows
|
||||
public Task<List<ShowMetadata>> GetPagedShows(int pageNumber, int pageSize) =>
|
||||
_dbContext.ShowMetadata.FromSqlRaw(
|
||||
@"SELECT * FROM ShowMetadata WHERE Id IN
|
||||
(SELECT MIN(Id) FROM ShowMetadata GROUP BY Title, Year, MetadataKind HAVING MetadataKind = MAX(MetadataKind))
|
||||
ORDER BY SortTitle
|
||||
LIMIT {0} OFFSET {1}",
|
||||
pageSize,
|
||||
(pageNumber - 1) * pageSize)
|
||||
.Include(mm => mm.Artwork)
|
||||
.OrderBy(mm => mm.SortTitle)
|
||||
.ToListAsync();
|
||||
|
||||
public Task<List<Season>> GetAllSeasons() =>
|
||||
_dbContext.Seasons
|
||||
.AsNoTracking()
|
||||
.Include(s => s.Metadata)
|
||||
.OrderBy(s => s.Metadata == null ? string.Empty : s.Metadata.SortTitle)
|
||||
.Include(s => s.SeasonMetadata)
|
||||
.ThenInclude(sm => sm.Artwork)
|
||||
.Include(s => s.Show)
|
||||
.ThenInclude(s => s.ShowMetadata)
|
||||
.ThenInclude(sm => sm.Artwork)
|
||||
.ToListAsync();
|
||||
|
||||
public Task<Option<Season>> GetSeason(int seasonId) =>
|
||||
_dbContext.Seasons
|
||||
.AsNoTracking()
|
||||
.Include(s => s.SeasonMetadata)
|
||||
.ThenInclude(sm => sm.Artwork)
|
||||
.Include(s => s.Show)
|
||||
.ThenInclude(s => s.ShowMetadata)
|
||||
.OrderBy(s => s.Id)
|
||||
.SingleOrDefaultAsync(s => s.Id == seasonId)
|
||||
.Map(Optional);
|
||||
|
||||
public Task<int> GetSeasonCount(int showId) =>
|
||||
_dbContext.Seasons
|
||||
.AsNoTracking()
|
||||
.CountAsync(s => s.ShowId == showId);
|
||||
|
||||
public async Task<List<Season>> GetPagedSeasons(int televisionShowId, int pageNumber, int pageSize)
|
||||
{
|
||||
List<int> showIds = await _dbConnection.QueryAsync<int>(
|
||||
@"SELECT m1.ShowId
|
||||
FROM ShowMetadata m1
|
||||
LEFT OUTER JOIN ShowMetadata m2 ON m2.ShowId = @ShowId
|
||||
WHERE m1.Title = m2.Title AND m1.Year = m2.Year",
|
||||
new { ShowId = televisionShowId })
|
||||
.Map(results => results.ToList());
|
||||
|
||||
return await _dbContext.Seasons
|
||||
.AsNoTracking()
|
||||
.Where(s => showIds.Contains(s.ShowId))
|
||||
.Include(s => s.SeasonMetadata)
|
||||
.ThenInclude(sm => sm.Artwork)
|
||||
.Include(s => s.Show)
|
||||
.ThenInclude(s => s.ShowMetadata)
|
||||
.OrderBy(s => s.SeasonNumber)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public Task<List<TelevisionSeason>> GetAllSeasons() =>
|
||||
_dbContext.TelevisionSeasons
|
||||
public Task<Option<Episode>> GetEpisode(int episodeId) =>
|
||||
_dbContext.Episodes
|
||||
.AsNoTracking()
|
||||
.Include(s => s.TelevisionShow)
|
||||
.ThenInclude(s => s.Metadata)
|
||||
.ToListAsync();
|
||||
|
||||
public Task<Option<TelevisionSeason>> GetSeason(int televisionSeasonId) =>
|
||||
_dbContext.TelevisionSeasons
|
||||
.AsNoTracking()
|
||||
.Include(s => s.TelevisionShow)
|
||||
.ThenInclude(s => s.Metadata)
|
||||
.SingleOrDefaultAsync(s => s.Id == televisionSeasonId)
|
||||
.Map(Optional);
|
||||
|
||||
public Task<int> GetSeasonCount(int televisionShowId) =>
|
||||
_dbContext.TelevisionSeasons
|
||||
.AsNoTracking()
|
||||
.Where(s => s.TelevisionShowId == televisionShowId)
|
||||
.CountAsync();
|
||||
|
||||
public Task<List<TelevisionSeason>> 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<Option<TelevisionEpisodeMediaItem>> GetEpisode(int televisionEpisodeId) =>
|
||||
_dbContext.TelevisionEpisodeMediaItems
|
||||
.AsNoTracking()
|
||||
.Include(s => s.Season)
|
||||
.Include(s => s.Metadata)
|
||||
.SingleOrDefaultAsync(s => s.Id == televisionEpisodeId)
|
||||
.Map(Optional);
|
||||
|
||||
public Task<int> GetEpisodeCount(int televisionSeasonId) =>
|
||||
_dbContext.TelevisionEpisodeMediaItems
|
||||
.AsNoTracking()
|
||||
.Where(e => e.SeasonId == televisionSeasonId)
|
||||
.CountAsync();
|
||||
|
||||
public Task<List<TelevisionEpisodeMediaItem>> 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)
|
||||
.Include(e => e.EpisodeMetadata)
|
||||
.ThenInclude(em => em.Artwork)
|
||||
.OrderBy(s => s.Id)
|
||||
.SingleOrDefaultAsync(s => s.Id == episodeId)
|
||||
.Map(Optional);
|
||||
|
||||
public Task<int> GetEpisodeCount(int seasonId) =>
|
||||
_dbContext.Episodes
|
||||
.AsNoTracking()
|
||||
.CountAsync(e => e.SeasonId == seasonId);
|
||||
|
||||
public Task<List<EpisodeMetadata>> GetPagedEpisodes(int seasonId, int pageNumber, int pageSize) =>
|
||||
_dbContext.EpisodeMetadata
|
||||
.AsNoTracking()
|
||||
.Filter(em => em.Episode.SeasonId == seasonId)
|
||||
.Include(em => em.Artwork)
|
||||
.Include(em => em.Episode)
|
||||
.ThenInclude(e => e.Season)
|
||||
.ThenInclude(s => s.Show)
|
||||
.ThenInclude(s => s.ShowMetadata)
|
||||
.OrderBy(em => em.Episode.EpisodeNumber)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<Option<TelevisionShow>> GetShowByPath(int mediaSourceId, string path)
|
||||
public async Task<Option<Show>> GetShowByMetadata(int libraryPathId, ShowMetadata metadata)
|
||||
{
|
||||
Option<int> maybeShowId = await _dbContext.LocalTelevisionShowSources
|
||||
.SingleOrDefaultAsync(s => s.MediaSourceId == mediaSourceId && s.Path == path)
|
||||
.Map(Optional)
|
||||
.MapT(s => s.TelevisionShowId);
|
||||
|
||||
return await maybeShowId.Match<Task<Option<TelevisionShow>>>(
|
||||
async id => await _dbContext.TelevisionShows
|
||||
.Include(s => s.Metadata)
|
||||
.Include(s => s.Sources)
|
||||
.SingleOrDefaultAsync(s => s.Id == id),
|
||||
() => Task.FromResult(Option<TelevisionShow>.None));
|
||||
}
|
||||
|
||||
public async Task<Option<TelevisionShow>> GetShowByMetadata(TelevisionShowMetadata metadata)
|
||||
{
|
||||
Option<TelevisionShow> maybeShow = await _dbContext.TelevisionShows
|
||||
.Include(s => s.Metadata)
|
||||
.Where(s => s.Metadata.Title == metadata.Title && s.Metadata.Year == metadata.Year)
|
||||
Option<int> maybeId = await _dbContext.ShowMetadata
|
||||
.Where(s => s.Title == metadata.Title && s.Year == metadata.Year)
|
||||
.Where(s => s.Show.LibraryPathId == libraryPathId)
|
||||
.SingleOrDefaultAsync()
|
||||
.Map(Optional);
|
||||
.Map(Optional)
|
||||
.MapT(sm => sm.ShowId);
|
||||
|
||||
await maybeShow.IfSomeAsync(
|
||||
async show =>
|
||||
return await maybeId.Match(
|
||||
id =>
|
||||
{
|
||||
await _dbContext.Entry(show).Reference(s => s.Metadata).LoadAsync();
|
||||
await _dbContext.Entry(show).Collection(s => s.Sources).LoadAsync();
|
||||
});
|
||||
|
||||
return maybeShow;
|
||||
return _dbContext.Shows
|
||||
.Include(s => s.ShowMetadata)
|
||||
.ThenInclude(sm => sm.Artwork)
|
||||
.OrderBy(s => s.Id)
|
||||
.SingleOrDefaultAsync(s => s.Id == id)
|
||||
.Map(Optional);
|
||||
},
|
||||
() => Option<Show>.None.AsTask());
|
||||
}
|
||||
|
||||
public async Task<Either<BaseError, TelevisionShow>> AddShow(
|
||||
int localMediaSourceId,
|
||||
string showFolder,
|
||||
TelevisionShowMetadata metadata)
|
||||
public async Task<Either<BaseError, Show>> AddShow(int libraryPathId, string showFolder, ShowMetadata metadata)
|
||||
{
|
||||
try
|
||||
{
|
||||
var show = new TelevisionShow
|
||||
metadata.DateAdded = DateTime.UtcNow;
|
||||
var show = new Show
|
||||
{
|
||||
Sources = new List<TelevisionShowSource>(),
|
||||
Metadata = metadata,
|
||||
Seasons = new List<TelevisionSeason>()
|
||||
LibraryPathId = libraryPathId,
|
||||
ShowMetadata = new List<ShowMetadata> { metadata },
|
||||
Seasons = new List<Season>()
|
||||
};
|
||||
|
||||
show.Sources.Add(
|
||||
new LocalTelevisionShowSource
|
||||
{
|
||||
MediaSourceId = localMediaSourceId,
|
||||
Path = showFolder,
|
||||
TelevisionShow = show
|
||||
});
|
||||
|
||||
await _dbContext.TelevisionShows.AddAsync(show);
|
||||
await _dbContext.Shows.AddAsync(show);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
return show;
|
||||
@@ -198,122 +200,86 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Either<BaseError, TelevisionSeason>> GetOrAddSeason(
|
||||
TelevisionShow show,
|
||||
string path,
|
||||
int seasonNumber)
|
||||
public async Task<Either<BaseError, Season>> GetOrAddSeason(Show show, int libraryPathId, int seasonNumber)
|
||||
{
|
||||
Option<TelevisionSeason> maybeExisting = await _dbContext.TelevisionSeasons
|
||||
.SingleOrDefaultAsync(i => i.Path == path);
|
||||
Option<Season> maybeExisting = await _dbContext.Seasons
|
||||
.Include(s => s.SeasonMetadata)
|
||||
.ThenInclude(sm => sm.Artwork)
|
||||
.SingleOrDefaultAsync(s => s.ShowId == show.Id && s.SeasonNumber == seasonNumber);
|
||||
|
||||
return await maybeExisting.Match(
|
||||
season => Right<BaseError, TelevisionSeason>(season).AsTask(),
|
||||
() => AddSeason(show, path, seasonNumber));
|
||||
season => Right<BaseError, Season>(season).AsTask(),
|
||||
() => AddSeason(show, libraryPathId, seasonNumber));
|
||||
}
|
||||
|
||||
public async Task<Either<BaseError, TelevisionEpisodeMediaItem>> GetOrAddEpisode(
|
||||
TelevisionSeason season,
|
||||
int mediaSourceId,
|
||||
public async Task<Either<BaseError, Episode>> GetOrAddEpisode(
|
||||
Season season,
|
||||
LibraryPath libraryPath,
|
||||
string path)
|
||||
{
|
||||
Option<TelevisionEpisodeMediaItem> maybeExisting = await _dbContext.TelevisionEpisodeMediaItems
|
||||
.Include(i => i.Metadata)
|
||||
.Include(i => i.Statistics)
|
||||
.SingleOrDefaultAsync(i => i.Path == path);
|
||||
Option<Episode> maybeExisting = await _dbContext.Episodes
|
||||
.Include(i => i.EpisodeMetadata)
|
||||
.ThenInclude(em => em.Artwork)
|
||||
.Include(i => i.MediaVersions)
|
||||
.ThenInclude(mv => mv.MediaFiles)
|
||||
.OrderBy(i => i.MediaVersions.First().MediaFiles.First().Path)
|
||||
.SingleOrDefaultAsync(i => i.MediaVersions.First().MediaFiles.First().Path == path);
|
||||
|
||||
return await maybeExisting.Match(
|
||||
episode => Right<BaseError, TelevisionEpisodeMediaItem>(episode).AsTask(),
|
||||
() => AddEpisode(season, mediaSourceId, path));
|
||||
episode => Right<BaseError, Episode>(episode).AsTask(),
|
||||
() => AddEpisode(season, libraryPath.Id, path));
|
||||
}
|
||||
|
||||
public Task<Unit> DeleteMissingSources(int localMediaSourceId, List<string> 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<Unit> DeleteEmptyShows() =>
|
||||
_dbContext.TelevisionShows
|
||||
.Where(s => s.Sources.Count == 0)
|
||||
_dbContext.Shows
|
||||
.Where(s => s.Seasons.Count == 0)
|
||||
.ToListAsync()
|
||||
.Bind(
|
||||
list =>
|
||||
{
|
||||
_dbContext.TelevisionShows.RemoveRange(list);
|
||||
_dbContext.Shows.RemoveRange(list);
|
||||
return _dbContext.SaveChangesAsync();
|
||||
})
|
||||
.ToUnit();
|
||||
|
||||
public async Task<List<TelevisionEpisodeMediaItem>> GetShowItems(int televisionShowId)
|
||||
public async Task<List<Episode>> GetShowItems(int showId)
|
||||
{
|
||||
var parameters = new { ShowId = televisionShowId };
|
||||
return await _dbConnection
|
||||
.QueryAsync<TelevisionEpisodeMediaItem, MediaItemStatistics, TelevisionEpisodeMetadata,
|
||||
TelevisionEpisodeMediaItem>(
|
||||
@"select tmi.Id, tmi.SeasonId, mi.MediaSourceId, mi.LastWriteTime, mi.Path, mi.Poster, mi.PosterLastWriteTime,
|
||||
mi.Statistics_AudioCodec as AudioCodec, mi.Statistics_DisplayAspectRatio as DisplayAspectRatio, mi.Statistics_Duration as Duration, mi.Statistics_Height as Height, mi.Statistics_LastWriteTime as LastWriteTime, mi.Statistics_SampleAspectRatio as SampleAspectRatio,
|
||||
mi.Statistics_VideoCodec as VideoCodec, mi.Statistics_VideoScanType as VideoScanType, mi.Statistics_Width as Width,
|
||||
tem.TelevisionEpisodeId, tem.Id, tem.Season, tem.Episode, tem.Plot, tem.Aired, tem.Source, tem.LastWriteTime, tem.Title, tem.SortTitle
|
||||
from TelevisionEpisodes tmi
|
||||
inner join MediaItems mi on tmi.Id = mi.Id
|
||||
inner join TelevisionEpisodeMetadata tem on tem.TelevisionEpisodeId = tmi.Id
|
||||
inner join TelevisionSeasons tsn on tsn.Id = tmi.SeasonId
|
||||
inner join TelevisionShows ts on ts.Id = tsn.TelevisionShowId
|
||||
where ts.Id = @ShowId",
|
||||
(episode, statistics, metadata) =>
|
||||
{
|
||||
episode.Statistics = statistics;
|
||||
episode.Metadata = metadata;
|
||||
return episode;
|
||||
},
|
||||
parameters,
|
||||
splitOn: "AudioCodec,TelevisionEpisodeId").Map(result => result.ToList());
|
||||
IEnumerable<int> ids = await _dbConnection.QueryAsync<int>(
|
||||
@"SELECT Episode.Id FROM Show
|
||||
INNER JOIN Season ON Season.ShowId = Show.Id
|
||||
INNER JOIN Episode ON Episode.SeasonId = Season.Id
|
||||
WHERE Show.Id = @ShowId",
|
||||
new { ShowId = showId });
|
||||
|
||||
return await _dbContext.Episodes
|
||||
.Include(e => e.EpisodeMetadata)
|
||||
.Include(e => e.MediaVersions)
|
||||
.Include(e => e.Season)
|
||||
.Filter(e => ids.Contains(e.Id))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<List<TelevisionEpisodeMediaItem>> GetSeasonItems(int televisionSeasonId)
|
||||
{
|
||||
var parameters = new { SeasonId = televisionSeasonId };
|
||||
return await _dbConnection
|
||||
.QueryAsync<TelevisionEpisodeMediaItem, MediaItemStatistics, TelevisionEpisodeMetadata,
|
||||
TelevisionEpisodeMediaItem>(
|
||||
@"select tmi.Id, tmi.SeasonId, mi.MediaSourceId, mi.LastWriteTime, mi.Path, mi.Poster, mi.PosterLastWriteTime,
|
||||
mi.Statistics_AudioCodec as AudioCodec, mi.Statistics_DisplayAspectRatio as DisplayAspectRatio, mi.Statistics_Duration as Duration, mi.Statistics_Height as Height, mi.Statistics_LastWriteTime as LastWriteTime, mi.Statistics_SampleAspectRatio as SampleAspectRatio,
|
||||
mi.Statistics_VideoCodec as VideoCodec, mi.Statistics_VideoScanType as VideoScanType, mi.Statistics_Width as Width,
|
||||
tem.TelevisionEpisodeId, tem.Id, tem.Season, tem.Episode, tem.Plot, tem.Aired, tem.Source, tem.LastWriteTime, tem.Title, tem.SortTitle
|
||||
from TelevisionEpisodes tmi
|
||||
inner join MediaItems mi on tmi.Id = mi.Id
|
||||
inner join TelevisionEpisodeMetadata tem on tem.TelevisionEpisodeId = tmi.Id
|
||||
inner join TelevisionSeasons tsn on tsn.Id = tmi.SeasonId
|
||||
where tsn.Id = @SeasonId",
|
||||
(episode, statistics, metadata) =>
|
||||
{
|
||||
episode.Statistics = statistics;
|
||||
episode.Metadata = metadata;
|
||||
return episode;
|
||||
},
|
||||
parameters,
|
||||
splitOn: "AudioCodec,TelevisionEpisodeId").Map(result => result.ToList());
|
||||
}
|
||||
public Task<List<Episode>> GetSeasonItems(int seasonId) =>
|
||||
_dbContext.Episodes
|
||||
.Include(e => e.EpisodeMetadata)
|
||||
.Include(e => e.MediaVersions)
|
||||
.Include(e => e.Season)
|
||||
.Filter(e => e.SeasonId == seasonId)
|
||||
.ToListAsync();
|
||||
|
||||
private async Task<Either<BaseError, TelevisionSeason>> AddSeason(
|
||||
TelevisionShow show,
|
||||
string path,
|
||||
int seasonNumber)
|
||||
private async Task<Either<BaseError, Season>> AddSeason(Show show, int libraryPathId, int seasonNumber)
|
||||
{
|
||||
try
|
||||
{
|
||||
var season = new TelevisionSeason
|
||||
var season = new Season
|
||||
{
|
||||
TelevisionShowId = show.Id, Path = path, Number = seasonNumber,
|
||||
Episodes = new List<TelevisionEpisodeMediaItem>()
|
||||
LibraryPathId = libraryPathId,
|
||||
ShowId = show.Id,
|
||||
SeasonNumber = seasonNumber,
|
||||
Episodes = new List<Episode>(),
|
||||
SeasonMetadata = new List<SeasonMetadata>()
|
||||
};
|
||||
await _dbContext.TelevisionSeasons.AddAsync(season);
|
||||
await _dbContext.Seasons.AddAsync(season);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
return season;
|
||||
}
|
||||
@@ -323,20 +289,27 @@ where tsn.Id = @SeasonId",
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Either<BaseError, TelevisionEpisodeMediaItem>> AddEpisode(
|
||||
TelevisionSeason season,
|
||||
int mediaSourceId,
|
||||
string path)
|
||||
private async Task<Either<BaseError, Episode>> AddEpisode(Season season, int libraryPathId, string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var episode = new TelevisionEpisodeMediaItem
|
||||
var episode = new Episode
|
||||
{
|
||||
MediaSourceId = mediaSourceId,
|
||||
LibraryPathId = libraryPathId,
|
||||
SeasonId = season.Id,
|
||||
Path = path
|
||||
EpisodeMetadata = new List<EpisodeMetadata>(),
|
||||
MediaVersions = new List<MediaVersion>
|
||||
{
|
||||
new()
|
||||
{
|
||||
MediaFiles = new List<MediaFile>
|
||||
{
|
||||
new() { Path = path }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
await _dbContext.TelevisionEpisodeMediaItems.AddAsync(episode);
|
||||
await _dbContext.Episodes.AddAsync(episode);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
return episode;
|
||||
}
|
||||
|
||||
@@ -14,19 +14,18 @@ namespace ErsatzTV.Infrastructure.Data
|
||||
public class DateTimeOffsetHandler : SqliteTypeHandler<DateTimeOffset>
|
||||
{
|
||||
public override DateTimeOffset Parse(object value)
|
||||
=> DateTimeOffset.Parse((string)value);
|
||||
=> DateTimeOffset.Parse((string) value);
|
||||
}
|
||||
|
||||
public class GuidHandler : SqliteTypeHandler<Guid>
|
||||
{
|
||||
public override Guid Parse(object value)
|
||||
=> Guid.Parse((string)value);
|
||||
=> Guid.Parse((string) value);
|
||||
}
|
||||
|
||||
public class TimeSpanHandler : SqliteTypeHandler<TimeSpan>
|
||||
{
|
||||
public override TimeSpan Parse(object value)
|
||||
=> TimeSpan.Parse((string)value);
|
||||
=> TimeSpan.Parse((string) value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,21 +17,28 @@ namespace ErsatzTV.Infrastructure.Data
|
||||
public DbSet<MediaSource> MediaSources { get; set; }
|
||||
public DbSet<LocalMediaSource> LocalMediaSources { get; set; }
|
||||
public DbSet<PlexMediaSource> PlexMediaSources { get; set; }
|
||||
public DbSet<Library> Libraries { get; set; }
|
||||
public DbSet<LocalLibrary> LocalLibraries { get; set; }
|
||||
public DbSet<LibraryPath> LibraryPaths { get; set; }
|
||||
public DbSet<PlexLibrary> PlexLibraries { get; set; }
|
||||
public DbSet<MediaItem> MediaItems { get; set; }
|
||||
public DbSet<MovieMediaItem> MovieMediaItems { get; set; }
|
||||
public DbSet<TelevisionEpisodeMediaItem> TelevisionEpisodeMediaItems { get; set; }
|
||||
public DbSet<MediaCollection> MediaCollections { get; set; }
|
||||
public DbSet<SimpleMediaCollection> SimpleMediaCollections { get; set; }
|
||||
public DbSet<MediaVersion> MediaVersions { get; set; }
|
||||
public DbSet<MediaFile> MediaFiles { get; set; }
|
||||
public DbSet<Movie> Movies { get; set; }
|
||||
public DbSet<MovieMetadata> MovieMetadata { get; set; }
|
||||
public DbSet<Show> Shows { get; set; }
|
||||
public DbSet<ShowMetadata> ShowMetadata { get; set; }
|
||||
public DbSet<Season> Seasons { get; set; }
|
||||
public DbSet<Episode> Episodes { get; set; }
|
||||
public DbSet<EpisodeMetadata> EpisodeMetadata { get; set; }
|
||||
public DbSet<PlexMovie> PlexMovieMediaItems { get; set; }
|
||||
public DbSet<Collection> Collections { 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; }
|
||||
public DbSet<TelevisionShow> TelevisionShows { get; set; }
|
||||
public DbSet<LocalTelevisionShowSource> LocalTelevisionShowSources { get; set; }
|
||||
public DbSet<TelevisionShowMetadata> TelevisionShowMetadata { get; set; }
|
||||
public DbSet<TelevisionSeason> TelevisionSeasons { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
|
||||
optionsBuilder.UseLoggerFactory(_loggerFactory);
|
||||
|
||||
Reference in New Issue
Block a user