diff --git a/ErsatzTV.Application/MediaCards/Mapper.cs b/ErsatzTV.Application/MediaCards/Mapper.cs index d087d3a40..719aa06c1 100644 --- a/ErsatzTV.Application/MediaCards/Mapper.cs +++ b/ErsatzTV.Application/MediaCards/Mapper.cs @@ -37,7 +37,7 @@ namespace ErsatzTV.Application.MediaCards televisionEpisode.Poster, televisionEpisode.Metadata?.Episode.ToString()); - internal static MovieCardViewModel ProjectToViewModel(MovieMediaItem movie) => + internal static MovieCardViewModel ProjectToViewModel(Movie movie) => new( movie.Id, movie.Metadata?.Title, diff --git a/ErsatzTV.Application/MediaCollections/Commands/AddMovieToSimpleMediaCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/AddMovieToSimpleMediaCollectionHandler.cs index a42304b0a..7920a4231 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/AddMovieToSimpleMediaCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/AddMovieToSimpleMediaCollectionHandler.cs @@ -49,14 +49,14 @@ namespace ErsatzTV.Application.MediaCollections.Commands _mediaCollectionRepository.GetSimpleMediaCollectionWithItems(updateSimpleMediaCollection.MediaCollectionId) .Map(v => v.ToValidation("SimpleMediaCollection does not exist.")); - private Task> ValidateMovies( + private Task> ValidateMovies( AddMovieToSimpleMediaCollection request) => LoadMovie(request) .Map(v => v.ToValidation("MovieMediaItem does not exist")); - private Task> LoadMovie(AddMovieToSimpleMediaCollection request) => + private Task> LoadMovie(AddMovieToSimpleMediaCollection request) => _movieRepository.GetMovie(request.MovieId); - private record RequestParameters(SimpleMediaCollection Collection, MovieMediaItem MovieToAdd); + private record RequestParameters(SimpleMediaCollection Collection, Movie MovieToAdd); } } diff --git a/ErsatzTV.Application/MediaCollections/Commands/CreateSimpleMediaCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/CreateSimpleMediaCollectionHandler.cs index 362fab63c..ecc90a757 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/CreateSimpleMediaCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/CreateSimpleMediaCollectionHandler.cs @@ -33,7 +33,7 @@ namespace ErsatzTV.Application.MediaCollections.Commands name => new SimpleMediaCollection { Name = name, - Movies = new List(), + Movies = new List(), TelevisionShows = new List(), TelevisionEpisodes = new List(), TelevisionSeasons = new List() diff --git a/ErsatzTV.Application/MediaItems/Mapper.cs b/ErsatzTV.Application/MediaItems/Mapper.cs index 59fb7c5f7..c975973cf 100644 --- a/ErsatzTV.Application/MediaItems/Mapper.cs +++ b/ErsatzTV.Application/MediaItems/Mapper.cs @@ -9,29 +9,29 @@ namespace ErsatzTV.Application.MediaItems internal static MediaItemViewModel ProjectToViewModel(MediaItem mediaItem) => new( mediaItem.Id, - mediaItem.MediaSourceId, + mediaItem.LibraryPathId, mediaItem.Path); internal static MediaItemSearchResultViewModel ProjectToSearchViewModel(MediaItem mediaItem) => mediaItem switch { TelevisionEpisodeMediaItem e => ProjectToSearchViewModel(e), - MovieMediaItem m => ProjectToSearchViewModel(m), + Movie m => ProjectToSearchViewModel(m), _ => throw new ArgumentOutOfRangeException() }; private static MediaItemSearchResultViewModel ProjectToSearchViewModel(TelevisionEpisodeMediaItem mediaItem) => new( mediaItem.Id, - GetSourceName(mediaItem.Source), + GetLibraryName(mediaItem), "TV Show", GetDisplayTitle(mediaItem), GetDisplayDuration(mediaItem)); - private static MediaItemSearchResultViewModel ProjectToSearchViewModel(MovieMediaItem mediaItem) => + private static MediaItemSearchResultViewModel ProjectToSearchViewModel(Movie mediaItem) => new( mediaItem.Id, - GetSourceName(mediaItem.Source), + GetLibraryName(mediaItem), "Movie", GetDisplayTitle(mediaItem), GetDisplayDuration(mediaItem)); @@ -43,7 +43,7 @@ namespace ErsatzTV.Application.MediaItems TelevisionEpisodeMediaItem e => e.Metadata != null ? $"{e.Metadata.Title} - s{e.Metadata.Season:00}e{e.Metadata.Episode:00}" : Path.GetFileName(e.Path), - MovieMediaItem m => m.Metadata?.Title ?? Path.GetFileName(m.Path), + Movie m => m.Metadata?.Title ?? Path.GetFileName(m.Path), _ => string.Empty }; @@ -52,11 +52,14 @@ namespace ErsatzTV.Application.MediaItems mediaItem.Statistics.Duration.TotalHours >= 1 ? @"{0:h\:mm\:ss}" : @"{0:mm\:ss}", mediaItem.Statistics.Duration); - private static string GetSourceName(MediaSource source) => - source switch - { - LocalMediaSource lms => lms.Folder, - _ => source.Name - }; + // TODO: fix this + private static string GetLibraryName(MediaItem item) => + "Library Name"; + + // source switch + // { + // LocalMediaSource lms => "Local Media Source", + // _ => "unknown source" + // }; } } diff --git a/ErsatzTV.Application/MediaItems/MediaItemViewModel.cs b/ErsatzTV.Application/MediaItems/MediaItemViewModel.cs index 108127c84..ce62ab340 100644 --- a/ErsatzTV.Application/MediaItems/MediaItemViewModel.cs +++ b/ErsatzTV.Application/MediaItems/MediaItemViewModel.cs @@ -1,4 +1,4 @@ namespace ErsatzTV.Application.MediaItems { - public record MediaItemViewModel(int Id, int MediaSourceId, string Path); + public record MediaItemViewModel(int Id, int LibraryPathId, string Path); } diff --git a/ErsatzTV.Application/MediaSources/Commands/CreateLocalMediaSource.cs b/ErsatzTV.Application/MediaSources/Commands/CreateLocalMediaSource.cs index 49bb842d0..00bdabbb2 100644 --- a/ErsatzTV.Application/MediaSources/Commands/CreateLocalMediaSource.cs +++ b/ErsatzTV.Application/MediaSources/Commands/CreateLocalMediaSource.cs @@ -1,10 +1,12 @@ -using ErsatzTV.Core; -using ErsatzTV.Core.Domain; -using LanguageExt; -using MediatR; +// using ErsatzTV.Core; +// using ErsatzTV.Core.Domain; +// using LanguageExt; +// using MediatR; +// +// namespace ErsatzTV.Application.MediaSources.Commands +// { +// public record CreateLocalMediaSource +// (string Name, MediaType MediaType, string Folder) : IRequest>; +// } + -namespace ErsatzTV.Application.MediaSources.Commands -{ - public record CreateLocalMediaSource - (string Name, MediaType MediaType, string Folder) : IRequest>; -} diff --git a/ErsatzTV.Application/MediaSources/Commands/CreateLocalMediaSourceHandler.cs b/ErsatzTV.Application/MediaSources/Commands/CreateLocalMediaSourceHandler.cs index da307a55e..248563da7 100644 --- a/ErsatzTV.Application/MediaSources/Commands/CreateLocalMediaSourceHandler.cs +++ b/ErsatzTV.Application/MediaSources/Commands/CreateLocalMediaSourceHandler.cs @@ -1,78 +1,80 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using ErsatzTV.Core; -using ErsatzTV.Core.Domain; -using ErsatzTV.Core.Interfaces.Repositories; -using LanguageExt; -using MediatR; -using static ErsatzTV.Application.MediaSources.Mapper; -using static LanguageExt.Prelude; +// using System; +// using System.Collections.Generic; +// using System.IO; +// using System.Linq; +// using System.Threading; +// using System.Threading.Tasks; +// using ErsatzTV.Core; +// using ErsatzTV.Core.Domain; +// using ErsatzTV.Core.Interfaces.Repositories; +// using LanguageExt; +// using MediatR; +// using static ErsatzTV.Application.MediaSources.Mapper; +// using static LanguageExt.Prelude; +// +// namespace ErsatzTV.Application.MediaSources.Commands +// { +// public class CreateLocalMediaSourceHandler : IRequestHandler> +// { +// private readonly IMediaSourceRepository _mediaSourceRepository; +// +// public CreateLocalMediaSourceHandler(IMediaSourceRepository mediaSourceRepository) => +// _mediaSourceRepository = mediaSourceRepository; +// +// public Task> Handle( +// CreateLocalMediaSource request, +// CancellationToken cancellationToken) => +// Validate(request).MapT(PersistLocalMediaSource).Bind(v => v.ToEitherAsync()); +// +// private Task PersistLocalMediaSource(LocalMediaSource c) => +// _mediaSourceRepository.Add(c).Map(ProjectToViewModel); +// +// private async Task> Validate(CreateLocalMediaSource request) => +// (await ValidateName(request), await ValidateFolder(request)) +// .Apply( +// (name, folder) => +// new LocalMediaSource +// { +// Name = name, +// MediaType = request.MediaType, +// Folder = folder +// }); +// +// private async Task> ValidateName(CreateLocalMediaSource request) +// { +// List allNames = await _mediaSourceRepository.GetAll() +// .Map(list => list.Map(c => c.Name).ToList()); +// +// Validation result1 = request.NotEmpty(c => c.Name) +// .Bind(_ => request.NotLongerThan(50)(c => c.Name)); +// +// var result2 = Optional(request.Name) +// .Filter(name => !allNames.Contains(name)) +// .ToValidation("Media source name must be unique"); +// +// return (result1, result2).Apply((_, _) => request.Name); +// } +// +// private async Task> ValidateFolder(CreateLocalMediaSource request) +// { +// List allFolders = await _mediaSourceRepository.GetAll() +// .Map(list => list.OfType().Map(c => c.Folder).ToList()); +// +// +// return Optional(request.Folder) +// .Filter(folder => allFolders.ForAll(f => !AreSubPaths(f, folder))) +// .ToValidation("Folder must not belong to another media source"); +// } +// +// private static bool AreSubPaths(string path1, string path2) +// { +// string one = path1 + Path.DirectorySeparatorChar; +// string two = path2 + Path.DirectorySeparatorChar; +// return one == two || one.StartsWith(two, StringComparison.OrdinalIgnoreCase) || +// two.StartsWith(one, StringComparison.OrdinalIgnoreCase); +// } +// } +// } -namespace ErsatzTV.Application.MediaSources.Commands -{ - public class CreateLocalMediaSourceHandler : IRequestHandler> - { - private readonly IMediaSourceRepository _mediaSourceRepository; - public CreateLocalMediaSourceHandler(IMediaSourceRepository mediaSourceRepository) => - _mediaSourceRepository = mediaSourceRepository; - - public Task> Handle( - CreateLocalMediaSource request, - CancellationToken cancellationToken) => - Validate(request).MapT(PersistLocalMediaSource).Bind(v => v.ToEitherAsync()); - - private Task PersistLocalMediaSource(LocalMediaSource c) => - _mediaSourceRepository.Add(c).Map(ProjectToViewModel); - - private async Task> Validate(CreateLocalMediaSource request) => - (await ValidateName(request), await ValidateFolder(request)) - .Apply( - (name, folder) => - new LocalMediaSource - { - Name = name, - MediaType = request.MediaType, - Folder = folder - }); - - private async Task> ValidateName(CreateLocalMediaSource request) - { - List allNames = await _mediaSourceRepository.GetAll() - .Map(list => list.Map(c => c.Name).ToList()); - - Validation result1 = request.NotEmpty(c => c.Name) - .Bind(_ => request.NotLongerThan(50)(c => c.Name)); - - var result2 = Optional(request.Name) - .Filter(name => !allNames.Contains(name)) - .ToValidation("Media source name must be unique"); - - return (result1, result2).Apply((_, _) => request.Name); - } - - private async Task> ValidateFolder(CreateLocalMediaSource request) - { - List allFolders = await _mediaSourceRepository.GetAll() - .Map(list => list.OfType().Map(c => c.Folder).ToList()); - - - return Optional(request.Folder) - .Filter(folder => allFolders.ForAll(f => !AreSubPaths(f, folder))) - .ToValidation("Folder must not belong to another media source"); - } - - private static bool AreSubPaths(string path1, string path2) - { - string one = path1 + Path.DirectorySeparatorChar; - string two = path2 + Path.DirectorySeparatorChar; - return one == two || one.StartsWith(two, StringComparison.OrdinalIgnoreCase) || - two.StartsWith(one, StringComparison.OrdinalIgnoreCase); - } - } -} diff --git a/ErsatzTV.Application/MediaSources/Commands/ScanLocalLibraryHandler.cs b/ErsatzTV.Application/MediaSources/Commands/ScanLocalLibraryHandler.cs new file mode 100644 index 000000000..53d527934 --- /dev/null +++ b/ErsatzTV.Application/MediaSources/Commands/ScanLocalLibraryHandler.cs @@ -0,0 +1,114 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using ErsatzTV.Core; +using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Interfaces.Locking; +using ErsatzTV.Core.Interfaces.Metadata; +using ErsatzTV.Core.Interfaces.Repositories; +using LanguageExt; +using MediatR; +using Microsoft.Extensions.Logging; +using Unit = LanguageExt.Unit; + +namespace ErsatzTV.Application.MediaSources.Commands +{ + public class ScanLocalLibraryHandler : IRequestHandler>, + IRequestHandler> + { + private readonly IConfigElementRepository _configElementRepository; + private readonly IEntityLocker _entityLocker; + private readonly ILibraryRepository _libraryRepository; + private readonly ILogger _logger; + private readonly IMovieFolderScanner _movieFolderScanner; + private readonly ITelevisionFolderScanner _televisionFolderScanner; + + public ScanLocalLibraryHandler( + ILibraryRepository libraryRepository, + IConfigElementRepository configElementRepository, + IMovieFolderScanner movieFolderScanner, + ITelevisionFolderScanner televisionFolderScanner, + IEntityLocker entityLocker, + ILogger logger) + { + _libraryRepository = libraryRepository; + _configElementRepository = configElementRepository; + _movieFolderScanner = movieFolderScanner; + _televisionFolderScanner = televisionFolderScanner; + _entityLocker = entityLocker; + _logger = logger; + } + + public Task> Handle( + ForceScanLocalLibrary request, + CancellationToken cancellationToken) => Handle(request); + + public Task> Handle( + ScanLocalLibraryIfNeeded request, + CancellationToken cancellationToken) => Handle(request); + + private Task> + Handle(IScanLocalLibrary request) => + Validate(request) + .MapT(parameters => PerformScan(parameters).Map(_ => parameters.LocalLibrary.Name)) + .Bind(v => v.ToEitherAsync()); + + private async Task PerformScan(RequestParameters parameters) + { + (LocalLibrary localLibrary, string ffprobePath, bool forceScan) = parameters; + + DateTimeOffset lastScan = localLibrary.LastScan ?? DateTime.MinValue; + if (forceScan || lastScan < DateTimeOffset.Now - TimeSpan.FromHours(6)) + { + foreach (LibraryPath libraryPath in localLibrary.Paths) + { + switch (localLibrary.MediaKind) + { + case LibraryMediaKind.Movies: + await _movieFolderScanner.ScanFolder(libraryPath, ffprobePath); + break; + case LibraryMediaKind.Shows: + await _televisionFolderScanner.ScanFolder(libraryPath, ffprobePath); + break; + } + } + + localLibrary.LastScan = DateTimeOffset.Now; + await _libraryRepository.UpdateLastScan(localLibrary); + } + else + { + _logger.LogDebug( + "Skipping unforced scan of library {Name}", + localLibrary.Name); + } + + _entityLocker.UnlockLibrary(localLibrary.Id); + return Unit.Default; + } + + private async Task> Validate(IScanLocalLibrary request) => + (await LocalLibraryMustExist(request), await ValidateFFprobePath()) + .Apply( + (library, ffprobePath) => new RequestParameters( + library, + ffprobePath, + request.ForceScan)); + + private Task> LocalLibraryMustExist( + IScanLocalLibrary request) => + _libraryRepository.Get(request.LibraryId) + .Map(maybeLibrary => maybeLibrary.Map(ms => ms as LocalLibrary)) + .Map(v => v.ToValidation($"Local library {request.LibraryId} does not exist.")); + + private Task> ValidateFFprobePath() => + _configElementRepository.GetValue(ConfigElementKey.FFprobePath) + .FilterT(File.Exists) + .Map( + ffprobePath => + ffprobePath.ToValidation("FFprobe path does not exist on the file system")); + + private record RequestParameters(LocalLibrary LocalLibrary, string FFprobePath, bool ForceScan); + } +} diff --git a/ErsatzTV.Application/MediaSources/Commands/ScanLocalMediaSource.cs b/ErsatzTV.Application/MediaSources/Commands/ScanLocalMediaSource.cs index c2d46be01..da4999e12 100644 --- a/ErsatzTV.Application/MediaSources/Commands/ScanLocalMediaSource.cs +++ b/ErsatzTV.Application/MediaSources/Commands/ScanLocalMediaSource.cs @@ -4,18 +4,18 @@ using MediatR; namespace ErsatzTV.Application.MediaSources.Commands { - public interface IScanLocalMediaSource : IRequest>, IBackgroundServiceRequest + public interface IScanLocalLibrary : IRequest>, IBackgroundServiceRequest { - int MediaSourceId { get; } + int LibraryId { get; } bool ForceScan { get; } } - public record ScanLocalMediaSourceIfNeeded(int MediaSourceId) : IScanLocalMediaSource + public record ScanLocalLibraryIfNeeded(int LibraryId) : IScanLocalLibrary { public bool ForceScan => false; } - public record ForceScanLocalMediaSource(int MediaSourceId) : IScanLocalMediaSource + public record ForceScanLocalLibrary(int LibraryId) : IScanLocalLibrary { public bool ForceScan => true; } diff --git a/ErsatzTV.Application/MediaSources/Commands/ScanLocalMediaSourceHandler.cs b/ErsatzTV.Application/MediaSources/Commands/ScanLocalMediaSourceHandler.cs deleted file mode 100644 index 4c387ee84..000000000 --- a/ErsatzTV.Application/MediaSources/Commands/ScanLocalMediaSourceHandler.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using ErsatzTV.Core; -using ErsatzTV.Core.Domain; -using ErsatzTV.Core.Interfaces.Locking; -using ErsatzTV.Core.Interfaces.Metadata; -using ErsatzTV.Core.Interfaces.Repositories; -using LanguageExt; -using MediatR; -using Microsoft.Extensions.Logging; -using Unit = LanguageExt.Unit; - -namespace ErsatzTV.Application.MediaSources.Commands -{ - public class ScanLocalMediaSourceHandler : IRequestHandler>, - IRequestHandler> - { - private readonly IConfigElementRepository _configElementRepository; - private readonly IEntityLocker _entityLocker; - private readonly ILogger _logger; - private readonly IMediaSourceRepository _mediaSourceRepository; - private readonly IMovieFolderScanner _movieFolderScanner; - private readonly ITelevisionFolderScanner _televisionFolderScanner; - - public ScanLocalMediaSourceHandler( - IMediaSourceRepository mediaSourceRepository, - IConfigElementRepository configElementRepository, - IMovieFolderScanner movieFolderScanner, - ITelevisionFolderScanner televisionFolderScanner, - IEntityLocker entityLocker, - ILogger logger) - { - _mediaSourceRepository = mediaSourceRepository; - _configElementRepository = configElementRepository; - _movieFolderScanner = movieFolderScanner; - _televisionFolderScanner = televisionFolderScanner; - _entityLocker = entityLocker; - _logger = logger; - } - - public Task> Handle( - ForceScanLocalMediaSource request, - CancellationToken cancellationToken) => - Handle((IScanLocalMediaSource) request, cancellationToken); - - public Task> Handle( - ScanLocalMediaSourceIfNeeded request, - CancellationToken cancellationToken) => - Handle((IScanLocalMediaSource) request, cancellationToken); - - private Task> - Handle(IScanLocalMediaSource request, CancellationToken cancellationToken) => - Validate(request) - .MapT(parameters => PerformScan(parameters).Map(_ => parameters.LocalMediaSource.Folder)) - .Bind(v => v.ToEitherAsync()); - - private async Task PerformScan(RequestParameters parameters) - { - DateTimeOffset lastScan = parameters.LocalMediaSource.LastScan ?? DateTime.MinValue; - if (parameters.ForceScan || lastScan < DateTimeOffset.Now - TimeSpan.FromHours(6)) - { - switch (parameters.LocalMediaSource.MediaType) - { - case MediaType.Movie: - await _movieFolderScanner.ScanFolder(parameters.LocalMediaSource, parameters.FFprobePath); - break; - case MediaType.TvShow: - await _televisionFolderScanner.ScanFolder(parameters.LocalMediaSource, parameters.FFprobePath); - break; - } - - parameters.LocalMediaSource.LastScan = DateTimeOffset.Now; - await _mediaSourceRepository.Update(parameters.LocalMediaSource); - } - else - { - _logger.LogDebug( - "Skipping unforced scan of media source {Folder}", - parameters.LocalMediaSource.Folder); - } - - _entityLocker.UnlockMediaSource(parameters.LocalMediaSource.Id); - return Unit.Default; - } - - private async Task> Validate(IScanLocalMediaSource request) => - (await LocalMediaSourceMustExist(request), await ValidateFFprobePath()) - .Apply( - (localMediaSource, ffprobePath) => new RequestParameters( - localMediaSource, - ffprobePath, - request.ForceScan)); - - private Task> LocalMediaSourceMustExist( - IScanLocalMediaSource request) => - _mediaSourceRepository.Get(request.MediaSourceId) - .Map(maybeMediaSource => maybeMediaSource.Map(ms => ms as LocalMediaSource)) - .Map(v => v.ToValidation($"Local media source {request.MediaSourceId} does not exist.")); - - private Task> ValidateFFprobePath() => - _configElementRepository.GetValue(ConfigElementKey.FFprobePath) - .FilterT(File.Exists) - .Map( - ffprobePath => - ffprobePath.ToValidation("FFprobe path does not exist on the file system")); - - private record RequestParameters(LocalMediaSource LocalMediaSource, string FFprobePath, bool ForceScan); - } -} diff --git a/ErsatzTV.Application/MediaSources/LocalMediaSourceViewModel.cs b/ErsatzTV.Application/MediaSources/LocalMediaSourceViewModel.cs index ad16412a4..4330f2364 100644 --- a/ErsatzTV.Application/MediaSources/LocalMediaSourceViewModel.cs +++ b/ErsatzTV.Application/MediaSources/LocalMediaSourceViewModel.cs @@ -1,7 +1,5 @@ -using ErsatzTV.Core.Domain; - -namespace ErsatzTV.Application.MediaSources +namespace ErsatzTV.Application.MediaSources { public record LocalMediaSourceViewModel(int Id, string Name, string Folder) - : MediaSourceViewModel(Id, Name, MediaSourceType.Local); + : MediaSourceViewModel(Id, Name); } diff --git a/ErsatzTV.Application/MediaSources/Mapper.cs b/ErsatzTV.Application/MediaSources/Mapper.cs index df68b4e3c..def00122f 100644 --- a/ErsatzTV.Application/MediaSources/Mapper.cs +++ b/ErsatzTV.Application/MediaSources/Mapper.cs @@ -1,7 +1,5 @@ using System; -using System.Linq; using ErsatzTV.Core.Domain; -using static LanguageExt.Prelude; namespace ErsatzTV.Application.MediaSources { @@ -10,15 +8,10 @@ namespace ErsatzTV.Application.MediaSources internal static MediaSourceViewModel ProjectToViewModel(MediaSource mediaSource) => mediaSource switch { - LocalMediaSource lms => new LocalMediaSourceViewModel(lms.Id, lms.Name, lms.Folder), - PlexMediaSource pms => ProjectToViewModel(pms), + // TODO: address this... + LocalMediaSource lms => new LocalMediaSourceViewModel(lms.Id, "LMS Name", "LMS Folder"), + PlexMediaSource pms => Plex.Mapper.ProjectToViewModel(pms), _ => throw new NotSupportedException($"Unsupported media source {mediaSource.GetType().Name}") }; - - internal static PlexMediaSourceViewModel ProjectToViewModel(PlexMediaSource plexMediaSource) => - new( - plexMediaSource.Id, - plexMediaSource.Name, - Optional(plexMediaSource.Connections.SingleOrDefault(c => c.IsActive)).Match(c => c.Uri, string.Empty)); } } diff --git a/ErsatzTV.Application/MediaSources/MediaSourceViewModel.cs b/ErsatzTV.Application/MediaSources/MediaSourceViewModel.cs index ab44f6947..036a23d78 100644 --- a/ErsatzTV.Application/MediaSources/MediaSourceViewModel.cs +++ b/ErsatzTV.Application/MediaSources/MediaSourceViewModel.cs @@ -1,6 +1,4 @@ -using ErsatzTV.Core.Domain; - -namespace ErsatzTV.Application.MediaSources +namespace ErsatzTV.Application.MediaSources { - public record MediaSourceViewModel(int Id, string Name, MediaSourceType SourceType); + public record MediaSourceViewModel(int Id, string Name); } diff --git a/ErsatzTV.Application/MediaSources/PlexMediaSourceViewModel.cs b/ErsatzTV.Application/MediaSources/PlexMediaSourceViewModel.cs deleted file mode 100644 index aa952a24e..000000000 --- a/ErsatzTV.Application/MediaSources/PlexMediaSourceViewModel.cs +++ /dev/null @@ -1,9 +0,0 @@ -using ErsatzTV.Core.Domain; - -namespace ErsatzTV.Application.MediaSources -{ - public record PlexMediaSourceViewModel(int Id, string Name, string Address) : MediaSourceViewModel( - Id, - Name, - MediaSourceType.Plex); -} diff --git a/ErsatzTV.Application/Movies/Mapper.cs b/ErsatzTV.Application/Movies/Mapper.cs index 4656151fa..42b4586cb 100644 --- a/ErsatzTV.Application/Movies/Mapper.cs +++ b/ErsatzTV.Application/Movies/Mapper.cs @@ -4,7 +4,7 @@ namespace ErsatzTV.Application.Movies { internal static class Mapper { - internal static MovieViewModel ProjectToViewModel(MovieMediaItem movie) => + internal static MovieViewModel ProjectToViewModel(Movie movie) => new(movie.Metadata.Title, movie.Metadata.Year?.ToString(), movie.Metadata.Plot, movie.Poster); } } diff --git a/ErsatzTV.Application/Playouts/Mapper.cs b/ErsatzTV.Application/Playouts/Mapper.cs index f2167f721..2bdd6873e 100644 --- a/ErsatzTV.Application/Playouts/Mapper.cs +++ b/ErsatzTV.Application/Playouts/Mapper.cs @@ -27,7 +27,7 @@ namespace ErsatzTV.Application.Playouts TelevisionEpisodeMediaItem e => e.Metadata != null ? $"{e.Metadata.Title} - s{e.Metadata.Season:00}e{e.Metadata.Episode:00}" : Path.GetFileName(e.Path), - MovieMediaItem m => m.Metadata?.Title ?? Path.GetFileName(m.Path), + Movie m => m.Metadata?.Title ?? Path.GetFileName(m.Path), _ => string.Empty }; diff --git a/ErsatzTV.Application/MediaSources/Commands/StartPlexPinFlow.cs b/ErsatzTV.Application/Plex/Commands/StartPlexPinFlow.cs similarity index 72% rename from ErsatzTV.Application/MediaSources/Commands/StartPlexPinFlow.cs rename to ErsatzTV.Application/Plex/Commands/StartPlexPinFlow.cs index 1aa6da317..5c5eebbc2 100644 --- a/ErsatzTV.Application/MediaSources/Commands/StartPlexPinFlow.cs +++ b/ErsatzTV.Application/Plex/Commands/StartPlexPinFlow.cs @@ -2,7 +2,7 @@ using LanguageExt; using MediatR; -namespace ErsatzTV.Application.MediaSources.Commands +namespace ErsatzTV.Application.Plex.Commands { public record StartPlexPinFlow : IRequest>; } diff --git a/ErsatzTV.Application/MediaSources/Commands/StartPlexPinFlowHandler.cs b/ErsatzTV.Application/Plex/Commands/StartPlexPinFlowHandler.cs similarity index 96% rename from ErsatzTV.Application/MediaSources/Commands/StartPlexPinFlowHandler.cs rename to ErsatzTV.Application/Plex/Commands/StartPlexPinFlowHandler.cs index 7a3cf2967..869897018 100644 --- a/ErsatzTV.Application/MediaSources/Commands/StartPlexPinFlowHandler.cs +++ b/ErsatzTV.Application/Plex/Commands/StartPlexPinFlowHandler.cs @@ -7,7 +7,7 @@ using LanguageExt; using MediatR; using static LanguageExt.Prelude; -namespace ErsatzTV.Application.MediaSources.Commands +namespace ErsatzTV.Application.Plex.Commands { public class StartPlexPinFlowHandler : IRequestHandler> { diff --git a/ErsatzTV.Application/MediaSources/Commands/SynchronizePlexLibraries.cs b/ErsatzTV.Application/Plex/Commands/SynchronizePlexLibraries.cs similarity index 75% rename from ErsatzTV.Application/MediaSources/Commands/SynchronizePlexLibraries.cs rename to ErsatzTV.Application/Plex/Commands/SynchronizePlexLibraries.cs index 38aba8f55..16bc46a52 100644 --- a/ErsatzTV.Application/MediaSources/Commands/SynchronizePlexLibraries.cs +++ b/ErsatzTV.Application/Plex/Commands/SynchronizePlexLibraries.cs @@ -1,7 +1,7 @@ using ErsatzTV.Core; using LanguageExt; -namespace ErsatzTV.Application.MediaSources.Commands +namespace ErsatzTV.Application.Plex.Commands { public record SynchronizePlexLibraries(int PlexMediaSourceId) : MediatR.IRequest>; } diff --git a/ErsatzTV.Application/MediaSources/Commands/SynchronizePlexLibrariesHandler.cs b/ErsatzTV.Application/Plex/Commands/SynchronizePlexLibrariesHandler.cs similarity index 90% rename from ErsatzTV.Application/MediaSources/Commands/SynchronizePlexLibrariesHandler.cs rename to ErsatzTV.Application/Plex/Commands/SynchronizePlexLibrariesHandler.cs index 398e0ad48..77d241b31 100644 --- a/ErsatzTV.Application/MediaSources/Commands/SynchronizePlexLibrariesHandler.cs +++ b/ErsatzTV.Application/Plex/Commands/SynchronizePlexLibrariesHandler.cs @@ -10,7 +10,7 @@ using ErsatzTV.Core.Plex; using LanguageExt; using Microsoft.Extensions.Logging; -namespace ErsatzTV.Application.MediaSources.Commands +namespace ErsatzTV.Application.Plex.Commands { public class SynchronizePlexLibrariesHandler : MediatR.IRequestHandler> @@ -51,7 +51,7 @@ namespace ErsatzTV.Application.MediaSources.Commands private Validation MediaSourceMustHaveActiveConnection( PlexMediaSource plexMediaSource) { - Option maybeConnection = + Option maybeConnection = plexMediaSource.Connections.SingleOrDefault(c => c.IsActive); return maybeConnection.Map(connection => new ConnectionParameters(plexMediaSource, connection)) .ToValidation("Plex media source requires an active connection"); @@ -68,14 +68,14 @@ namespace ErsatzTV.Application.MediaSources.Commands private async Task SynchronizeLibraries(ConnectionParameters connectionParameters) { - Either> maybeLibraries = await _plexServerApiClient.GetLibraries( + Either> maybeLibraries = await _plexServerApiClient.GetLibraries( connectionParameters.ActiveConnection, connectionParameters.PlexServerAuthToken); await maybeLibraries.Match( libraries => { - List existing = connectionParameters.PlexMediaSource.Libraries; + var existing = connectionParameters.PlexMediaSource.Libraries.OfType().ToList(); var toAdd = libraries.Filter(library => existing.All(l => l.Key != library.Key)).ToList(); var toRemove = existing.Filter(library => libraries.All(l => l.Key != library.Key)).ToList(); existing.AddRange(toAdd); @@ -87,7 +87,7 @@ namespace ErsatzTV.Application.MediaSources.Commands { _logger.LogWarning( "Unable to synchronize libraries from plex server {PlexServer}: {Error}", - connectionParameters.PlexMediaSource.Name, + connectionParameters.PlexMediaSource.ServerName, error.Value); return Task.CompletedTask; @@ -98,7 +98,7 @@ namespace ErsatzTV.Application.MediaSources.Commands private record ConnectionParameters( PlexMediaSource PlexMediaSource, - PlexMediaSourceConnection ActiveConnection) + PlexConnection ActiveConnection) { public PlexServerAuthToken PlexServerAuthToken { get; set; } } diff --git a/ErsatzTV.Application/Plex/Commands/SynchronizePlexLibraryById.cs b/ErsatzTV.Application/Plex/Commands/SynchronizePlexLibraryById.cs new file mode 100644 index 000000000..46e11ab55 --- /dev/null +++ b/ErsatzTV.Application/Plex/Commands/SynchronizePlexLibraryById.cs @@ -0,0 +1,25 @@ +using ErsatzTV.Core; +using LanguageExt; +using MediatR; + +namespace ErsatzTV.Application.Plex.Commands +{ + public interface ISynchronizePlexLibraryById : IRequest>, IBackgroundServiceRequest + { + int PlexMediaSourceId { get; } + int PlexLibraryId { get; } + bool ForceScan { get; } + } + + public record SynchronizePlexLibraryByIdIfNeeded + (int PlexMediaSourceId, int PlexLibraryId) : ISynchronizePlexLibraryById + { + public bool ForceScan => false; + } + + public record ForceSynchronizePlexLibraryById + (int PlexMediaSourceId, int PlexLibraryId) : ISynchronizePlexLibraryById + { + public bool ForceScan => true; + } +} diff --git a/ErsatzTV.Application/Plex/Commands/SynchronizePlexLibraryByIdHandler.cs b/ErsatzTV.Application/Plex/Commands/SynchronizePlexLibraryByIdHandler.cs new file mode 100644 index 000000000..269211153 --- /dev/null +++ b/ErsatzTV.Application/Plex/Commands/SynchronizePlexLibraryByIdHandler.cs @@ -0,0 +1,144 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using ErsatzTV.Core; +using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Interfaces.Locking; +using ErsatzTV.Core.Interfaces.Plex; +using ErsatzTV.Core.Interfaces.Repositories; +using ErsatzTV.Core.Plex; +using LanguageExt; +using MediatR; +using Microsoft.Extensions.Logging; +using Unit = LanguageExt.Unit; + +namespace ErsatzTV.Application.Plex.Commands +{ + public class + SynchronizePlexLibraryByIdHandler : IRequestHandler>, + IRequestHandler> + { + private readonly IEntityLocker _entityLocker; + private readonly ILogger _logger; + private readonly IMediaSourceRepository _mediaSourceRepository; + private readonly IPlexMovieLibraryScanner _plexMovieLibraryScanner; + private readonly IPlexSecretStore _plexSecretStore; + + public SynchronizePlexLibraryByIdHandler( + IMediaSourceRepository mediaSourceRepository, + IPlexSecretStore plexSecretStore, + IPlexMovieLibraryScanner plexMovieLibraryScanner, + IEntityLocker entityLocker, + ILogger logger) + { + _mediaSourceRepository = mediaSourceRepository; + _plexSecretStore = plexSecretStore; + _plexMovieLibraryScanner = plexMovieLibraryScanner; + _entityLocker = entityLocker; + _logger = logger; + } + + public Task> Handle( + ForceSynchronizePlexLibraryById request, + CancellationToken cancellationToken) => Handle(request); + + public Task> Handle( + SynchronizePlexLibraryByIdIfNeeded request, + CancellationToken cancellationToken) => Handle(request); + + private Task> + Handle(ISynchronizePlexLibraryById request) => + Validate(request) + .MapT(parameters => Synchronize(parameters).Map(_ => parameters.Library.Name)) + .Bind(v => v.ToEitherAsync()); + + private async Task Synchronize(RequestParameters parameters) + { + DateTimeOffset lastScan = parameters.Library.LastScan ?? DateTime.MinValue; + if (parameters.ForceScan || lastScan < DateTimeOffset.Now - TimeSpan.FromHours(6)) + { + switch (parameters.Library.MediaKind) + { + case LibraryMediaKind.Movies: + await _plexMovieLibraryScanner.ScanLibrary( + parameters.ConnectionParameters.ActiveConnection, + parameters.ConnectionParameters.PlexServerAuthToken, + parameters.Library); + break; + case LibraryMediaKind.Shows: + // TODO: plex tv scanner + // await _televisionFolderScanner.ScanFolder(parameters.LocalMediaSource, parameters.FFprobePath); + break; + } + + parameters.Library.LastScan = DateTimeOffset.Now; + await _mediaSourceRepository.Update(parameters.Library); + } + else + { + _logger.LogDebug( + "Skipping unforced scan of plex media library {Name}", + parameters.Library.Name); + } + + // _entityLocker.UnlockMediaSource(parameters.MediaSource.Id); + return Unit.Default; + } + + private async Task> Validate(ISynchronizePlexLibraryById request) => + (await ValidateConnection(request), await PlexLibraryMustExist(request)) + .Apply( + (connectionParameters, plexLibrary) => new RequestParameters( + connectionParameters, + plexLibrary, + request.ForceScan + )); + + private Task> ValidateConnection( + ISynchronizePlexLibraryById request) => + PlexMediaSourceMustExist(request) + .BindT(MediaSourceMustHaveActiveConnection) + .BindT(MediaSourceMustHaveToken); + + private Task> PlexMediaSourceMustExist( + ISynchronizePlexLibraryById request) => + _mediaSourceRepository.GetPlex(request.PlexMediaSourceId) + .Map(v => v.ToValidation($"Plex media source {request.PlexMediaSourceId} does not exist.")); + + private Validation MediaSourceMustHaveActiveConnection( + PlexMediaSource plexMediaSource) + { + Option maybeConnection = + plexMediaSource.Connections.SingleOrDefault(c => c.IsActive); + return maybeConnection.Map(connection => new ConnectionParameters(plexMediaSource, connection)) + .ToValidation("Plex media source requires an active connection"); + } + + private async Task> MediaSourceMustHaveToken( + ConnectionParameters connectionParameters) + { + Option maybeToken = await + _plexSecretStore.GetServerAuthToken(connectionParameters.PlexMediaSource.ClientIdentifier); + return maybeToken.Map(token => connectionParameters with { PlexServerAuthToken = token }) + .ToValidation("Plex media source requires a token"); + } + + private Task> PlexLibraryMustExist( + ISynchronizePlexLibraryById request) => + _mediaSourceRepository.GetPlexLibrary(request.PlexLibraryId) + .Map(v => v.ToValidation($"Plex library {request.PlexLibraryId} does not exist.")); + + private record RequestParameters( + ConnectionParameters ConnectionParameters, + PlexLibrary Library, + bool ForceScan); + + private record ConnectionParameters( + PlexMediaSource PlexMediaSource, + PlexConnection ActiveConnection) + { + public PlexServerAuthToken PlexServerAuthToken { get; set; } + } + } +} diff --git a/ErsatzTV.Application/MediaSources/Commands/SynchronizePlexMediaSources.cs b/ErsatzTV.Application/Plex/Commands/SynchronizePlexMediaSources.cs similarity index 83% rename from ErsatzTV.Application/MediaSources/Commands/SynchronizePlexMediaSources.cs rename to ErsatzTV.Application/Plex/Commands/SynchronizePlexMediaSources.cs index 55d1bab42..c1c7680a5 100644 --- a/ErsatzTV.Application/MediaSources/Commands/SynchronizePlexMediaSources.cs +++ b/ErsatzTV.Application/Plex/Commands/SynchronizePlexMediaSources.cs @@ -4,7 +4,7 @@ using ErsatzTV.Core.Domain; using LanguageExt; using MediatR; -namespace ErsatzTV.Application.MediaSources.Commands +namespace ErsatzTV.Application.Plex.Commands { public record SynchronizePlexMediaSources : IRequest>>, IPlexBackgroundServiceRequest; diff --git a/ErsatzTV.Application/MediaSources/Commands/SynchronizePlexMediaSourcesHandler.cs b/ErsatzTV.Application/Plex/Commands/SynchronizePlexMediaSourcesHandler.cs similarity index 93% rename from ErsatzTV.Application/MediaSources/Commands/SynchronizePlexMediaSourcesHandler.cs rename to ErsatzTV.Application/Plex/Commands/SynchronizePlexMediaSourcesHandler.cs index 9cd53fe14..a1e45e261 100644 --- a/ErsatzTV.Application/MediaSources/Commands/SynchronizePlexMediaSourcesHandler.cs +++ b/ErsatzTV.Application/Plex/Commands/SynchronizePlexMediaSourcesHandler.cs @@ -9,7 +9,7 @@ using ErsatzTV.Core.Interfaces.Repositories; using LanguageExt; using MediatR; -namespace ErsatzTV.Application.MediaSources.Commands +namespace ErsatzTV.Application.Plex.Commands { public class SynchronizePlexMediaSourcesHandler : IRequestHandler { existing.ProductVersion = server.ProductVersion; - existing.Name = server.Name; + existing.ServerName = server.ServerName; MergeConnections(existing.Connections, server.Connections); if (existing.Connections.Any() && existing.Connections.All(c => !c.IsActive)) { @@ -72,8 +72,8 @@ namespace ErsatzTV.Application.MediaSources.Commands } private void MergeConnections( - List existing, - List incoming) + List existing, + List incoming) { var toAdd = incoming.Filter(connection => existing.All(c => c.Uri != connection.Uri)).ToList(); var toRemove = existing.Filter(connection => incoming.All(c => c.Uri != connection.Uri)).ToList(); diff --git a/ErsatzTV.Application/MediaSources/Commands/TryCompletePlexPinFlow.cs b/ErsatzTV.Application/Plex/Commands/TryCompletePlexPinFlow.cs similarity index 81% rename from ErsatzTV.Application/MediaSources/Commands/TryCompletePlexPinFlow.cs rename to ErsatzTV.Application/Plex/Commands/TryCompletePlexPinFlow.cs index e6d16ea40..97dc66389 100644 --- a/ErsatzTV.Application/MediaSources/Commands/TryCompletePlexPinFlow.cs +++ b/ErsatzTV.Application/Plex/Commands/TryCompletePlexPinFlow.cs @@ -3,7 +3,7 @@ using ErsatzTV.Core.Plex; using LanguageExt; using MediatR; -namespace ErsatzTV.Application.MediaSources.Commands +namespace ErsatzTV.Application.Plex.Commands { public record TryCompletePlexPinFlow(PlexAuthPin AuthPin) : IRequest>, IPlexBackgroundServiceRequest; diff --git a/ErsatzTV.Application/MediaSources/Commands/TryCompletePlexPinFlowHandler.cs b/ErsatzTV.Application/Plex/Commands/TryCompletePlexPinFlowHandler.cs similarity index 96% rename from ErsatzTV.Application/MediaSources/Commands/TryCompletePlexPinFlowHandler.cs rename to ErsatzTV.Application/Plex/Commands/TryCompletePlexPinFlowHandler.cs index 3bdadd988..c8f535b29 100644 --- a/ErsatzTV.Application/MediaSources/Commands/TryCompletePlexPinFlowHandler.cs +++ b/ErsatzTV.Application/Plex/Commands/TryCompletePlexPinFlowHandler.cs @@ -7,7 +7,7 @@ using ErsatzTV.Core.Interfaces.Plex; using LanguageExt; using MediatR; -namespace ErsatzTV.Application.MediaSources.Commands +namespace ErsatzTV.Application.Plex.Commands { public class TryCompletePlexPinFlowHandler : IRequestHandler> { diff --git a/ErsatzTV.Application/Plex/Commands/UpdatePlexLibraryPreferences.cs b/ErsatzTV.Application/Plex/Commands/UpdatePlexLibraryPreferences.cs new file mode 100644 index 000000000..244c15f8d --- /dev/null +++ b/ErsatzTV.Application/Plex/Commands/UpdatePlexLibraryPreferences.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using ErsatzTV.Core; +using LanguageExt; + +namespace ErsatzTV.Application.Plex.Commands +{ + public record UpdatePlexLibraryPreferences + (List Preferences) : MediatR.IRequest>; + + public record PlexLibraryPreference(int Id, bool ShouldSyncItems); +} diff --git a/ErsatzTV.Application/Plex/Commands/UpdatePlexLibraryPreferencesHandler.cs b/ErsatzTV.Application/Plex/Commands/UpdatePlexLibraryPreferencesHandler.cs new file mode 100644 index 000000000..831811b35 --- /dev/null +++ b/ErsatzTV.Application/Plex/Commands/UpdatePlexLibraryPreferencesHandler.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using ErsatzTV.Core; +using ErsatzTV.Core.Interfaces.Repositories; +using LanguageExt; + +namespace ErsatzTV.Application.Plex.Commands +{ + public class + UpdatePlexLibraryPreferencesHandler : MediatR.IRequestHandler> + { + private readonly IMediaSourceRepository _mediaSourceRepository; + + public UpdatePlexLibraryPreferencesHandler(IMediaSourceRepository mediaSourceRepository) => + _mediaSourceRepository = mediaSourceRepository; + + public async Task> Handle( + UpdatePlexLibraryPreferences request, + CancellationToken cancellationToken) + { + IEnumerable toDisable = request.Preferences.Filter(p => p.ShouldSyncItems == false).Map(p => p.Id); + await _mediaSourceRepository.DisablePlexLibrarySync(toDisable); + + IEnumerable toEnable = request.Preferences.Filter(p => p.ShouldSyncItems).Map(p => p.Id); + await _mediaSourceRepository.EnablePlexLibrarySync(toEnable); + + return Unit.Default; + } + } +} diff --git a/ErsatzTV.Application/Plex/Mapper.cs b/ErsatzTV.Application/Plex/Mapper.cs new file mode 100644 index 000000000..8c80e28f0 --- /dev/null +++ b/ErsatzTV.Application/Plex/Mapper.cs @@ -0,0 +1,18 @@ +using System.Linq; +using ErsatzTV.Core.Domain; +using static LanguageExt.Prelude; + +namespace ErsatzTV.Application.Plex +{ + internal static class Mapper + { + internal static PlexMediaSourceViewModel ProjectToViewModel(PlexMediaSource plexMediaSource) => + new( + plexMediaSource.Id, + plexMediaSource.ServerName, + Optional(plexMediaSource.Connections.SingleOrDefault(c => c.IsActive)).Match(c => c.Uri, string.Empty)); + + internal static PlexLibraryViewModel ProjectToViewModel(PlexLibrary library) => + new(library.Id, library.Name, library.MediaKind, library.ShouldSyncItems); + } +} diff --git a/ErsatzTV.Application/Plex/PlexLibraryViewModel.cs b/ErsatzTV.Application/Plex/PlexLibraryViewModel.cs new file mode 100644 index 000000000..d0dae98ee --- /dev/null +++ b/ErsatzTV.Application/Plex/PlexLibraryViewModel.cs @@ -0,0 +1,6 @@ +using ErsatzTV.Core.Domain; + +namespace ErsatzTV.Application.Plex +{ + public record PlexLibraryViewModel(int Id, string Name, LibraryMediaKind MediaKind, bool ShouldSyncItems); +} diff --git a/ErsatzTV.Application/Plex/PlexMediaSourceViewModel.cs b/ErsatzTV.Application/Plex/PlexMediaSourceViewModel.cs new file mode 100644 index 000000000..a5567d43b --- /dev/null +++ b/ErsatzTV.Application/Plex/PlexMediaSourceViewModel.cs @@ -0,0 +1,6 @@ +using ErsatzTV.Application.MediaSources; + +namespace ErsatzTV.Application.Plex +{ + public record PlexMediaSourceViewModel(int Id, string Name, string Address) : MediaSourceViewModel(Id, Name); +} diff --git a/ErsatzTV.Application/MediaSources/Queries/GetAllPlexMediaSources.cs b/ErsatzTV.Application/Plex/Queries/GetAllPlexMediaSources.cs similarity index 73% rename from ErsatzTV.Application/MediaSources/Queries/GetAllPlexMediaSources.cs rename to ErsatzTV.Application/Plex/Queries/GetAllPlexMediaSources.cs index 741332396..c99fa95fc 100644 --- a/ErsatzTV.Application/MediaSources/Queries/GetAllPlexMediaSources.cs +++ b/ErsatzTV.Application/Plex/Queries/GetAllPlexMediaSources.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using MediatR; -namespace ErsatzTV.Application.MediaSources.Queries +namespace ErsatzTV.Application.Plex.Queries { public record GetAllPlexMediaSources : IRequest>; } diff --git a/ErsatzTV.Application/MediaSources/Queries/GetAllPlexMediaSourcesHandler.cs b/ErsatzTV.Application/Plex/Queries/GetAllPlexMediaSourcesHandler.cs similarity index 88% rename from ErsatzTV.Application/MediaSources/Queries/GetAllPlexMediaSourcesHandler.cs rename to ErsatzTV.Application/Plex/Queries/GetAllPlexMediaSourcesHandler.cs index f124bdb29..22761238f 100644 --- a/ErsatzTV.Application/MediaSources/Queries/GetAllPlexMediaSourcesHandler.cs +++ b/ErsatzTV.Application/Plex/Queries/GetAllPlexMediaSourcesHandler.cs @@ -5,9 +5,9 @@ using System.Threading.Tasks; using ErsatzTV.Core.Interfaces.Repositories; using LanguageExt; using MediatR; -using static ErsatzTV.Application.MediaSources.Mapper; +using static ErsatzTV.Application.Plex.Mapper; -namespace ErsatzTV.Application.MediaSources.Queries +namespace ErsatzTV.Application.Plex.Queries { public class GetAllPlexMediaSourcesHandler : IRequestHandler> { diff --git a/ErsatzTV.Application/Plex/Queries/GetPlexLibrariesBySourceId.cs b/ErsatzTV.Application/Plex/Queries/GetPlexLibrariesBySourceId.cs new file mode 100644 index 000000000..11da4bc88 --- /dev/null +++ b/ErsatzTV.Application/Plex/Queries/GetPlexLibrariesBySourceId.cs @@ -0,0 +1,7 @@ +using System.Collections.Generic; +using MediatR; + +namespace ErsatzTV.Application.Plex.Queries +{ + public record GetPlexLibrariesBySourceId(int PlexMediaSourceId) : IRequest>; +} diff --git a/ErsatzTV.Application/Plex/Queries/GetPlexLibrariesBySourceIdHandler.cs b/ErsatzTV.Application/Plex/Queries/GetPlexLibrariesBySourceIdHandler.cs new file mode 100644 index 000000000..e73dd612f --- /dev/null +++ b/ErsatzTV.Application/Plex/Queries/GetPlexLibrariesBySourceIdHandler.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using ErsatzTV.Core.Interfaces.Repositories; +using LanguageExt; +using MediatR; +using static ErsatzTV.Application.Plex.Mapper; + +namespace ErsatzTV.Application.Plex.Queries +{ + public class + GetPlexLibrariesBySourceIdHandler : IRequestHandler> + { + private readonly IMediaSourceRepository _mediaSourceRepository; + + public GetPlexLibrariesBySourceIdHandler(IMediaSourceRepository mediaSourceRepository) => + _mediaSourceRepository = mediaSourceRepository; + + public Task> Handle( + GetPlexLibrariesBySourceId request, + CancellationToken cancellationToken) => + _mediaSourceRepository.GetPlexLibraries(request.PlexMediaSourceId) + .Map(list => list.Map(ProjectToViewModel).ToList()); + } +} diff --git a/ErsatzTV.Application/Plex/Queries/GetPlexMediaSourceById.cs b/ErsatzTV.Application/Plex/Queries/GetPlexMediaSourceById.cs new file mode 100644 index 000000000..5fdc05044 --- /dev/null +++ b/ErsatzTV.Application/Plex/Queries/GetPlexMediaSourceById.cs @@ -0,0 +1,7 @@ +using LanguageExt; +using MediatR; + +namespace ErsatzTV.Application.Plex.Queries +{ + public record GetPlexMediaSourceById(int PlexMediaSourceId) : IRequest>; +} diff --git a/ErsatzTV.Application/Plex/Queries/GetPlexMediaSourceByIdHandler.cs b/ErsatzTV.Application/Plex/Queries/GetPlexMediaSourceByIdHandler.cs new file mode 100644 index 000000000..8bd9f9f19 --- /dev/null +++ b/ErsatzTV.Application/Plex/Queries/GetPlexMediaSourceByIdHandler.cs @@ -0,0 +1,23 @@ +using System.Threading; +using System.Threading.Tasks; +using ErsatzTV.Core.Interfaces.Repositories; +using LanguageExt; +using MediatR; +using static ErsatzTV.Application.Plex.Mapper; + +namespace ErsatzTV.Application.Plex.Queries +{ + public class + GetPlexMediaSourceByIdHandler : IRequestHandler> + { + private readonly IMediaSourceRepository _mediaSourceRepository; + + public GetPlexMediaSourceByIdHandler(IMediaSourceRepository mediaSourceRepository) => + _mediaSourceRepository = mediaSourceRepository; + + public Task> Handle( + GetPlexMediaSourceById request, + CancellationToken cancellationToken) => + _mediaSourceRepository.GetPlex(request.PlexMediaSourceId).MapT(ProjectToViewModel); + } +} diff --git a/ErsatzTV.Core.Tests/Fakes/FakeLocalFileSystem.cs b/ErsatzTV.Core.Tests/Fakes/FakeLocalFileSystem.cs index 0e28632c8..84233f643 100644 --- a/ErsatzTV.Core.Tests/Fakes/FakeLocalFileSystem.cs +++ b/ErsatzTV.Core.Tests/Fakes/FakeLocalFileSystem.cs @@ -41,8 +41,8 @@ namespace ErsatzTV.Core.Tests.Fakes .Map(f => f.LastWriteTime) .IfNone(DateTime.MinValue); - public bool IsMediaSourceAccessible(LocalMediaSource localMediaSource) => - _files.Any(f => f.Path.StartsWith(localMediaSource.Folder)); + public bool IsLibraryPathAccessible(LibraryPath libraryPath) => + _files.Any(f => f.Path.StartsWith(libraryPath.Path + Path.DirectorySeparatorChar)); public IEnumerable ListSubdirectories(string folder) => _folders.Map(f => f.Path).Filter(f => f.StartsWith(folder) && Directory.GetParent(f)?.FullName == folder); diff --git a/ErsatzTV.Core.Tests/Fakes/FakeMediaCollectionRepository.cs b/ErsatzTV.Core.Tests/Fakes/FakeMediaCollectionRepository.cs index 62142b4b7..eb788716c 100644 --- a/ErsatzTV.Core.Tests/Fakes/FakeMediaCollectionRepository.cs +++ b/ErsatzTV.Core.Tests/Fakes/FakeMediaCollectionRepository.cs @@ -11,9 +11,9 @@ namespace ErsatzTV.Core.Tests.Fakes { public class FakeMediaCollectionRepository : IMediaCollectionRepository { - private readonly Map> _data; + private readonly Map> _data; - public FakeMediaCollectionRepository(Map> data) => _data = data; + public FakeMediaCollectionRepository(Map> data) => _data = data; public Task Add(SimpleMediaCollection collection) => throw new NotSupportedException(); diff --git a/ErsatzTV.Core.Tests/Fakes/FakeTelevisionRepository.cs b/ErsatzTV.Core.Tests/Fakes/FakeTelevisionRepository.cs index 002c86c73..70eb00d8a 100644 --- a/ErsatzTV.Core.Tests/Fakes/FakeTelevisionRepository.cs +++ b/ErsatzTV.Core.Tests/Fakes/FakeTelevisionRepository.cs @@ -67,7 +67,7 @@ namespace ErsatzTV.Core.Tests.Fakes public Task> GetOrAddEpisode( TelevisionSeason season, - int mediaSourceId, + LibraryPath libraryPath, string path) => throw new NotSupportedException(); public Task DeleteMissingSources(int localMediaSourceId, List allFolders) => diff --git a/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs b/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs index 6d1a5ad67..6136db275 100644 --- a/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs +++ b/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs @@ -39,7 +39,7 @@ namespace ErsatzTV.Core.Tests.Metadata { TelevisionEpisodeMetadata metadata = FallbackMetadataProvider.GetFallbackMetadata( new TelevisionEpisodeMediaItem - { Path = path, Source = new LocalMediaSource { MediaType = MediaType.TvShow } }); + { Path = path, LibraryPath = new LibraryPath() }); metadata.Title.Should().Be(title); metadata.Season.Should().Be(season); diff --git a/ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs b/ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs index 8f0e75df1..513abc9e8 100644 --- a/ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs +++ b/ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs @@ -40,10 +40,10 @@ namespace ErsatzTV.Core.Tests.Metadata public void SetUp() { _movieRepository = new Mock(); - _movieRepository.Setup(x => x.GetOrAdd(It.IsAny(), It.IsAny())) + _movieRepository.Setup(x => x.GetOrAdd(It.IsAny(), It.IsAny())) .Returns( - (int _, string path) => - Right(new MovieMediaItem { Path = path }).AsTask()); + (LibraryPath _, string path) => + Right(new Movie { Path = path }).AsTask()); _localStatisticsProvider = new Mock(); _localMetadataProvider = new Mock(); @@ -65,9 +65,9 @@ namespace ErsatzTV.Core.Tests.Metadata MovieFolderScanner service = GetService( new FakeFileEntry(Path.Combine(FakeRoot, Path.Combine("Movie (2020)", "Movie (2020).mkv"))) ); - var source = new LocalMediaSource { Folder = BadFakeRoot }; + var libraryPath = new LibraryPath { Path = BadFakeRoot }; - Either result = await service.ScanFolder(source, FFprobePath); + Either result = await service.ScanFolder(libraryPath, FFprobePath); result.IsLeft.Should().BeTrue(); result.IfLeft(error => error.Should().BeOfType()); @@ -85,14 +85,14 @@ namespace ErsatzTV.Core.Tests.Metadata MovieFolderScanner service = GetService( new FakeFileEntry(moviePath) ); - var source = new LocalMediaSource { Id = 1, Folder = FakeRoot }; + var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot }; - Either result = await service.ScanFolder(source, FFprobePath); + Either result = await service.ScanFolder(libraryPath, FFprobePath); result.IsRight.Should().BeTrue(); - _movieRepository.Verify(x => x.GetOrAdd(It.IsAny(), It.IsAny()), Times.Once); - _movieRepository.Verify(x => x.GetOrAdd(1, moviePath), Times.Once); + _movieRepository.Verify(x => x.GetOrAdd(It.IsAny(), It.IsAny()), Times.Once); + _movieRepository.Verify(x => x.GetOrAdd(libraryPath, moviePath), Times.Once); _localStatisticsProvider.Verify( x => x.RefreshStatistics(FFprobePath, It.Is(i => i.Path == moviePath)), @@ -118,14 +118,14 @@ namespace ErsatzTV.Core.Tests.Metadata new FakeFileEntry(moviePath), new FakeFileEntry(metadataPath) ); - var source = new LocalMediaSource { Id = 1, Folder = FakeRoot }; + var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot }; - Either result = await service.ScanFolder(source, FFprobePath); + Either result = await service.ScanFolder(libraryPath, FFprobePath); result.IsRight.Should().BeTrue(); - _movieRepository.Verify(x => x.GetOrAdd(It.IsAny(), It.IsAny()), Times.Once); - _movieRepository.Verify(x => x.GetOrAdd(1, moviePath), Times.Once); + _movieRepository.Verify(x => x.GetOrAdd(It.IsAny(), It.IsAny()), Times.Once); + _movieRepository.Verify(x => x.GetOrAdd(libraryPath, moviePath), Times.Once); _localStatisticsProvider.Verify( x => x.RefreshStatistics(FFprobePath, It.Is(i => i.Path == moviePath)), @@ -151,14 +151,14 @@ namespace ErsatzTV.Core.Tests.Metadata new FakeFileEntry(moviePath), new FakeFileEntry(metadataPath) ); - var source = new LocalMediaSource { Id = 1, Folder = FakeRoot }; + var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot }; - Either result = await service.ScanFolder(source, FFprobePath); + Either result = await service.ScanFolder(libraryPath, FFprobePath); result.IsRight.Should().BeTrue(); - _movieRepository.Verify(x => x.GetOrAdd(It.IsAny(), It.IsAny()), Times.Once); - _movieRepository.Verify(x => x.GetOrAdd(1, moviePath), Times.Once); + _movieRepository.Verify(x => x.GetOrAdd(It.IsAny(), It.IsAny()), Times.Once); + _movieRepository.Verify(x => x.GetOrAdd(libraryPath, moviePath), Times.Once); _localStatisticsProvider.Verify( x => x.RefreshStatistics(FFprobePath, It.Is(i => i.Path == moviePath)), @@ -188,14 +188,14 @@ namespace ErsatzTV.Core.Tests.Metadata new FakeFileEntry(moviePath), new FakeFileEntry(posterPath) ); - var source = new LocalMediaSource { Id = 1, Folder = FakeRoot }; + var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot }; - Either result = await service.ScanFolder(source, FFprobePath); + Either result = await service.ScanFolder(libraryPath, FFprobePath); result.IsRight.Should().BeTrue(); - _movieRepository.Verify(x => x.GetOrAdd(It.IsAny(), It.IsAny()), Times.Once); - _movieRepository.Verify(x => x.GetOrAdd(1, moviePath), Times.Once); + _movieRepository.Verify(x => x.GetOrAdd(It.IsAny(), It.IsAny()), Times.Once); + _movieRepository.Verify(x => x.GetOrAdd(libraryPath, moviePath), Times.Once); _localStatisticsProvider.Verify( x => x.RefreshStatistics(FFprobePath, It.Is(i => i.Path == moviePath)), @@ -229,14 +229,14 @@ namespace ErsatzTV.Core.Tests.Metadata new FakeFileEntry(moviePath), new FakeFileEntry(posterPath) ); - var source = new LocalMediaSource { Id = 1, Folder = FakeRoot }; + var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot }; - Either result = await service.ScanFolder(source, FFprobePath); + Either result = await service.ScanFolder(libraryPath, FFprobePath); result.IsRight.Should().BeTrue(); - _movieRepository.Verify(x => x.GetOrAdd(It.IsAny(), It.IsAny()), Times.Once); - _movieRepository.Verify(x => x.GetOrAdd(1, moviePath), Times.Once); + _movieRepository.Verify(x => x.GetOrAdd(It.IsAny(), It.IsAny()), Times.Once); + _movieRepository.Verify(x => x.GetOrAdd(libraryPath, moviePath), Times.Once); _localStatisticsProvider.Verify( x => x.RefreshStatistics(FFprobePath, It.Is(i => i.Path == moviePath)), @@ -269,14 +269,14 @@ namespace ErsatzTV.Core.Tests.Metadata Path.GetDirectoryName(moviePath) ?? string.Empty, $"Movie (2020)-{extraFile}{videoExtension}")) ); - var source = new LocalMediaSource { Id = 1, Folder = FakeRoot }; + var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot }; - Either result = await service.ScanFolder(source, FFprobePath); + Either result = await service.ScanFolder(libraryPath, FFprobePath); result.IsRight.Should().BeTrue(); - _movieRepository.Verify(x => x.GetOrAdd(It.IsAny(), It.IsAny()), Times.Once); - _movieRepository.Verify(x => x.GetOrAdd(1, moviePath), Times.Once); + _movieRepository.Verify(x => x.GetOrAdd(It.IsAny(), It.IsAny()), Times.Once); + _movieRepository.Verify(x => x.GetOrAdd(libraryPath, moviePath), Times.Once); _localStatisticsProvider.Verify( x => x.RefreshStatistics(FFprobePath, It.Is(i => i.Path == moviePath)), @@ -305,14 +305,14 @@ namespace ErsatzTV.Core.Tests.Metadata Path.GetDirectoryName(moviePath) ?? string.Empty, Path.Combine(extraFolder, $"Movie (2020){videoExtension}"))) ); - var source = new LocalMediaSource { Id = 1, Folder = FakeRoot }; + var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot }; - Either result = await service.ScanFolder(source, FFprobePath); + Either result = await service.ScanFolder(libraryPath, FFprobePath); result.IsRight.Should().BeTrue(); - _movieRepository.Verify(x => x.GetOrAdd(It.IsAny(), It.IsAny()), Times.Once); - _movieRepository.Verify(x => x.GetOrAdd(1, moviePath), Times.Once); + _movieRepository.Verify(x => x.GetOrAdd(It.IsAny(), It.IsAny()), Times.Once); + _movieRepository.Verify(x => x.GetOrAdd(libraryPath, moviePath), Times.Once); _localStatisticsProvider.Verify( x => x.RefreshStatistics(FFprobePath, It.Is(i => i.Path == moviePath)), @@ -335,14 +335,14 @@ namespace ErsatzTV.Core.Tests.Metadata MovieFolderScanner service = GetService( new FakeFileEntry(moviePath) ); - var source = new LocalMediaSource { Id = 1, Folder = FakeRoot }; + var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot }; - Either result = await service.ScanFolder(source, FFprobePath); + Either result = await service.ScanFolder(libraryPath, FFprobePath); result.IsRight.Should().BeTrue(); - _movieRepository.Verify(x => x.GetOrAdd(It.IsAny(), It.IsAny()), Times.Once); - _movieRepository.Verify(x => x.GetOrAdd(1, moviePath), Times.Once); + _movieRepository.Verify(x => x.GetOrAdd(It.IsAny(), It.IsAny()), Times.Once); + _movieRepository.Verify(x => x.GetOrAdd(libraryPath, moviePath), Times.Once); _localStatisticsProvider.Verify( x => x.RefreshStatistics(FFprobePath, It.Is(i => i.Path == moviePath)), diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs index acee730a0..1e616ac57 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs @@ -40,7 +40,7 @@ namespace ErsatzTV.Core.Tests.Scheduling [Test] public async Task InitialFlood_Should_StartAtMidnight() { - var mediaItems = new List + var mediaItems = new List { TestMovie(1, TimeSpan.FromHours(6), DateTime.Today) }; @@ -59,7 +59,7 @@ namespace ErsatzTV.Core.Tests.Scheduling [Test] public async Task InitialFlood_Should_StartAtMidnight_With_LateStart() { - var mediaItems = new List + var mediaItems = new List { TestMovie(1, TimeSpan.FromHours(6), DateTime.Today) }; @@ -79,7 +79,7 @@ namespace ErsatzTV.Core.Tests.Scheduling [Test] public async Task ChronologicalContent_Should_CreateChronologicalItems() { - var mediaItems = new List + var mediaItems = new List { TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) @@ -105,7 +105,7 @@ namespace ErsatzTV.Core.Tests.Scheduling [Test] public async Task ChronologicalFlood_Should_AnchorAndMaintainExistingPlayout() { - var mediaItems = new List + var mediaItems = new List { TestMovie(1, TimeSpan.FromHours(6), DateTime.Today), TestMovie(2, TimeSpan.FromHours(6), DateTime.Today.AddHours(1)) @@ -142,7 +142,7 @@ namespace ErsatzTV.Core.Tests.Scheduling [Test] public async Task ChronologicalFlood_Should_AnchorAndReturnNewPlayoutItems() { - var mediaItems = new List + var mediaItems = new List { TestMovie(1, TimeSpan.FromHours(6), DateTime.Today), TestMovie(2, TimeSpan.FromHours(6), DateTime.Today.AddHours(1)) @@ -180,7 +180,7 @@ namespace ErsatzTV.Core.Tests.Scheduling [Test] public async Task ShuffleFloodRebuild_Should_IgnoreAnchors() { - var mediaItems = new List + var mediaItems = new List { TestMovie(1, TimeSpan.FromHours(1), DateTime.Today), TestMovie(2, TimeSpan.FromHours(1), DateTime.Today.AddHours(1)), @@ -223,7 +223,7 @@ namespace ErsatzTV.Core.Tests.Scheduling [Test] public async Task ShuffleFlood_Should_MaintainRandomSeed() { - var mediaItems = new List + var mediaItems = new List { TestMovie(1, TimeSpan.FromHours(1), DateTime.Today), TestMovie(2, TimeSpan.FromHours(1), DateTime.Today.AddHours(1)), @@ -259,7 +259,7 @@ namespace ErsatzTV.Core.Tests.Scheduling { Id = 1, Name = "Flood Items", - Movies = new List + Movies = new List { TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) @@ -270,7 +270,7 @@ namespace ErsatzTV.Core.Tests.Scheduling { Id = 2, Name = "Fixed Items", - Movies = new List + Movies = new List { TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)) } @@ -337,7 +337,7 @@ namespace ErsatzTV.Core.Tests.Scheduling { Id = 1, Name = "Flood Items", - Movies = new List + Movies = new List { TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) @@ -348,7 +348,7 @@ namespace ErsatzTV.Core.Tests.Scheduling { Id = 2, Name = "Fixed Items", - Movies = new List + Movies = new List { TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)), TestMovie(4, TimeSpan.FromHours(1), new DateTime(2020, 1, 2)) @@ -422,7 +422,7 @@ namespace ErsatzTV.Core.Tests.Scheduling { Id = 1, Name = "Flood Items", - Movies = new List + Movies = new List { TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) @@ -433,7 +433,7 @@ namespace ErsatzTV.Core.Tests.Scheduling { Id = 2, Name = "Fixed Items", - Movies = new List + Movies = new List { TestMovie(3, TimeSpan.FromHours(0.75), new DateTime(2020, 1, 1)), TestMovie(4, TimeSpan.FromHours(1.5), new DateTime(2020, 1, 2)) @@ -511,7 +511,7 @@ namespace ErsatzTV.Core.Tests.Scheduling { Id = 1, Name = "Multiple Items", - Movies = new List + Movies = new List { TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) @@ -522,7 +522,7 @@ namespace ErsatzTV.Core.Tests.Scheduling { Id = 2, Name = "Dynamic Items", - Movies = new List + Movies = new List { TestMovie(3, TimeSpan.FromHours(0.75), new DateTime(2020, 1, 1)), TestMovie(4, TimeSpan.FromHours(1.5), new DateTime(2020, 1, 2)) @@ -607,7 +607,7 @@ namespace ErsatzTV.Core.Tests.Scheduling StartTime = null }; - private static MovieMediaItem TestMovie(int id, TimeSpan duration, DateTime aired) => + private static Movie TestMovie(int id, TimeSpan duration, DateTime aired) => new() { Id = id, @@ -615,7 +615,7 @@ namespace ErsatzTV.Core.Tests.Scheduling Statistics = new MediaItemStatistics { Duration = duration } }; - private TestData TestDataFloodForItems(List mediaItems, PlaybackOrder playbackOrder) + private TestData TestDataFloodForItems(List mediaItems, PlaybackOrder playbackOrder) { var mediaCollection = new SimpleMediaCollection { diff --git a/ErsatzTV.Core/Domain/Collection/Collection.cs b/ErsatzTV.Core/Domain/Collection/Collection.cs new file mode 100644 index 000000000..9f1dc2926 --- /dev/null +++ b/ErsatzTV.Core/Domain/Collection/Collection.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace ErsatzTV.Core.Domain +{ + public class Collection + { + public int Id { get; set; } + public string Name { get; set; } + public List MediaItems { get; set; } + public List CollectionItems { get; set; } + } +} diff --git a/ErsatzTV.Core/Domain/Collection/CollectionItem.cs b/ErsatzTV.Core/Domain/Collection/CollectionItem.cs new file mode 100644 index 000000000..4581d1247 --- /dev/null +++ b/ErsatzTV.Core/Domain/Collection/CollectionItem.cs @@ -0,0 +1,10 @@ +namespace ErsatzTV.Core.Domain +{ + public class CollectionItem + { + public int CollectionId { get; set; } + public Collection Collection { get; set; } + public int MediaItemId { get; set; } + public MediaItem MediaItem { get; set; } + } +} diff --git a/ErsatzTV.Core/Domain/Library/Library.cs b/ErsatzTV.Core/Domain/Library/Library.cs new file mode 100644 index 000000000..082019c66 --- /dev/null +++ b/ErsatzTV.Core/Domain/Library/Library.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +namespace ErsatzTV.Core.Domain +{ + public abstract class Library + { + public int Id { get; set; } + public string Name { get; set; } + public LibraryMediaKind MediaKind { get; set; } + public DateTimeOffset? LastScan { get; set; } + + public int MediaSourceId { get; set; } + public MediaSource MediaSource { get; set; } + + public List Paths { get; set; } + } +} diff --git a/ErsatzTV.Core/Domain/Library/LibraryMediaKind.cs b/ErsatzTV.Core/Domain/Library/LibraryMediaKind.cs new file mode 100644 index 000000000..4ac8aab6e --- /dev/null +++ b/ErsatzTV.Core/Domain/Library/LibraryMediaKind.cs @@ -0,0 +1,8 @@ +namespace ErsatzTV.Core.Domain +{ + public enum LibraryMediaKind + { + Movies = 1, + Shows = 2 + } +} diff --git a/ErsatzTV.Core/Domain/Library/LibraryPath.cs b/ErsatzTV.Core/Domain/Library/LibraryPath.cs new file mode 100644 index 000000000..074d7fa76 --- /dev/null +++ b/ErsatzTV.Core/Domain/Library/LibraryPath.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace ErsatzTV.Core.Domain +{ + public class LibraryPath + { + public int Id { get; set; } + public string Path { get; set; } + + public int LibraryId { get; set; } + public Library Library { get; set; } + + public List MediaItems { get; set; } + } +} diff --git a/ErsatzTV.Core/Domain/Library/LocalLibrary.cs b/ErsatzTV.Core/Domain/Library/LocalLibrary.cs new file mode 100644 index 000000000..787f0df50 --- /dev/null +++ b/ErsatzTV.Core/Domain/Library/LocalLibrary.cs @@ -0,0 +1,6 @@ +namespace ErsatzTV.Core.Domain +{ + public class LocalLibrary : Library + { + } +} diff --git a/ErsatzTV.Core/Domain/Library/PlexLibrary.cs b/ErsatzTV.Core/Domain/Library/PlexLibrary.cs new file mode 100644 index 000000000..bbd35c13e --- /dev/null +++ b/ErsatzTV.Core/Domain/Library/PlexLibrary.cs @@ -0,0 +1,8 @@ +namespace ErsatzTV.Core.Domain +{ + public class PlexLibrary : Library + { + public string Key { get; set; } + public bool ShouldSyncItems { get; set; } + } +} diff --git a/ErsatzTV.Core/Domain/LocalMediaSource.cs b/ErsatzTV.Core/Domain/LocalMediaSource.cs deleted file mode 100644 index 23cfcdc9f..000000000 --- a/ErsatzTV.Core/Domain/LocalMediaSource.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ErsatzTV.Core.Domain -{ - public class LocalMediaSource : MediaSource - { - public LocalMediaSource() => SourceType = MediaSourceType.Local; - - public MediaType MediaType { get; set; } - public string Folder { get; set; } - } -} diff --git a/ErsatzTV.Core/Domain/MediaItem.cs b/ErsatzTV.Core/Domain/MediaItem.cs deleted file mode 100644 index a17b305e4..000000000 --- a/ErsatzTV.Core/Domain/MediaItem.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using ErsatzTV.Core.Interfaces.Domain; - -namespace ErsatzTV.Core.Domain -{ - public class MediaItem : IHasAPoster - { - public int Id { get; set; } - public int MediaSourceId { get; set; } - public MediaSource Source { get; set; } - public MediaItemStatistics Statistics { get; set; } - public DateTime? LastWriteTime { get; set; } - public string Path { get; set; } - public string Poster { get; set; } - public DateTime? PosterLastWriteTime { get; set; } - } -} diff --git a/ErsatzTV.Core/Domain/MediaItem/Episode.cs b/ErsatzTV.Core/Domain/MediaItem/Episode.cs new file mode 100644 index 000000000..2fcc5f003 --- /dev/null +++ b/ErsatzTV.Core/Domain/MediaItem/Episode.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace ErsatzTV.Core.Domain +{ + public class Episode : MediaItem + { + public int EpisodeNumber { get; set; } + public int SeasonId { get; set; } + public Season Season { get; set; } + public List EpisodeMetadata { get; set; } + public List MediaVersions { get; set; } + } +} diff --git a/ErsatzTV.Core/Domain/MediaItem/MediaFile.cs b/ErsatzTV.Core/Domain/MediaItem/MediaFile.cs new file mode 100644 index 000000000..5673ec66e --- /dev/null +++ b/ErsatzTV.Core/Domain/MediaItem/MediaFile.cs @@ -0,0 +1,11 @@ +namespace ErsatzTV.Core.Domain +{ + public class MediaFile + { + public int Id { get; set; } + public string Path { get; set; } + + public int MediaVersionId { get; set; } + public MediaVersion MediaVersion { get; set; } + } +} diff --git a/ErsatzTV.Core/Domain/MediaItem/MediaItem.cs b/ErsatzTV.Core/Domain/MediaItem/MediaItem.cs new file mode 100644 index 000000000..0b275d1db --- /dev/null +++ b/ErsatzTV.Core/Domain/MediaItem/MediaItem.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using ErsatzTV.Core.Interfaces.Domain; + +namespace ErsatzTV.Core.Domain +{ + public class MediaItem : IHasAPoster + { + public int Id { get; set; } + public MediaItemStatistics Statistics { get; set; } + public DateTime? LastWriteTime { get; set; } + + public int LibraryPathId { get; set; } + public LibraryPath LibraryPath { get; set; } + public string Path { get; set; } + public string Poster { get; set; } + public DateTime? PosterLastWriteTime { get; set; } + + // temporary fields to help migrations... + public int TelevisionShowId { get; set; } + public int TelevisionSeasonId { get; set; } + public int TelevisionEpisodeId { get; set; } + + public List Collections { get; set; } + public List CollectionItems { get; set; } + } +} diff --git a/ErsatzTV.Core/Domain/MediaItem/MediaVersion.cs b/ErsatzTV.Core/Domain/MediaItem/MediaVersion.cs new file mode 100644 index 000000000..e7151baed --- /dev/null +++ b/ErsatzTV.Core/Domain/MediaItem/MediaVersion.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; + +namespace ErsatzTV.Core.Domain +{ + public class MediaVersion + { + public int Id { get; set; } + public string Name { get; set; } + + public List MediaFiles { get; set; } + + public TimeSpan Duration { get; set; } + public string SampleAspectRatio { get; set; } + public string DisplayAspectRatio { get; set; } + public string VideoCodec { get; set; } + public string AudioCodec { get; set; } + public bool IsInterlaced { get; set; } + public int Width { get; set; } + public int Height { get; set; } + } +} diff --git a/ErsatzTV.Core/Domain/MediaItem/Movie.cs b/ErsatzTV.Core/Domain/MediaItem/Movie.cs new file mode 100644 index 000000000..29b7c5732 --- /dev/null +++ b/ErsatzTV.Core/Domain/MediaItem/Movie.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace ErsatzTV.Core.Domain +{ + public class Movie : MediaItem + { + // TODO: remove MetadataId + public int MetadataId { get; set; } + // TODO: remove Metadata + public MovieMetadata Metadata { get; set; } + public List MovieMetadata { get; set; } + + public List MediaVersions { get; set; } + + // TODO: remove SimpleMediaCollections + public List SimpleMediaCollections { get; set; } + } +} diff --git a/ErsatzTV.Core/Domain/MediaItem/PlexMediaItemPart.cs b/ErsatzTV.Core/Domain/MediaItem/PlexMediaItemPart.cs new file mode 100644 index 000000000..83b803bdb --- /dev/null +++ b/ErsatzTV.Core/Domain/MediaItem/PlexMediaItemPart.cs @@ -0,0 +1,12 @@ +namespace ErsatzTV.Core.Domain +{ + public class PlexMediaItemPart + { + public int Id { get; set; } + public int PlexId { get; set; } + public string Key { get; set; } + public int Duration { get; set; } + public string File { get; set; } + public int Size { get; set; } + } +} diff --git a/ErsatzTV.Core/Domain/MediaItem/PlexMovie.cs b/ErsatzTV.Core/Domain/MediaItem/PlexMovie.cs new file mode 100644 index 000000000..326ced9dc --- /dev/null +++ b/ErsatzTV.Core/Domain/MediaItem/PlexMovie.cs @@ -0,0 +1,8 @@ +namespace ErsatzTV.Core.Domain +{ + public class PlexMovie : Movie + { + public string Key { get; set; } + public PlexMediaItemPart Part { get; set; } + } +} diff --git a/ErsatzTV.Core/Domain/MediaItem/Season.cs b/ErsatzTV.Core/Domain/MediaItem/Season.cs new file mode 100644 index 000000000..69168c3e0 --- /dev/null +++ b/ErsatzTV.Core/Domain/MediaItem/Season.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace ErsatzTV.Core.Domain +{ + public class Season : MediaItem + { + public int SeasonNumber { get; set; } + public int ShowId { get; set; } + public Show Show { get; set; } + + public List Episodes { get; set; } + } +} diff --git a/ErsatzTV.Core/Domain/MediaItem/Show.cs b/ErsatzTV.Core/Domain/MediaItem/Show.cs new file mode 100644 index 000000000..230e6f3d3 --- /dev/null +++ b/ErsatzTV.Core/Domain/MediaItem/Show.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace ErsatzTV.Core.Domain +{ + public class Show : MediaItem + { + public List Seasons { get; set; } + public List ShowMetadata { get; set; } + } +} diff --git a/ErsatzTV.Core/Domain/MediaSource.cs b/ErsatzTV.Core/Domain/MediaSource.cs deleted file mode 100644 index dd81b6afa..000000000 --- a/ErsatzTV.Core/Domain/MediaSource.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace ErsatzTV.Core.Domain -{ - public abstract class MediaSource - { - public int Id { get; set; } - public MediaSourceType SourceType { get; set; } - public string Name { get; set; } - public DateTimeOffset? LastScan { get; set; } - } -} diff --git a/ErsatzTV.Core/Domain/MediaSource/LocalMediaSource.cs b/ErsatzTV.Core/Domain/MediaSource/LocalMediaSource.cs new file mode 100644 index 000000000..ad0c4642c --- /dev/null +++ b/ErsatzTV.Core/Domain/MediaSource/LocalMediaSource.cs @@ -0,0 +1,6 @@ +namespace ErsatzTV.Core.Domain +{ + public class LocalMediaSource : MediaSource + { + } +} diff --git a/ErsatzTV.Core/Domain/MediaSource/MediaSource.cs b/ErsatzTV.Core/Domain/MediaSource/MediaSource.cs new file mode 100644 index 000000000..62340ebd6 --- /dev/null +++ b/ErsatzTV.Core/Domain/MediaSource/MediaSource.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace ErsatzTV.Core.Domain +{ + public abstract class MediaSource + { + public int Id { get; set; } + + public List Libraries { get; set; } + } +} diff --git a/ErsatzTV.Core/Domain/PlexMediaSourceConnection.cs b/ErsatzTV.Core/Domain/MediaSource/PlexConnection.cs similarity index 53% rename from ErsatzTV.Core/Domain/PlexMediaSourceConnection.cs rename to ErsatzTV.Core/Domain/MediaSource/PlexConnection.cs index 9083a5b45..18313b8f2 100644 --- a/ErsatzTV.Core/Domain/PlexMediaSourceConnection.cs +++ b/ErsatzTV.Core/Domain/MediaSource/PlexConnection.cs @@ -1,9 +1,11 @@ namespace ErsatzTV.Core.Domain { - public class PlexMediaSourceConnection + public class PlexConnection { public int Id { get; set; } public bool IsActive { get; set; } public string Uri { get; set; } + public int PlexMediaSourceId { get; set; } + public PlexMediaSource PlexMediaSource { get; set; } } } diff --git a/ErsatzTV.Core/Domain/PlexMediaSource.cs b/ErsatzTV.Core/Domain/MediaSource/PlexMediaSource.cs similarity index 56% rename from ErsatzTV.Core/Domain/PlexMediaSource.cs rename to ErsatzTV.Core/Domain/MediaSource/PlexMediaSource.cs index ef882cce0..4206c93ff 100644 --- a/ErsatzTV.Core/Domain/PlexMediaSource.cs +++ b/ErsatzTV.Core/Domain/MediaSource/PlexMediaSource.cs @@ -4,13 +4,11 @@ namespace ErsatzTV.Core.Domain { public class PlexMediaSource : MediaSource { - public PlexMediaSource() => SourceType = MediaSourceType.Plex; + public string ServerName { get; set; } public string ProductVersion { get; set; } - public string ClientIdentifier { get; set; } // public bool IsOwned { get; set; } - public List Connections { get; set; } - public List Libraries { get; set; } + public List Connections { get; set; } } } diff --git a/ErsatzTV.Core/Domain/MediaSourceType.cs b/ErsatzTV.Core/Domain/MediaSourceType.cs deleted file mode 100644 index 05406efa5..000000000 --- a/ErsatzTV.Core/Domain/MediaSourceType.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ErsatzTV.Core.Domain -{ - public enum MediaSourceType - { - None = 0, - Local = 1, - Plex = 2, - Jellyfin = 3 - } -} diff --git a/ErsatzTV.Core/Domain/MediaType.cs b/ErsatzTV.Core/Domain/MediaType.cs deleted file mode 100644 index 49d208210..000000000 --- a/ErsatzTV.Core/Domain/MediaType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace ErsatzTV.Core.Domain -{ - public enum MediaType - { - Other = 0, - TvShow = 1, - Movie = 2 - } -} diff --git a/ErsatzTV.Core/Domain/Metadata/EpisodeMetadata.cs b/ErsatzTV.Core/Domain/Metadata/EpisodeMetadata.cs new file mode 100644 index 000000000..24121dc0a --- /dev/null +++ b/ErsatzTV.Core/Domain/Metadata/EpisodeMetadata.cs @@ -0,0 +1,11 @@ +namespace ErsatzTV.Core.Domain +{ + public class EpisodeMetadata : Metadata + { + public string Outline { get; set; } + public string Plot { get; set; } + public string Tagline { get; set; } + public int EpisodeId { get; set; } + public Episode Episode { get; set; } + } +} diff --git a/ErsatzTV.Core/Domain/Metadata/Metadata.cs b/ErsatzTV.Core/Domain/Metadata/Metadata.cs new file mode 100644 index 000000000..3b06e29ed --- /dev/null +++ b/ErsatzTV.Core/Domain/Metadata/Metadata.cs @@ -0,0 +1,16 @@ +using System; + +namespace ErsatzTV.Core.Domain +{ + public class Metadata + { + public int Id { get; set; } + public MetadataKind MetadataKind { get; set; } + public string Title { get; set; } + public string OriginalTitle { get; set; } + public string SortTitle { get; set; } + public DateTimeOffset? ReleaseDate { get; set; } + public DateTimeOffset DateAdded { get; set; } + public DateTimeOffset DateUpdated { get; set; } + } +} diff --git a/ErsatzTV.Core/Domain/Metadata/MetadataKind.cs b/ErsatzTV.Core/Domain/Metadata/MetadataKind.cs new file mode 100644 index 000000000..ea86b6cee --- /dev/null +++ b/ErsatzTV.Core/Domain/Metadata/MetadataKind.cs @@ -0,0 +1,8 @@ +namespace ErsatzTV.Core.Domain +{ + public enum MetadataKind + { + Fallback, + Sidecar + } +} diff --git a/ErsatzTV.Core/Domain/Metadata/NewMovieMetadata.cs b/ErsatzTV.Core/Domain/Metadata/NewMovieMetadata.cs new file mode 100644 index 000000000..103e21040 --- /dev/null +++ b/ErsatzTV.Core/Domain/Metadata/NewMovieMetadata.cs @@ -0,0 +1,11 @@ +namespace ErsatzTV.Core.Domain +{ + public class NewMovieMetadata : Metadata + { + public string Outline { get; set; } + public string Plot { get; set; } + public string Tagline { get; set; } + public int MovieId { get; set; } + public Movie Movie { get; set; } + } +} diff --git a/ErsatzTV.Core/Domain/Metadata/ShowMetadata.cs b/ErsatzTV.Core/Domain/Metadata/ShowMetadata.cs new file mode 100644 index 000000000..f9b0141cd --- /dev/null +++ b/ErsatzTV.Core/Domain/Metadata/ShowMetadata.cs @@ -0,0 +1,11 @@ +namespace ErsatzTV.Core.Domain +{ + public class ShowMetadata : Metadata + { + public string Outline { get; set; } + public string Plot { get; set; } + public string Tagline { get; set; } + public int ShowId { get; set; } + public Show Show { get; set; } + } +} diff --git a/ErsatzTV.Core/Domain/MovieMediaItem.cs b/ErsatzTV.Core/Domain/MovieMediaItem.cs deleted file mode 100644 index 9b2d44fee..000000000 --- a/ErsatzTV.Core/Domain/MovieMediaItem.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; - -namespace ErsatzTV.Core.Domain -{ - public class MovieMediaItem : MediaItem - { - public int MetadataId { get; set; } - public MovieMetadata Metadata { get; set; } - public List SimpleMediaCollections { get; set; } - } -} diff --git a/ErsatzTV.Core/Domain/MovieMetadata.cs b/ErsatzTV.Core/Domain/MovieMetadata.cs index 3c4009eea..fb3fa2474 100644 --- a/ErsatzTV.Core/Domain/MovieMetadata.cs +++ b/ErsatzTV.Core/Domain/MovieMetadata.cs @@ -6,7 +6,7 @@ namespace ErsatzTV.Core.Domain { public int Id { get; set; } public int MovieId { get; set; } - public MovieMediaItem Movie { get; set; } + public Movie Movie { get; set; } public int? Year { get; set; } public DateTime? Premiered { get; set; } public string Plot { get; set; } diff --git a/ErsatzTV.Core/Domain/PlayoutProgramScheduleAnchor.cs b/ErsatzTV.Core/Domain/PlayoutProgramScheduleAnchor.cs index d7cea7059..f694020f6 100644 --- a/ErsatzTV.Core/Domain/PlayoutProgramScheduleAnchor.cs +++ b/ErsatzTV.Core/Domain/PlayoutProgramScheduleAnchor.cs @@ -8,7 +8,13 @@ public int ProgramScheduleId { get; set; } public ProgramSchedule ProgramSchedule { get; set; } public ProgramScheduleItemCollectionType CollectionType { get; set; } + // TODO: remove this public int CollectionId { get; set; } + // TODO: rename this + public int? NewCollectionId { get; set; } + public Collection Collection { get; set; } + public int? MediaItemId { get; set; } + public MediaItem MediaItem { get; set; } public MediaCollectionEnumeratorState EnumeratorState { get; set; } } } diff --git a/ErsatzTV.Core/Domain/PlexMediaSourceLibrary.cs b/ErsatzTV.Core/Domain/PlexMediaSourceLibrary.cs deleted file mode 100644 index 204ffe0e6..000000000 --- a/ErsatzTV.Core/Domain/PlexMediaSourceLibrary.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ErsatzTV.Core.Domain -{ - public class PlexMediaSourceLibrary - { - public int Id { get; set; } - public string Key { get; init; } - public string Name { get; init; } - public MediaType MediaType { get; init; } - } -} diff --git a/ErsatzTV.Core/Domain/ProgramScheduleItem.cs b/ErsatzTV.Core/Domain/ProgramScheduleItem.cs index ecc12d028..abcddb516 100644 --- a/ErsatzTV.Core/Domain/ProgramScheduleItem.cs +++ b/ErsatzTV.Core/Domain/ProgramScheduleItem.cs @@ -9,6 +9,7 @@ namespace ErsatzTV.Core.Domain public StartType StartType => StartTime.HasValue ? StartType.Fixed : StartType.Dynamic; public TimeSpan? StartTime { get; set; } public ProgramScheduleItemCollectionType CollectionType { get; set; } + // TODO: remove these public int? MediaCollectionId { get; set; } public MediaCollection MediaCollection { get; set; } public int? TelevisionShowId { get; set; } @@ -17,5 +18,10 @@ namespace ErsatzTV.Core.Domain public TelevisionSeason TelevisionSeason { get; set; } public int ProgramScheduleId { get; set; } public ProgramSchedule ProgramSchedule { get; set; } + + public int? CollectionId { get; set; } + public Collection Collection { get; set; } + public int? MediaItemId { get; set; } + public MediaItem MediaItem { get; set; } } } diff --git a/ErsatzTV.Core/Domain/SimpleMediaCollection.cs b/ErsatzTV.Core/Domain/SimpleMediaCollection.cs index f475a4adc..69fb39432 100644 --- a/ErsatzTV.Core/Domain/SimpleMediaCollection.cs +++ b/ErsatzTV.Core/Domain/SimpleMediaCollection.cs @@ -4,7 +4,7 @@ namespace ErsatzTV.Core.Domain { public class SimpleMediaCollection : MediaCollection { - public List Movies { get; set; } + public List Movies { get; set; } public List TelevisionShows { get; set; } public List TelevisionSeasons { get; set; } public List TelevisionEpisodes { get; set; } diff --git a/ErsatzTV.Core/Domain/TelevisionShowMetadata.cs b/ErsatzTV.Core/Domain/TelevisionShowMetadata.cs index 914de83d9..4d4faeaa4 100644 --- a/ErsatzTV.Core/Domain/TelevisionShowMetadata.cs +++ b/ErsatzTV.Core/Domain/TelevisionShowMetadata.cs @@ -7,6 +7,8 @@ namespace ErsatzTV.Core.Domain public int Id { get; set; } public int TelevisionShowId { get; set; } public TelevisionShow TelevisionShow { get; set; } + // public int ShowId { get; set; } + // public Show Show { get; set; } public MetadataSource Source { get; set; } public DateTime? LastWriteTime { get; set; } public string Title { get; set; } diff --git a/ErsatzTV.Core/Domain/TelevisionShowSource.cs b/ErsatzTV.Core/Domain/TelevisionShowSource.cs index 43f7890af..877f1332d 100644 --- a/ErsatzTV.Core/Domain/TelevisionShowSource.cs +++ b/ErsatzTV.Core/Domain/TelevisionShowSource.cs @@ -1,5 +1,7 @@ namespace ErsatzTV.Core.Domain { + // used for shows split across sources (folders) + // won't need to convert, since shows will be linked via metadata public class TelevisionShowSource { public int Id { get; set; } diff --git a/ErsatzTV.Core/ErsatzTV.Core.csproj.DotSettings b/ErsatzTV.Core/ErsatzTV.Core.csproj.DotSettings new file mode 100644 index 000000000..4184bb168 --- /dev/null +++ b/ErsatzTV.Core/ErsatzTV.Core.csproj.DotSettings @@ -0,0 +1,7 @@ + + True + True + True + True + True + True \ No newline at end of file diff --git a/ErsatzTV.Core/Interfaces/Locking/IEntityLocker.cs b/ErsatzTV.Core/Interfaces/Locking/IEntityLocker.cs index e51ccf70f..fc8676545 100644 --- a/ErsatzTV.Core/Interfaces/Locking/IEntityLocker.cs +++ b/ErsatzTV.Core/Interfaces/Locking/IEntityLocker.cs @@ -4,9 +4,9 @@ namespace ErsatzTV.Core.Interfaces.Locking { public interface IEntityLocker { - event EventHandler OnMediaSourceChanged; - bool LockMediaSource(int mediaSourceId); - bool UnlockMediaSource(int mediaSourceId); - bool IsMediaSourceLocked(int mediaSourceId); + event EventHandler OnLibraryChanged; + bool LockLibrary(int libraryId); + bool UnlockLibrary(int libraryId); + bool IsLibraryLocked(int libraryId); } } diff --git a/ErsatzTV.Core/Interfaces/Metadata/ILocalFileSystem.cs b/ErsatzTV.Core/Interfaces/Metadata/ILocalFileSystem.cs index 4a698e0df..46e1e39b7 100644 --- a/ErsatzTV.Core/Interfaces/Metadata/ILocalFileSystem.cs +++ b/ErsatzTV.Core/Interfaces/Metadata/ILocalFileSystem.cs @@ -8,7 +8,7 @@ namespace ErsatzTV.Core.Interfaces.Metadata public interface ILocalFileSystem { DateTime GetLastWriteTime(string path); - bool IsMediaSourceAccessible(LocalMediaSource localMediaSource); + bool IsLibraryPathAccessible(LibraryPath libraryPath); IEnumerable ListSubdirectories(string folder); IEnumerable ListFiles(string folder); bool FileExists(string path); diff --git a/ErsatzTV.Core/Interfaces/Metadata/IMovieFolderScanner.cs b/ErsatzTV.Core/Interfaces/Metadata/IMovieFolderScanner.cs index cf43a87d1..0c01b009e 100644 --- a/ErsatzTV.Core/Interfaces/Metadata/IMovieFolderScanner.cs +++ b/ErsatzTV.Core/Interfaces/Metadata/IMovieFolderScanner.cs @@ -6,6 +6,6 @@ namespace ErsatzTV.Core.Interfaces.Metadata { public interface IMovieFolderScanner { - Task> ScanFolder(LocalMediaSource localMediaSource, string ffprobePath); + Task> ScanFolder(LibraryPath libraryPath, string ffprobePath); } } diff --git a/ErsatzTV.Core/Interfaces/Metadata/ITelevisionFolderScanner.cs b/ErsatzTV.Core/Interfaces/Metadata/ITelevisionFolderScanner.cs index b000216be..c32b7705a 100644 --- a/ErsatzTV.Core/Interfaces/Metadata/ITelevisionFolderScanner.cs +++ b/ErsatzTV.Core/Interfaces/Metadata/ITelevisionFolderScanner.cs @@ -6,6 +6,6 @@ namespace ErsatzTV.Core.Interfaces.Metadata { public interface ITelevisionFolderScanner { - Task ScanFolder(LocalMediaSource localMediaSource, string ffprobePath); + Task> ScanFolder(LibraryPath libraryPath, string ffprobePath); } } diff --git a/ErsatzTV.Core/Interfaces/Plex/IPlexMovieLibraryScanner.cs b/ErsatzTV.Core/Interfaces/Plex/IPlexMovieLibraryScanner.cs new file mode 100644 index 000000000..f802b3347 --- /dev/null +++ b/ErsatzTV.Core/Interfaces/Plex/IPlexMovieLibraryScanner.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; +using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Plex; +using LanguageExt; + +namespace ErsatzTV.Core.Interfaces.Plex +{ + public interface IPlexMovieLibraryScanner + { + Task> ScanLibrary( + PlexConnection connection, + PlexServerAuthToken token, + PlexLibrary plexMediaSourceLibrary); + } +} diff --git a/ErsatzTV.Core/Interfaces/Plex/IPlexServerApiClient.cs b/ErsatzTV.Core/Interfaces/Plex/IPlexServerApiClient.cs index e933d3319..87e650db1 100644 --- a/ErsatzTV.Core/Interfaces/Plex/IPlexServerApiClient.cs +++ b/ErsatzTV.Core/Interfaces/Plex/IPlexServerApiClient.cs @@ -8,8 +8,13 @@ namespace ErsatzTV.Core.Interfaces.Plex { public interface IPlexServerApiClient { - Task>> GetLibraries( - PlexMediaSourceConnection connection, + Task>> GetLibraries( + PlexConnection connection, + PlexServerAuthToken token); + + Task>> GetLibraryContents( + PlexLibrary library, + PlexConnection connection, PlexServerAuthToken token); } } diff --git a/ErsatzTV.Core/Interfaces/Repositories/ILibraryRepository.cs b/ErsatzTV.Core/Interfaces/Repositories/ILibraryRepository.cs new file mode 100644 index 000000000..740c2e382 --- /dev/null +++ b/ErsatzTV.Core/Interfaces/Repositories/ILibraryRepository.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; +using ErsatzTV.Core.Domain; +using LanguageExt; + +namespace ErsatzTV.Core.Interfaces.Repositories +{ + public interface ILibraryRepository + { + Task> Get(int libraryId); + Task UpdateLastScan(Library library); + } +} diff --git a/ErsatzTV.Core/Interfaces/Repositories/IMediaSourceRepository.cs b/ErsatzTV.Core/Interfaces/Repositories/IMediaSourceRepository.cs index e7a3f5955..3828cc084 100644 --- a/ErsatzTV.Core/Interfaces/Repositories/IMediaSourceRepository.cs +++ b/ErsatzTV.Core/Interfaces/Repositories/IMediaSourceRepository.cs @@ -11,11 +11,16 @@ namespace ErsatzTV.Core.Interfaces.Repositories Task Add(PlexMediaSource plexMediaSource); Task> GetAll(); Task> GetAllPlex(); + Task> GetPlexLibraries(int plexMediaSourceId); + Task> GetPlexLibrary(int plexLibraryId); Task> Get(int id); Task> GetPlex(int id); Task CountMediaItems(int id); Task Update(LocalMediaSource localMediaSource); Task Update(PlexMediaSource plexMediaSource); + Task Update(PlexLibrary plexMediaSourceLibrary); Task Delete(int id); + Task DisablePlexLibrarySync(IEnumerable libraryIds); + Task EnablePlexLibrarySync(IEnumerable libraryIds); } } diff --git a/ErsatzTV.Core/Interfaces/Repositories/IMovieRepository.cs b/ErsatzTV.Core/Interfaces/Repositories/IMovieRepository.cs index 08371123f..393000bb9 100644 --- a/ErsatzTV.Core/Interfaces/Repositories/IMovieRepository.cs +++ b/ErsatzTV.Core/Interfaces/Repositories/IMovieRepository.cs @@ -7,10 +7,11 @@ namespace ErsatzTV.Core.Interfaces.Repositories { public interface IMovieRepository { - Task> GetMovie(int movieId); - Task> GetOrAdd(int mediaSourceId, string path); - Task Update(MovieMediaItem movie); + Task> GetMovie(int movieId); + Task> GetOrAdd(LibraryPath libraryPath, string path); + Task> GetOrAdd(PlexLibrary library, PlexMovie item); + Task Update(Movie movie); Task GetMovieCount(); - Task> GetPagedMovies(int pageNumber, int pageSize); + Task> GetPagedMovies(int pageNumber, int pageSize); } } diff --git a/ErsatzTV.Core/Interfaces/Repositories/ITelevisionRepository.cs b/ErsatzTV.Core/Interfaces/Repositories/ITelevisionRepository.cs index 88d7ebcae..990706bbb 100644 --- a/ErsatzTV.Core/Interfaces/Repositories/ITelevisionRepository.cs +++ b/ErsatzTV.Core/Interfaces/Repositories/ITelevisionRepository.cs @@ -38,7 +38,7 @@ namespace ErsatzTV.Core.Interfaces.Repositories Task> GetOrAddEpisode( TelevisionSeason season, - int mediaSourceId, + LibraryPath libraryPath, string path); Task DeleteMissingSources(int localMediaSourceId, List allFolders); diff --git a/ErsatzTV.Core/Iptv/ChannelGuide.cs b/ErsatzTV.Core/Iptv/ChannelGuide.cs index 55d371714..a76d47566 100644 --- a/ErsatzTV.Core/Iptv/ChannelGuide.cs +++ b/ErsatzTV.Core/Iptv/ChannelGuide.cs @@ -60,21 +60,21 @@ namespace ErsatzTV.Core.Iptv string title = playoutItem.MediaItem switch { - MovieMediaItem m => m.Metadata?.Title ?? m.Path, + Movie m => m.Metadata?.Title ?? m.Path, TelevisionEpisodeMediaItem e => e.Metadata?.Title ?? e.Path, _ => "[unknown]" }; string description = playoutItem.MediaItem switch { - MovieMediaItem m => m.Metadata?.Plot, + Movie m => m.Metadata?.Plot, TelevisionEpisodeMediaItem e => e.Metadata?.Plot, _ => string.Empty }; string contentRating = playoutItem.MediaItem switch { - MovieMediaItem m => m.Metadata?.ContentRating, + Movie m => m.Metadata?.ContentRating, _ => string.Empty }; diff --git a/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs b/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs index 726708c42..a6e51f3ce 100644 --- a/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs +++ b/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs @@ -24,10 +24,11 @@ namespace ErsatzTV.Core.Metadata if (fileName != null) { - if (!(mediaItem.Source is LocalMediaSource)) - { - return metadata; - } + // TODO: ensure local? + // if (!(mediaItem.LibraryPath is LocalMediaSource)) + // { + // return metadata; + // } return GetEpisodeMetadata(fileName, metadata); } @@ -35,17 +36,18 @@ namespace ErsatzTV.Core.Metadata return metadata; } - public static MovieMetadata GetFallbackMetadata(MovieMediaItem mediaItem) + public static MovieMetadata GetFallbackMetadata(Movie mediaItem) { string fileName = Path.GetFileName(mediaItem.Path); var metadata = new MovieMetadata { Source = MetadataSource.Fallback, Title = fileName ?? mediaItem.Path }; if (fileName != null) { - if (!(mediaItem.Source is LocalMediaSource)) - { - return metadata; - } + // TODO: ensure local? + // if (!(mediaItem.Source is LocalMediaSource)) + // { + // return metadata; + // } return GetMovieMetadata(fileName, metadata); } diff --git a/ErsatzTV.Core/Metadata/LocalFileSystem.cs b/ErsatzTV.Core/Metadata/LocalFileSystem.cs index 3042e8d1a..02057240c 100644 --- a/ErsatzTV.Core/Metadata/LocalFileSystem.cs +++ b/ErsatzTV.Core/Metadata/LocalFileSystem.cs @@ -13,8 +13,8 @@ namespace ErsatzTV.Core.Metadata public DateTime GetLastWriteTime(string path) => Try(File.GetLastWriteTimeUtc(path)).IfFail(() => DateTime.MinValue); - public bool IsMediaSourceAccessible(LocalMediaSource localMediaSource) => - Directory.Exists(localMediaSource.Folder); + public bool IsLibraryPathAccessible(LibraryPath libraryPath) => + Directory.Exists(libraryPath.Path); public IEnumerable ListSubdirectories(string folder) => Try(Directory.EnumerateDirectories(folder)).IfFail(new List()); diff --git a/ErsatzTV.Core/Metadata/LocalMetadataProvider.cs b/ErsatzTV.Core/Metadata/LocalMetadataProvider.cs index 01cdb4078..3b89f15f4 100644 --- a/ErsatzTV.Core/Metadata/LocalMetadataProvider.cs +++ b/ErsatzTV.Core/Metadata/LocalMetadataProvider.cs @@ -57,7 +57,7 @@ namespace ErsatzTV.Core.Metadata { TelevisionEpisodeMediaItem e => LoadMetadata(e, path) .Bind(maybeMetadata => maybeMetadata.IfSomeAsync(metadata => ApplyMetadataUpdate(e, metadata))), - MovieMediaItem m => LoadMetadata(m, path) + Movie m => LoadMetadata(m, path) .Bind(maybeMetadata => maybeMetadata.IfSomeAsync(metadata => ApplyMetadataUpdate(m, metadata))), _ => Task.FromResult(Unit.Default) }; @@ -71,7 +71,7 @@ namespace ErsatzTV.Core.Metadata { TelevisionEpisodeMediaItem e => ApplyMetadataUpdate(e, FallbackMetadataProvider.GetFallbackMetadata(e)) .ToUnit(), - MovieMediaItem m => ApplyMetadataUpdate(m, FallbackMetadataProvider.GetFallbackMetadata(m)).ToUnit(), + Movie m => ApplyMetadataUpdate(m, FallbackMetadataProvider.GetFallbackMetadata(m)).ToUnit(), _ => Task.FromResult(Unit.Default) }; @@ -94,7 +94,7 @@ namespace ErsatzTV.Core.Metadata await _televisionRepository.Update(mediaItem); } - private async Task ApplyMetadataUpdate(MovieMediaItem mediaItem, MovieMetadata metadata) + private async Task ApplyMetadataUpdate(Movie mediaItem, MovieMetadata metadata) { mediaItem.Metadata ??= new MovieMetadata(); mediaItem.Metadata.Source = metadata.Source; @@ -124,7 +124,7 @@ namespace ErsatzTV.Core.Metadata await _televisionRepository.Update(televisionShow); } - private async Task> LoadMetadata(MovieMediaItem mediaItem, string nfoFileName) + private async Task> LoadMetadata(Movie mediaItem, string nfoFileName) { if (nfoFileName == null || !File.Exists(nfoFileName)) { @@ -132,11 +132,11 @@ namespace ErsatzTV.Core.Metadata return None; } - if (!(mediaItem.Source is LocalMediaSource)) - { - _logger.LogDebug("Media source {Name} is not a local media source", mediaItem.Source.Name); - return None; - } + // if (!(mediaItem.Source is LocalMediaSource)) + // { + // _logger.LogDebug("Media source {Name} is not a local media source", mediaItem.Source.Name); + // return None; + // } return await LoadMovieMetadata(mediaItem, nfoFileName); } @@ -151,11 +151,11 @@ namespace ErsatzTV.Core.Metadata return None; } - if (!(mediaItem.Source is LocalMediaSource)) - { - _logger.LogDebug("Media source {Name} is not a local media source", mediaItem.Source.Name); - return None; - } + // if (!(mediaItem.Source is LocalMediaSource)) + // { + // _logger.LogDebug("Media source {Name} is not a local media source", mediaItem.Source.Name); + // return None; + // } return await LoadEpisodeMetadata(mediaItem, nfoFileName); } @@ -225,7 +225,7 @@ namespace ErsatzTV.Core.Metadata } } - private async Task> LoadMovieMetadata(MovieMediaItem mediaItem, string nfoFileName) + private async Task> LoadMovieMetadata(Movie mediaItem, string nfoFileName) { try { diff --git a/ErsatzTV.Core/Metadata/MovieFolderScanner.cs b/ErsatzTV.Core/Metadata/MovieFolderScanner.cs index 0c402e757..541ef69d4 100644 --- a/ErsatzTV.Core/Metadata/MovieFolderScanner.cs +++ b/ErsatzTV.Core/Metadata/MovieFolderScanner.cs @@ -37,15 +37,15 @@ namespace ErsatzTV.Core.Metadata _logger = logger; } - public async Task> ScanFolder(LocalMediaSource localMediaSource, string ffprobePath) + public async Task> ScanFolder(LibraryPath libraryPath, string ffprobePath) { - if (!_localFileSystem.IsMediaSourceAccessible(localMediaSource)) + if (!_localFileSystem.IsLibraryPathAccessible(libraryPath)) { return new MediaSourceInaccessible(); } var folderQueue = new Queue(); - foreach (string folder in _localFileSystem.ListSubdirectories(localMediaSource.Folder).OrderBy(identity)) + foreach (string folder in _localFileSystem.ListSubdirectories(libraryPath.Path).OrderBy(identity)) { folderQueue.Enqueue(folder); } @@ -73,10 +73,10 @@ namespace ErsatzTV.Core.Metadata foreach (string file in allFiles.OrderBy(identity)) { + // TODO: optimize dbcontext use here, do we need tracking? can we make partial updates with dapper? // TODO: figure out how to rebuild playlists - Either x = await _movieRepository.GetOrAdd(localMediaSource.Id, file); - - Either maybeMovie = await x.AsTask() + Either maybeMovie = await _movieRepository + .GetOrAdd(libraryPath, file) .BindT(movie => UpdateStatistics(movie, ffprobePath).MapT(_ => movie)) .BindT(UpdateMetadata) .BindT(UpdatePoster); @@ -89,7 +89,7 @@ namespace ErsatzTV.Core.Metadata return Unit.Default; } - private async Task> UpdateMetadata(MovieMediaItem movie) + private async Task> UpdateMetadata(Movie movie) { try { @@ -121,7 +121,7 @@ namespace ErsatzTV.Core.Metadata } } - private async Task> UpdatePoster(MovieMediaItem movie) + private async Task> UpdatePoster(Movie movie) { try { @@ -145,7 +145,7 @@ namespace ErsatzTV.Core.Metadata } } - private Option LocateNfoFile(MovieMediaItem movie) + private Option LocateNfoFile(Movie movie) { string movieAsNfo = Path.ChangeExtension(movie.Path, "nfo"); string movieNfo = Path.Combine(Path.GetDirectoryName(movie.Path) ?? string.Empty, "movie.nfo"); @@ -154,7 +154,7 @@ namespace ErsatzTV.Core.Metadata .HeadOrNone(); } - private Option LocatePoster(MovieMediaItem movie) + private Option LocatePoster(Movie movie) { string folder = Path.GetDirectoryName(movie.Path) ?? string.Empty; IEnumerable possibleMoviePosters = ImageFileExtensions.Collect( diff --git a/ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs b/ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs index d1fe84f2f..c37c57a85 100644 --- a/ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs +++ b/ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Errors; using ErsatzTV.Core.Interfaces.Images; using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; @@ -37,17 +38,14 @@ namespace ErsatzTV.Core.Metadata _logger = logger; } - public async Task ScanFolder(LocalMediaSource localMediaSource, string ffprobePath) + public async Task> ScanFolder(LibraryPath libraryPath, string ffprobePath) { - if (!_localFileSystem.IsMediaSourceAccessible(localMediaSource)) + if (!_localFileSystem.IsLibraryPathAccessible(libraryPath)) { - _logger.LogWarning( - "Media source is not accessible or missing; skipping scan of {Folder}", - localMediaSource.Folder); - return Unit.Default; + return new MediaSourceInaccessible(); } - var allShowFolders = _localFileSystem.ListSubdirectories(localMediaSource.Folder) + var allShowFolders = _localFileSystem.ListSubdirectories(libraryPath.Path) .Filter(ShouldIncludeFolder) .OrderBy(identity) .ToList(); @@ -56,27 +54,28 @@ namespace ErsatzTV.Core.Metadata { // TODO: check all sources for latest metadata? Either maybeShow = - await FindOrCreateShow(localMediaSource.Id, showFolder) + await FindOrCreateShow(libraryPath.Id, showFolder) .BindT(show => UpdateMetadataForShow(show, showFolder)) .BindT(show => UpdatePosterForShow(show, showFolder)); await maybeShow.Match( - show => ScanSeasons(localMediaSource, ffprobePath, show, showFolder), + show => ScanSeasons(libraryPath, ffprobePath, show, showFolder), _ => Task.FromResult(Unit.Default)); } - await _televisionRepository.DeleteMissingSources(localMediaSource.Id, allShowFolders); + // TODO: remove this? + // await _televisionRepository.DeleteMissingSources(libraryPath.Id, allShowFolders); await _televisionRepository.DeleteEmptyShows(); return Unit.Default; } private async Task> FindOrCreateShow( - int localMediaSourceId, + int libraryPathId, string showFolder) { Option maybeShowByPath = - await _televisionRepository.GetShowByPath(localMediaSourceId, showFolder); + await _televisionRepository.GetShowByPath(libraryPathId, showFolder); return await maybeShowByPath.Match( show => Right(show).AsTask(), async () => @@ -89,19 +88,19 @@ namespace ErsatzTV.Core.Metadata show.Sources.Add( new LocalTelevisionShowSource { - MediaSourceId = localMediaSourceId, + MediaSourceId = libraryPathId, Path = showFolder, TelevisionShow = show }); await _televisionRepository.Update(show); return Right(show); }, - async () => await _televisionRepository.AddShow(localMediaSourceId, showFolder, metadata)); + async () => await _televisionRepository.AddShow(libraryPathId, showFolder, metadata)); }); } private async Task ScanSeasons( - LocalMediaSource localMediaSource, + LibraryPath libraryPath, string ffprobePath, TelevisionShow show, string showPath) @@ -118,7 +117,7 @@ namespace ErsatzTV.Core.Metadata .BindT(UpdatePoster); await maybeSeason.Match( - season => ScanEpisodes(localMediaSource, ffprobePath, season), + season => ScanEpisodes(libraryPath, ffprobePath, season), _ => Task.FromResult(Unit.Default)); }); } @@ -127,7 +126,7 @@ namespace ErsatzTV.Core.Metadata } private async Task ScanEpisodes( - LocalMediaSource localMediaSource, + LibraryPath libraryPath, string ffprobePath, TelevisionSeason season) { @@ -136,7 +135,7 @@ namespace ErsatzTV.Core.Metadata { // TODO: figure out how to rebuild playlists Either maybeEpisode = await _televisionRepository - .GetOrAddEpisode(season, localMediaSource.Id, file) + .GetOrAddEpisode(season, libraryPath, file) .BindT(episode => UpdateStatistics(episode, ffprobePath).MapT(_ => episode)) .BindT(UpdateMetadata) .BindT(UpdateThumbnail); diff --git a/ErsatzTV.Core/Plex/PlexMediaEntry.cs b/ErsatzTV.Core/Plex/PlexMediaEntry.cs new file mode 100644 index 000000000..cfe062457 --- /dev/null +++ b/ErsatzTV.Core/Plex/PlexMediaEntry.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace ErsatzTV.Core.Plex +{ + public class PlexMediaEntry + { + public int Id { get; set; } + public int Duration { get; set; } + public int Bitrate { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public double AspectRatio { get; set; } + public int AudioChannels { get; set; } + public string AudioCodec { get; set; } + public string VideoCodec { get; set; } + public string Container { get; set; } + public string VideoFrameRate { get; set; } + public List Part { get; set; } + } +} diff --git a/ErsatzTV.Core/Plex/PlexMetadataEntry.cs b/ErsatzTV.Core/Plex/PlexMetadataEntry.cs new file mode 100644 index 000000000..f5089fcee --- /dev/null +++ b/ErsatzTV.Core/Plex/PlexMetadataEntry.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace ErsatzTV.Core.Plex +{ + public class PlexMetadataEntry + { + public string Key { get; set; } + public string Title { get; set; } + public string Summary { get; set; } + public int Year { get; set; } + public string Tagline { get; set; } + public string Thumb { get; set; } + public string Art { get; set; } + public string OriginallyAvailableAt { get; set; } + public int AddedAt { get; set; } + public int UpdatedAt { get; set; } + public List Media { get; set; } + } +} diff --git a/ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs b/ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs new file mode 100644 index 000000000..2c00197e8 --- /dev/null +++ b/ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Interfaces.Plex; +using ErsatzTV.Core.Interfaces.Repositories; +using LanguageExt; +using Microsoft.Extensions.Logging; + +namespace ErsatzTV.Core.Plex +{ + public class PlexMovieLibraryScanner : IPlexMovieLibraryScanner + { + private readonly ILogger _logger; + private readonly IMovieRepository _movieRepository; + private readonly IPlexServerApiClient _plexServerApiClient; + + public PlexMovieLibraryScanner( + IPlexServerApiClient plexServerApiClient, + IMovieRepository movieRepository, + ILogger logger) + { + _plexServerApiClient = plexServerApiClient; + _movieRepository = movieRepository; + _logger = logger; + } + + public async Task> ScanLibrary( + PlexConnection connection, + PlexServerAuthToken token, + PlexLibrary plexMediaSourceLibrary) + { + Either> entries = await _plexServerApiClient.GetLibraryContents( + plexMediaSourceLibrary, + connection, + token); + + await entries.Match( + async movieEntries => + { + foreach (PlexMovie entry in movieEntries) + { + // TODO: optimize dbcontext use here, do we need tracking? can we make partial updates with dapper? + // TODO: figure out how to rebuild playlists + Either maybeMovie = await _movieRepository + .GetOrAdd(plexMediaSourceLibrary, entry) + .BindT(UpdateIfNeeded); + + maybeMovie.IfLeft( + error => _logger.LogWarning( + "Error processing plex movie at {Key}: {Error}", + entry.Key, + error.Value)); + } + }, + error => + { + _logger.LogWarning( + "Error synchronizing plex library {Path}: {Error}", + plexMediaSourceLibrary.Name, + error.Value); + + return Task.CompletedTask; + }); + + // need plex media item model that can be used to lookup by unique id (metadata key?) + + return Unit.Default; + } + + private async Task> UpdateIfNeeded(PlexMovie plexMovie) => + // .BindT(movie => UpdateStatistics(movie, ffprobePath).MapT(_ => movie)) + // .BindT(UpdateMetadata) + // .BindT(UpdatePoster); + plexMovie; + } +} diff --git a/ErsatzTV.Core/Plex/PlexPartEntry.cs b/ErsatzTV.Core/Plex/PlexPartEntry.cs new file mode 100644 index 000000000..bd6b90730 --- /dev/null +++ b/ErsatzTV.Core/Plex/PlexPartEntry.cs @@ -0,0 +1,11 @@ +namespace ErsatzTV.Core.Plex +{ + public class PlexPartEntry + { + public int Id { get; set; } + public string Key { get; set; } + public int Duration { get; set; } + public string File { get; set; } + public int Size { get; set; } + } +} diff --git a/ErsatzTV.Core/Scheduling/ChronologicalMediaCollectionEnumerator.cs b/ErsatzTV.Core/Scheduling/ChronologicalMediaCollectionEnumerator.cs index 1bf7eeda4..9ba3bf57b 100644 --- a/ErsatzTV.Core/Scheduling/ChronologicalMediaCollectionEnumerator.cs +++ b/ErsatzTV.Core/Scheduling/ChronologicalMediaCollectionEnumerator.cs @@ -43,14 +43,14 @@ namespace ErsatzTV.Core.Scheduling DateTime date1 = x switch { TelevisionEpisodeMediaItem e => e.Metadata?.Aired ?? DateTime.MaxValue, - MovieMediaItem m => m.Metadata?.Premiered ?? DateTime.MaxValue, + Movie m => m.Metadata?.Premiered ?? DateTime.MaxValue, _ => DateTime.MaxValue }; DateTime date2 = y switch { TelevisionEpisodeMediaItem e => e.Metadata?.Aired ?? DateTime.MaxValue, - MovieMediaItem m => m.Metadata?.Premiered ?? DateTime.MaxValue, + Movie m => m.Metadata?.Premiered ?? DateTime.MaxValue, _ => DateTime.MaxValue }; diff --git a/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs b/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs index 16d70a2cd..36bc592a4 100644 --- a/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs +++ b/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs @@ -388,7 +388,7 @@ namespace ErsatzTV.Core.Scheduling TelevisionEpisodeMediaItem e => e.Metadata != null ? $"{e.Metadata.Title} - s{e.Metadata.Season:00}e{e.Metadata.Episode:00}" : Path.GetFileName(e.Path), - MovieMediaItem m => m.Metadata?.Title ?? Path.GetFileName(m.Path), + Movie m => m.Metadata?.Title ?? Path.GetFileName(m.Path), _ => string.Empty }; diff --git a/ErsatzTV.Infrastructure/Data/Configurations/ChannelConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/ChannelConfiguration.cs index 62e0c458c..3b055f0a9 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/ChannelConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/ChannelConfiguration.cs @@ -8,6 +8,8 @@ namespace ErsatzTV.Infrastructure.Data.Configurations { public void Configure(EntityTypeBuilder builder) { + builder.ToTable("Channel"); + builder.HasIndex(c => c.Number) .IsUnique(); diff --git a/ErsatzTV.Infrastructure/Data/Configurations/Collection/CollectionConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/Collection/CollectionConfiguration.cs new file mode 100644 index 000000000..dbd79916b --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Configurations/Collection/CollectionConfiguration.cs @@ -0,0 +1,25 @@ +using ErsatzTV.Core.Domain; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ErsatzTV.Infrastructure.Data.Configurations +{ + public class CollectionConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Collection"); + + builder.HasMany(c => c.MediaItems) + .WithMany(m => m.Collections) + .UsingEntity( + j => j.HasOne(ci => ci.MediaItem) + .WithMany(mi => mi.CollectionItems) + .HasForeignKey(ci => ci.MediaItemId), + j => j.HasOne(ci => ci.Collection) + .WithMany(c => c.CollectionItems) + .HasForeignKey(ci => ci.CollectionId), + j => j.HasKey(ci => new { ci.CollectionId, ci.MediaItemId })); + } + } +} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/ConfigElementConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/ConfigElementConfiguration.cs index ca5e3bea3..0f1da93bb 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/ConfigElementConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/ConfigElementConfiguration.cs @@ -6,8 +6,12 @@ namespace ErsatzTV.Infrastructure.Data.Configurations { public class ConfigElementConfiguration : IEntityTypeConfiguration { - public void Configure(EntityTypeBuilder builder) => + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("ConfigElement"); + builder.HasIndex(ce => ce.Key) .IsUnique(); + } } } diff --git a/ErsatzTV.Infrastructure/Data/Configurations/FFmpegProfileConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/FFmpegProfileConfiguration.cs new file mode 100644 index 000000000..bcfbf503c --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Configurations/FFmpegProfileConfiguration.cs @@ -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 + { + public void Configure(EntityTypeBuilder builder) => builder.ToTable("FFmpegProfile"); + } +} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/Library/LibraryConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/Library/LibraryConfiguration.cs new file mode 100644 index 000000000..3cc2a3ab5 --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Configurations/Library/LibraryConfiguration.cs @@ -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 + { + public void Configure(EntityTypeBuilder builder) => + builder.ToTable("Library"); + } +} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/Library/LibraryPathConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/Library/LibraryPathConfiguration.cs new file mode 100644 index 000000000..3238aca14 --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Configurations/Library/LibraryPathConfiguration.cs @@ -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 + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("LibraryPath"); + + builder.HasMany(p => p.MediaItems) + .WithOne(i => i.LibraryPath) + .HasForeignKey(i => i.LibraryPathId) + .OnDelete(DeleteBehavior.Cascade); + } + } +} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/Library/LocalLibraryConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/Library/LocalLibraryConfiguration.cs new file mode 100644 index 000000000..bf2edef97 --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Configurations/Library/LocalLibraryConfiguration.cs @@ -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 + { + public void Configure(EntityTypeBuilder builder) => + builder.ToTable("LocalLibrary"); + } +} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/Library/PlexLibraryConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/Library/PlexLibraryConfiguration.cs new file mode 100644 index 000000000..777887f7b --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Configurations/Library/PlexLibraryConfiguration.cs @@ -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 + { + public void Configure(EntityTypeBuilder builder) => + builder.ToTable("PlexLibrary"); + } +} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/MediaCollectionConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/MediaCollectionConfiguration.cs index 1b4fec1b3..b566ec857 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/MediaCollectionConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/MediaCollectionConfiguration.cs @@ -6,7 +6,11 @@ namespace ErsatzTV.Infrastructure.Data.Configurations { public class MediaCollectionConfiguration : IEntityTypeConfiguration { - public void Configure(EntityTypeBuilder builder) => + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("MediaCollection"); + builder.HasIndex(c => c.Name).IsUnique(); + } } } diff --git a/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/EpisodeConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/EpisodeConfiguration.cs new file mode 100644 index 000000000..b541896db --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/EpisodeConfiguration.cs @@ -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 + { + public void Configure(EntityTypeBuilder 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); + } + } +} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/MediaFileConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/MediaFileConfiguration.cs new file mode 100644 index 000000000..a73ebd661 --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/MediaFileConfiguration.cs @@ -0,0 +1,11 @@ +using ErsatzTV.Core.Domain; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ErsatzTV.Infrastructure.Data.Configurations +{ + public class MediaFileConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) => builder.ToTable("MediaFile"); + } +} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/MediaItemConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/MediaItemConfiguration.cs similarity index 90% rename from ErsatzTV.Infrastructure/Data/Configurations/MediaItemConfiguration.cs rename to ErsatzTV.Infrastructure/Data/Configurations/MediaItem/MediaItemConfiguration.cs index dcb5df392..24e36a773 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/MediaItemConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/MediaItemConfiguration.cs @@ -8,7 +8,8 @@ namespace ErsatzTV.Infrastructure.Data.Configurations { public void Configure(EntityTypeBuilder builder) { - builder.ToTable("MediaItems"); + builder.ToTable("MediaItem"); + builder.OwnsOne(c => c.Statistics).WithOwner(); } } diff --git a/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/MediaVersionConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/MediaVersionConfiguration.cs new file mode 100644 index 000000000..2cd0e35e7 --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/MediaVersionConfiguration.cs @@ -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 + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("MediaVersion"); + + builder.HasMany(v => v.MediaFiles) + .WithOne(f => f.MediaVersion) + .HasForeignKey(f => f.MediaVersionId) + .OnDelete(DeleteBehavior.Cascade); + } + } +} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/MovieConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/MovieConfiguration.cs new file mode 100644 index 000000000..1f17ced57 --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/MovieConfiguration.cs @@ -0,0 +1,29 @@ +using ErsatzTV.Core.Domain; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ErsatzTV.Infrastructure.Data.Configurations +{ + public class MovieConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Movie"); + + // TODO: remove this + builder.HasOne(i => i.Metadata) + .WithOne(m => m.Movie) + .HasForeignKey(m => m.MovieId) + .OnDelete(DeleteBehavior.Cascade); + + 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); + } + } +} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/PlexMovieMediaItemConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/PlexMovieMediaItemConfiguration.cs new file mode 100644 index 000000000..ea5269f16 --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/PlexMovieMediaItemConfiguration.cs @@ -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 + { + public void Configure(EntityTypeBuilder builder) => builder.ToTable("PlexMovie"); + } +} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/SeasonConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/SeasonConfiguration.cs new file mode 100644 index 000000000..1a9c9f714 --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/SeasonConfiguration.cs @@ -0,0 +1,19 @@ +using ErsatzTV.Core.Domain; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ErsatzTV.Infrastructure.Data.Configurations +{ + public class SeasonConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Season"); + + builder.HasMany(s => s.Episodes) + .WithOne(e => e.Season) + .HasForeignKey(e => e.SeasonId) + .OnDelete(DeleteBehavior.Cascade); + } + } +} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/ShowConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/ShowConfiguration.cs new file mode 100644 index 000000000..f07b36098 --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Configurations/MediaItem/ShowConfiguration.cs @@ -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 + { + public void Configure(EntityTypeBuilder 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); + } + } +} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/LocalMediaSourceConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/MediaSource/LocalMediaSourceConfiguration.cs similarity index 87% rename from ErsatzTV.Infrastructure/Data/Configurations/LocalMediaSourceConfiguration.cs rename to ErsatzTV.Infrastructure/Data/Configurations/MediaSource/LocalMediaSourceConfiguration.cs index 2132f6d07..9ffbced3e 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/LocalMediaSourceConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/MediaSource/LocalMediaSourceConfiguration.cs @@ -7,6 +7,6 @@ namespace ErsatzTV.Infrastructure.Data.Configurations public class LocalMediaSourceConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) => - builder.ToTable("LocalMediaSources"); + builder.ToTable("LocalMediaSource"); } } diff --git a/ErsatzTV.Infrastructure/Data/Configurations/MediaSourceConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/MediaSource/MediaSourceConfiguration.cs similarity index 56% rename from ErsatzTV.Infrastructure/Data/Configurations/MediaSourceConfiguration.cs rename to ErsatzTV.Infrastructure/Data/Configurations/MediaSource/MediaSourceConfiguration.cs index debf17ced..92d55e5be 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/MediaSourceConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/MediaSource/MediaSourceConfiguration.cs @@ -6,7 +6,14 @@ namespace ErsatzTV.Infrastructure.Data.Configurations { public class MediaSourceConfiguration : IEntityTypeConfiguration { - public void Configure(EntityTypeBuilder builder) => - builder.HasIndex(ms => ms.Name).IsUnique(); + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("MediaSource"); + + builder.HasMany(s => s.Libraries) + .WithOne(l => l.MediaSource) + .HasForeignKey(l => l.MediaSourceId) + .OnDelete(DeleteBehavior.Cascade); + } } } diff --git a/ErsatzTV.Infrastructure/Data/Configurations/MediaSource/PlexConnectionConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/MediaSource/PlexConnectionConfiguration.cs new file mode 100644 index 000000000..d0a100504 --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Configurations/MediaSource/PlexConnectionConfiguration.cs @@ -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 + { + public void Configure(EntityTypeBuilder builder) => + builder.ToTable("PlexConnection"); + } +} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/PlexMediaSourceConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/MediaSource/PlexMediaSourceConfiguration.cs similarity index 55% rename from ErsatzTV.Infrastructure/Data/Configurations/PlexMediaSourceConfiguration.cs rename to ErsatzTV.Infrastructure/Data/Configurations/MediaSource/PlexMediaSourceConfiguration.cs index 42ec3fdc2..990521c73 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/PlexMediaSourceConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/MediaSource/PlexMediaSourceConfiguration.cs @@ -6,6 +6,14 @@ namespace ErsatzTV.Infrastructure.Data.Configurations { public class PlexMediaSourceConfiguration : IEntityTypeConfiguration { - public void Configure(EntityTypeBuilder builder) => builder.ToTable("PlexMediaSources"); + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("PlexMediaSource"); + + builder.HasMany(s => s.Connections) + .WithOne(c => c.PlexMediaSource) + .HasForeignKey(c => c.PlexMediaSourceId) + .OnDelete(DeleteBehavior.Cascade); + } } } diff --git a/ErsatzTV.Infrastructure/Data/Configurations/Metadata/EpisodeMetadataConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/Metadata/EpisodeMetadataConfiguration.cs new file mode 100644 index 000000000..69426dc24 --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Configurations/Metadata/EpisodeMetadataConfiguration.cs @@ -0,0 +1,11 @@ +using ErsatzTV.Core.Domain; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ErsatzTV.Infrastructure.Data.Configurations +{ + public class EpisodeMetadataConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) => builder.ToTable("EpisodeMetadata"); + } +} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/Metadata/NewMovieMetadataConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/Metadata/NewMovieMetadataConfiguration.cs new file mode 100644 index 000000000..44164c489 --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Configurations/Metadata/NewMovieMetadataConfiguration.cs @@ -0,0 +1,11 @@ +using ErsatzTV.Core.Domain; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ErsatzTV.Infrastructure.Data.Configurations +{ + public class NewMovieMetadataConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) => builder.ToTable("NewMovieMetadata"); + } +} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/Metadata/ShowMetadataConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/Metadata/ShowMetadataConfiguration.cs new file mode 100644 index 000000000..45593a5ec --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Configurations/Metadata/ShowMetadataConfiguration.cs @@ -0,0 +1,11 @@ +using ErsatzTV.Core.Domain; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ErsatzTV.Infrastructure.Data.Configurations +{ + public class ShowMetadataConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) => builder.ToTable("ShowMetadata"); + } +} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/MovieMediaItemConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/MovieMediaItemConfiguration.cs deleted file mode 100644 index b1a5ca665..000000000 --- a/ErsatzTV.Infrastructure/Data/Configurations/MovieMediaItemConfiguration.cs +++ /dev/null @@ -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 - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Movies"); - - builder.HasOne(i => i.Metadata) - .WithOne(m => m.Movie) - .HasForeignKey(m => m.MovieId) - .OnDelete(DeleteBehavior.Cascade); - } - } -} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/PlayoutConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/PlayoutConfiguration.cs index 4f38fbb46..e39f981dc 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/PlayoutConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/PlayoutConfiguration.cs @@ -8,6 +8,8 @@ namespace ErsatzTV.Infrastructure.Data.Configurations { public void Configure(EntityTypeBuilder builder) { + builder.ToTable("Playout"); + builder.HasMany(p => p.Items) .WithOne(pi => pi.Playout) .HasForeignKey(pi => pi.PlayoutId) diff --git a/ErsatzTV.Infrastructure/Data/Configurations/PlayoutItemConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/PlayoutItemConfiguration.cs new file mode 100644 index 000000000..7a5685da8 --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Configurations/PlayoutItemConfiguration.cs @@ -0,0 +1,12 @@ +using ErsatzTV.Core.Domain; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ErsatzTV.Infrastructure.Data.Configurations +{ + public class PlayoutItemConfiguration : IEntityTypeConfiguration + + { + public void Configure(EntityTypeBuilder builder) => builder.ToTable("PlayoutItem"); + } +} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/PlayoutProgramScheduleAnchorConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/PlayoutProgramScheduleAnchorConfiguration.cs index 8585e9207..87a35ecb2 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/PlayoutProgramScheduleAnchorConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/PlayoutProgramScheduleAnchorConfiguration.cs @@ -6,8 +6,23 @@ namespace ErsatzTV.Infrastructure.Data.Configurations { public class PlayoutProgramScheduleAnchorConfiguration : IEntityTypeConfiguration { - public void Configure(EntityTypeBuilder builder) => + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("PlayoutProgramScheduleAnchor"); + builder.OwnsOne(a => a.EnumeratorState) .WithOwner(); + + // TODO: fix this foreign key + builder.HasOne(i => i.Collection) + .WithMany() + .HasForeignKey(i => i.NewCollectionId) + .IsRequired(false); + + builder.HasOne(i => i.MediaItem) + .WithMany() + .HasForeignKey(i => i.MediaItemId) + .IsRequired(false); + } } } diff --git a/ErsatzTV.Infrastructure/Data/Configurations/PlexMediaSourceConnectionConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/PlexMediaSourceConnectionConfiguration.cs deleted file mode 100644 index 01850c869..000000000 --- a/ErsatzTV.Infrastructure/Data/Configurations/PlexMediaSourceConnectionConfiguration.cs +++ /dev/null @@ -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 - { - public void Configure(EntityTypeBuilder builder) => - builder.ToTable("PlexMediaSourceConnections"); - } -} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/PlexMediaSourceLibraryConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/PlexMediaSourceLibraryConfiguration.cs deleted file mode 100644 index aca02c0fa..000000000 --- a/ErsatzTV.Infrastructure/Data/Configurations/PlexMediaSourceLibraryConfiguration.cs +++ /dev/null @@ -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 - { - public void Configure(EntityTypeBuilder builder) => - builder.ToTable("PlexMediaSourceLibraries"); - } -} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleConfiguration.cs index f9008782e..221704c4e 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleConfiguration.cs @@ -8,6 +8,8 @@ namespace ErsatzTV.Infrastructure.Data.Configurations { public void Configure(EntityTypeBuilder builder) { + builder.ToTable("ProgramSchedule"); + builder.HasIndex(ps => ps.Name) .IsUnique(); diff --git a/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemConfiguration.cs index f3c72b358..2796298ef 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemConfiguration.cs @@ -8,7 +8,7 @@ namespace ErsatzTV.Infrastructure.Data.Configurations { public void Configure(EntityTypeBuilder builder) { - builder.ToTable("ProgramScheduleItems"); + builder.ToTable("ProgramScheduleItem"); builder.HasOne(i => i.MediaCollection) .WithMany() @@ -24,6 +24,16 @@ namespace ErsatzTV.Infrastructure.Data.Configurations .WithMany() .HasForeignKey(i => i.TelevisionSeasonId) .IsRequired(false); + + builder.HasOne(i => i.Collection) + .WithMany() + .HasForeignKey(i => i.CollectionId) + .IsRequired(false); + + builder.HasOne(i => i.MediaItem) + .WithMany() + .HasForeignKey(i => i.MediaItemId) + .IsRequired(false); } } } diff --git a/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemDurationConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemDurationConfiguration.cs index 7587586ca..cdf510dd0 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemDurationConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemDurationConfiguration.cs @@ -7,6 +7,6 @@ namespace ErsatzTV.Infrastructure.Data.Configurations public class ProgramScheduleItemDurationConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) => - builder.ToTable("ProgramScheduleDurationItems"); + builder.ToTable("ProgramScheduleDurationItem"); } } diff --git a/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemFloodConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemFloodConfiguration.cs index c127ab4e3..c58329ecd 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemFloodConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemFloodConfiguration.cs @@ -7,6 +7,6 @@ namespace ErsatzTV.Infrastructure.Data.Configurations public class ProgramScheduleItemFloodConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) => - builder.ToTable("ProgramScheduleFloodItems"); + builder.ToTable("ProgramScheduleFloodItem"); } } diff --git a/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemMultipleConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemMultipleConfiguration.cs index acc449516..c2117e5b8 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemMultipleConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemMultipleConfiguration.cs @@ -7,6 +7,6 @@ namespace ErsatzTV.Infrastructure.Data.Configurations public class ProgramScheduleItemMultipleConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) => - builder.ToTable("ProgramScheduleMultipleItems"); + builder.ToTable("ProgramScheduleMultipleItem"); } } diff --git a/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemOneConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemOneConfiguration.cs index 5ef4b164e..20376f3ce 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemOneConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemOneConfiguration.cs @@ -7,6 +7,6 @@ namespace ErsatzTV.Infrastructure.Data.Configurations public class ProgramScheduleItemOneConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) => - builder.ToTable("ProgramScheduleOneItems"); + builder.ToTable("ProgramScheduleOneItem"); } } diff --git a/ErsatzTV.Infrastructure/Data/Configurations/ResolutionConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/ResolutionConfiguration.cs new file mode 100644 index 000000000..49c35ab91 --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Configurations/ResolutionConfiguration.cs @@ -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 + { + public void Configure(EntityTypeBuilder builder) => builder.ToTable("Resolution"); + } +} diff --git a/ErsatzTV.Infrastructure/Data/Configurations/SimpleMediaCollectionConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/SimpleMediaCollectionConfiguration.cs index 5f79f77a2..43fcb9cc5 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/SimpleMediaCollectionConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/SimpleMediaCollectionConfiguration.cs @@ -8,23 +8,23 @@ namespace ErsatzTV.Infrastructure.Data.Configurations { public void Configure(EntityTypeBuilder builder) { - builder.ToTable("SimpleMediaCollections"); + builder.ToTable("SimpleMediaCollection"); builder.HasMany(c => c.Movies) .WithMany(m => m.SimpleMediaCollections) - .UsingEntity(join => join.ToTable("SimpleMediaCollectionMovies")); + .UsingEntity(join => join.ToTable("SimpleMediaCollectionMovie")); builder.HasMany(c => c.TelevisionShows) .WithMany(s => s.SimpleMediaCollections) - .UsingEntity(join => join.ToTable("SimpleMediaCollectionShows")); + .UsingEntity(join => join.ToTable("SimpleMediaCollectionShow")); builder.HasMany(c => c.TelevisionSeasons) .WithMany(s => s.SimpleMediaCollections) - .UsingEntity(join => join.ToTable("SimpleMediaCollectionSeasons")); + .UsingEntity(join => join.ToTable("SimpleMediaCollectionSeason")); builder.HasMany(c => c.TelevisionEpisodes) .WithMany(e => e.SimpleMediaCollections) - .UsingEntity(join => join.ToTable("SimpleMediaCollectionEpisodes")); + .UsingEntity(join => join.ToTable("SimpleMediaCollectionEpisode")); } } } diff --git a/ErsatzTV.Infrastructure/Data/Configurations/TelevisionEpisodeMediaItemConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/TelevisionEpisodeMediaItemConfiguration.cs index 829316bbb..01bc60372 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/TelevisionEpisodeMediaItemConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/TelevisionEpisodeMediaItemConfiguration.cs @@ -8,7 +8,7 @@ namespace ErsatzTV.Infrastructure.Data.Configurations { public void Configure(EntityTypeBuilder builder) { - builder.ToTable("TelevisionEpisodes"); + builder.ToTable("TelevisionEpisode"); builder.HasOne(i => i.Metadata) .WithOne(m => m.TelevisionEpisode) diff --git a/ErsatzTV.Infrastructure/Data/Configurations/TelevisionSeasonConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/TelevisionSeasonConfiguration.cs index 8ebc89e8c..2e6d78deb 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/TelevisionSeasonConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/TelevisionSeasonConfiguration.cs @@ -8,7 +8,7 @@ namespace ErsatzTV.Infrastructure.Data.Configurations { public void Configure(EntityTypeBuilder builder) { - builder.ToTable("TelevisionSeasons"); + builder.ToTable("TelevisionSeason"); builder.HasMany(season => season.Episodes) .WithOne(episode => episode.Season) diff --git a/ErsatzTV.Infrastructure/Data/Configurations/TelevisionShowConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/TelevisionShowConfiguration.cs index 75464a52e..abc0a8fbd 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/TelevisionShowConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/TelevisionShowConfiguration.cs @@ -8,7 +8,7 @@ namespace ErsatzTV.Infrastructure.Data.Configurations { public void Configure(EntityTypeBuilder builder) { - builder.ToTable("TelevisionShows"); + builder.ToTable("TelevisionShow"); builder.HasOne(show => show.Metadata) .WithOne(metadata => metadata.TelevisionShow) diff --git a/ErsatzTV.Infrastructure/Data/Repositories/ChannelRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/ChannelRepository.cs index a7c6f5ddb..a2a1930b1 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/ChannelRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/ChannelRepository.cs @@ -42,7 +42,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories .Include(c => c.Playouts) .ThenInclude(p => p.Items) .ThenInclude(i => i.MediaItem) - .ThenInclude(i => (i as MovieMediaItem).Metadata) + .ThenInclude(i => (i as Movie).Metadata) .ToListAsync(); public async Task Update(Channel channel) diff --git a/ErsatzTV.Infrastructure/Data/Repositories/LibraryRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/LibraryRepository.cs new file mode 100644 index 000000000..384c49b02 --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Repositories/LibraryRepository.cs @@ -0,0 +1,38 @@ +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 _dbContextFactory; + + public LibraryRepository(IDbContextFactory dbContextFactory, IDbConnection dbConnection) + { + _dbContextFactory = dbContextFactory; + _dbConnection = dbConnection; + } + + public Task> 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 UpdateLastScan(Library library) => _dbConnection.ExecuteAsync( + "UPDATE Library SET LastScan = @LastScan WHERE Id = @Id", + new { library.LastScan, library.Id }).ToUnit(); + } +} diff --git a/ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs index 8dfa21a68..9f8c529e2 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs @@ -41,7 +41,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories public Task> GetSimpleMediaCollectionWithItems(int id) => _dbContext.SimpleMediaCollections .Include(s => s.Movies) - .ThenInclude(m => m.Source) + .ThenInclude(m => m.LibraryPath) .Include(s => s.TelevisionShows) .ThenInclude(s => s.Metadata) .Include(s => s.TelevisionSeasons) @@ -54,7 +54,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories _dbContext.SimpleMediaCollections .AsNoTracking() .Include(s => s.Movies) - .ThenInclude(i => i.Source) + .ThenInclude(i => i.LibraryPath) .Include(s => s.Movies) .ThenInclude(m => m.Metadata) .Include(s => s.TelevisionShows) @@ -121,16 +121,16 @@ namespace ErsatzTV.Infrastructure.Data.Repositories return await _dbConnection .QueryAsync( - @"select tmi.Id, tmi.SeasonId, mi.MediaSourceId, mi.LastWriteTime, mi.Path, mi.Poster, mi.PosterLastWriteTime, + @"select tmi.Id, tmi.SeasonId, mi.LibraryPathId, 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 +from TelevisionEpisode tmi +inner join MediaItem 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 +inner join TelevisionSeason tsn on tsn.Id = tmi.SeasonId +inner join TelevisionShow ts on ts.Id = tsn.TelevisionShowId +inner join SimpleMediaCollectionShow s on s.TelevisionShowsId = ts.Id where s.SimpleMediaCollectionsId = @CollectionId", (episode, statistics, metadata) => { @@ -148,15 +148,15 @@ where s.SimpleMediaCollectionsId = @CollectionId", return await _dbConnection .QueryAsync( - @"select tmi.Id, tmi.SeasonId, mi.MediaSourceId, mi.LastWriteTime, mi.Path, mi.Poster, mi.PosterLastWriteTime, + @"select tmi.Id, tmi.SeasonId, mi.LibraryPathId, 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 +from TelevisionEpisode tmi +inner join MediaItem 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 +inner join TelevisionSeason tsn on tsn.Id = tmi.SeasonId +inner join SimpleMediaCollectionSeason s on s.TelevisionSeasonsId = tsn.Id where s.SimpleMediaCollectionsId = @CollectionId", (episode, statistics, metadata) => { @@ -174,14 +174,14 @@ where s.SimpleMediaCollectionsId = @CollectionId", return await _dbConnection .QueryAsync( - @"select tmi.Id, tmi.SeasonId, mi.MediaSourceId, mi.LastWriteTime, mi.Path, mi.Poster, mi.PosterLastWriteTime, + @"select tmi.Id, tmi.SeasonId, mi.LibraryPathId, 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 +from TelevisionEpisode tmi +inner join MediaItem mi on tmi.Id = mi.Id inner join TelevisionEpisodeMetadata tem on tem.TelevisionEpisodeId = tmi.Id -inner join SimpleMediaCollectionEpisodes s on s.TelevisionEpisodesId = tmi.Id +inner join SimpleMediaCollectionEpisode s on s.TelevisionEpisodesId = tmi.Id where s.SimpleMediaCollectionsId = @CollectionId", (episode, statistics, metadata) => { diff --git a/ErsatzTV.Infrastructure/Data/Repositories/MediaItemRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/MediaItemRepository.cs index add440fd5..d2da8cd29 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/MediaItemRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/MediaItemRepository.cs @@ -17,7 +17,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories public Task> Get(int id) => _dbContext.MediaItems - .Include(i => i.Source) + .Include(i => i.LibraryPath) .SingleOrDefaultAsync(i => i.Id == id) .Map(Optional); @@ -26,15 +26,15 @@ namespace ErsatzTV.Infrastructure.Data.Repositories public Task> Search(string searchString) { IQueryable episodeData = - from c in _dbContext.TelevisionEpisodeMediaItems.Include(c => c.Source) select c; + 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 movieData = - from c in _dbContext.MovieMediaItems.Include(c => c.Source) select c; + IQueryable movieData = + from c in _dbContext.Movies.Include(c => c.LibraryPath) select c; if (!string.IsNullOrEmpty(searchString)) { diff --git a/ErsatzTV.Infrastructure/Data/Repositories/MediaSourceRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/MediaSourceRepository.cs index d1462f4d3..b012e461a 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/MediaSourceRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/MediaSourceRepository.cs @@ -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 _dbContextFactory; - public MediaSourceRepository(TvContext dbContext) => _dbContext = dbContext; + public MediaSourceRepository( + IDbContextFactory dbContextFactory, + IDbConnection dbConnection) + { + _dbContextFactory = dbContextFactory; + _dbConnection = dbConnection; + } public async Task 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 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> GetAll() { - List all = await _dbContext.MediaSources.ToListAsync(); + await using TvContext context = _dbContextFactory.CreateDbContext(); + List all = await context.MediaSources.ToListAsync(); foreach (PlexMediaSource plex in all.OfType()) { - await _dbContext.Entry(plex).Collection(p => p.Connections).LoadAsync(); + await context.Entry(plex).Collection(p => p.Connections).LoadAsync(); } return all; } - public Task> GetAllPlex() => - _dbContext.PlexMediaSources + public Task> GetAllPlex() + { + using TvContext context = _dbContextFactory.CreateDbContext(); + return context.PlexMediaSources .Include(p => p.Connections) .ToListAsync(); + } + + public Task> GetPlexLibraries(int plexMediaSourceId) + { + using TvContext context = _dbContextFactory.CreateDbContext(); + return context.PlexLibraries + .Filter(l => l.MediaSourceId == plexMediaSourceId) + .ToListAsync(); + } - public Task> Get(int id) => - _dbContext.MediaSources + public Task> 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> Get(int id) + { + using TvContext context = _dbContextFactory.CreateDbContext(); + return context.MediaSources + .OrderBy(s => s.Id) // https://github.com/dotnet/efcore/issues/22579 .SingleOrDefaultAsync(s => s.Id == id) .Map(Optional); + } - public Task> GetPlex(int id) => - _dbContext.PlexMediaSources + public Task> 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 CountMediaItems(int id) => - _dbContext.MediaItems.CountAsync(i => i.MediaSourceId == id); + public Task 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 libraryIds) => + _dbConnection.ExecuteAsync( + "UPDATE PlexMediaSourceLibraries SET ShouldSyncItems = 0 WHERE Id in @ids", + new { ids = libraryIds }); + + public Task EnablePlexLibrarySync(IEnumerable libraryIds) => + _dbConnection.ExecuteAsync( + "UPDATE PlexMediaSourceLibraries SET ShouldSyncItems = 1 WHERE Id in @ids", + new { ids = libraryIds }); } } diff --git a/ErsatzTV.Infrastructure/Data/Repositories/MovieRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/MovieRepository.cs index 29b7a3a61..747ddb9b9 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/MovieRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/MovieRepository.cs @@ -17,36 +17,52 @@ namespace ErsatzTV.Infrastructure.Data.Repositories public MovieRepository(TvContext dbContext) => _dbContext = dbContext; - public Task> GetMovie(int movieId) => - _dbContext.MovieMediaItems + public Task> GetMovie(int movieId) => + _dbContext.Movies .Include(m => m.Metadata) .SingleOrDefaultAsync(m => m.Id == movieId) .Map(Optional); - public async Task> GetOrAdd(int mediaSourceId, string path) + // TODO: fix this - need to add to library path, not media source + public async Task> GetOrAdd(LibraryPath libraryPath, string path) { - Option maybeExisting = await _dbContext.MovieMediaItems + Option maybeExisting = await _dbContext.Movies .Include(i => i.Metadata) + .Include(i => i.LibraryPath) .SingleOrDefaultAsync(i => i.Path == path); return await maybeExisting.Match( - mediaItem => Right(mediaItem).AsTask(), - async () => await AddMovie(mediaSourceId, path)); + mediaItem => Right(mediaItem).AsTask(), + async () => await AddMovie(libraryPath.Id, path)); } - public async Task Update(MovieMediaItem movie) + public async Task> GetOrAdd( + PlexLibrary library, + PlexMovie item) { - _dbContext.MovieMediaItems.Update(movie); + Option maybeExisting = await _dbContext.PlexMovieMediaItems + .Include(i => i.Metadata) + .Include(i => i.Part) + .SingleOrDefaultAsync(i => i.Key == item.Key); + + return await maybeExisting.Match( + plexMovie => Right(plexMovie).AsTask(), + async () => await AddPlexMovie(library, item)); + } + + public async Task Update(Movie movie) + { + _dbContext.Movies.Update(movie); return await _dbContext.SaveChangesAsync() > 0; } public Task GetMovieCount() => - _dbContext.MovieMediaItems + _dbContext.Movies .AsNoTracking() .CountAsync(); - public Task> GetPagedMovies(int pageNumber, int pageSize) => - _dbContext.MovieMediaItems + public Task> GetPagedMovies(int pageNumber, int pageSize) => + _dbContext.Movies .Include(s => s.Metadata) .OrderBy(s => s.Metadata.SortTitle) .Skip((pageNumber - 1) * pageSize) @@ -54,13 +70,14 @@ namespace ErsatzTV.Infrastructure.Data.Repositories .AsNoTracking() .ToListAsync(); - private async Task> AddMovie(int mediaSourceId, string path) + private async Task> 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, 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 +85,25 @@ namespace ErsatzTV.Infrastructure.Data.Repositories return BaseError.New(ex.Message); } } + + private async Task> AddPlexMovie( + PlexLibrary library, + PlexMovie item) + { + try + { + // TODO: this should be library path id + 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); + } + } } } diff --git a/ErsatzTV.Infrastructure/Data/Repositories/PlayoutRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/PlayoutRepository.cs index 42e3657d9..9b6cb543a 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/PlayoutRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/PlayoutRepository.cs @@ -58,7 +58,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories public Task> GetPlayoutItems(int playoutId) => _dbContext.PlayoutItems .Include(i => i.MediaItem) - .ThenInclude(m => (m as MovieMediaItem).Metadata) + .ThenInclude(m => (m as Movie).Metadata) .Include(i => i.MediaItem) .ThenInclude(m => (m as TelevisionEpisodeMediaItem).Metadata) .Filter(i => i.PlayoutId == playoutId) diff --git a/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs index ce2894ec0..031890d2c 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs @@ -132,6 +132,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories .Take(pageSize) .ToListAsync(); + // TODO: lookup by library path id, not media source id public async Task> GetShowByPath(int mediaSourceId, string path) { Option maybeShowId = await _dbContext.LocalTelevisionShowSources @@ -211,9 +212,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories () => AddSeason(show, path, seasonNumber)); } + // TODO: don't use mediaSourceId, use library path id public async Task> GetOrAddEpisode( TelevisionSeason season, - int mediaSourceId, + LibraryPath libraryPath, string path) { Option maybeExisting = await _dbContext.TelevisionEpisodeMediaItems @@ -223,7 +225,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories return await maybeExisting.Match( episode => Right(episode).AsTask(), - () => AddEpisode(season, mediaSourceId, path)); + () => AddEpisode(season, libraryPath.Id, path)); } public Task DeleteMissingSources(int localMediaSourceId, List allFolders) => @@ -256,15 +258,15 @@ namespace ErsatzTV.Infrastructure.Data.Repositories return await _dbConnection .QueryAsync( - @"select tmi.Id, tmi.SeasonId, mi.MediaSourceId, mi.LastWriteTime, mi.Path, mi.Poster, mi.PosterLastWriteTime, + @"select tmi.Id, tmi.SeasonId, mi.LibraryPathId, 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 +from TelevisionEpisode tmi +inner join MediaItem 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 TelevisionSeason tsn on tsn.Id = tmi.SeasonId +inner join TelevisionShow ts on ts.Id = tsn.TelevisionShowId where ts.Id = @ShowId", (episode, statistics, metadata) => { @@ -282,14 +284,14 @@ where ts.Id = @ShowId", return await _dbConnection .QueryAsync( - @"select tmi.Id, tmi.SeasonId, mi.MediaSourceId, mi.LastWriteTime, mi.Path, mi.Poster, mi.PosterLastWriteTime, + @"select tmi.Id, tmi.SeasonId, mi.LibraryPathId, 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 +from TelevisionEpisode tmi +inner join MediaItem 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 TelevisionSeason tsn on tsn.Id = tmi.SeasonId where tsn.Id = @SeasonId", (episode, statistics, metadata) => { @@ -332,7 +334,7 @@ where tsn.Id = @SeasonId", { var episode = new TelevisionEpisodeMediaItem { - MediaSourceId = mediaSourceId, + LibraryPathId = mediaSourceId, SeasonId = season.Id, Path = path }; diff --git a/ErsatzTV.Infrastructure/Data/SqliteTypeHandlers.cs b/ErsatzTV.Infrastructure/Data/SqliteTypeHandlers.cs index c9ac2fb99..3fe2ece00 100644 --- a/ErsatzTV.Infrastructure/Data/SqliteTypeHandlers.cs +++ b/ErsatzTV.Infrastructure/Data/SqliteTypeHandlers.cs @@ -14,19 +14,18 @@ namespace ErsatzTV.Infrastructure.Data public class DateTimeOffsetHandler : SqliteTypeHandler { public override DateTimeOffset Parse(object value) - => DateTimeOffset.Parse((string)value); + => DateTimeOffset.Parse((string) value); } public class GuidHandler : SqliteTypeHandler { public override Guid Parse(object value) - => Guid.Parse((string)value); + => Guid.Parse((string) value); } public class TimeSpanHandler : SqliteTypeHandler { public override TimeSpan Parse(object value) - => TimeSpan.Parse((string)value); + => TimeSpan.Parse((string) value); } - } diff --git a/ErsatzTV.Infrastructure/Data/TvContext.cs b/ErsatzTV.Infrastructure/Data/TvContext.cs index d6a0854f6..d14ed5b83 100644 --- a/ErsatzTV.Infrastructure/Data/TvContext.cs +++ b/ErsatzTV.Infrastructure/Data/TvContext.cs @@ -17,8 +17,14 @@ namespace ErsatzTV.Infrastructure.Data public DbSet MediaSources { get; set; } public DbSet LocalMediaSources { get; set; } public DbSet PlexMediaSources { get; set; } + public DbSet Libraries { get; set; } + public DbSet PlexLibraries { get; set; } public DbSet MediaItems { get; set; } - public DbSet MovieMediaItems { get; set; } + public DbSet Movies { get; set; } + public DbSet Shows { get; set; } + public DbSet Seasons { get; set; } + public DbSet Episodes { get; set; } + public DbSet PlexMovieMediaItems { get; set; } public DbSet TelevisionEpisodeMediaItems { get; set; } public DbSet MediaCollections { get; set; } public DbSet SimpleMediaCollections { get; set; } diff --git a/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj.DotSettings b/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj.DotSettings new file mode 100644 index 000000000..8447872c0 --- /dev/null +++ b/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj.DotSettings @@ -0,0 +1,6 @@ + + True + True + True + True + True \ No newline at end of file diff --git a/ErsatzTV.Infrastructure/Locking/EntityLocker.cs b/ErsatzTV.Infrastructure/Locking/EntityLocker.cs index 26122230b..a0e528bb8 100644 --- a/ErsatzTV.Infrastructure/Locking/EntityLocker.cs +++ b/ErsatzTV.Infrastructure/Locking/EntityLocker.cs @@ -10,31 +10,31 @@ namespace ErsatzTV.Infrastructure.Locking public EntityLocker() => _lockedMediaSources = new ConcurrentDictionary(); - public event EventHandler OnMediaSourceChanged; + public event EventHandler OnLibraryChanged; - public bool LockMediaSource(int mediaSourceId) + public bool LockLibrary(int mediaSourceId) { if (!_lockedMediaSources.ContainsKey(mediaSourceId) && _lockedMediaSources.TryAdd(mediaSourceId, 0)) { - OnMediaSourceChanged?.Invoke(this, EventArgs.Empty); + OnLibraryChanged?.Invoke(this, EventArgs.Empty); return true; } return false; } - public bool UnlockMediaSource(int mediaSourceId) + public bool UnlockLibrary(int mediaSourceId) { if (_lockedMediaSources.TryRemove(mediaSourceId, out byte _)) { - OnMediaSourceChanged?.Invoke(this, EventArgs.Empty); + OnLibraryChanged?.Invoke(this, EventArgs.Empty); return true; } return false; } - public bool IsMediaSourceLocked(int mediaSourceId) => + public bool IsLibraryLocked(int mediaSourceId) => _lockedMediaSources.ContainsKey(mediaSourceId); } } diff --git a/ErsatzTV.Infrastructure/Migrations/20210225145537_LibraryRework.Designer.cs b/ErsatzTV.Infrastructure/Migrations/20210225145537_LibraryRework.Designer.cs new file mode 100644 index 000000000..3fa1c3c7c --- /dev/null +++ b/ErsatzTV.Infrastructure/Migrations/20210225145537_LibraryRework.Designer.cs @@ -0,0 +1,1402 @@ +// +using System; +using ErsatzTV.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace ErsatzTV.Infrastructure.Migrations +{ + [DbContext(typeof(TvContext))] + [Migration("20210225145537_LibraryRework")] + partial class LibraryRework + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.3"); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FFmpegProfileId") + .HasColumnType("INTEGER"); + + b.Property("Logo") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("StreamingMode") + .HasColumnType("INTEGER"); + + b.Property("UniqueId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FFmpegProfileId"); + + b.HasIndex("Number") + .IsUnique(); + + b.ToTable("Channels"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ConfigElement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("ConfigElements"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.FFmpegProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AudioBitrate") + .HasColumnType("INTEGER"); + + b.Property("AudioBufferSize") + .HasColumnType("INTEGER"); + + b.Property("AudioChannels") + .HasColumnType("INTEGER"); + + b.Property("AudioCodec") + .HasColumnType("TEXT"); + + b.Property("AudioSampleRate") + .HasColumnType("INTEGER"); + + b.Property("AudioVolume") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizeAudio") + .HasColumnType("INTEGER"); + + b.Property("NormalizeAudioCodec") + .HasColumnType("INTEGER"); + + b.Property("NormalizeResolution") + .HasColumnType("INTEGER"); + + b.Property("NormalizeVideoCodec") + .HasColumnType("INTEGER"); + + b.Property("ResolutionId") + .HasColumnType("INTEGER"); + + b.Property("ThreadCount") + .HasColumnType("INTEGER"); + + b.Property("Transcode") + .HasColumnType("INTEGER"); + + b.Property("VideoBitrate") + .HasColumnType("INTEGER"); + + b.Property("VideoBufferSize") + .HasColumnType("INTEGER"); + + b.Property("VideoCodec") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ResolutionId"); + + b.ToTable("FFmpegProfiles"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastScan") + .HasColumnType("TEXT"); + + b.Property("MediaKind") + .HasColumnType("INTEGER"); + + b.Property("MediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MediaSourceId"); + + b.ToTable("Library"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("LibraryPath"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaCollection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("MediaCollections"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("LibraryPathId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("Poster") + .HasColumnType("TEXT"); + + b.Property("PosterLastWriteTime") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryPathId"); + + b.ToTable("MediaItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("MediaSources"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ContentRating") + .HasColumnType("TEXT"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("MovieId") + .HasColumnType("INTEGER"); + + b.Property("MovieId1") + .HasColumnType("INTEGER"); + + b.Property("Outline") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("Premiered") + .HasColumnType("TEXT"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("Tagline") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MovieId") + .IsUnique(); + + b.HasIndex("MovieId1"); + + b.ToTable("MovieMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("ProgramScheduleId") + .HasColumnType("INTEGER"); + + b.Property("ProgramSchedulePlayoutType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("ProgramScheduleId"); + + b.ToTable("Playouts"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Finish") + .HasColumnType("TEXT"); + + b.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b.Property("PlayoutId") + .HasColumnType("INTEGER"); + + b.Property("Start") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MediaItemId"); + + b.HasIndex("PlayoutId"); + + b.ToTable("PlayoutItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CollectionId") + .HasColumnType("INTEGER"); + + b.Property("CollectionType") + .HasColumnType("INTEGER"); + + b.Property("PlayoutId") + .HasColumnType("INTEGER"); + + b.Property("ProgramScheduleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayoutId"); + + b.HasIndex("ProgramScheduleId"); + + b.ToTable("PlayoutProgramScheduleItemAnchors"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("PlexMediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlexMediaSourceId"); + + b.ToTable("PlexConnection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaItemPart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Duration") + .HasColumnType("INTEGER"); + + b.Property("File") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("PlexId") + .HasColumnType("INTEGER"); + + b.Property("Size") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("PlexMediaItemPart"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("MediaCollectionPlaybackOrder") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ProgramSchedules"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CollectionType") + .HasColumnType("INTEGER"); + + b.Property("Index") + .HasColumnType("INTEGER"); + + b.Property("MediaCollectionId") + .HasColumnType("INTEGER"); + + b.Property("ProgramScheduleId") + .HasColumnType("INTEGER"); + + b.Property("StartTime") + .HasColumnType("TEXT"); + + b.Property("TelevisionSeasonId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MediaCollectionId"); + + b.HasIndex("ProgramScheduleId"); + + b.HasIndex("TelevisionSeasonId"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("ProgramScheduleItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Resolution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Resolutions"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Aired") + .HasColumnType("TEXT"); + + b.Property("Episode") + .HasColumnType("INTEGER"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("Season") + .HasColumnType("INTEGER"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("TelevisionEpisodeId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionEpisodeId") + .IsUnique(); + + b.ToTable("TelevisionEpisodeMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("Poster") + .HasColumnType("TEXT"); + + b.Property("PosterLastWriteTime") + .HasColumnType("TEXT"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("TelevisionSeasons"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Poster") + .HasColumnType("TEXT"); + + b.Property("PosterLastWriteTime") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TelevisionShows"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionShowId") + .IsUnique(); + + b.ToTable("TelevisionShowMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("TelevisionShowSource"); + + b.HasDiscriminator("Discriminator").HasValue("TelevisionShowSource"); + }); + + modelBuilder.Entity("MovieMediaItemSimpleMediaCollection", b => + { + b.Property("MoviesId") + .HasColumnType("INTEGER"); + + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.HasKey("MoviesId", "SimpleMediaCollectionsId"); + + b.HasIndex("SimpleMediaCollectionsId"); + + b.ToTable("SimpleMediaCollectionMovies"); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionEpisodeMediaItem", b => + { + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionEpisodesId") + .HasColumnType("INTEGER"); + + b.HasKey("SimpleMediaCollectionsId", "TelevisionEpisodesId"); + + b.HasIndex("TelevisionEpisodesId"); + + b.ToTable("SimpleMediaCollectionEpisodes"); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionSeason", b => + { + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionSeasonsId") + .HasColumnType("INTEGER"); + + b.HasKey("SimpleMediaCollectionsId", "TelevisionSeasonsId"); + + b.HasIndex("TelevisionSeasonsId"); + + b.ToTable("SimpleMediaCollectionSeasons"); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionShow", b => + { + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowsId") + .HasColumnType("INTEGER"); + + b.HasKey("SimpleMediaCollectionsId", "TelevisionShowsId"); + + b.HasIndex("TelevisionShowsId"); + + b.ToTable("SimpleMediaCollectionShows"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalLibrary", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.Library"); + + b.ToTable("LocalLibrary"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexLibrary", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.Library"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("ShouldSyncItems") + .HasColumnType("INTEGER"); + + b.ToTable("PlexLibrary"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.SimpleMediaCollection", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaCollection"); + + b.ToTable("SimpleMediaCollections"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMediaItem", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("MetadataId") + .HasColumnType("INTEGER"); + + b.ToTable("Movies"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("SeasonId") + .HasColumnType("INTEGER"); + + b.HasIndex("SeasonId"); + + b.ToTable("TelevisionEpisodes"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaSource"); + + b.ToTable("LocalMediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaSource"); + + b.Property("ClientIdentifier") + .HasColumnType("TEXT"); + + b.Property("ProductVersion") + .HasColumnType("TEXT"); + + b.Property("ServerName") + .HasColumnType("TEXT"); + + b.ToTable("PlexMediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.Property("OfflineTail") + .HasColumnType("INTEGER"); + + b.Property("PlayoutDuration") + .HasColumnType("TEXT"); + + b.ToTable("ProgramScheduleDurationItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.ToTable("ProgramScheduleFloodItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.ToTable("ProgramScheduleMultipleItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemOne", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.ToTable("ProgramScheduleOneItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalTelevisionShowSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.TelevisionShowSource"); + + b.Property("MediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasIndex("MediaSourceId"); + + b.HasDiscriminator().HasValue("LocalTelevisionShowSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMovieMediaItem", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MovieMediaItem"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("PartId") + .HasColumnType("INTEGER"); + + b.HasIndex("PartId"); + + b.ToTable("PlexMovies"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.HasOne("ErsatzTV.Core.Domain.FFmpegProfile", "FFmpegProfile") + .WithMany() + .HasForeignKey("FFmpegProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FFmpegProfile"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.FFmpegProfile", b => + { + b.HasOne("ErsatzTV.Core.Domain.Resolution", "Resolution") + .WithMany() + .HasForeignKey("ResolutionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Resolution"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaSource", "MediaSource") + .WithMany("Libraries") + .HasForeignKey("MediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", "Library") + .WithMany("Paths") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.LibraryPath", "LibraryPath") + .WithMany("MediaItems") + .HasForeignKey("LibraryPathId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.MediaItemStatistics", "Statistics", b1 => + { + b1.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b1.Property("AudioCodec") + .HasColumnType("TEXT"); + + b1.Property("DisplayAspectRatio") + .HasColumnType("TEXT"); + + b1.Property("Duration") + .HasColumnType("TEXT"); + + b1.Property("Height") + .HasColumnType("INTEGER"); + + b1.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b1.Property("SampleAspectRatio") + .HasColumnType("TEXT"); + + b1.Property("VideoCodec") + .HasColumnType("TEXT"); + + b1.Property("VideoScanType") + .HasColumnType("INTEGER"); + + b1.Property("Width") + .HasColumnType("INTEGER"); + + b1.HasKey("MediaItemId"); + + b1.ToTable("MediaItems"); + + b1.WithOwner() + .HasForeignKey("MediaItemId"); + }); + + b.Navigation("LibraryPath"); + + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.MovieMediaItem", null) + .WithOne("Metadata") + .HasForeignKey("ErsatzTV.Core.Domain.MovieMetadata", "MovieId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.MovieMediaItem", "Movie") + .WithMany() + .HasForeignKey("MovieId1"); + + b.Navigation("Movie"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => + { + b.HasOne("ErsatzTV.Core.Domain.Channel", "Channel") + .WithMany("Playouts") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") + .WithMany("Playouts") + .HasForeignKey("ProgramScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.PlayoutAnchor", "Anchor", b1 => + { + b1.Property("PlayoutId") + .HasColumnType("INTEGER"); + + b1.Property("NextScheduleItemId") + .HasColumnType("INTEGER"); + + b1.Property("NextStart") + .HasColumnType("TEXT"); + + b1.HasKey("PlayoutId"); + + b1.HasIndex("NextScheduleItemId"); + + b1.ToTable("Playouts"); + + b1.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", "NextScheduleItem") + .WithMany() + .HasForeignKey("NextScheduleItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.WithOwner() + .HasForeignKey("PlayoutId"); + + b1.Navigation("NextScheduleItem"); + }); + + b.Navigation("Anchor"); + + b.Navigation("Channel"); + + b.Navigation("ProgramSchedule"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") + .WithMany() + .HasForeignKey("MediaItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout") + .WithMany("Items") + .HasForeignKey("PlayoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaItem"); + + b.Navigation("Playout"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", b => + { + b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout") + .WithMany("ProgramScheduleAnchors") + .HasForeignKey("PlayoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") + .WithMany() + .HasForeignKey("ProgramScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.MediaCollectionEnumeratorState", "EnumeratorState", b1 => + { + b1.Property("PlayoutProgramScheduleAnchorId") + .HasColumnType("INTEGER"); + + b1.Property("Index") + .HasColumnType("INTEGER"); + + b1.Property("Seed") + .HasColumnType("INTEGER"); + + b1.HasKey("PlayoutProgramScheduleAnchorId"); + + b1.ToTable("PlayoutProgramScheduleItemAnchors"); + + b1.WithOwner() + .HasForeignKey("PlayoutProgramScheduleAnchorId"); + }); + + b.Navigation("EnumeratorState"); + + b.Navigation("Playout"); + + b.Navigation("ProgramSchedule"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexConnection", b => + { + b.HasOne("ErsatzTV.Core.Domain.PlexMediaSource", "PlexMediaSource") + .WithMany("Connections") + .HasForeignKey("PlexMediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlexMediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaCollection", "MediaCollection") + .WithMany() + .HasForeignKey("MediaCollectionId"); + + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") + .WithMany("Items") + .HasForeignKey("ProgramScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionSeason", "TelevisionSeason") + .WithMany() + .HasForeignKey("TelevisionSeasonId"); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithMany() + .HasForeignKey("TelevisionShowId"); + + b.Navigation("MediaCollection"); + + b.Navigation("ProgramSchedule"); + + b.Navigation("TelevisionSeason"); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", "TelevisionEpisode") + .WithOne("Metadata") + .HasForeignKey("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", "TelevisionEpisodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionEpisode"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithMany("Seasons") + .HasForeignKey("TelevisionShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithOne("Metadata") + .HasForeignKey("ErsatzTV.Core.Domain.TelevisionShowMetadata", "TelevisionShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithMany("Sources") + .HasForeignKey("TelevisionShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("MovieMediaItemSimpleMediaCollection", b => + { + b.HasOne("ErsatzTV.Core.Domain.MovieMediaItem", null) + .WithMany() + .HasForeignKey("MoviesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionEpisodeMediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", null) + .WithMany() + .HasForeignKey("TelevisionEpisodesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionSeason", b => + { + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionSeason", null) + .WithMany() + .HasForeignKey("TelevisionSeasonsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionShow", b => + { + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", null) + .WithMany() + .HasForeignKey("TelevisionShowsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalLibrary", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.LocalLibrary", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexLibrary", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexLibrary", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.SimpleMediaCollection", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaCollection", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.SimpleMediaCollection", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.MovieMediaItem", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionSeason", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaSource", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.LocalMediaSource", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaSource", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexMediaSource", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemOne", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemOne", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalTelevisionShowSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.LocalMediaSource", "MediaSource") + .WithMany() + .HasForeignKey("MediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMovieMediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MovieMediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexMovieMediaItem", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.PlexMediaItemPart", "Part") + .WithMany() + .HasForeignKey("PartId"); + + b.Navigation("Part"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.Navigation("Playouts"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.Navigation("Paths"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.Navigation("MediaItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaSource", b => + { + b.Navigation("Libraries"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => + { + b.Navigation("Items"); + + b.Navigation("ProgramScheduleAnchors"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b => + { + b.Navigation("Items"); + + b.Navigation("Playouts"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShow", b => + { + b.Navigation("Metadata"); + + b.Navigation("Seasons"); + + b.Navigation("Sources"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMediaItem", b => + { + b.Navigation("Metadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.Navigation("Metadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.Navigation("Connections"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ErsatzTV.Infrastructure/Migrations/20210225145537_LibraryRework.cs b/ErsatzTV.Infrastructure/Migrations/20210225145537_LibraryRework.cs new file mode 100644 index 000000000..1ed94b86a --- /dev/null +++ b/ErsatzTV.Infrastructure/Migrations/20210225145537_LibraryRework.cs @@ -0,0 +1,574 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace ErsatzTV.Infrastructure.Migrations +{ + public partial class LibraryRework : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + // create local media source to attach all paths to + migrationBuilder.Sql("INSERT INTO MediaSources (SourceType) Values (99)"); + migrationBuilder.Sql("INSERT INTO LocalMediaSources (Id, MediaType) Values (last_insert_rowid(), 99)"); + + migrationBuilder.CreateTable( + "Library", + table => new + { + Id = table.Column("INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column("TEXT", nullable: true), + MediaKind = table.Column("INTEGER", nullable: false), + LastScan = table.Column("TEXT", nullable: true), + MediaSourceId = table.Column("INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Library", x => x.Id); + table.ForeignKey( + "FK_Library_MediaSources_MediaSourceId", + x => x.MediaSourceId, + "MediaSources", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + + migrationBuilder.CreateTable( + "LocalLibrary", + table => new + { + Id = table.Column("INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true) + }, + constraints: table => + { + table.PrimaryKey("PK_LocalLibrary", x => x.Id); + table.ForeignKey( + "FK_LocalLibrary_Library_Id", + x => x.Id, + "Library", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + // create local movies library + migrationBuilder.Sql( + @"INSERT INTO Library (Name, MediaKind, MediaSourceId) + SELECT 'Movies', 1, Id FROM LocalMediaSources WHERE MediaType = 99"); + migrationBuilder.Sql("INSERT INTO LocalLibrary (Id) Values (last_insert_rowid())"); + + // create local shows library + migrationBuilder.Sql( + @"INSERT INTO Library (Name, MediaKind, MediaSourceId) + SELECT 'Shows', 2, Id FROM LocalMediaSources WHERE MediaType = 99"); + migrationBuilder.Sql("INSERT INTO LocalLibrary (Id) Values (last_insert_rowid())"); + + migrationBuilder.CreateTable( + "LibraryPath", + table => new + { + Id = table.Column("INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Path = table.Column("TEXT", nullable: true), + LibraryId = table.Column("INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LibraryPath", x => x.Id); + table.ForeignKey( + "FK_LibraryPath_Library_LibraryId", + x => x.LibraryId, + "Library", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + // migrate movie source/folders to library paths + migrationBuilder.Sql( + @"INSERT INTO LibraryPath (Path, LibraryId) + SELECT lms.Folder, l.Id + FROM LocalMediaSources lms + LEFT OUTER JOIN Library l ON l.MediaKind = 1 + WHERE lms.MediaType = 2"); + + // migrate show source/folders to library paths + migrationBuilder.Sql( + @"INSERT INTO LibraryPath (Path, LibraryId) + SELECT lms.Folder, l.Id + FROM LocalMediaSources lms + LEFT OUTER JOIN Library l ON l.MediaKind = 2 + WHERE lms.MediaType = 1"); + + // migrate media item links + migrationBuilder.AddColumn( + "LibraryPathId", + "MediaItems", + "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.Sql( + @"UPDATE MediaItems SET LibraryPathId = + (SELECT l.Id FROM LibraryPath l INNER JOIN LocalMediaSources lms ON lms.Folder = l.Path WHERE lms.Id = MediaItems.MediaSourceId)"); + + migrationBuilder.DropColumn( + "MediaSourceId", + "MediaItems"); + + migrationBuilder.DropIndex( + "IX_MediaItems_MediaSourceId", + "MediaItems"); + + migrationBuilder.DropForeignKey( + "FK_MediaItems_MediaSources_MediaSourceId", + "MediaItems"); + + migrationBuilder.CreateIndex( + "IX_MediaItems_LibraryPathId", + "MediaItems", + "LibraryPathId"); + + migrationBuilder.DropForeignKey( + "FK_LocalMediaSources_MediaSources_Id", + "LocalMediaSources"); + + migrationBuilder.DropForeignKey( + "FK_PlexMediaSources_MediaSources_Id", + "PlexMediaSources"); + + migrationBuilder.DropForeignKey( + "FK_TelevisionShowSource_LocalMediaSources_MediaSourceId", + "TelevisionShowSource"); + + migrationBuilder.DropTable( + "PlexMediaSourceConnections"); + + migrationBuilder.DropTable( + "PlexMediaSourceLibraries"); + + migrationBuilder.DropIndex( + "IX_MediaSources_Name", + "MediaSources"); + + migrationBuilder.DropPrimaryKey( + "PK_PlexMediaSources", + "PlexMediaSources"); + + migrationBuilder.DropPrimaryKey( + "PK_LocalMediaSources", + "LocalMediaSources"); + + migrationBuilder.DropColumn( + "LastScan", + "MediaSources"); + + migrationBuilder.DropColumn( + "Name", + "MediaSources"); + + migrationBuilder.DropColumn( + "SourceType", + "MediaSources"); + + migrationBuilder.DropColumn( + "Folder", + "LocalMediaSources"); + + migrationBuilder.DropColumn( + "MediaType", + "LocalMediaSources"); + + migrationBuilder.RenameTable( + "PlexMediaSources", + newName: "PlexMediaSource"); + + migrationBuilder.RenameTable( + "LocalMediaSources", + newName: "LocalMediaSource"); + + migrationBuilder.AddColumn( + "MovieId1", + "MovieMetadata", + "INTEGER", + nullable: true); + + migrationBuilder.AddColumn( + "ServerName", + "PlexMediaSource", + "TEXT", + nullable: true); + + migrationBuilder.AddPrimaryKey( + "PK_PlexMediaSource", + "PlexMediaSource", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_LocalMediaSource", + "LocalMediaSource", + "Id"); + + migrationBuilder.CreateTable( + "PlexConnection", + table => new + { + Id = table.Column("INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + IsActive = table.Column("INTEGER", nullable: false), + Uri = table.Column("TEXT", nullable: true), + PlexMediaSourceId = table.Column("INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PlexConnection", x => x.Id); + table.ForeignKey( + "FK_PlexConnection_PlexMediaSource_PlexMediaSourceId", + x => x.PlexMediaSourceId, + "PlexMediaSource", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + "PlexMediaItemPart", + table => new + { + Id = table.Column("INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PlexId = table.Column("INTEGER", nullable: false), + Key = table.Column("TEXT", nullable: true), + Duration = table.Column("INTEGER", nullable: false), + File = table.Column("TEXT", nullable: true), + Size = table.Column("INTEGER", nullable: false) + }, + constraints: table => { table.PrimaryKey("PK_PlexMediaItemPart", x => x.Id); }); + + migrationBuilder.CreateTable( + "PlexLibrary", + table => new + { + Id = table.Column("INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Key = table.Column("TEXT", nullable: true), + ShouldSyncItems = table.Column("INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PlexLibrary", x => x.Id); + table.ForeignKey( + "FK_PlexLibrary_Library_Id", + x => x.Id, + "Library", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + "PlexMovies", + table => new + { + Id = table.Column("INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Key = table.Column("TEXT", nullable: true), + PartId = table.Column("INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PlexMovies", x => x.Id); + table.ForeignKey( + "FK_PlexMovies_Movies_Id", + x => x.Id, + "Movies", + "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + "FK_PlexMovies_PlexMediaItemPart_PartId", + x => x.PartId, + "PlexMediaItemPart", + "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + "IX_MovieMetadata_MovieId1", + "MovieMetadata", + "MovieId1"); + + migrationBuilder.CreateIndex( + "IX_Library_MediaSourceId", + "Library", + "MediaSourceId"); + + migrationBuilder.CreateIndex( + "IX_LibraryPath_LibraryId", + "LibraryPath", + "LibraryId"); + + migrationBuilder.CreateIndex( + "IX_PlexConnection_PlexMediaSourceId", + "PlexConnection", + "PlexMediaSourceId"); + + migrationBuilder.CreateIndex( + "IX_PlexMovies_PartId", + "PlexMovies", + "PartId"); + + migrationBuilder.AddForeignKey( + "FK_LocalMediaSource_MediaSources_Id", + "LocalMediaSource", + "Id", + "MediaSources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_MediaItems_LibraryPath_LibraryPathId", + "MediaItems", + "LibraryPathId", + "LibraryPath", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_MovieMetadata_Movies_MovieId1", + "MovieMetadata", + "MovieId1", + "Movies", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + "FK_PlexMediaSource_MediaSources_Id", + "PlexMediaSource", + "Id", + "MediaSources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_TelevisionShowSource_LocalMediaSource_MediaSourceId", + "TelevisionShowSource", + "MediaSourceId", + "LocalMediaSource", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + "FK_LocalMediaSource_MediaSources_Id", + "LocalMediaSource"); + + migrationBuilder.DropForeignKey( + "FK_MediaItems_LibraryPath_LibraryPathId", + "MediaItems"); + + migrationBuilder.DropForeignKey( + "FK_MovieMetadata_Movies_MovieId1", + "MovieMetadata"); + + migrationBuilder.DropForeignKey( + "FK_PlexMediaSource_MediaSources_Id", + "PlexMediaSource"); + + migrationBuilder.DropForeignKey( + "FK_TelevisionShowSource_LocalMediaSource_MediaSourceId", + "TelevisionShowSource"); + + migrationBuilder.DropTable( + "LibraryPath"); + + migrationBuilder.DropTable( + "LocalLibrary"); + + migrationBuilder.DropTable( + "PlexConnection"); + + migrationBuilder.DropTable( + "PlexLibrary"); + + migrationBuilder.DropTable( + "PlexMovies"); + + migrationBuilder.DropTable( + "Library"); + + migrationBuilder.DropTable( + "PlexMediaItemPart"); + + migrationBuilder.DropIndex( + "IX_MovieMetadata_MovieId1", + "MovieMetadata"); + + migrationBuilder.DropPrimaryKey( + "PK_PlexMediaSource", + "PlexMediaSource"); + + migrationBuilder.DropPrimaryKey( + "PK_LocalMediaSource", + "LocalMediaSource"); + + migrationBuilder.DropColumn( + "MovieId1", + "MovieMetadata"); + + migrationBuilder.DropColumn( + "ServerName", + "PlexMediaSource"); + + migrationBuilder.RenameTable( + "PlexMediaSource", + newName: "PlexMediaSources"); + + migrationBuilder.RenameTable( + "LocalMediaSource", + newName: "LocalMediaSources"); + + migrationBuilder.RenameColumn( + "LibraryPathId", + "MediaItems", + "MediaSourceId"); + + migrationBuilder.RenameIndex( + "IX_MediaItems_LibraryPathId", + table: "MediaItems", + newName: "IX_MediaItems_MediaSourceId"); + + migrationBuilder.AddColumn( + "LastScan", + "MediaSources", + "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + "Name", + "MediaSources", + "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + "SourceType", + "MediaSources", + "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + "Folder", + "LocalMediaSources", + "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + "MediaType", + "LocalMediaSources", + "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddPrimaryKey( + "PK_PlexMediaSources", + "PlexMediaSources", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_LocalMediaSources", + "LocalMediaSources", + "Id"); + + migrationBuilder.CreateTable( + "PlexMediaSourceConnections", + table => new + { + Id = table.Column("INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + IsActive = table.Column("INTEGER", nullable: false), + PlexMediaSourceId = table.Column("INTEGER", nullable: true), + Uri = table.Column("TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PlexMediaSourceConnections", x => x.Id); + table.ForeignKey( + "FK_PlexMediaSourceConnections_PlexMediaSources_PlexMediaSourceId", + x => x.PlexMediaSourceId, + "PlexMediaSources", + "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + "PlexMediaSourceLibraries", + table => new + { + Id = table.Column("INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Key = table.Column("TEXT", nullable: true), + MediaType = table.Column("INTEGER", nullable: false), + Name = table.Column("TEXT", nullable: true), + PlexMediaSourceId = table.Column("INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PlexMediaSourceLibraries", x => x.Id); + table.ForeignKey( + "FK_PlexMediaSourceLibraries_PlexMediaSources_PlexMediaSourceId", + x => x.PlexMediaSourceId, + "PlexMediaSources", + "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + "IX_MediaSources_Name", + "MediaSources", + "Name", + unique: true); + + migrationBuilder.CreateIndex( + "IX_PlexMediaSourceConnections_PlexMediaSourceId", + "PlexMediaSourceConnections", + "PlexMediaSourceId"); + + migrationBuilder.CreateIndex( + "IX_PlexMediaSourceLibraries_PlexMediaSourceId", + "PlexMediaSourceLibraries", + "PlexMediaSourceId"); + + migrationBuilder.AddForeignKey( + "FK_LocalMediaSources_MediaSources_Id", + "LocalMediaSources", + "Id", + "MediaSources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_MediaItems_MediaSources_MediaSourceId", + "MediaItems", + "MediaSourceId", + "MediaSources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_PlexMediaSources_MediaSources_Id", + "PlexMediaSources", + "Id", + "MediaSources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_TelevisionShowSource_LocalMediaSources_MediaSourceId", + "TelevisionShowSource", + "MediaSourceId", + "LocalMediaSources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/ErsatzTV.Infrastructure/Migrations/20210225153055_RemoveOldMediaSources.Designer.cs b/ErsatzTV.Infrastructure/Migrations/20210225153055_RemoveOldMediaSources.Designer.cs new file mode 100644 index 000000000..05a26a152 --- /dev/null +++ b/ErsatzTV.Infrastructure/Migrations/20210225153055_RemoveOldMediaSources.Designer.cs @@ -0,0 +1,1402 @@ +// +using System; +using ErsatzTV.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace ErsatzTV.Infrastructure.Migrations +{ + [DbContext(typeof(TvContext))] + [Migration("20210225153055_RemoveOldMediaSources")] + partial class RemoveOldMediaSources + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.3"); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FFmpegProfileId") + .HasColumnType("INTEGER"); + + b.Property("Logo") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("StreamingMode") + .HasColumnType("INTEGER"); + + b.Property("UniqueId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FFmpegProfileId"); + + b.HasIndex("Number") + .IsUnique(); + + b.ToTable("Channels"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ConfigElement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("ConfigElements"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.FFmpegProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AudioBitrate") + .HasColumnType("INTEGER"); + + b.Property("AudioBufferSize") + .HasColumnType("INTEGER"); + + b.Property("AudioChannels") + .HasColumnType("INTEGER"); + + b.Property("AudioCodec") + .HasColumnType("TEXT"); + + b.Property("AudioSampleRate") + .HasColumnType("INTEGER"); + + b.Property("AudioVolume") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizeAudio") + .HasColumnType("INTEGER"); + + b.Property("NormalizeAudioCodec") + .HasColumnType("INTEGER"); + + b.Property("NormalizeResolution") + .HasColumnType("INTEGER"); + + b.Property("NormalizeVideoCodec") + .HasColumnType("INTEGER"); + + b.Property("ResolutionId") + .HasColumnType("INTEGER"); + + b.Property("ThreadCount") + .HasColumnType("INTEGER"); + + b.Property("Transcode") + .HasColumnType("INTEGER"); + + b.Property("VideoBitrate") + .HasColumnType("INTEGER"); + + b.Property("VideoBufferSize") + .HasColumnType("INTEGER"); + + b.Property("VideoCodec") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ResolutionId"); + + b.ToTable("FFmpegProfiles"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastScan") + .HasColumnType("TEXT"); + + b.Property("MediaKind") + .HasColumnType("INTEGER"); + + b.Property("MediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MediaSourceId"); + + b.ToTable("Library"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("LibraryPath"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaCollection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("MediaCollections"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("LibraryPathId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("Poster") + .HasColumnType("TEXT"); + + b.Property("PosterLastWriteTime") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryPathId"); + + b.ToTable("MediaItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("MediaSources"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ContentRating") + .HasColumnType("TEXT"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("MovieId") + .HasColumnType("INTEGER"); + + b.Property("MovieId1") + .HasColumnType("INTEGER"); + + b.Property("Outline") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("Premiered") + .HasColumnType("TEXT"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("Tagline") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MovieId") + .IsUnique(); + + b.HasIndex("MovieId1"); + + b.ToTable("MovieMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("ProgramScheduleId") + .HasColumnType("INTEGER"); + + b.Property("ProgramSchedulePlayoutType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("ProgramScheduleId"); + + b.ToTable("Playouts"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Finish") + .HasColumnType("TEXT"); + + b.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b.Property("PlayoutId") + .HasColumnType("INTEGER"); + + b.Property("Start") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MediaItemId"); + + b.HasIndex("PlayoutId"); + + b.ToTable("PlayoutItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CollectionId") + .HasColumnType("INTEGER"); + + b.Property("CollectionType") + .HasColumnType("INTEGER"); + + b.Property("PlayoutId") + .HasColumnType("INTEGER"); + + b.Property("ProgramScheduleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayoutId"); + + b.HasIndex("ProgramScheduleId"); + + b.ToTable("PlayoutProgramScheduleItemAnchors"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("PlexMediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlexMediaSourceId"); + + b.ToTable("PlexConnection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaItemPart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Duration") + .HasColumnType("INTEGER"); + + b.Property("File") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("PlexId") + .HasColumnType("INTEGER"); + + b.Property("Size") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("PlexMediaItemPart"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("MediaCollectionPlaybackOrder") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ProgramSchedules"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CollectionType") + .HasColumnType("INTEGER"); + + b.Property("Index") + .HasColumnType("INTEGER"); + + b.Property("MediaCollectionId") + .HasColumnType("INTEGER"); + + b.Property("ProgramScheduleId") + .HasColumnType("INTEGER"); + + b.Property("StartTime") + .HasColumnType("TEXT"); + + b.Property("TelevisionSeasonId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MediaCollectionId"); + + b.HasIndex("ProgramScheduleId"); + + b.HasIndex("TelevisionSeasonId"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("ProgramScheduleItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Resolution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Resolutions"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Aired") + .HasColumnType("TEXT"); + + b.Property("Episode") + .HasColumnType("INTEGER"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("Season") + .HasColumnType("INTEGER"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("TelevisionEpisodeId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionEpisodeId") + .IsUnique(); + + b.ToTable("TelevisionEpisodeMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("Poster") + .HasColumnType("TEXT"); + + b.Property("PosterLastWriteTime") + .HasColumnType("TEXT"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("TelevisionSeasons"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Poster") + .HasColumnType("TEXT"); + + b.Property("PosterLastWriteTime") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TelevisionShows"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionShowId") + .IsUnique(); + + b.ToTable("TelevisionShowMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("TelevisionShowSource"); + + b.HasDiscriminator("Discriminator").HasValue("TelevisionShowSource"); + }); + + modelBuilder.Entity("MovieMediaItemSimpleMediaCollection", b => + { + b.Property("MoviesId") + .HasColumnType("INTEGER"); + + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.HasKey("MoviesId", "SimpleMediaCollectionsId"); + + b.HasIndex("SimpleMediaCollectionsId"); + + b.ToTable("SimpleMediaCollectionMovies"); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionEpisodeMediaItem", b => + { + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionEpisodesId") + .HasColumnType("INTEGER"); + + b.HasKey("SimpleMediaCollectionsId", "TelevisionEpisodesId"); + + b.HasIndex("TelevisionEpisodesId"); + + b.ToTable("SimpleMediaCollectionEpisodes"); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionSeason", b => + { + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionSeasonsId") + .HasColumnType("INTEGER"); + + b.HasKey("SimpleMediaCollectionsId", "TelevisionSeasonsId"); + + b.HasIndex("TelevisionSeasonsId"); + + b.ToTable("SimpleMediaCollectionSeasons"); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionShow", b => + { + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowsId") + .HasColumnType("INTEGER"); + + b.HasKey("SimpleMediaCollectionsId", "TelevisionShowsId"); + + b.HasIndex("TelevisionShowsId"); + + b.ToTable("SimpleMediaCollectionShows"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalLibrary", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.Library"); + + b.ToTable("LocalLibrary"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexLibrary", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.Library"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("ShouldSyncItems") + .HasColumnType("INTEGER"); + + b.ToTable("PlexLibrary"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.SimpleMediaCollection", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaCollection"); + + b.ToTable("SimpleMediaCollections"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMediaItem", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("MetadataId") + .HasColumnType("INTEGER"); + + b.ToTable("Movies"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("SeasonId") + .HasColumnType("INTEGER"); + + b.HasIndex("SeasonId"); + + b.ToTable("TelevisionEpisodes"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaSource"); + + b.ToTable("LocalMediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaSource"); + + b.Property("ClientIdentifier") + .HasColumnType("TEXT"); + + b.Property("ProductVersion") + .HasColumnType("TEXT"); + + b.Property("ServerName") + .HasColumnType("TEXT"); + + b.ToTable("PlexMediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.Property("OfflineTail") + .HasColumnType("INTEGER"); + + b.Property("PlayoutDuration") + .HasColumnType("TEXT"); + + b.ToTable("ProgramScheduleDurationItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.ToTable("ProgramScheduleFloodItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.ToTable("ProgramScheduleMultipleItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemOne", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.ToTable("ProgramScheduleOneItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalTelevisionShowSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.TelevisionShowSource"); + + b.Property("MediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasIndex("MediaSourceId"); + + b.HasDiscriminator().HasValue("LocalTelevisionShowSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMovieMediaItem", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MovieMediaItem"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("PartId") + .HasColumnType("INTEGER"); + + b.HasIndex("PartId"); + + b.ToTable("PlexMovies"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.HasOne("ErsatzTV.Core.Domain.FFmpegProfile", "FFmpegProfile") + .WithMany() + .HasForeignKey("FFmpegProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FFmpegProfile"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.FFmpegProfile", b => + { + b.HasOne("ErsatzTV.Core.Domain.Resolution", "Resolution") + .WithMany() + .HasForeignKey("ResolutionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Resolution"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaSource", "MediaSource") + .WithMany("Libraries") + .HasForeignKey("MediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", "Library") + .WithMany("Paths") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.LibraryPath", "LibraryPath") + .WithMany("MediaItems") + .HasForeignKey("LibraryPathId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.MediaItemStatistics", "Statistics", b1 => + { + b1.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b1.Property("AudioCodec") + .HasColumnType("TEXT"); + + b1.Property("DisplayAspectRatio") + .HasColumnType("TEXT"); + + b1.Property("Duration") + .HasColumnType("TEXT"); + + b1.Property("Height") + .HasColumnType("INTEGER"); + + b1.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b1.Property("SampleAspectRatio") + .HasColumnType("TEXT"); + + b1.Property("VideoCodec") + .HasColumnType("TEXT"); + + b1.Property("VideoScanType") + .HasColumnType("INTEGER"); + + b1.Property("Width") + .HasColumnType("INTEGER"); + + b1.HasKey("MediaItemId"); + + b1.ToTable("MediaItems"); + + b1.WithOwner() + .HasForeignKey("MediaItemId"); + }); + + b.Navigation("LibraryPath"); + + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.MovieMediaItem", null) + .WithOne("Metadata") + .HasForeignKey("ErsatzTV.Core.Domain.MovieMetadata", "MovieId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.MovieMediaItem", "Movie") + .WithMany() + .HasForeignKey("MovieId1"); + + b.Navigation("Movie"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => + { + b.HasOne("ErsatzTV.Core.Domain.Channel", "Channel") + .WithMany("Playouts") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") + .WithMany("Playouts") + .HasForeignKey("ProgramScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.PlayoutAnchor", "Anchor", b1 => + { + b1.Property("PlayoutId") + .HasColumnType("INTEGER"); + + b1.Property("NextScheduleItemId") + .HasColumnType("INTEGER"); + + b1.Property("NextStart") + .HasColumnType("TEXT"); + + b1.HasKey("PlayoutId"); + + b1.HasIndex("NextScheduleItemId"); + + b1.ToTable("Playouts"); + + b1.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", "NextScheduleItem") + .WithMany() + .HasForeignKey("NextScheduleItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.WithOwner() + .HasForeignKey("PlayoutId"); + + b1.Navigation("NextScheduleItem"); + }); + + b.Navigation("Anchor"); + + b.Navigation("Channel"); + + b.Navigation("ProgramSchedule"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") + .WithMany() + .HasForeignKey("MediaItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout") + .WithMany("Items") + .HasForeignKey("PlayoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaItem"); + + b.Navigation("Playout"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", b => + { + b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout") + .WithMany("ProgramScheduleAnchors") + .HasForeignKey("PlayoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") + .WithMany() + .HasForeignKey("ProgramScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.MediaCollectionEnumeratorState", "EnumeratorState", b1 => + { + b1.Property("PlayoutProgramScheduleAnchorId") + .HasColumnType("INTEGER"); + + b1.Property("Index") + .HasColumnType("INTEGER"); + + b1.Property("Seed") + .HasColumnType("INTEGER"); + + b1.HasKey("PlayoutProgramScheduleAnchorId"); + + b1.ToTable("PlayoutProgramScheduleItemAnchors"); + + b1.WithOwner() + .HasForeignKey("PlayoutProgramScheduleAnchorId"); + }); + + b.Navigation("EnumeratorState"); + + b.Navigation("Playout"); + + b.Navigation("ProgramSchedule"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexConnection", b => + { + b.HasOne("ErsatzTV.Core.Domain.PlexMediaSource", "PlexMediaSource") + .WithMany("Connections") + .HasForeignKey("PlexMediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlexMediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaCollection", "MediaCollection") + .WithMany() + .HasForeignKey("MediaCollectionId"); + + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") + .WithMany("Items") + .HasForeignKey("ProgramScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionSeason", "TelevisionSeason") + .WithMany() + .HasForeignKey("TelevisionSeasonId"); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithMany() + .HasForeignKey("TelevisionShowId"); + + b.Navigation("MediaCollection"); + + b.Navigation("ProgramSchedule"); + + b.Navigation("TelevisionSeason"); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", "TelevisionEpisode") + .WithOne("Metadata") + .HasForeignKey("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", "TelevisionEpisodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionEpisode"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithMany("Seasons") + .HasForeignKey("TelevisionShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithOne("Metadata") + .HasForeignKey("ErsatzTV.Core.Domain.TelevisionShowMetadata", "TelevisionShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithMany("Sources") + .HasForeignKey("TelevisionShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("MovieMediaItemSimpleMediaCollection", b => + { + b.HasOne("ErsatzTV.Core.Domain.MovieMediaItem", null) + .WithMany() + .HasForeignKey("MoviesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionEpisodeMediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", null) + .WithMany() + .HasForeignKey("TelevisionEpisodesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionSeason", b => + { + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionSeason", null) + .WithMany() + .HasForeignKey("TelevisionSeasonsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionShow", b => + { + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", null) + .WithMany() + .HasForeignKey("TelevisionShowsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalLibrary", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.LocalLibrary", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexLibrary", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexLibrary", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.SimpleMediaCollection", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaCollection", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.SimpleMediaCollection", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.MovieMediaItem", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionSeason", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaSource", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.LocalMediaSource", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaSource", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexMediaSource", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemOne", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemOne", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalTelevisionShowSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.LocalMediaSource", "MediaSource") + .WithMany() + .HasForeignKey("MediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMovieMediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MovieMediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexMovieMediaItem", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.PlexMediaItemPart", "Part") + .WithMany() + .HasForeignKey("PartId"); + + b.Navigation("Part"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.Navigation("Playouts"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.Navigation("Paths"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.Navigation("MediaItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaSource", b => + { + b.Navigation("Libraries"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => + { + b.Navigation("Items"); + + b.Navigation("ProgramScheduleAnchors"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b => + { + b.Navigation("Items"); + + b.Navigation("Playouts"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShow", b => + { + b.Navigation("Metadata"); + + b.Navigation("Seasons"); + + b.Navigation("Sources"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMediaItem", b => + { + b.Navigation("Metadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.Navigation("Metadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.Navigation("Connections"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ErsatzTV.Infrastructure/Migrations/20210225153055_RemoveOldMediaSources.cs b/ErsatzTV.Infrastructure/Migrations/20210225153055_RemoveOldMediaSources.cs new file mode 100644 index 000000000..221d006a5 --- /dev/null +++ b/ErsatzTV.Infrastructure/Migrations/20210225153055_RemoveOldMediaSources.cs @@ -0,0 +1,15 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace ErsatzTV.Infrastructure.Migrations +{ + public partial class RemoveOldMediaSources : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) => + // remove old source/folders + migrationBuilder.Sql(@"DELETE FROM MediaSources WHERE Id NOT IN (SELECT MediaSourceId FROM Library)"); + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/ErsatzTV.Infrastructure/Migrations/20210225203458_SingularTableNames.Designer.cs b/ErsatzTV.Infrastructure/Migrations/20210225203458_SingularTableNames.Designer.cs new file mode 100644 index 000000000..d04c2422b --- /dev/null +++ b/ErsatzTV.Infrastructure/Migrations/20210225203458_SingularTableNames.Designer.cs @@ -0,0 +1,1393 @@ +// +using System; +using ErsatzTV.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace ErsatzTV.Infrastructure.Migrations +{ + [DbContext(typeof(TvContext))] + [Migration("20210225203458_SingularTableNames")] + partial class SingularTableNames + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.3"); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FFmpegProfileId") + .HasColumnType("INTEGER"); + + b.Property("Logo") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("StreamingMode") + .HasColumnType("INTEGER"); + + b.Property("UniqueId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FFmpegProfileId"); + + b.HasIndex("Number") + .IsUnique(); + + b.ToTable("Channel"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ConfigElement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("ConfigElement"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.FFmpegProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AudioBitrate") + .HasColumnType("INTEGER"); + + b.Property("AudioBufferSize") + .HasColumnType("INTEGER"); + + b.Property("AudioChannels") + .HasColumnType("INTEGER"); + + b.Property("AudioCodec") + .HasColumnType("TEXT"); + + b.Property("AudioSampleRate") + .HasColumnType("INTEGER"); + + b.Property("AudioVolume") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizeAudio") + .HasColumnType("INTEGER"); + + b.Property("NormalizeAudioCodec") + .HasColumnType("INTEGER"); + + b.Property("NormalizeResolution") + .HasColumnType("INTEGER"); + + b.Property("NormalizeVideoCodec") + .HasColumnType("INTEGER"); + + b.Property("ResolutionId") + .HasColumnType("INTEGER"); + + b.Property("ThreadCount") + .HasColumnType("INTEGER"); + + b.Property("Transcode") + .HasColumnType("INTEGER"); + + b.Property("VideoBitrate") + .HasColumnType("INTEGER"); + + b.Property("VideoBufferSize") + .HasColumnType("INTEGER"); + + b.Property("VideoCodec") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ResolutionId"); + + b.ToTable("FFmpegProfile"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastScan") + .HasColumnType("TEXT"); + + b.Property("MediaKind") + .HasColumnType("INTEGER"); + + b.Property("MediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MediaSourceId"); + + b.ToTable("Library"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("LibraryPath"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaCollection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("MediaCollection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("LibraryPathId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("Poster") + .HasColumnType("TEXT"); + + b.Property("PosterLastWriteTime") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryPathId"); + + b.ToTable("MediaItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("MediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ContentRating") + .HasColumnType("TEXT"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("MovieId") + .HasColumnType("INTEGER"); + + b.Property("Outline") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("Premiered") + .HasColumnType("TEXT"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("Tagline") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MovieId") + .IsUnique(); + + b.ToTable("MovieMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("ProgramScheduleId") + .HasColumnType("INTEGER"); + + b.Property("ProgramSchedulePlayoutType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("ProgramScheduleId"); + + b.ToTable("Playout"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Finish") + .HasColumnType("TEXT"); + + b.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b.Property("PlayoutId") + .HasColumnType("INTEGER"); + + b.Property("Start") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MediaItemId"); + + b.HasIndex("PlayoutId"); + + b.ToTable("PlayoutItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CollectionId") + .HasColumnType("INTEGER"); + + b.Property("CollectionType") + .HasColumnType("INTEGER"); + + b.Property("PlayoutId") + .HasColumnType("INTEGER"); + + b.Property("ProgramScheduleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayoutId"); + + b.HasIndex("ProgramScheduleId"); + + b.ToTable("PlayoutProgramScheduleAnchor"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("PlexMediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlexMediaSourceId"); + + b.ToTable("PlexConnection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaItemPart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Duration") + .HasColumnType("INTEGER"); + + b.Property("File") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("PlexId") + .HasColumnType("INTEGER"); + + b.Property("Size") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("PlexMediaItemPart"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("MediaCollectionPlaybackOrder") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ProgramSchedule"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CollectionType") + .HasColumnType("INTEGER"); + + b.Property("Index") + .HasColumnType("INTEGER"); + + b.Property("MediaCollectionId") + .HasColumnType("INTEGER"); + + b.Property("ProgramScheduleId") + .HasColumnType("INTEGER"); + + b.Property("StartTime") + .HasColumnType("TEXT"); + + b.Property("TelevisionSeasonId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MediaCollectionId"); + + b.HasIndex("ProgramScheduleId"); + + b.HasIndex("TelevisionSeasonId"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("ProgramScheduleItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Resolution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Resolution"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Aired") + .HasColumnType("TEXT"); + + b.Property("Episode") + .HasColumnType("INTEGER"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("Season") + .HasColumnType("INTEGER"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("TelevisionEpisodeId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionEpisodeId") + .IsUnique(); + + b.ToTable("TelevisionEpisodeMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("Poster") + .HasColumnType("TEXT"); + + b.Property("PosterLastWriteTime") + .HasColumnType("TEXT"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("TelevisionSeason"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Poster") + .HasColumnType("TEXT"); + + b.Property("PosterLastWriteTime") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionShowId") + .IsUnique(); + + b.ToTable("TelevisionShowMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("TelevisionShowSource"); + + b.HasDiscriminator("Discriminator").HasValue("TelevisionShowSource"); + }); + + modelBuilder.Entity("MovieMediaItemSimpleMediaCollection", b => + { + b.Property("MoviesId") + .HasColumnType("INTEGER"); + + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.HasKey("MoviesId", "SimpleMediaCollectionsId"); + + b.HasIndex("SimpleMediaCollectionsId"); + + b.ToTable("SimpleMediaCollectionMovie"); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionEpisodeMediaItem", b => + { + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionEpisodesId") + .HasColumnType("INTEGER"); + + b.HasKey("SimpleMediaCollectionsId", "TelevisionEpisodesId"); + + b.HasIndex("TelevisionEpisodesId"); + + b.ToTable("SimpleMediaCollectionEpisode"); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionSeason", b => + { + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionSeasonsId") + .HasColumnType("INTEGER"); + + b.HasKey("SimpleMediaCollectionsId", "TelevisionSeasonsId"); + + b.HasIndex("TelevisionSeasonsId"); + + b.ToTable("SimpleMediaCollectionSeason"); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionShow", b => + { + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowsId") + .HasColumnType("INTEGER"); + + b.HasKey("SimpleMediaCollectionsId", "TelevisionShowsId"); + + b.HasIndex("TelevisionShowsId"); + + b.ToTable("SimpleMediaCollectionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalLibrary", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.Library"); + + b.ToTable("LocalLibrary"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexLibrary", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.Library"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("ShouldSyncItems") + .HasColumnType("INTEGER"); + + b.ToTable("PlexLibrary"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.SimpleMediaCollection", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaCollection"); + + b.ToTable("SimpleMediaCollection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMediaItem", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("MetadataId") + .HasColumnType("INTEGER"); + + b.ToTable("Movie"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("SeasonId") + .HasColumnType("INTEGER"); + + b.HasIndex("SeasonId"); + + b.ToTable("TelevisionEpisode"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaSource"); + + b.ToTable("LocalMediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaSource"); + + b.Property("ClientIdentifier") + .HasColumnType("TEXT"); + + b.Property("ProductVersion") + .HasColumnType("TEXT"); + + b.Property("ServerName") + .HasColumnType("TEXT"); + + b.ToTable("PlexMediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.Property("OfflineTail") + .HasColumnType("INTEGER"); + + b.Property("PlayoutDuration") + .HasColumnType("TEXT"); + + b.ToTable("ProgramScheduleDurationItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.ToTable("ProgramScheduleFloodItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.ToTable("ProgramScheduleMultipleItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemOne", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.ToTable("ProgramScheduleOneItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalTelevisionShowSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.TelevisionShowSource"); + + b.Property("MediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasIndex("MediaSourceId"); + + b.HasDiscriminator().HasValue("LocalTelevisionShowSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMovieMediaItem", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MovieMediaItem"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("PartId") + .HasColumnType("INTEGER"); + + b.HasIndex("PartId"); + + b.ToTable("PlexMovie"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.HasOne("ErsatzTV.Core.Domain.FFmpegProfile", "FFmpegProfile") + .WithMany() + .HasForeignKey("FFmpegProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FFmpegProfile"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.FFmpegProfile", b => + { + b.HasOne("ErsatzTV.Core.Domain.Resolution", "Resolution") + .WithMany() + .HasForeignKey("ResolutionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Resolution"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaSource", "MediaSource") + .WithMany("Libraries") + .HasForeignKey("MediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", "Library") + .WithMany("Paths") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.LibraryPath", "LibraryPath") + .WithMany("MediaItems") + .HasForeignKey("LibraryPathId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.MediaItemStatistics", "Statistics", b1 => + { + b1.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b1.Property("AudioCodec") + .HasColumnType("TEXT"); + + b1.Property("DisplayAspectRatio") + .HasColumnType("TEXT"); + + b1.Property("Duration") + .HasColumnType("TEXT"); + + b1.Property("Height") + .HasColumnType("INTEGER"); + + b1.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b1.Property("SampleAspectRatio") + .HasColumnType("TEXT"); + + b1.Property("VideoCodec") + .HasColumnType("TEXT"); + + b1.Property("VideoScanType") + .HasColumnType("INTEGER"); + + b1.Property("Width") + .HasColumnType("INTEGER"); + + b1.HasKey("MediaItemId"); + + b1.ToTable("MediaItem"); + + b1.WithOwner() + .HasForeignKey("MediaItemId"); + }); + + b.Navigation("LibraryPath"); + + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.MovieMediaItem", "Movie") + .WithOne("Metadata") + .HasForeignKey("ErsatzTV.Core.Domain.MovieMetadata", "MovieId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Movie"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => + { + b.HasOne("ErsatzTV.Core.Domain.Channel", "Channel") + .WithMany("Playouts") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") + .WithMany("Playouts") + .HasForeignKey("ProgramScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.PlayoutAnchor", "Anchor", b1 => + { + b1.Property("PlayoutId") + .HasColumnType("INTEGER"); + + b1.Property("NextScheduleItemId") + .HasColumnType("INTEGER"); + + b1.Property("NextStart") + .HasColumnType("TEXT"); + + b1.HasKey("PlayoutId"); + + b1.HasIndex("NextScheduleItemId"); + + b1.ToTable("Playout"); + + b1.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", "NextScheduleItem") + .WithMany() + .HasForeignKey("NextScheduleItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.WithOwner() + .HasForeignKey("PlayoutId"); + + b1.Navigation("NextScheduleItem"); + }); + + b.Navigation("Anchor"); + + b.Navigation("Channel"); + + b.Navigation("ProgramSchedule"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") + .WithMany() + .HasForeignKey("MediaItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout") + .WithMany("Items") + .HasForeignKey("PlayoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaItem"); + + b.Navigation("Playout"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", b => + { + b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout") + .WithMany("ProgramScheduleAnchors") + .HasForeignKey("PlayoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") + .WithMany() + .HasForeignKey("ProgramScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.MediaCollectionEnumeratorState", "EnumeratorState", b1 => + { + b1.Property("PlayoutProgramScheduleAnchorId") + .HasColumnType("INTEGER"); + + b1.Property("Index") + .HasColumnType("INTEGER"); + + b1.Property("Seed") + .HasColumnType("INTEGER"); + + b1.HasKey("PlayoutProgramScheduleAnchorId"); + + b1.ToTable("PlayoutProgramScheduleAnchor"); + + b1.WithOwner() + .HasForeignKey("PlayoutProgramScheduleAnchorId"); + }); + + b.Navigation("EnumeratorState"); + + b.Navigation("Playout"); + + b.Navigation("ProgramSchedule"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexConnection", b => + { + b.HasOne("ErsatzTV.Core.Domain.PlexMediaSource", "PlexMediaSource") + .WithMany("Connections") + .HasForeignKey("PlexMediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlexMediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaCollection", "MediaCollection") + .WithMany() + .HasForeignKey("MediaCollectionId"); + + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") + .WithMany("Items") + .HasForeignKey("ProgramScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionSeason", "TelevisionSeason") + .WithMany() + .HasForeignKey("TelevisionSeasonId"); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithMany() + .HasForeignKey("TelevisionShowId"); + + b.Navigation("MediaCollection"); + + b.Navigation("ProgramSchedule"); + + b.Navigation("TelevisionSeason"); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", "TelevisionEpisode") + .WithOne("Metadata") + .HasForeignKey("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", "TelevisionEpisodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionEpisode"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithMany("Seasons") + .HasForeignKey("TelevisionShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithOne("Metadata") + .HasForeignKey("ErsatzTV.Core.Domain.TelevisionShowMetadata", "TelevisionShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithMany("Sources") + .HasForeignKey("TelevisionShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("MovieMediaItemSimpleMediaCollection", b => + { + b.HasOne("ErsatzTV.Core.Domain.MovieMediaItem", null) + .WithMany() + .HasForeignKey("MoviesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionEpisodeMediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", null) + .WithMany() + .HasForeignKey("TelevisionEpisodesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionSeason", b => + { + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionSeason", null) + .WithMany() + .HasForeignKey("TelevisionSeasonsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionShow", b => + { + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", null) + .WithMany() + .HasForeignKey("TelevisionShowsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalLibrary", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.LocalLibrary", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexLibrary", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexLibrary", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.SimpleMediaCollection", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaCollection", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.SimpleMediaCollection", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.MovieMediaItem", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionSeason", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaSource", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.LocalMediaSource", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaSource", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexMediaSource", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemOne", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemOne", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalTelevisionShowSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.LocalMediaSource", "MediaSource") + .WithMany() + .HasForeignKey("MediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMovieMediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MovieMediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexMovieMediaItem", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.PlexMediaItemPart", "Part") + .WithMany() + .HasForeignKey("PartId"); + + b.Navigation("Part"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.Navigation("Playouts"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.Navigation("Paths"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.Navigation("MediaItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaSource", b => + { + b.Navigation("Libraries"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => + { + b.Navigation("Items"); + + b.Navigation("ProgramScheduleAnchors"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b => + { + b.Navigation("Items"); + + b.Navigation("Playouts"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShow", b => + { + b.Navigation("Metadata"); + + b.Navigation("Seasons"); + + b.Navigation("Sources"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMediaItem", b => + { + b.Navigation("Metadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.Navigation("Metadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.Navigation("Connections"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ErsatzTV.Infrastructure/Migrations/20210225203458_SingularTableNames.cs b/ErsatzTV.Infrastructure/Migrations/20210225203458_SingularTableNames.cs new file mode 100644 index 000000000..029507ba4 --- /dev/null +++ b/ErsatzTV.Infrastructure/Migrations/20210225203458_SingularTableNames.cs @@ -0,0 +1,1930 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace ErsatzTV.Infrastructure.Migrations +{ + public partial class SingularTableNames : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + "FK_Channels_FFmpegProfiles_FFmpegProfileId", + "Channels"); + + migrationBuilder.DropForeignKey( + "FK_FFmpegProfiles_Resolutions_ResolutionId", + "FFmpegProfiles"); + + migrationBuilder.DropForeignKey( + "FK_Library_MediaSources_MediaSourceId", + "Library"); + + migrationBuilder.DropForeignKey( + "FK_LocalMediaSource_MediaSources_Id", + "LocalMediaSource"); + + migrationBuilder.DropForeignKey( + "FK_MediaItems_LibraryPath_LibraryPathId", + "MediaItems"); + + migrationBuilder.DropForeignKey( + "FK_MovieMetadata_Movies_MovieId", + "MovieMetadata"); + + migrationBuilder.DropForeignKey( + "FK_MovieMetadata_Movies_MovieId1", + "MovieMetadata"); + + migrationBuilder.DropForeignKey( + "FK_Movies_MediaItems_Id", + "Movies"); + + migrationBuilder.DropForeignKey( + "FK_PlayoutItems_MediaItems_MediaItemId", + "PlayoutItems"); + + migrationBuilder.DropForeignKey( + "FK_PlayoutItems_Playouts_PlayoutId", + "PlayoutItems"); + + migrationBuilder.DropForeignKey( + "FK_PlayoutProgramScheduleItemAnchors_Playouts_PlayoutId", + "PlayoutProgramScheduleItemAnchors"); + + migrationBuilder.DropForeignKey( + "FK_PlayoutProgramScheduleItemAnchors_ProgramSchedules_ProgramScheduleId", + "PlayoutProgramScheduleItemAnchors"); + + migrationBuilder.DropForeignKey( + "FK_Playouts_Channels_ChannelId", + "Playouts"); + + migrationBuilder.DropForeignKey( + "FK_Playouts_ProgramScheduleItems_Anchor_NextScheduleItemId", + "Playouts"); + + migrationBuilder.DropForeignKey( + "FK_Playouts_ProgramSchedules_ProgramScheduleId", + "Playouts"); + + migrationBuilder.DropForeignKey( + "FK_PlexMediaSource_MediaSources_Id", + "PlexMediaSource"); + + migrationBuilder.DropForeignKey( + "FK_PlexMovies_Movies_Id", + "PlexMovies"); + + migrationBuilder.DropForeignKey( + "FK_PlexMovies_PlexMediaItemPart_PartId", + "PlexMovies"); + + migrationBuilder.DropForeignKey( + "FK_ProgramScheduleDurationItems_ProgramScheduleItems_Id", + "ProgramScheduleDurationItems"); + + migrationBuilder.DropForeignKey( + "FK_ProgramScheduleFloodItems_ProgramScheduleItems_Id", + "ProgramScheduleFloodItems"); + + migrationBuilder.DropForeignKey( + "FK_ProgramScheduleItems_MediaCollections_MediaCollectionId", + "ProgramScheduleItems"); + + migrationBuilder.DropForeignKey( + "FK_ProgramScheduleItems_ProgramSchedules_ProgramScheduleId", + "ProgramScheduleItems"); + + migrationBuilder.DropForeignKey( + "FK_ProgramScheduleItems_TelevisionSeasons_TelevisionSeasonId", + "ProgramScheduleItems"); + + migrationBuilder.DropForeignKey( + "FK_ProgramScheduleItems_TelevisionShows_TelevisionShowId", + "ProgramScheduleItems"); + + migrationBuilder.DropForeignKey( + "FK_ProgramScheduleMultipleItems_ProgramScheduleItems_Id", + "ProgramScheduleMultipleItems"); + + migrationBuilder.DropForeignKey( + "FK_ProgramScheduleOneItems_ProgramScheduleItems_Id", + "ProgramScheduleOneItems"); + + migrationBuilder.DropForeignKey( + "FK_SimpleMediaCollectionEpisodes_SimpleMediaCollections_SimpleMediaCollectionsId", + "SimpleMediaCollectionEpisodes"); + + migrationBuilder.DropForeignKey( + "FK_SimpleMediaCollectionEpisodes_TelevisionEpisodes_TelevisionEpisodesId", + "SimpleMediaCollectionEpisodes"); + + migrationBuilder.DropForeignKey( + "FK_SimpleMediaCollectionMovies_Movies_MoviesId", + "SimpleMediaCollectionMovies"); + + migrationBuilder.DropForeignKey( + "FK_SimpleMediaCollectionMovies_SimpleMediaCollections_SimpleMediaCollectionsId", + "SimpleMediaCollectionMovies"); + + migrationBuilder.DropForeignKey( + "FK_SimpleMediaCollections_MediaCollections_Id", + "SimpleMediaCollections"); + + migrationBuilder.DropForeignKey( + "FK_SimpleMediaCollectionSeasons_SimpleMediaCollections_SimpleMediaCollectionsId", + "SimpleMediaCollectionSeasons"); + + migrationBuilder.DropForeignKey( + "FK_SimpleMediaCollectionSeasons_TelevisionSeasons_TelevisionSeasonsId", + "SimpleMediaCollectionSeasons"); + + migrationBuilder.DropForeignKey( + "FK_SimpleMediaCollectionShows_SimpleMediaCollections_SimpleMediaCollectionsId", + "SimpleMediaCollectionShows"); + + migrationBuilder.DropForeignKey( + "FK_SimpleMediaCollectionShows_TelevisionShows_TelevisionShowsId", + "SimpleMediaCollectionShows"); + + migrationBuilder.DropForeignKey( + "FK_TelevisionEpisodeMetadata_TelevisionEpisodes_TelevisionEpisodeId", + "TelevisionEpisodeMetadata"); + + migrationBuilder.DropForeignKey( + "FK_TelevisionEpisodes_MediaItems_Id", + "TelevisionEpisodes"); + + migrationBuilder.DropForeignKey( + "FK_TelevisionEpisodes_TelevisionSeasons_SeasonId", + "TelevisionEpisodes"); + + migrationBuilder.DropForeignKey( + "FK_TelevisionSeasons_TelevisionShows_TelevisionShowId", + "TelevisionSeasons"); + + migrationBuilder.DropForeignKey( + "FK_TelevisionShowMetadata_TelevisionShows_TelevisionShowId", + "TelevisionShowMetadata"); + + migrationBuilder.DropForeignKey( + "FK_TelevisionShowSource_TelevisionShows_TelevisionShowId", + "TelevisionShowSource"); + + migrationBuilder.DropIndex( + "IX_MovieMetadata_MovieId1", + "MovieMetadata"); + + migrationBuilder.DropPrimaryKey( + "PK_TelevisionShows", + "TelevisionShows"); + + migrationBuilder.DropPrimaryKey( + "PK_TelevisionSeasons", + "TelevisionSeasons"); + + migrationBuilder.DropPrimaryKey( + "PK_TelevisionEpisodes", + "TelevisionEpisodes"); + + migrationBuilder.DropPrimaryKey( + "PK_SimpleMediaCollectionShows", + "SimpleMediaCollectionShows"); + + migrationBuilder.DropPrimaryKey( + "PK_SimpleMediaCollectionSeasons", + "SimpleMediaCollectionSeasons"); + + migrationBuilder.DropPrimaryKey( + "PK_SimpleMediaCollections", + "SimpleMediaCollections"); + + migrationBuilder.DropPrimaryKey( + "PK_SimpleMediaCollectionMovies", + "SimpleMediaCollectionMovies"); + + migrationBuilder.DropPrimaryKey( + "PK_SimpleMediaCollectionEpisodes", + "SimpleMediaCollectionEpisodes"); + + migrationBuilder.DropPrimaryKey( + "PK_Resolutions", + "Resolutions"); + + migrationBuilder.DropPrimaryKey( + "PK_ProgramSchedules", + "ProgramSchedules"); + + migrationBuilder.DropPrimaryKey( + "PK_ProgramScheduleOneItems", + "ProgramScheduleOneItems"); + + migrationBuilder.DropPrimaryKey( + "PK_ProgramScheduleMultipleItems", + "ProgramScheduleMultipleItems"); + + migrationBuilder.DropPrimaryKey( + "PK_ProgramScheduleItems", + "ProgramScheduleItems"); + + migrationBuilder.DropPrimaryKey( + "PK_ProgramScheduleFloodItems", + "ProgramScheduleFloodItems"); + + migrationBuilder.DropPrimaryKey( + "PK_ProgramScheduleDurationItems", + "ProgramScheduleDurationItems"); + + migrationBuilder.DropPrimaryKey( + "PK_PlexMovies", + "PlexMovies"); + + migrationBuilder.DropPrimaryKey( + "PK_Playouts", + "Playouts"); + + migrationBuilder.DropPrimaryKey( + "PK_PlayoutProgramScheduleItemAnchors", + "PlayoutProgramScheduleItemAnchors"); + + migrationBuilder.DropPrimaryKey( + "PK_PlayoutItems", + "PlayoutItems"); + + migrationBuilder.DropPrimaryKey( + "PK_Movies", + "Movies"); + + migrationBuilder.DropPrimaryKey( + "PK_MediaSources", + "MediaSources"); + + migrationBuilder.DropPrimaryKey( + "PK_MediaItems", + "MediaItems"); + + migrationBuilder.DropPrimaryKey( + "PK_MediaCollections", + "MediaCollections"); + + migrationBuilder.DropPrimaryKey( + "PK_FFmpegProfiles", + "FFmpegProfiles"); + + migrationBuilder.DropPrimaryKey( + "PK_ConfigElements", + "ConfigElements"); + + migrationBuilder.DropPrimaryKey( + "PK_Channels", + "Channels"); + + migrationBuilder.DropColumn( + "MovieId1", + "MovieMetadata"); + + migrationBuilder.RenameTable( + "TelevisionShows", + newName: "TelevisionShow"); + + migrationBuilder.RenameTable( + "TelevisionSeasons", + newName: "TelevisionSeason"); + + migrationBuilder.RenameTable( + "TelevisionEpisodes", + newName: "TelevisionEpisode"); + + migrationBuilder.RenameTable( + "SimpleMediaCollectionShows", + newName: "SimpleMediaCollectionShow"); + + migrationBuilder.RenameTable( + "SimpleMediaCollectionSeasons", + newName: "SimpleMediaCollectionSeason"); + + migrationBuilder.RenameTable( + "SimpleMediaCollections", + newName: "SimpleMediaCollection"); + + migrationBuilder.RenameTable( + "SimpleMediaCollectionMovies", + newName: "SimpleMediaCollectionMovie"); + + migrationBuilder.RenameTable( + "SimpleMediaCollectionEpisodes", + newName: "SimpleMediaCollectionEpisode"); + + migrationBuilder.RenameTable( + "Resolutions", + newName: "Resolution"); + + migrationBuilder.RenameTable( + "ProgramSchedules", + newName: "ProgramSchedule"); + + migrationBuilder.RenameTable( + "ProgramScheduleOneItems", + newName: "ProgramScheduleOneItem"); + + migrationBuilder.RenameTable( + "ProgramScheduleMultipleItems", + newName: "ProgramScheduleMultipleItem"); + + migrationBuilder.RenameTable( + "ProgramScheduleItems", + newName: "ProgramScheduleItem"); + + migrationBuilder.RenameTable( + "ProgramScheduleFloodItems", + newName: "ProgramScheduleFloodItem"); + + migrationBuilder.RenameTable( + "ProgramScheduleDurationItems", + newName: "ProgramScheduleDurationItem"); + + migrationBuilder.RenameTable( + "PlexMovies", + newName: "PlexMovie"); + + migrationBuilder.RenameTable( + "Playouts", + newName: "Playout"); + + migrationBuilder.RenameTable( + "PlayoutProgramScheduleItemAnchors", + newName: "PlayoutProgramScheduleAnchor"); + + migrationBuilder.RenameTable( + "PlayoutItems", + newName: "PlayoutItem"); + + migrationBuilder.RenameTable( + "Movies", + newName: "Movie"); + + migrationBuilder.RenameTable( + "MediaSources", + newName: "MediaSource"); + + migrationBuilder.RenameTable( + "MediaItems", + newName: "MediaItem"); + + migrationBuilder.RenameTable( + "MediaCollections", + newName: "MediaCollection"); + + migrationBuilder.RenameTable( + "FFmpegProfiles", + newName: "FFmpegProfile"); + + migrationBuilder.RenameTable( + "ConfigElements", + newName: "ConfigElement"); + + migrationBuilder.RenameTable( + "Channels", + newName: "Channel"); + + migrationBuilder.RenameIndex( + "IX_TelevisionSeasons_TelevisionShowId", + table: "TelevisionSeason", + newName: "IX_TelevisionSeason_TelevisionShowId"); + + migrationBuilder.RenameIndex( + "IX_TelevisionEpisodes_SeasonId", + table: "TelevisionEpisode", + newName: "IX_TelevisionEpisode_SeasonId"); + + migrationBuilder.RenameIndex( + "IX_SimpleMediaCollectionShows_TelevisionShowsId", + table: "SimpleMediaCollectionShow", + newName: "IX_SimpleMediaCollectionShow_TelevisionShowsId"); + + migrationBuilder.RenameIndex( + "IX_SimpleMediaCollectionSeasons_TelevisionSeasonsId", + table: "SimpleMediaCollectionSeason", + newName: "IX_SimpleMediaCollectionSeason_TelevisionSeasonsId"); + + migrationBuilder.RenameIndex( + "IX_SimpleMediaCollectionMovies_SimpleMediaCollectionsId", + table: "SimpleMediaCollectionMovie", + newName: "IX_SimpleMediaCollectionMovie_SimpleMediaCollectionsId"); + + migrationBuilder.RenameIndex( + "IX_SimpleMediaCollectionEpisodes_TelevisionEpisodesId", + table: "SimpleMediaCollectionEpisode", + newName: "IX_SimpleMediaCollectionEpisode_TelevisionEpisodesId"); + + migrationBuilder.RenameIndex( + "IX_ProgramSchedules_Name", + table: "ProgramSchedule", + newName: "IX_ProgramSchedule_Name"); + + migrationBuilder.RenameIndex( + "IX_ProgramScheduleItems_TelevisionShowId", + table: "ProgramScheduleItem", + newName: "IX_ProgramScheduleItem_TelevisionShowId"); + + migrationBuilder.RenameIndex( + "IX_ProgramScheduleItems_TelevisionSeasonId", + table: "ProgramScheduleItem", + newName: "IX_ProgramScheduleItem_TelevisionSeasonId"); + + migrationBuilder.RenameIndex( + "IX_ProgramScheduleItems_ProgramScheduleId", + table: "ProgramScheduleItem", + newName: "IX_ProgramScheduleItem_ProgramScheduleId"); + + migrationBuilder.RenameIndex( + "IX_ProgramScheduleItems_MediaCollectionId", + table: "ProgramScheduleItem", + newName: "IX_ProgramScheduleItem_MediaCollectionId"); + + migrationBuilder.RenameIndex( + "IX_PlexMovies_PartId", + table: "PlexMovie", + newName: "IX_PlexMovie_PartId"); + + migrationBuilder.RenameIndex( + "IX_Playouts_ProgramScheduleId", + table: "Playout", + newName: "IX_Playout_ProgramScheduleId"); + + migrationBuilder.RenameIndex( + "IX_Playouts_ChannelId", + table: "Playout", + newName: "IX_Playout_ChannelId"); + + migrationBuilder.RenameIndex( + "IX_Playouts_Anchor_NextScheduleItemId", + table: "Playout", + newName: "IX_Playout_Anchor_NextScheduleItemId"); + + migrationBuilder.RenameIndex( + "IX_PlayoutProgramScheduleItemAnchors_ProgramScheduleId", + table: "PlayoutProgramScheduleAnchor", + newName: "IX_PlayoutProgramScheduleAnchor_ProgramScheduleId"); + + migrationBuilder.RenameIndex( + "IX_PlayoutProgramScheduleItemAnchors_PlayoutId", + table: "PlayoutProgramScheduleAnchor", + newName: "IX_PlayoutProgramScheduleAnchor_PlayoutId"); + + migrationBuilder.RenameIndex( + "IX_PlayoutItems_PlayoutId", + table: "PlayoutItem", + newName: "IX_PlayoutItem_PlayoutId"); + + migrationBuilder.RenameIndex( + "IX_PlayoutItems_MediaItemId", + table: "PlayoutItem", + newName: "IX_PlayoutItem_MediaItemId"); + + migrationBuilder.RenameIndex( + "IX_MediaItems_LibraryPathId", + table: "MediaItem", + newName: "IX_MediaItem_LibraryPathId"); + + migrationBuilder.RenameIndex( + "IX_MediaCollections_Name", + table: "MediaCollection", + newName: "IX_MediaCollection_Name"); + + migrationBuilder.RenameIndex( + "IX_FFmpegProfiles_ResolutionId", + table: "FFmpegProfile", + newName: "IX_FFmpegProfile_ResolutionId"); + + migrationBuilder.RenameIndex( + "IX_ConfigElements_Key", + table: "ConfigElement", + newName: "IX_ConfigElement_Key"); + + migrationBuilder.RenameIndex( + "IX_Channels_Number", + table: "Channel", + newName: "IX_Channel_Number"); + + migrationBuilder.RenameIndex( + "IX_Channels_FFmpegProfileId", + table: "Channel", + newName: "IX_Channel_FFmpegProfileId"); + + migrationBuilder.AddPrimaryKey( + "PK_TelevisionShow", + "TelevisionShow", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_TelevisionSeason", + "TelevisionSeason", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_TelevisionEpisode", + "TelevisionEpisode", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_SimpleMediaCollectionShow", + "SimpleMediaCollectionShow", + new[] { "SimpleMediaCollectionsId", "TelevisionShowsId" }); + + migrationBuilder.AddPrimaryKey( + "PK_SimpleMediaCollectionSeason", + "SimpleMediaCollectionSeason", + new[] { "SimpleMediaCollectionsId", "TelevisionSeasonsId" }); + + migrationBuilder.AddPrimaryKey( + "PK_SimpleMediaCollection", + "SimpleMediaCollection", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_SimpleMediaCollectionMovie", + "SimpleMediaCollectionMovie", + new[] { "MoviesId", "SimpleMediaCollectionsId" }); + + migrationBuilder.AddPrimaryKey( + "PK_SimpleMediaCollectionEpisode", + "SimpleMediaCollectionEpisode", + new[] { "SimpleMediaCollectionsId", "TelevisionEpisodesId" }); + + migrationBuilder.AddPrimaryKey( + "PK_Resolution", + "Resolution", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_ProgramSchedule", + "ProgramSchedule", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_ProgramScheduleOneItem", + "ProgramScheduleOneItem", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_ProgramScheduleMultipleItem", + "ProgramScheduleMultipleItem", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_ProgramScheduleItem", + "ProgramScheduleItem", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_ProgramScheduleFloodItem", + "ProgramScheduleFloodItem", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_ProgramScheduleDurationItem", + "ProgramScheduleDurationItem", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_PlexMovie", + "PlexMovie", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_Playout", + "Playout", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_PlayoutProgramScheduleAnchor", + "PlayoutProgramScheduleAnchor", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_PlayoutItem", + "PlayoutItem", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_Movie", + "Movie", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_MediaSource", + "MediaSource", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_MediaItem", + "MediaItem", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_MediaCollection", + "MediaCollection", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_FFmpegProfile", + "FFmpegProfile", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_ConfigElement", + "ConfigElement", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_Channel", + "Channel", + "Id"); + + migrationBuilder.AddForeignKey( + "FK_Channel_FFmpegProfile_FFmpegProfileId", + "Channel", + "FFmpegProfileId", + "FFmpegProfile", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_FFmpegProfile_Resolution_ResolutionId", + "FFmpegProfile", + "ResolutionId", + "Resolution", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_Library_MediaSource_MediaSourceId", + "Library", + "MediaSourceId", + "MediaSource", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_LocalMediaSource_MediaSource_Id", + "LocalMediaSource", + "Id", + "MediaSource", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_MediaItem_LibraryPath_LibraryPathId", + "MediaItem", + "LibraryPathId", + "LibraryPath", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_Movie_MediaItem_Id", + "Movie", + "Id", + "MediaItem", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_MovieMetadata_Movie_MovieId", + "MovieMetadata", + "MovieId", + "Movie", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_Playout_Channel_ChannelId", + "Playout", + "ChannelId", + "Channel", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_Playout_ProgramSchedule_ProgramScheduleId", + "Playout", + "ProgramScheduleId", + "ProgramSchedule", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_Playout_ProgramScheduleItem_Anchor_NextScheduleItemId", + "Playout", + "Anchor_NextScheduleItemId", + "ProgramScheduleItem", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_PlayoutItem_MediaItem_MediaItemId", + "PlayoutItem", + "MediaItemId", + "MediaItem", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_PlayoutItem_Playout_PlayoutId", + "PlayoutItem", + "PlayoutId", + "Playout", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_PlayoutProgramScheduleAnchor_Playout_PlayoutId", + "PlayoutProgramScheduleAnchor", + "PlayoutId", + "Playout", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_PlayoutProgramScheduleAnchor_ProgramSchedule_ProgramScheduleId", + "PlayoutProgramScheduleAnchor", + "ProgramScheduleId", + "ProgramSchedule", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_PlexMediaSource_MediaSource_Id", + "PlexMediaSource", + "Id", + "MediaSource", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_PlexMovie_Movie_Id", + "PlexMovie", + "Id", + "Movie", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_PlexMovie_PlexMediaItemPart_PartId", + "PlexMovie", + "PartId", + "PlexMediaItemPart", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + "FK_ProgramScheduleDurationItem_ProgramScheduleItem_Id", + "ProgramScheduleDurationItem", + "Id", + "ProgramScheduleItem", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_ProgramScheduleFloodItem_ProgramScheduleItem_Id", + "ProgramScheduleFloodItem", + "Id", + "ProgramScheduleItem", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_ProgramScheduleItem_MediaCollection_MediaCollectionId", + "ProgramScheduleItem", + "MediaCollectionId", + "MediaCollection", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + "FK_ProgramScheduleItem_ProgramSchedule_ProgramScheduleId", + "ProgramScheduleItem", + "ProgramScheduleId", + "ProgramSchedule", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_ProgramScheduleItem_TelevisionSeason_TelevisionSeasonId", + "ProgramScheduleItem", + "TelevisionSeasonId", + "TelevisionSeason", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + "FK_ProgramScheduleItem_TelevisionShow_TelevisionShowId", + "ProgramScheduleItem", + "TelevisionShowId", + "TelevisionShow", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + "FK_ProgramScheduleMultipleItem_ProgramScheduleItem_Id", + "ProgramScheduleMultipleItem", + "Id", + "ProgramScheduleItem", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_ProgramScheduleOneItem_ProgramScheduleItem_Id", + "ProgramScheduleOneItem", + "Id", + "ProgramScheduleItem", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_SimpleMediaCollection_MediaCollection_Id", + "SimpleMediaCollection", + "Id", + "MediaCollection", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_SimpleMediaCollectionEpisode_SimpleMediaCollection_SimpleMediaCollectionsId", + "SimpleMediaCollectionEpisode", + "SimpleMediaCollectionsId", + "SimpleMediaCollection", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_SimpleMediaCollectionEpisode_TelevisionEpisode_TelevisionEpisodesId", + "SimpleMediaCollectionEpisode", + "TelevisionEpisodesId", + "TelevisionEpisode", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_SimpleMediaCollectionMovie_Movie_MoviesId", + "SimpleMediaCollectionMovie", + "MoviesId", + "Movie", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_SimpleMediaCollectionMovie_SimpleMediaCollection_SimpleMediaCollectionsId", + "SimpleMediaCollectionMovie", + "SimpleMediaCollectionsId", + "SimpleMediaCollection", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_SimpleMediaCollectionSeason_SimpleMediaCollection_SimpleMediaCollectionsId", + "SimpleMediaCollectionSeason", + "SimpleMediaCollectionsId", + "SimpleMediaCollection", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_SimpleMediaCollectionSeason_TelevisionSeason_TelevisionSeasonsId", + "SimpleMediaCollectionSeason", + "TelevisionSeasonsId", + "TelevisionSeason", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_SimpleMediaCollectionShow_SimpleMediaCollection_SimpleMediaCollectionsId", + "SimpleMediaCollectionShow", + "SimpleMediaCollectionsId", + "SimpleMediaCollection", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_SimpleMediaCollectionShow_TelevisionShow_TelevisionShowsId", + "SimpleMediaCollectionShow", + "TelevisionShowsId", + "TelevisionShow", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_TelevisionEpisode_MediaItem_Id", + "TelevisionEpisode", + "Id", + "MediaItem", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_TelevisionEpisode_TelevisionSeason_SeasonId", + "TelevisionEpisode", + "SeasonId", + "TelevisionSeason", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_TelevisionEpisodeMetadata_TelevisionEpisode_TelevisionEpisodeId", + "TelevisionEpisodeMetadata", + "TelevisionEpisodeId", + "TelevisionEpisode", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_TelevisionSeason_TelevisionShow_TelevisionShowId", + "TelevisionSeason", + "TelevisionShowId", + "TelevisionShow", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_TelevisionShowMetadata_TelevisionShow_TelevisionShowId", + "TelevisionShowMetadata", + "TelevisionShowId", + "TelevisionShow", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_TelevisionShowSource_TelevisionShow_TelevisionShowId", + "TelevisionShowSource", + "TelevisionShowId", + "TelevisionShow", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + "FK_Channel_FFmpegProfile_FFmpegProfileId", + "Channel"); + + migrationBuilder.DropForeignKey( + "FK_FFmpegProfile_Resolution_ResolutionId", + "FFmpegProfile"); + + migrationBuilder.DropForeignKey( + "FK_Library_MediaSource_MediaSourceId", + "Library"); + + migrationBuilder.DropForeignKey( + "FK_LocalMediaSource_MediaSource_Id", + "LocalMediaSource"); + + migrationBuilder.DropForeignKey( + "FK_MediaItem_LibraryPath_LibraryPathId", + "MediaItem"); + + migrationBuilder.DropForeignKey( + "FK_Movie_MediaItem_Id", + "Movie"); + + migrationBuilder.DropForeignKey( + "FK_MovieMetadata_Movie_MovieId", + "MovieMetadata"); + + migrationBuilder.DropForeignKey( + "FK_Playout_Channel_ChannelId", + "Playout"); + + migrationBuilder.DropForeignKey( + "FK_Playout_ProgramSchedule_ProgramScheduleId", + "Playout"); + + migrationBuilder.DropForeignKey( + "FK_Playout_ProgramScheduleItem_Anchor_NextScheduleItemId", + "Playout"); + + migrationBuilder.DropForeignKey( + "FK_PlayoutItem_MediaItem_MediaItemId", + "PlayoutItem"); + + migrationBuilder.DropForeignKey( + "FK_PlayoutItem_Playout_PlayoutId", + "PlayoutItem"); + + migrationBuilder.DropForeignKey( + "FK_PlayoutProgramScheduleAnchor_Playout_PlayoutId", + "PlayoutProgramScheduleAnchor"); + + migrationBuilder.DropForeignKey( + "FK_PlayoutProgramScheduleAnchor_ProgramSchedule_ProgramScheduleId", + "PlayoutProgramScheduleAnchor"); + + migrationBuilder.DropForeignKey( + "FK_PlexMediaSource_MediaSource_Id", + "PlexMediaSource"); + + migrationBuilder.DropForeignKey( + "FK_PlexMovie_Movie_Id", + "PlexMovie"); + + migrationBuilder.DropForeignKey( + "FK_PlexMovie_PlexMediaItemPart_PartId", + "PlexMovie"); + + migrationBuilder.DropForeignKey( + "FK_ProgramScheduleDurationItem_ProgramScheduleItem_Id", + "ProgramScheduleDurationItem"); + + migrationBuilder.DropForeignKey( + "FK_ProgramScheduleFloodItem_ProgramScheduleItem_Id", + "ProgramScheduleFloodItem"); + + migrationBuilder.DropForeignKey( + "FK_ProgramScheduleItem_MediaCollection_MediaCollectionId", + "ProgramScheduleItem"); + + migrationBuilder.DropForeignKey( + "FK_ProgramScheduleItem_ProgramSchedule_ProgramScheduleId", + "ProgramScheduleItem"); + + migrationBuilder.DropForeignKey( + "FK_ProgramScheduleItem_TelevisionSeason_TelevisionSeasonId", + "ProgramScheduleItem"); + + migrationBuilder.DropForeignKey( + "FK_ProgramScheduleItem_TelevisionShow_TelevisionShowId", + "ProgramScheduleItem"); + + migrationBuilder.DropForeignKey( + "FK_ProgramScheduleMultipleItem_ProgramScheduleItem_Id", + "ProgramScheduleMultipleItem"); + + migrationBuilder.DropForeignKey( + "FK_ProgramScheduleOneItem_ProgramScheduleItem_Id", + "ProgramScheduleOneItem"); + + migrationBuilder.DropForeignKey( + "FK_SimpleMediaCollection_MediaCollection_Id", + "SimpleMediaCollection"); + + migrationBuilder.DropForeignKey( + "FK_SimpleMediaCollectionEpisode_SimpleMediaCollection_SimpleMediaCollectionsId", + "SimpleMediaCollectionEpisode"); + + migrationBuilder.DropForeignKey( + "FK_SimpleMediaCollectionEpisode_TelevisionEpisode_TelevisionEpisodesId", + "SimpleMediaCollectionEpisode"); + + migrationBuilder.DropForeignKey( + "FK_SimpleMediaCollectionMovie_Movie_MoviesId", + "SimpleMediaCollectionMovie"); + + migrationBuilder.DropForeignKey( + "FK_SimpleMediaCollectionMovie_SimpleMediaCollection_SimpleMediaCollectionsId", + "SimpleMediaCollectionMovie"); + + migrationBuilder.DropForeignKey( + "FK_SimpleMediaCollectionSeason_SimpleMediaCollection_SimpleMediaCollectionsId", + "SimpleMediaCollectionSeason"); + + migrationBuilder.DropForeignKey( + "FK_SimpleMediaCollectionSeason_TelevisionSeason_TelevisionSeasonsId", + "SimpleMediaCollectionSeason"); + + migrationBuilder.DropForeignKey( + "FK_SimpleMediaCollectionShow_SimpleMediaCollection_SimpleMediaCollectionsId", + "SimpleMediaCollectionShow"); + + migrationBuilder.DropForeignKey( + "FK_SimpleMediaCollectionShow_TelevisionShow_TelevisionShowsId", + "SimpleMediaCollectionShow"); + + migrationBuilder.DropForeignKey( + "FK_TelevisionEpisode_MediaItem_Id", + "TelevisionEpisode"); + + migrationBuilder.DropForeignKey( + "FK_TelevisionEpisode_TelevisionSeason_SeasonId", + "TelevisionEpisode"); + + migrationBuilder.DropForeignKey( + "FK_TelevisionEpisodeMetadata_TelevisionEpisode_TelevisionEpisodeId", + "TelevisionEpisodeMetadata"); + + migrationBuilder.DropForeignKey( + "FK_TelevisionSeason_TelevisionShow_TelevisionShowId", + "TelevisionSeason"); + + migrationBuilder.DropForeignKey( + "FK_TelevisionShowMetadata_TelevisionShow_TelevisionShowId", + "TelevisionShowMetadata"); + + migrationBuilder.DropForeignKey( + "FK_TelevisionShowSource_TelevisionShow_TelevisionShowId", + "TelevisionShowSource"); + + migrationBuilder.DropPrimaryKey( + "PK_TelevisionShow", + "TelevisionShow"); + + migrationBuilder.DropPrimaryKey( + "PK_TelevisionSeason", + "TelevisionSeason"); + + migrationBuilder.DropPrimaryKey( + "PK_TelevisionEpisode", + "TelevisionEpisode"); + + migrationBuilder.DropPrimaryKey( + "PK_SimpleMediaCollectionShow", + "SimpleMediaCollectionShow"); + + migrationBuilder.DropPrimaryKey( + "PK_SimpleMediaCollectionSeason", + "SimpleMediaCollectionSeason"); + + migrationBuilder.DropPrimaryKey( + "PK_SimpleMediaCollectionMovie", + "SimpleMediaCollectionMovie"); + + migrationBuilder.DropPrimaryKey( + "PK_SimpleMediaCollectionEpisode", + "SimpleMediaCollectionEpisode"); + + migrationBuilder.DropPrimaryKey( + "PK_SimpleMediaCollection", + "SimpleMediaCollection"); + + migrationBuilder.DropPrimaryKey( + "PK_Resolution", + "Resolution"); + + migrationBuilder.DropPrimaryKey( + "PK_ProgramScheduleOneItem", + "ProgramScheduleOneItem"); + + migrationBuilder.DropPrimaryKey( + "PK_ProgramScheduleMultipleItem", + "ProgramScheduleMultipleItem"); + + migrationBuilder.DropPrimaryKey( + "PK_ProgramScheduleItem", + "ProgramScheduleItem"); + + migrationBuilder.DropPrimaryKey( + "PK_ProgramScheduleFloodItem", + "ProgramScheduleFloodItem"); + + migrationBuilder.DropPrimaryKey( + "PK_ProgramScheduleDurationItem", + "ProgramScheduleDurationItem"); + + migrationBuilder.DropPrimaryKey( + "PK_ProgramSchedule", + "ProgramSchedule"); + + migrationBuilder.DropPrimaryKey( + "PK_PlexMovie", + "PlexMovie"); + + migrationBuilder.DropPrimaryKey( + "PK_PlayoutProgramScheduleAnchor", + "PlayoutProgramScheduleAnchor"); + + migrationBuilder.DropPrimaryKey( + "PK_PlayoutItem", + "PlayoutItem"); + + migrationBuilder.DropPrimaryKey( + "PK_Playout", + "Playout"); + + migrationBuilder.DropPrimaryKey( + "PK_Movie", + "Movie"); + + migrationBuilder.DropPrimaryKey( + "PK_MediaSource", + "MediaSource"); + + migrationBuilder.DropPrimaryKey( + "PK_MediaItem", + "MediaItem"); + + migrationBuilder.DropPrimaryKey( + "PK_MediaCollection", + "MediaCollection"); + + migrationBuilder.DropPrimaryKey( + "PK_FFmpegProfile", + "FFmpegProfile"); + + migrationBuilder.DropPrimaryKey( + "PK_ConfigElement", + "ConfigElement"); + + migrationBuilder.DropPrimaryKey( + "PK_Channel", + "Channel"); + + migrationBuilder.RenameTable( + "TelevisionShow", + newName: "TelevisionShows"); + + migrationBuilder.RenameTable( + "TelevisionSeason", + newName: "TelevisionSeasons"); + + migrationBuilder.RenameTable( + "TelevisionEpisode", + newName: "TelevisionEpisodes"); + + migrationBuilder.RenameTable( + "SimpleMediaCollectionShow", + newName: "SimpleMediaCollectionShows"); + + migrationBuilder.RenameTable( + "SimpleMediaCollectionSeason", + newName: "SimpleMediaCollectionSeasons"); + + migrationBuilder.RenameTable( + "SimpleMediaCollectionMovie", + newName: "SimpleMediaCollectionMovies"); + + migrationBuilder.RenameTable( + "SimpleMediaCollectionEpisode", + newName: "SimpleMediaCollectionEpisodes"); + + migrationBuilder.RenameTable( + "SimpleMediaCollection", + newName: "SimpleMediaCollections"); + + migrationBuilder.RenameTable( + "Resolution", + newName: "Resolutions"); + + migrationBuilder.RenameTable( + "ProgramScheduleOneItem", + newName: "ProgramScheduleOneItems"); + + migrationBuilder.RenameTable( + "ProgramScheduleMultipleItem", + newName: "ProgramScheduleMultipleItems"); + + migrationBuilder.RenameTable( + "ProgramScheduleItem", + newName: "ProgramScheduleItems"); + + migrationBuilder.RenameTable( + "ProgramScheduleFloodItem", + newName: "ProgramScheduleFloodItems"); + + migrationBuilder.RenameTable( + "ProgramScheduleDurationItem", + newName: "ProgramScheduleDurationItems"); + + migrationBuilder.RenameTable( + "ProgramSchedule", + newName: "ProgramSchedules"); + + migrationBuilder.RenameTable( + "PlexMovie", + newName: "PlexMovies"); + + migrationBuilder.RenameTable( + "PlayoutProgramScheduleAnchor", + newName: "PlayoutProgramScheduleItemAnchors"); + + migrationBuilder.RenameTable( + "PlayoutItem", + newName: "PlayoutItems"); + + migrationBuilder.RenameTable( + "Playout", + newName: "Playouts"); + + migrationBuilder.RenameTable( + "Movie", + newName: "Movies"); + + migrationBuilder.RenameTable( + "MediaSource", + newName: "MediaSources"); + + migrationBuilder.RenameTable( + "MediaItem", + newName: "MediaItems"); + + migrationBuilder.RenameTable( + "MediaCollection", + newName: "MediaCollections"); + + migrationBuilder.RenameTable( + "FFmpegProfile", + newName: "FFmpegProfiles"); + + migrationBuilder.RenameTable( + "ConfigElement", + newName: "ConfigElements"); + + migrationBuilder.RenameTable( + "Channel", + newName: "Channels"); + + migrationBuilder.RenameIndex( + "IX_TelevisionSeason_TelevisionShowId", + table: "TelevisionSeasons", + newName: "IX_TelevisionSeasons_TelevisionShowId"); + + migrationBuilder.RenameIndex( + "IX_TelevisionEpisode_SeasonId", + table: "TelevisionEpisodes", + newName: "IX_TelevisionEpisodes_SeasonId"); + + migrationBuilder.RenameIndex( + "IX_SimpleMediaCollectionShow_TelevisionShowsId", + table: "SimpleMediaCollectionShows", + newName: "IX_SimpleMediaCollectionShows_TelevisionShowsId"); + + migrationBuilder.RenameIndex( + "IX_SimpleMediaCollectionSeason_TelevisionSeasonsId", + table: "SimpleMediaCollectionSeasons", + newName: "IX_SimpleMediaCollectionSeasons_TelevisionSeasonsId"); + + migrationBuilder.RenameIndex( + "IX_SimpleMediaCollectionMovie_SimpleMediaCollectionsId", + table: "SimpleMediaCollectionMovies", + newName: "IX_SimpleMediaCollectionMovies_SimpleMediaCollectionsId"); + + migrationBuilder.RenameIndex( + "IX_SimpleMediaCollectionEpisode_TelevisionEpisodesId", + table: "SimpleMediaCollectionEpisodes", + newName: "IX_SimpleMediaCollectionEpisodes_TelevisionEpisodesId"); + + migrationBuilder.RenameIndex( + "IX_ProgramScheduleItem_TelevisionShowId", + table: "ProgramScheduleItems", + newName: "IX_ProgramScheduleItems_TelevisionShowId"); + + migrationBuilder.RenameIndex( + "IX_ProgramScheduleItem_TelevisionSeasonId", + table: "ProgramScheduleItems", + newName: "IX_ProgramScheduleItems_TelevisionSeasonId"); + + migrationBuilder.RenameIndex( + "IX_ProgramScheduleItem_ProgramScheduleId", + table: "ProgramScheduleItems", + newName: "IX_ProgramScheduleItems_ProgramScheduleId"); + + migrationBuilder.RenameIndex( + "IX_ProgramScheduleItem_MediaCollectionId", + table: "ProgramScheduleItems", + newName: "IX_ProgramScheduleItems_MediaCollectionId"); + + migrationBuilder.RenameIndex( + "IX_ProgramSchedule_Name", + table: "ProgramSchedules", + newName: "IX_ProgramSchedules_Name"); + + migrationBuilder.RenameIndex( + "IX_PlexMovie_PartId", + table: "PlexMovies", + newName: "IX_PlexMovies_PartId"); + + migrationBuilder.RenameIndex( + "IX_PlayoutProgramScheduleAnchor_ProgramScheduleId", + table: "PlayoutProgramScheduleItemAnchors", + newName: "IX_PlayoutProgramScheduleItemAnchors_ProgramScheduleId"); + + migrationBuilder.RenameIndex( + "IX_PlayoutProgramScheduleAnchor_PlayoutId", + table: "PlayoutProgramScheduleItemAnchors", + newName: "IX_PlayoutProgramScheduleItemAnchors_PlayoutId"); + + migrationBuilder.RenameIndex( + "IX_PlayoutItem_PlayoutId", + table: "PlayoutItems", + newName: "IX_PlayoutItems_PlayoutId"); + + migrationBuilder.RenameIndex( + "IX_PlayoutItem_MediaItemId", + table: "PlayoutItems", + newName: "IX_PlayoutItems_MediaItemId"); + + migrationBuilder.RenameIndex( + "IX_Playout_ProgramScheduleId", + table: "Playouts", + newName: "IX_Playouts_ProgramScheduleId"); + + migrationBuilder.RenameIndex( + "IX_Playout_ChannelId", + table: "Playouts", + newName: "IX_Playouts_ChannelId"); + + migrationBuilder.RenameIndex( + "IX_Playout_Anchor_NextScheduleItemId", + table: "Playouts", + newName: "IX_Playouts_Anchor_NextScheduleItemId"); + + migrationBuilder.RenameIndex( + "IX_MediaItem_LibraryPathId", + table: "MediaItems", + newName: "IX_MediaItems_LibraryPathId"); + + migrationBuilder.RenameIndex( + "IX_MediaCollection_Name", + table: "MediaCollections", + newName: "IX_MediaCollections_Name"); + + migrationBuilder.RenameIndex( + "IX_FFmpegProfile_ResolutionId", + table: "FFmpegProfiles", + newName: "IX_FFmpegProfiles_ResolutionId"); + + migrationBuilder.RenameIndex( + "IX_ConfigElement_Key", + table: "ConfigElements", + newName: "IX_ConfigElements_Key"); + + migrationBuilder.RenameIndex( + "IX_Channel_Number", + table: "Channels", + newName: "IX_Channels_Number"); + + migrationBuilder.RenameIndex( + "IX_Channel_FFmpegProfileId", + table: "Channels", + newName: "IX_Channels_FFmpegProfileId"); + + migrationBuilder.AddColumn( + "MovieId1", + "MovieMetadata", + "INTEGER", + nullable: true); + + migrationBuilder.AddPrimaryKey( + "PK_TelevisionShows", + "TelevisionShows", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_TelevisionSeasons", + "TelevisionSeasons", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_TelevisionEpisodes", + "TelevisionEpisodes", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_SimpleMediaCollectionShows", + "SimpleMediaCollectionShows", + new[] { "SimpleMediaCollectionsId", "TelevisionShowsId" }); + + migrationBuilder.AddPrimaryKey( + "PK_SimpleMediaCollectionSeasons", + "SimpleMediaCollectionSeasons", + new[] { "SimpleMediaCollectionsId", "TelevisionSeasonsId" }); + + migrationBuilder.AddPrimaryKey( + "PK_SimpleMediaCollectionMovies", + "SimpleMediaCollectionMovies", + new[] { "MoviesId", "SimpleMediaCollectionsId" }); + + migrationBuilder.AddPrimaryKey( + "PK_SimpleMediaCollectionEpisodes", + "SimpleMediaCollectionEpisodes", + new[] { "SimpleMediaCollectionsId", "TelevisionEpisodesId" }); + + migrationBuilder.AddPrimaryKey( + "PK_SimpleMediaCollections", + "SimpleMediaCollections", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_Resolutions", + "Resolutions", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_ProgramScheduleOneItems", + "ProgramScheduleOneItems", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_ProgramScheduleMultipleItems", + "ProgramScheduleMultipleItems", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_ProgramScheduleItems", + "ProgramScheduleItems", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_ProgramScheduleFloodItems", + "ProgramScheduleFloodItems", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_ProgramScheduleDurationItems", + "ProgramScheduleDurationItems", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_ProgramSchedules", + "ProgramSchedules", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_PlexMovies", + "PlexMovies", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_PlayoutProgramScheduleItemAnchors", + "PlayoutProgramScheduleItemAnchors", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_PlayoutItems", + "PlayoutItems", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_Playouts", + "Playouts", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_Movies", + "Movies", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_MediaSources", + "MediaSources", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_MediaItems", + "MediaItems", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_MediaCollections", + "MediaCollections", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_FFmpegProfiles", + "FFmpegProfiles", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_ConfigElements", + "ConfigElements", + "Id"); + + migrationBuilder.AddPrimaryKey( + "PK_Channels", + "Channels", + "Id"); + + migrationBuilder.CreateIndex( + "IX_MovieMetadata_MovieId1", + "MovieMetadata", + "MovieId1"); + + migrationBuilder.AddForeignKey( + "FK_Channels_FFmpegProfiles_FFmpegProfileId", + "Channels", + "FFmpegProfileId", + "FFmpegProfiles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_FFmpegProfiles_Resolutions_ResolutionId", + "FFmpegProfiles", + "ResolutionId", + "Resolutions", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_Library_MediaSources_MediaSourceId", + "Library", + "MediaSourceId", + "MediaSources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_LocalMediaSource_MediaSources_Id", + "LocalMediaSource", + "Id", + "MediaSources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_MediaItems_LibraryPath_LibraryPathId", + "MediaItems", + "LibraryPathId", + "LibraryPath", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_MovieMetadata_Movies_MovieId", + "MovieMetadata", + "MovieId", + "Movies", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_MovieMetadata_Movies_MovieId1", + "MovieMetadata", + "MovieId1", + "Movies", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + "FK_Movies_MediaItems_Id", + "Movies", + "Id", + "MediaItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_PlayoutItems_MediaItems_MediaItemId", + "PlayoutItems", + "MediaItemId", + "MediaItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_PlayoutItems_Playouts_PlayoutId", + "PlayoutItems", + "PlayoutId", + "Playouts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_PlayoutProgramScheduleItemAnchors_Playouts_PlayoutId", + "PlayoutProgramScheduleItemAnchors", + "PlayoutId", + "Playouts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_PlayoutProgramScheduleItemAnchors_ProgramSchedules_ProgramScheduleId", + "PlayoutProgramScheduleItemAnchors", + "ProgramScheduleId", + "ProgramSchedules", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_Playouts_Channels_ChannelId", + "Playouts", + "ChannelId", + "Channels", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_Playouts_ProgramScheduleItems_Anchor_NextScheduleItemId", + "Playouts", + "Anchor_NextScheduleItemId", + "ProgramScheduleItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_Playouts_ProgramSchedules_ProgramScheduleId", + "Playouts", + "ProgramScheduleId", + "ProgramSchedules", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_PlexMediaSource_MediaSources_Id", + "PlexMediaSource", + "Id", + "MediaSources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_PlexMovies_Movies_Id", + "PlexMovies", + "Id", + "Movies", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_PlexMovies_PlexMediaItemPart_PartId", + "PlexMovies", + "PartId", + "PlexMediaItemPart", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + "FK_ProgramScheduleDurationItems_ProgramScheduleItems_Id", + "ProgramScheduleDurationItems", + "Id", + "ProgramScheduleItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_ProgramScheduleFloodItems_ProgramScheduleItems_Id", + "ProgramScheduleFloodItems", + "Id", + "ProgramScheduleItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_ProgramScheduleItems_MediaCollections_MediaCollectionId", + "ProgramScheduleItems", + "MediaCollectionId", + "MediaCollections", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + "FK_ProgramScheduleItems_ProgramSchedules_ProgramScheduleId", + "ProgramScheduleItems", + "ProgramScheduleId", + "ProgramSchedules", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_ProgramScheduleItems_TelevisionSeasons_TelevisionSeasonId", + "ProgramScheduleItems", + "TelevisionSeasonId", + "TelevisionSeasons", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + "FK_ProgramScheduleItems_TelevisionShows_TelevisionShowId", + "ProgramScheduleItems", + "TelevisionShowId", + "TelevisionShows", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + "FK_ProgramScheduleMultipleItems_ProgramScheduleItems_Id", + "ProgramScheduleMultipleItems", + "Id", + "ProgramScheduleItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_ProgramScheduleOneItems_ProgramScheduleItems_Id", + "ProgramScheduleOneItems", + "Id", + "ProgramScheduleItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_SimpleMediaCollectionEpisodes_SimpleMediaCollections_SimpleMediaCollectionsId", + "SimpleMediaCollectionEpisodes", + "SimpleMediaCollectionsId", + "SimpleMediaCollections", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_SimpleMediaCollectionEpisodes_TelevisionEpisodes_TelevisionEpisodesId", + "SimpleMediaCollectionEpisodes", + "TelevisionEpisodesId", + "TelevisionEpisodes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_SimpleMediaCollectionMovies_Movies_MoviesId", + "SimpleMediaCollectionMovies", + "MoviesId", + "Movies", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_SimpleMediaCollectionMovies_SimpleMediaCollections_SimpleMediaCollectionsId", + "SimpleMediaCollectionMovies", + "SimpleMediaCollectionsId", + "SimpleMediaCollections", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_SimpleMediaCollections_MediaCollections_Id", + "SimpleMediaCollections", + "Id", + "MediaCollections", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_SimpleMediaCollectionSeasons_SimpleMediaCollections_SimpleMediaCollectionsId", + "SimpleMediaCollectionSeasons", + "SimpleMediaCollectionsId", + "SimpleMediaCollections", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_SimpleMediaCollectionSeasons_TelevisionSeasons_TelevisionSeasonsId", + "SimpleMediaCollectionSeasons", + "TelevisionSeasonsId", + "TelevisionSeasons", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_SimpleMediaCollectionShows_SimpleMediaCollections_SimpleMediaCollectionsId", + "SimpleMediaCollectionShows", + "SimpleMediaCollectionsId", + "SimpleMediaCollections", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_SimpleMediaCollectionShows_TelevisionShows_TelevisionShowsId", + "SimpleMediaCollectionShows", + "TelevisionShowsId", + "TelevisionShows", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_TelevisionEpisodeMetadata_TelevisionEpisodes_TelevisionEpisodeId", + "TelevisionEpisodeMetadata", + "TelevisionEpisodeId", + "TelevisionEpisodes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_TelevisionEpisodes_MediaItems_Id", + "TelevisionEpisodes", + "Id", + "MediaItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_TelevisionEpisodes_TelevisionSeasons_SeasonId", + "TelevisionEpisodes", + "SeasonId", + "TelevisionSeasons", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_TelevisionSeasons_TelevisionShows_TelevisionShowId", + "TelevisionSeasons", + "TelevisionShowId", + "TelevisionShows", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_TelevisionShowMetadata_TelevisionShows_TelevisionShowId", + "TelevisionShowMetadata", + "TelevisionShowId", + "TelevisionShows", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + "FK_TelevisionShowSource_TelevisionShows_TelevisionShowId", + "TelevisionShowSource", + "TelevisionShowId", + "TelevisionShows", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/ErsatzTV.Infrastructure/Migrations/20210226025109_TelevisionMediaItems.Designer.cs b/ErsatzTV.Infrastructure/Migrations/20210226025109_TelevisionMediaItems.Designer.cs new file mode 100644 index 000000000..d82488143 --- /dev/null +++ b/ErsatzTV.Infrastructure/Migrations/20210226025109_TelevisionMediaItems.Designer.cs @@ -0,0 +1,1544 @@ +// +using System; +using ErsatzTV.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace ErsatzTV.Infrastructure.Migrations +{ + [DbContext(typeof(TvContext))] + [Migration("20210226025109_TelevisionMediaItems")] + partial class TelevisionMediaItems + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.3"); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FFmpegProfileId") + .HasColumnType("INTEGER"); + + b.Property("Logo") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("StreamingMode") + .HasColumnType("INTEGER"); + + b.Property("UniqueId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FFmpegProfileId"); + + b.HasIndex("Number") + .IsUnique(); + + b.ToTable("Channel"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Collection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Collection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.CollectionItem", b => + { + b.Property("CollectionId") + .HasColumnType("INTEGER"); + + b.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "MediaItemId"); + + b.HasIndex("MediaItemId"); + + b.ToTable("CollectionItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ConfigElement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("ConfigElement"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.FFmpegProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AudioBitrate") + .HasColumnType("INTEGER"); + + b.Property("AudioBufferSize") + .HasColumnType("INTEGER"); + + b.Property("AudioChannels") + .HasColumnType("INTEGER"); + + b.Property("AudioCodec") + .HasColumnType("TEXT"); + + b.Property("AudioSampleRate") + .HasColumnType("INTEGER"); + + b.Property("AudioVolume") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizeAudio") + .HasColumnType("INTEGER"); + + b.Property("NormalizeAudioCodec") + .HasColumnType("INTEGER"); + + b.Property("NormalizeResolution") + .HasColumnType("INTEGER"); + + b.Property("NormalizeVideoCodec") + .HasColumnType("INTEGER"); + + b.Property("ResolutionId") + .HasColumnType("INTEGER"); + + b.Property("ThreadCount") + .HasColumnType("INTEGER"); + + b.Property("Transcode") + .HasColumnType("INTEGER"); + + b.Property("VideoBitrate") + .HasColumnType("INTEGER"); + + b.Property("VideoBufferSize") + .HasColumnType("INTEGER"); + + b.Property("VideoCodec") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ResolutionId"); + + b.ToTable("FFmpegProfile"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastScan") + .HasColumnType("TEXT"); + + b.Property("MediaKind") + .HasColumnType("INTEGER"); + + b.Property("MediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MediaSourceId"); + + b.ToTable("Library"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("LibraryPath"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaCollection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("MediaCollection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("LibraryPathId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("Poster") + .HasColumnType("TEXT"); + + b.Property("PosterLastWriteTime") + .HasColumnType("TEXT"); + + b.Property("TelevisionEpisodeId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionSeasonId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LibraryPathId"); + + b.ToTable("MediaItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("MediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ContentRating") + .HasColumnType("TEXT"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("MovieId") + .HasColumnType("INTEGER"); + + b.Property("Outline") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("Premiered") + .HasColumnType("TEXT"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("Tagline") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MovieId") + .IsUnique(); + + b.ToTable("MovieMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("ProgramScheduleId") + .HasColumnType("INTEGER"); + + b.Property("ProgramSchedulePlayoutType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("ProgramScheduleId"); + + b.ToTable("Playout"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Finish") + .HasColumnType("TEXT"); + + b.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b.Property("PlayoutId") + .HasColumnType("INTEGER"); + + b.Property("Start") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MediaItemId"); + + b.HasIndex("PlayoutId"); + + b.ToTable("PlayoutItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CollectionId") + .HasColumnType("INTEGER"); + + b.Property("CollectionType") + .HasColumnType("INTEGER"); + + b.Property("PlayoutId") + .HasColumnType("INTEGER"); + + b.Property("ProgramScheduleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayoutId"); + + b.HasIndex("ProgramScheduleId"); + + b.ToTable("PlayoutProgramScheduleAnchor"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("PlexMediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlexMediaSourceId"); + + b.ToTable("PlexConnection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaItemPart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Duration") + .HasColumnType("INTEGER"); + + b.Property("File") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("PlexId") + .HasColumnType("INTEGER"); + + b.Property("Size") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("PlexMediaItemPart"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("MediaCollectionPlaybackOrder") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ProgramSchedule"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CollectionType") + .HasColumnType("INTEGER"); + + b.Property("Index") + .HasColumnType("INTEGER"); + + b.Property("MediaCollectionId") + .HasColumnType("INTEGER"); + + b.Property("ProgramScheduleId") + .HasColumnType("INTEGER"); + + b.Property("StartTime") + .HasColumnType("TEXT"); + + b.Property("TelevisionSeasonId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MediaCollectionId"); + + b.HasIndex("ProgramScheduleId"); + + b.HasIndex("TelevisionSeasonId"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("ProgramScheduleItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Resolution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Resolution"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Aired") + .HasColumnType("TEXT"); + + b.Property("Episode") + .HasColumnType("INTEGER"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("Season") + .HasColumnType("INTEGER"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("TelevisionEpisodeId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionEpisodeId") + .IsUnique(); + + b.ToTable("TelevisionEpisodeMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("Poster") + .HasColumnType("TEXT"); + + b.Property("PosterLastWriteTime") + .HasColumnType("TEXT"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("TelevisionSeason"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Poster") + .HasColumnType("TEXT"); + + b.Property("PosterLastWriteTime") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionShowId") + .IsUnique(); + + b.ToTable("TelevisionShowMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("TelevisionShowSource"); + + b.HasDiscriminator("Discriminator").HasValue("TelevisionShowSource"); + }); + + modelBuilder.Entity("MovieSimpleMediaCollection", b => + { + b.Property("MoviesId") + .HasColumnType("INTEGER"); + + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.HasKey("MoviesId", "SimpleMediaCollectionsId"); + + b.HasIndex("SimpleMediaCollectionsId"); + + b.ToTable("SimpleMediaCollectionMovie"); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionEpisodeMediaItem", b => + { + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionEpisodesId") + .HasColumnType("INTEGER"); + + b.HasKey("SimpleMediaCollectionsId", "TelevisionEpisodesId"); + + b.HasIndex("TelevisionEpisodesId"); + + b.ToTable("SimpleMediaCollectionEpisode"); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionSeason", b => + { + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionSeasonsId") + .HasColumnType("INTEGER"); + + b.HasKey("SimpleMediaCollectionsId", "TelevisionSeasonsId"); + + b.HasIndex("TelevisionSeasonsId"); + + b.ToTable("SimpleMediaCollectionSeason"); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionShow", b => + { + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowsId") + .HasColumnType("INTEGER"); + + b.HasKey("SimpleMediaCollectionsId", "TelevisionShowsId"); + + b.HasIndex("TelevisionShowsId"); + + b.ToTable("SimpleMediaCollectionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalLibrary", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.Library"); + + b.ToTable("LocalLibrary"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexLibrary", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.Library"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("ShouldSyncItems") + .HasColumnType("INTEGER"); + + b.ToTable("PlexLibrary"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.SimpleMediaCollection", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaCollection"); + + b.ToTable("SimpleMediaCollection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Episode", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("SeasonId") + .HasColumnType("INTEGER"); + + b.HasIndex("SeasonId"); + + b.ToTable("Episode"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Movie", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("MetadataId") + .HasColumnType("INTEGER"); + + b.ToTable("Movie"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Season", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("ShowId") + .HasColumnType("INTEGER"); + + b.HasIndex("ShowId"); + + b.ToTable("Season"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Show", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.ToTable("Show"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("SeasonId") + .HasColumnType("INTEGER"); + + b.HasIndex("SeasonId"); + + b.ToTable("TelevisionEpisode"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaSource"); + + b.ToTable("LocalMediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaSource"); + + b.Property("ClientIdentifier") + .HasColumnType("TEXT"); + + b.Property("ProductVersion") + .HasColumnType("TEXT"); + + b.Property("ServerName") + .HasColumnType("TEXT"); + + b.ToTable("PlexMediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.Property("OfflineTail") + .HasColumnType("INTEGER"); + + b.Property("PlayoutDuration") + .HasColumnType("TEXT"); + + b.ToTable("ProgramScheduleDurationItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.ToTable("ProgramScheduleFloodItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.ToTable("ProgramScheduleMultipleItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemOne", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.ToTable("ProgramScheduleOneItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalTelevisionShowSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.TelevisionShowSource"); + + b.Property("MediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasIndex("MediaSourceId"); + + b.HasDiscriminator().HasValue("LocalTelevisionShowSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMovie", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.Movie"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("PartId") + .HasColumnType("INTEGER"); + + b.HasIndex("PartId"); + + b.ToTable("PlexMovie"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.HasOne("ErsatzTV.Core.Domain.FFmpegProfile", "FFmpegProfile") + .WithMany() + .HasForeignKey("FFmpegProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FFmpegProfile"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.CollectionItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.Collection", "Collection") + .WithMany("CollectionItems") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") + .WithMany("CollectionItems") + .HasForeignKey("MediaItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("MediaItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.FFmpegProfile", b => + { + b.HasOne("ErsatzTV.Core.Domain.Resolution", "Resolution") + .WithMany() + .HasForeignKey("ResolutionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Resolution"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaSource", "MediaSource") + .WithMany("Libraries") + .HasForeignKey("MediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", "Library") + .WithMany("Paths") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.LibraryPath", "LibraryPath") + .WithMany("MediaItems") + .HasForeignKey("LibraryPathId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.MediaItemStatistics", "Statistics", b1 => + { + b1.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b1.Property("AudioCodec") + .HasColumnType("TEXT"); + + b1.Property("DisplayAspectRatio") + .HasColumnType("TEXT"); + + b1.Property("Duration") + .HasColumnType("TEXT"); + + b1.Property("Height") + .HasColumnType("INTEGER"); + + b1.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b1.Property("SampleAspectRatio") + .HasColumnType("TEXT"); + + b1.Property("VideoCodec") + .HasColumnType("TEXT"); + + b1.Property("VideoScanType") + .HasColumnType("INTEGER"); + + b1.Property("Width") + .HasColumnType("INTEGER"); + + b1.HasKey("MediaItemId"); + + b1.ToTable("MediaItem"); + + b1.WithOwner() + .HasForeignKey("MediaItemId"); + }); + + b.Navigation("LibraryPath"); + + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.Movie", "Movie") + .WithOne("Metadata") + .HasForeignKey("ErsatzTV.Core.Domain.MovieMetadata", "MovieId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Movie"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => + { + b.HasOne("ErsatzTV.Core.Domain.Channel", "Channel") + .WithMany("Playouts") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") + .WithMany("Playouts") + .HasForeignKey("ProgramScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.PlayoutAnchor", "Anchor", b1 => + { + b1.Property("PlayoutId") + .HasColumnType("INTEGER"); + + b1.Property("NextScheduleItemId") + .HasColumnType("INTEGER"); + + b1.Property("NextStart") + .HasColumnType("TEXT"); + + b1.HasKey("PlayoutId"); + + b1.HasIndex("NextScheduleItemId"); + + b1.ToTable("Playout"); + + b1.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", "NextScheduleItem") + .WithMany() + .HasForeignKey("NextScheduleItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.WithOwner() + .HasForeignKey("PlayoutId"); + + b1.Navigation("NextScheduleItem"); + }); + + b.Navigation("Anchor"); + + b.Navigation("Channel"); + + b.Navigation("ProgramSchedule"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") + .WithMany() + .HasForeignKey("MediaItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout") + .WithMany("Items") + .HasForeignKey("PlayoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaItem"); + + b.Navigation("Playout"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", b => + { + b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout") + .WithMany("ProgramScheduleAnchors") + .HasForeignKey("PlayoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") + .WithMany() + .HasForeignKey("ProgramScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.MediaCollectionEnumeratorState", "EnumeratorState", b1 => + { + b1.Property("PlayoutProgramScheduleAnchorId") + .HasColumnType("INTEGER"); + + b1.Property("Index") + .HasColumnType("INTEGER"); + + b1.Property("Seed") + .HasColumnType("INTEGER"); + + b1.HasKey("PlayoutProgramScheduleAnchorId"); + + b1.ToTable("PlayoutProgramScheduleAnchor"); + + b1.WithOwner() + .HasForeignKey("PlayoutProgramScheduleAnchorId"); + }); + + b.Navigation("EnumeratorState"); + + b.Navigation("Playout"); + + b.Navigation("ProgramSchedule"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexConnection", b => + { + b.HasOne("ErsatzTV.Core.Domain.PlexMediaSource", "PlexMediaSource") + .WithMany("Connections") + .HasForeignKey("PlexMediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlexMediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaCollection", "MediaCollection") + .WithMany() + .HasForeignKey("MediaCollectionId"); + + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") + .WithMany("Items") + .HasForeignKey("ProgramScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionSeason", "TelevisionSeason") + .WithMany() + .HasForeignKey("TelevisionSeasonId"); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithMany() + .HasForeignKey("TelevisionShowId"); + + b.Navigation("MediaCollection"); + + b.Navigation("ProgramSchedule"); + + b.Navigation("TelevisionSeason"); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", "TelevisionEpisode") + .WithOne("Metadata") + .HasForeignKey("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", "TelevisionEpisodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionEpisode"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithMany("Seasons") + .HasForeignKey("TelevisionShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithOne("Metadata") + .HasForeignKey("ErsatzTV.Core.Domain.TelevisionShowMetadata", "TelevisionShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithMany("Sources") + .HasForeignKey("TelevisionShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("MovieSimpleMediaCollection", b => + { + b.HasOne("ErsatzTV.Core.Domain.Movie", null) + .WithMany() + .HasForeignKey("MoviesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionEpisodeMediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", null) + .WithMany() + .HasForeignKey("TelevisionEpisodesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionSeason", b => + { + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionSeason", null) + .WithMany() + .HasForeignKey("TelevisionSeasonsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionShow", b => + { + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", null) + .WithMany() + .HasForeignKey("TelevisionShowsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalLibrary", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.LocalLibrary", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexLibrary", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexLibrary", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.SimpleMediaCollection", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaCollection", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.SimpleMediaCollection", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Episode", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.Episode", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.Season", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Movie", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.Movie", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Season", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.Season", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.Show", "Show") + .WithMany("Seasons") + .HasForeignKey("ShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Show", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.Show", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionSeason", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaSource", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.LocalMediaSource", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaSource", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexMediaSource", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemOne", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemOne", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalTelevisionShowSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.LocalMediaSource", "MediaSource") + .WithMany() + .HasForeignKey("MediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMovie", b => + { + b.HasOne("ErsatzTV.Core.Domain.Movie", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexMovie", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.PlexMediaItemPart", "Part") + .WithMany() + .HasForeignKey("PartId"); + + b.Navigation("Part"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.Navigation("Playouts"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Collection", b => + { + b.Navigation("CollectionItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.Navigation("Paths"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.Navigation("MediaItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => + { + b.Navigation("CollectionItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaSource", b => + { + b.Navigation("Libraries"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => + { + b.Navigation("Items"); + + b.Navigation("ProgramScheduleAnchors"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b => + { + b.Navigation("Items"); + + b.Navigation("Playouts"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShow", b => + { + b.Navigation("Metadata"); + + b.Navigation("Seasons"); + + b.Navigation("Sources"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Movie", b => + { + b.Navigation("Metadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Season", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Show", b => + { + b.Navigation("Seasons"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.Navigation("Metadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.Navigation("Connections"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ErsatzTV.Infrastructure/Migrations/20210226025109_TelevisionMediaItems.cs b/ErsatzTV.Infrastructure/Migrations/20210226025109_TelevisionMediaItems.cs new file mode 100644 index 000000000..ccb1b0069 --- /dev/null +++ b/ErsatzTV.Infrastructure/Migrations/20210226025109_TelevisionMediaItems.cs @@ -0,0 +1,248 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace ErsatzTV.Infrastructure.Migrations +{ + public partial class TelevisionMediaItems : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + "TelevisionEpisodeId", + "MediaItem", + "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + "TelevisionSeasonId", + "MediaItem", + "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + "TelevisionShowId", + "MediaItem", + "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.CreateTable( + "Collection", + table => new + { + Id = table.Column("INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column("TEXT", nullable: true) + }, + constraints: table => { table.PrimaryKey("PK_Collection", x => x.Id); }); + + migrationBuilder.CreateTable( + "Show", + table => new + { + Id = table.Column("INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true) + }, + constraints: table => + { + table.PrimaryKey("PK_Show", x => x.Id); + table.ForeignKey( + "FK_Show_MediaItem_Id", + x => x.Id, + "MediaItem", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + "CollectionItem", + table => new + { + CollectionId = table.Column("INTEGER", nullable: false), + MediaItemId = table.Column("INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CollectionItem", x => new { x.CollectionId, x.MediaItemId }); + table.ForeignKey( + "FK_CollectionItem_Collection_CollectionId", + x => x.CollectionId, + "Collection", + "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + "FK_CollectionItem_MediaItem_MediaItemId", + x => x.MediaItemId, + "MediaItem", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + "Season", + table => new + { + Id = table.Column("INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ShowId = table.Column("INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Season", x => x.Id); + table.ForeignKey( + "FK_Season_MediaItem_Id", + x => x.Id, + "MediaItem", + "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + "FK_Season_Show_ShowId", + x => x.ShowId, + "Show", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + "Episode", + table => new + { + Id = table.Column("INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + SeasonId = table.Column("INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Episode", x => x.Id); + table.ForeignKey( + "FK_Episode_MediaItem_Id", + x => x.Id, + "MediaItem", + "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + "FK_Episode_Season_SeasonId", + x => x.SeasonId, + "Season", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + "IX_CollectionItem_MediaItemId", + "CollectionItem", + "MediaItemId"); + + migrationBuilder.CreateIndex( + "IX_Episode_SeasonId", + "Episode", + "SeasonId"); + + migrationBuilder.CreateIndex( + "IX_Season_ShowId", + "Season", + "ShowId"); + + // create show media items + migrationBuilder.Sql(@"INSERT INTO MediaItem (TelevisionShowId, LibraryPathId) + SELECT distinct ts.Id, mi.LibraryPathId + FROM TelevisionShow ts + INNER JOIN TelevisionSeason tsn ON tsn.TelevisionShowId = ts.Id + INNER JOIN TelevisionEpisode te ON te.SeasonId = tsn.Id + INNER JOIN MediaItem mi on mi.Id = te.Id"); + + // create shows + migrationBuilder.Sql(@"INSERT INTO Show (Id) SELECT Id FROM MediaItem WHERE TelevisionShowId > 0"); + + // create season media items + migrationBuilder.Sql(@"INSERT INTO MediaItem (TelevisionSeasonId, LibraryPathId) + SELECT distinct tsn.Id, mi.LibraryPathId + FROM TelevisionSeason tsn + INNER JOIN TelevisionEpisode te ON te.SeasonId = tsn.Id + INNER JOIN MediaItem mi on mi.Id = te.Id"); + + // create seasons + migrationBuilder.Sql(@"INSERT INTO Season (Id, ShowId) + SELECT mi.Id, mi2.Id + FROM MediaItem mi + INNER JOIN TelevisionSeason tsn ON tsn.Id = mi.TelevisionSeasonId + INNER JOIN MediaItem mi2 ON mi2.TelevisionShowId = tsn.TelevisionShowId AND mi.LibraryPathId = mi2.LibraryPathId"); + + // mark episode media items + migrationBuilder.Sql(@"UPDATE MediaItem SET TelevisionEpisodeId = Id + WHERE Id IN (SELECT Id FROM TelevisionEpisode)"); + + // create episodes + migrationBuilder.Sql(@"INSERT INTO Episode (Id, SeasonId) + SELECT mi.Id, mi2.Id + FROM MediaItem mi + INNER JOIN TelevisionEpisode te on mi.Id = te.Id + INNER JOIN MediaItem mi2 ON mi2.TelevisionSeasonId = te.SeasonId AND mi.LibraryPathId = mi2.LibraryPathId"); + + // collections + migrationBuilder.Sql(@"INSERT INTO Collection (Name) SELECT Name FROM MediaCollection"); + + // collection movies + migrationBuilder.Sql(@"INSERT INTO CollectionItem (CollectionId, MediaItemId) + SELECT c.Id, smcm.MoviesId + FROM Collection c + INNER JOIN MediaCollection mc on mc.Name = c.Name + INNER JOIN SimpleMediaCollectionMovie smcm ON smcm.SimpleMediaCollectionsId = mc.Id"); + + // collection shows + migrationBuilder.Sql(@"INSERT INTO CollectionItem (CollectionId, MediaItemId) + SELECT c.Id, mi.Id + FROM Collection c + INNER JOIN MediaCollection mc on mc.Name = c.Name + INNER JOIN SimpleMediaCollectionShow smcs ON smcs.SimpleMediaCollectionsId = mc.Id + INNER JOIN MediaItem mi ON mi.TelevisionShowId = smcs.TelevisionShowsId"); + + // collection seasons + migrationBuilder.Sql(@"INSERT INTO CollectionItem (CollectionId, MediaItemId) + SELECT c.Id, mi.Id + FROM Collection c + INNER JOIN MediaCollection mc on mc.Name = c.Name + INNER JOIN SimpleMediaCollectionSeason smcs ON smcs.SimpleMediaCollectionsId = mc.Id + INNER JOIN MediaItem mi ON mi.TelevisionSeasonId = smcs.TelevisionSeasonsId"); + + // collection episodes + migrationBuilder.Sql(@"INSERT INTO CollectionItem (CollectionId, MediaItemId) + SELECT c.Id, mi.Id + FROM Collection c + INNER JOIN MediaCollection mc on mc.Name = c.Name + INNER JOIN SimpleMediaCollectionEpisode smce ON smce.SimpleMediaCollectionsId = mc.Id + INNER JOIN MediaItem mi ON mi.TelevisionEpisodeId = smce.TelevisionEpisodesId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + "CollectionItem"); + + migrationBuilder.DropTable( + "Episode"); + + migrationBuilder.DropTable( + "Collection"); + + migrationBuilder.DropTable( + "Season"); + + migrationBuilder.DropTable( + "Show"); + + migrationBuilder.DropColumn( + "TelevisionEpisodeId", + "MediaItem"); + + migrationBuilder.DropColumn( + "TelevisionSeasonId", + "MediaItem"); + + migrationBuilder.DropColumn( + "TelevisionShowId", + "MediaItem"); + } + } +} diff --git a/ErsatzTV.Infrastructure/Migrations/20210226110438_ProgramSchedule_CollectionAndMediaItem.Designer.cs b/ErsatzTV.Infrastructure/Migrations/20210226110438_ProgramSchedule_CollectionAndMediaItem.Designer.cs new file mode 100644 index 000000000..63b7c2559 --- /dev/null +++ b/ErsatzTV.Infrastructure/Migrations/20210226110438_ProgramSchedule_CollectionAndMediaItem.Designer.cs @@ -0,0 +1,1588 @@ +// +using System; +using ErsatzTV.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace ErsatzTV.Infrastructure.Migrations +{ + [DbContext(typeof(TvContext))] + [Migration("20210226110438_ProgramSchedule_CollectionAndMediaItem")] + partial class ProgramSchedule_CollectionAndMediaItem + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.3"); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FFmpegProfileId") + .HasColumnType("INTEGER"); + + b.Property("Logo") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("StreamingMode") + .HasColumnType("INTEGER"); + + b.Property("UniqueId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FFmpegProfileId"); + + b.HasIndex("Number") + .IsUnique(); + + b.ToTable("Channel"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Collection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Collection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.CollectionItem", b => + { + b.Property("CollectionId") + .HasColumnType("INTEGER"); + + b.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "MediaItemId"); + + b.HasIndex("MediaItemId"); + + b.ToTable("CollectionItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ConfigElement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("ConfigElement"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.FFmpegProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AudioBitrate") + .HasColumnType("INTEGER"); + + b.Property("AudioBufferSize") + .HasColumnType("INTEGER"); + + b.Property("AudioChannels") + .HasColumnType("INTEGER"); + + b.Property("AudioCodec") + .HasColumnType("TEXT"); + + b.Property("AudioSampleRate") + .HasColumnType("INTEGER"); + + b.Property("AudioVolume") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizeAudio") + .HasColumnType("INTEGER"); + + b.Property("NormalizeAudioCodec") + .HasColumnType("INTEGER"); + + b.Property("NormalizeResolution") + .HasColumnType("INTEGER"); + + b.Property("NormalizeVideoCodec") + .HasColumnType("INTEGER"); + + b.Property("ResolutionId") + .HasColumnType("INTEGER"); + + b.Property("ThreadCount") + .HasColumnType("INTEGER"); + + b.Property("Transcode") + .HasColumnType("INTEGER"); + + b.Property("VideoBitrate") + .HasColumnType("INTEGER"); + + b.Property("VideoBufferSize") + .HasColumnType("INTEGER"); + + b.Property("VideoCodec") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ResolutionId"); + + b.ToTable("FFmpegProfile"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastScan") + .HasColumnType("TEXT"); + + b.Property("MediaKind") + .HasColumnType("INTEGER"); + + b.Property("MediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MediaSourceId"); + + b.ToTable("Library"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("LibraryPath"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaCollection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("MediaCollection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("LibraryPathId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("Poster") + .HasColumnType("TEXT"); + + b.Property("PosterLastWriteTime") + .HasColumnType("TEXT"); + + b.Property("TelevisionEpisodeId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionSeasonId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LibraryPathId"); + + b.ToTable("MediaItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("MediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ContentRating") + .HasColumnType("TEXT"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("MovieId") + .HasColumnType("INTEGER"); + + b.Property("Outline") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("Premiered") + .HasColumnType("TEXT"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("Tagline") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MovieId") + .IsUnique(); + + b.ToTable("MovieMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("ProgramScheduleId") + .HasColumnType("INTEGER"); + + b.Property("ProgramSchedulePlayoutType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("ProgramScheduleId"); + + b.ToTable("Playout"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Finish") + .HasColumnType("TEXT"); + + b.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b.Property("PlayoutId") + .HasColumnType("INTEGER"); + + b.Property("Start") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MediaItemId"); + + b.HasIndex("PlayoutId"); + + b.ToTable("PlayoutItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CollectionId") + .HasColumnType("INTEGER"); + + b.Property("CollectionType") + .HasColumnType("INTEGER"); + + b.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b.Property("NewCollectionId") + .HasColumnType("INTEGER"); + + b.Property("PlayoutId") + .HasColumnType("INTEGER"); + + b.Property("ProgramScheduleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MediaItemId"); + + b.HasIndex("NewCollectionId"); + + b.HasIndex("PlayoutId"); + + b.HasIndex("ProgramScheduleId"); + + b.ToTable("PlayoutProgramScheduleAnchor"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("PlexMediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlexMediaSourceId"); + + b.ToTable("PlexConnection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaItemPart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Duration") + .HasColumnType("INTEGER"); + + b.Property("File") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("PlexId") + .HasColumnType("INTEGER"); + + b.Property("Size") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("PlexMediaItemPart"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("MediaCollectionPlaybackOrder") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ProgramSchedule"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CollectionId") + .HasColumnType("INTEGER"); + + b.Property("CollectionType") + .HasColumnType("INTEGER"); + + b.Property("Index") + .HasColumnType("INTEGER"); + + b.Property("MediaCollectionId") + .HasColumnType("INTEGER"); + + b.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b.Property("ProgramScheduleId") + .HasColumnType("INTEGER"); + + b.Property("StartTime") + .HasColumnType("TEXT"); + + b.Property("TelevisionSeasonId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CollectionId"); + + b.HasIndex("MediaCollectionId"); + + b.HasIndex("MediaItemId"); + + b.HasIndex("ProgramScheduleId"); + + b.HasIndex("TelevisionSeasonId"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("ProgramScheduleItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Resolution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Resolution"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Aired") + .HasColumnType("TEXT"); + + b.Property("Episode") + .HasColumnType("INTEGER"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("Season") + .HasColumnType("INTEGER"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("TelevisionEpisodeId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionEpisodeId") + .IsUnique(); + + b.ToTable("TelevisionEpisodeMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("Poster") + .HasColumnType("TEXT"); + + b.Property("PosterLastWriteTime") + .HasColumnType("TEXT"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("TelevisionSeason"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Poster") + .HasColumnType("TEXT"); + + b.Property("PosterLastWriteTime") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionShowId") + .IsUnique(); + + b.ToTable("TelevisionShowMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("TelevisionShowSource"); + + b.HasDiscriminator("Discriminator").HasValue("TelevisionShowSource"); + }); + + modelBuilder.Entity("MovieSimpleMediaCollection", b => + { + b.Property("MoviesId") + .HasColumnType("INTEGER"); + + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.HasKey("MoviesId", "SimpleMediaCollectionsId"); + + b.HasIndex("SimpleMediaCollectionsId"); + + b.ToTable("SimpleMediaCollectionMovie"); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionEpisodeMediaItem", b => + { + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionEpisodesId") + .HasColumnType("INTEGER"); + + b.HasKey("SimpleMediaCollectionsId", "TelevisionEpisodesId"); + + b.HasIndex("TelevisionEpisodesId"); + + b.ToTable("SimpleMediaCollectionEpisode"); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionSeason", b => + { + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionSeasonsId") + .HasColumnType("INTEGER"); + + b.HasKey("SimpleMediaCollectionsId", "TelevisionSeasonsId"); + + b.HasIndex("TelevisionSeasonsId"); + + b.ToTable("SimpleMediaCollectionSeason"); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionShow", b => + { + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowsId") + .HasColumnType("INTEGER"); + + b.HasKey("SimpleMediaCollectionsId", "TelevisionShowsId"); + + b.HasIndex("TelevisionShowsId"); + + b.ToTable("SimpleMediaCollectionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalLibrary", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.Library"); + + b.ToTable("LocalLibrary"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexLibrary", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.Library"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("ShouldSyncItems") + .HasColumnType("INTEGER"); + + b.ToTable("PlexLibrary"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.SimpleMediaCollection", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaCollection"); + + b.ToTable("SimpleMediaCollection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Episode", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("SeasonId") + .HasColumnType("INTEGER"); + + b.HasIndex("SeasonId"); + + b.ToTable("Episode"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Movie", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("MetadataId") + .HasColumnType("INTEGER"); + + b.ToTable("Movie"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Season", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("ShowId") + .HasColumnType("INTEGER"); + + b.HasIndex("ShowId"); + + b.ToTable("Season"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Show", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.ToTable("Show"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("SeasonId") + .HasColumnType("INTEGER"); + + b.HasIndex("SeasonId"); + + b.ToTable("TelevisionEpisode"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaSource"); + + b.ToTable("LocalMediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaSource"); + + b.Property("ClientIdentifier") + .HasColumnType("TEXT"); + + b.Property("ProductVersion") + .HasColumnType("TEXT"); + + b.Property("ServerName") + .HasColumnType("TEXT"); + + b.ToTable("PlexMediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.Property("OfflineTail") + .HasColumnType("INTEGER"); + + b.Property("PlayoutDuration") + .HasColumnType("TEXT"); + + b.ToTable("ProgramScheduleDurationItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.ToTable("ProgramScheduleFloodItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.ToTable("ProgramScheduleMultipleItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemOne", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.ToTable("ProgramScheduleOneItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalTelevisionShowSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.TelevisionShowSource"); + + b.Property("MediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasIndex("MediaSourceId"); + + b.HasDiscriminator().HasValue("LocalTelevisionShowSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMovie", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.Movie"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("PartId") + .HasColumnType("INTEGER"); + + b.HasIndex("PartId"); + + b.ToTable("PlexMovie"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.HasOne("ErsatzTV.Core.Domain.FFmpegProfile", "FFmpegProfile") + .WithMany() + .HasForeignKey("FFmpegProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FFmpegProfile"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.CollectionItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.Collection", "Collection") + .WithMany("CollectionItems") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") + .WithMany("CollectionItems") + .HasForeignKey("MediaItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("MediaItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.FFmpegProfile", b => + { + b.HasOne("ErsatzTV.Core.Domain.Resolution", "Resolution") + .WithMany() + .HasForeignKey("ResolutionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Resolution"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaSource", "MediaSource") + .WithMany("Libraries") + .HasForeignKey("MediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", "Library") + .WithMany("Paths") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.LibraryPath", "LibraryPath") + .WithMany("MediaItems") + .HasForeignKey("LibraryPathId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.MediaItemStatistics", "Statistics", b1 => + { + b1.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b1.Property("AudioCodec") + .HasColumnType("TEXT"); + + b1.Property("DisplayAspectRatio") + .HasColumnType("TEXT"); + + b1.Property("Duration") + .HasColumnType("TEXT"); + + b1.Property("Height") + .HasColumnType("INTEGER"); + + b1.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b1.Property("SampleAspectRatio") + .HasColumnType("TEXT"); + + b1.Property("VideoCodec") + .HasColumnType("TEXT"); + + b1.Property("VideoScanType") + .HasColumnType("INTEGER"); + + b1.Property("Width") + .HasColumnType("INTEGER"); + + b1.HasKey("MediaItemId"); + + b1.ToTable("MediaItem"); + + b1.WithOwner() + .HasForeignKey("MediaItemId"); + }); + + b.Navigation("LibraryPath"); + + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.Movie", "Movie") + .WithOne("Metadata") + .HasForeignKey("ErsatzTV.Core.Domain.MovieMetadata", "MovieId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Movie"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => + { + b.HasOne("ErsatzTV.Core.Domain.Channel", "Channel") + .WithMany("Playouts") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") + .WithMany("Playouts") + .HasForeignKey("ProgramScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.PlayoutAnchor", "Anchor", b1 => + { + b1.Property("PlayoutId") + .HasColumnType("INTEGER"); + + b1.Property("NextScheduleItemId") + .HasColumnType("INTEGER"); + + b1.Property("NextStart") + .HasColumnType("TEXT"); + + b1.HasKey("PlayoutId"); + + b1.HasIndex("NextScheduleItemId"); + + b1.ToTable("Playout"); + + b1.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", "NextScheduleItem") + .WithMany() + .HasForeignKey("NextScheduleItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.WithOwner() + .HasForeignKey("PlayoutId"); + + b1.Navigation("NextScheduleItem"); + }); + + b.Navigation("Anchor"); + + b.Navigation("Channel"); + + b.Navigation("ProgramSchedule"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") + .WithMany() + .HasForeignKey("MediaItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout") + .WithMany("Items") + .HasForeignKey("PlayoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaItem"); + + b.Navigation("Playout"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") + .WithMany() + .HasForeignKey("MediaItemId"); + + b.HasOne("ErsatzTV.Core.Domain.Collection", "Collection") + .WithMany() + .HasForeignKey("NewCollectionId"); + + b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout") + .WithMany("ProgramScheduleAnchors") + .HasForeignKey("PlayoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") + .WithMany() + .HasForeignKey("ProgramScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.MediaCollectionEnumeratorState", "EnumeratorState", b1 => + { + b1.Property("PlayoutProgramScheduleAnchorId") + .HasColumnType("INTEGER"); + + b1.Property("Index") + .HasColumnType("INTEGER"); + + b1.Property("Seed") + .HasColumnType("INTEGER"); + + b1.HasKey("PlayoutProgramScheduleAnchorId"); + + b1.ToTable("PlayoutProgramScheduleAnchor"); + + b1.WithOwner() + .HasForeignKey("PlayoutProgramScheduleAnchorId"); + }); + + b.Navigation("Collection"); + + b.Navigation("EnumeratorState"); + + b.Navigation("MediaItem"); + + b.Navigation("Playout"); + + b.Navigation("ProgramSchedule"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexConnection", b => + { + b.HasOne("ErsatzTV.Core.Domain.PlexMediaSource", "PlexMediaSource") + .WithMany("Connections") + .HasForeignKey("PlexMediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlexMediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.Collection", "Collection") + .WithMany() + .HasForeignKey("CollectionId"); + + b.HasOne("ErsatzTV.Core.Domain.MediaCollection", "MediaCollection") + .WithMany() + .HasForeignKey("MediaCollectionId"); + + b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") + .WithMany() + .HasForeignKey("MediaItemId"); + + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") + .WithMany("Items") + .HasForeignKey("ProgramScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionSeason", "TelevisionSeason") + .WithMany() + .HasForeignKey("TelevisionSeasonId"); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithMany() + .HasForeignKey("TelevisionShowId"); + + b.Navigation("Collection"); + + b.Navigation("MediaCollection"); + + b.Navigation("MediaItem"); + + b.Navigation("ProgramSchedule"); + + b.Navigation("TelevisionSeason"); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", "TelevisionEpisode") + .WithOne("Metadata") + .HasForeignKey("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", "TelevisionEpisodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionEpisode"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithMany("Seasons") + .HasForeignKey("TelevisionShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithOne("Metadata") + .HasForeignKey("ErsatzTV.Core.Domain.TelevisionShowMetadata", "TelevisionShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithMany("Sources") + .HasForeignKey("TelevisionShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("MovieSimpleMediaCollection", b => + { + b.HasOne("ErsatzTV.Core.Domain.Movie", null) + .WithMany() + .HasForeignKey("MoviesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionEpisodeMediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", null) + .WithMany() + .HasForeignKey("TelevisionEpisodesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionSeason", b => + { + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionSeason", null) + .WithMany() + .HasForeignKey("TelevisionSeasonsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionShow", b => + { + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", null) + .WithMany() + .HasForeignKey("TelevisionShowsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalLibrary", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.LocalLibrary", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexLibrary", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexLibrary", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.SimpleMediaCollection", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaCollection", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.SimpleMediaCollection", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Episode", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.Episode", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.Season", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Movie", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.Movie", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Season", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.Season", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.Show", "Show") + .WithMany("Seasons") + .HasForeignKey("ShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Show", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.Show", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionSeason", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaSource", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.LocalMediaSource", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaSource", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexMediaSource", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemOne", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemOne", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalTelevisionShowSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.LocalMediaSource", "MediaSource") + .WithMany() + .HasForeignKey("MediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMovie", b => + { + b.HasOne("ErsatzTV.Core.Domain.Movie", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexMovie", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.PlexMediaItemPart", "Part") + .WithMany() + .HasForeignKey("PartId"); + + b.Navigation("Part"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.Navigation("Playouts"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Collection", b => + { + b.Navigation("CollectionItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.Navigation("Paths"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.Navigation("MediaItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => + { + b.Navigation("CollectionItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaSource", b => + { + b.Navigation("Libraries"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => + { + b.Navigation("Items"); + + b.Navigation("ProgramScheduleAnchors"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b => + { + b.Navigation("Items"); + + b.Navigation("Playouts"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShow", b => + { + b.Navigation("Metadata"); + + b.Navigation("Seasons"); + + b.Navigation("Sources"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Movie", b => + { + b.Navigation("Metadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Season", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Show", b => + { + b.Navigation("Seasons"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.Navigation("Metadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.Navigation("Connections"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ErsatzTV.Infrastructure/Migrations/20210226110438_ProgramSchedule_CollectionAndMediaItem.cs b/ErsatzTV.Infrastructure/Migrations/20210226110438_ProgramSchedule_CollectionAndMediaItem.cs new file mode 100644 index 000000000..41729c517 --- /dev/null +++ b/ErsatzTV.Infrastructure/Migrations/20210226110438_ProgramSchedule_CollectionAndMediaItem.cs @@ -0,0 +1,137 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace ErsatzTV.Infrastructure.Migrations +{ + public partial class ProgramSchedule_CollectionAndMediaItem : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + "CollectionId", + "ProgramScheduleItem", + "INTEGER", + nullable: true); + + migrationBuilder.AddColumn( + "MediaItemId", + "ProgramScheduleItem", + "INTEGER", + nullable: true); + + migrationBuilder.AddColumn( + "MediaItemId", + "PlayoutProgramScheduleAnchor", + "INTEGER", + nullable: true); + + migrationBuilder.AddColumn( + "NewCollectionId", + "PlayoutProgramScheduleAnchor", + "INTEGER", + nullable: true); + + migrationBuilder.CreateIndex( + "IX_ProgramScheduleItem_CollectionId", + "ProgramScheduleItem", + "CollectionId"); + + migrationBuilder.CreateIndex( + "IX_ProgramScheduleItem_MediaItemId", + "ProgramScheduleItem", + "MediaItemId"); + + migrationBuilder.CreateIndex( + "IX_PlayoutProgramScheduleAnchor_MediaItemId", + "PlayoutProgramScheduleAnchor", + "MediaItemId"); + + migrationBuilder.CreateIndex( + "IX_PlayoutProgramScheduleAnchor_NewCollectionId", + "PlayoutProgramScheduleAnchor", + "NewCollectionId"); + + migrationBuilder.AddForeignKey( + "FK_PlayoutProgramScheduleAnchor_Collection_NewCollectionId", + "PlayoutProgramScheduleAnchor", + "NewCollectionId", + "Collection", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + "FK_PlayoutProgramScheduleAnchor_MediaItem_MediaItemId", + "PlayoutProgramScheduleAnchor", + "MediaItemId", + "MediaItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + "FK_ProgramScheduleItem_Collection_CollectionId", + "ProgramScheduleItem", + "CollectionId", + "Collection", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + "FK_ProgramScheduleItem_MediaItem_MediaItemId", + "ProgramScheduleItem", + "MediaItemId", + "MediaItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + "FK_PlayoutProgramScheduleAnchor_Collection_NewCollectionId", + "PlayoutProgramScheduleAnchor"); + + migrationBuilder.DropForeignKey( + "FK_PlayoutProgramScheduleAnchor_MediaItem_MediaItemId", + "PlayoutProgramScheduleAnchor"); + + migrationBuilder.DropForeignKey( + "FK_ProgramScheduleItem_Collection_CollectionId", + "ProgramScheduleItem"); + + migrationBuilder.DropForeignKey( + "FK_ProgramScheduleItem_MediaItem_MediaItemId", + "ProgramScheduleItem"); + + migrationBuilder.DropIndex( + "IX_ProgramScheduleItem_CollectionId", + "ProgramScheduleItem"); + + migrationBuilder.DropIndex( + "IX_ProgramScheduleItem_MediaItemId", + "ProgramScheduleItem"); + + migrationBuilder.DropIndex( + "IX_PlayoutProgramScheduleAnchor_MediaItemId", + "PlayoutProgramScheduleAnchor"); + + migrationBuilder.DropIndex( + "IX_PlayoutProgramScheduleAnchor_NewCollectionId", + "PlayoutProgramScheduleAnchor"); + + migrationBuilder.DropColumn( + "CollectionId", + "ProgramScheduleItem"); + + migrationBuilder.DropColumn( + "MediaItemId", + "ProgramScheduleItem"); + + migrationBuilder.DropColumn( + "MediaItemId", + "PlayoutProgramScheduleAnchor"); + + migrationBuilder.DropColumn( + "NewCollectionId", + "PlayoutProgramScheduleAnchor"); + } + } +} diff --git a/ErsatzTV.Infrastructure/Migrations/20210226110531_ProgramSchedule_UpdateCollectionAndMediaItem.Designer.cs b/ErsatzTV.Infrastructure/Migrations/20210226110531_ProgramSchedule_UpdateCollectionAndMediaItem.Designer.cs new file mode 100644 index 000000000..f6fd58718 --- /dev/null +++ b/ErsatzTV.Infrastructure/Migrations/20210226110531_ProgramSchedule_UpdateCollectionAndMediaItem.Designer.cs @@ -0,0 +1,1588 @@ +// +using System; +using ErsatzTV.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace ErsatzTV.Infrastructure.Migrations +{ + [DbContext(typeof(TvContext))] + [Migration("20210226110531_ProgramSchedule_UpdateCollectionAndMediaItem")] + partial class ProgramSchedule_UpdateCollectionAndMediaItem + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.3"); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FFmpegProfileId") + .HasColumnType("INTEGER"); + + b.Property("Logo") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("StreamingMode") + .HasColumnType("INTEGER"); + + b.Property("UniqueId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FFmpegProfileId"); + + b.HasIndex("Number") + .IsUnique(); + + b.ToTable("Channel"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Collection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Collection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.CollectionItem", b => + { + b.Property("CollectionId") + .HasColumnType("INTEGER"); + + b.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "MediaItemId"); + + b.HasIndex("MediaItemId"); + + b.ToTable("CollectionItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ConfigElement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("ConfigElement"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.FFmpegProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AudioBitrate") + .HasColumnType("INTEGER"); + + b.Property("AudioBufferSize") + .HasColumnType("INTEGER"); + + b.Property("AudioChannels") + .HasColumnType("INTEGER"); + + b.Property("AudioCodec") + .HasColumnType("TEXT"); + + b.Property("AudioSampleRate") + .HasColumnType("INTEGER"); + + b.Property("AudioVolume") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizeAudio") + .HasColumnType("INTEGER"); + + b.Property("NormalizeAudioCodec") + .HasColumnType("INTEGER"); + + b.Property("NormalizeResolution") + .HasColumnType("INTEGER"); + + b.Property("NormalizeVideoCodec") + .HasColumnType("INTEGER"); + + b.Property("ResolutionId") + .HasColumnType("INTEGER"); + + b.Property("ThreadCount") + .HasColumnType("INTEGER"); + + b.Property("Transcode") + .HasColumnType("INTEGER"); + + b.Property("VideoBitrate") + .HasColumnType("INTEGER"); + + b.Property("VideoBufferSize") + .HasColumnType("INTEGER"); + + b.Property("VideoCodec") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ResolutionId"); + + b.ToTable("FFmpegProfile"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastScan") + .HasColumnType("TEXT"); + + b.Property("MediaKind") + .HasColumnType("INTEGER"); + + b.Property("MediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MediaSourceId"); + + b.ToTable("Library"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("LibraryPath"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaCollection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("MediaCollection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("LibraryPathId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("Poster") + .HasColumnType("TEXT"); + + b.Property("PosterLastWriteTime") + .HasColumnType("TEXT"); + + b.Property("TelevisionEpisodeId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionSeasonId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LibraryPathId"); + + b.ToTable("MediaItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("MediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ContentRating") + .HasColumnType("TEXT"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("MovieId") + .HasColumnType("INTEGER"); + + b.Property("Outline") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("Premiered") + .HasColumnType("TEXT"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("Tagline") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MovieId") + .IsUnique(); + + b.ToTable("MovieMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("ProgramScheduleId") + .HasColumnType("INTEGER"); + + b.Property("ProgramSchedulePlayoutType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("ProgramScheduleId"); + + b.ToTable("Playout"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Finish") + .HasColumnType("TEXT"); + + b.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b.Property("PlayoutId") + .HasColumnType("INTEGER"); + + b.Property("Start") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MediaItemId"); + + b.HasIndex("PlayoutId"); + + b.ToTable("PlayoutItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CollectionId") + .HasColumnType("INTEGER"); + + b.Property("CollectionType") + .HasColumnType("INTEGER"); + + b.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b.Property("NewCollectionId") + .HasColumnType("INTEGER"); + + b.Property("PlayoutId") + .HasColumnType("INTEGER"); + + b.Property("ProgramScheduleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MediaItemId"); + + b.HasIndex("NewCollectionId"); + + b.HasIndex("PlayoutId"); + + b.HasIndex("ProgramScheduleId"); + + b.ToTable("PlayoutProgramScheduleAnchor"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("PlexMediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlexMediaSourceId"); + + b.ToTable("PlexConnection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaItemPart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Duration") + .HasColumnType("INTEGER"); + + b.Property("File") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("PlexId") + .HasColumnType("INTEGER"); + + b.Property("Size") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("PlexMediaItemPart"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("MediaCollectionPlaybackOrder") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ProgramSchedule"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CollectionId") + .HasColumnType("INTEGER"); + + b.Property("CollectionType") + .HasColumnType("INTEGER"); + + b.Property("Index") + .HasColumnType("INTEGER"); + + b.Property("MediaCollectionId") + .HasColumnType("INTEGER"); + + b.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b.Property("ProgramScheduleId") + .HasColumnType("INTEGER"); + + b.Property("StartTime") + .HasColumnType("TEXT"); + + b.Property("TelevisionSeasonId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CollectionId"); + + b.HasIndex("MediaCollectionId"); + + b.HasIndex("MediaItemId"); + + b.HasIndex("ProgramScheduleId"); + + b.HasIndex("TelevisionSeasonId"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("ProgramScheduleItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Resolution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Resolution"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Aired") + .HasColumnType("TEXT"); + + b.Property("Episode") + .HasColumnType("INTEGER"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("Season") + .HasColumnType("INTEGER"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("TelevisionEpisodeId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionEpisodeId") + .IsUnique(); + + b.ToTable("TelevisionEpisodeMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("Poster") + .HasColumnType("TEXT"); + + b.Property("PosterLastWriteTime") + .HasColumnType("TEXT"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("TelevisionSeason"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Poster") + .HasColumnType("TEXT"); + + b.Property("PosterLastWriteTime") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionShowId") + .IsUnique(); + + b.ToTable("TelevisionShowMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("TelevisionShowSource"); + + b.HasDiscriminator("Discriminator").HasValue("TelevisionShowSource"); + }); + + modelBuilder.Entity("MovieSimpleMediaCollection", b => + { + b.Property("MoviesId") + .HasColumnType("INTEGER"); + + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.HasKey("MoviesId", "SimpleMediaCollectionsId"); + + b.HasIndex("SimpleMediaCollectionsId"); + + b.ToTable("SimpleMediaCollectionMovie"); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionEpisodeMediaItem", b => + { + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionEpisodesId") + .HasColumnType("INTEGER"); + + b.HasKey("SimpleMediaCollectionsId", "TelevisionEpisodesId"); + + b.HasIndex("TelevisionEpisodesId"); + + b.ToTable("SimpleMediaCollectionEpisode"); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionSeason", b => + { + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionSeasonsId") + .HasColumnType("INTEGER"); + + b.HasKey("SimpleMediaCollectionsId", "TelevisionSeasonsId"); + + b.HasIndex("TelevisionSeasonsId"); + + b.ToTable("SimpleMediaCollectionSeason"); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionShow", b => + { + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowsId") + .HasColumnType("INTEGER"); + + b.HasKey("SimpleMediaCollectionsId", "TelevisionShowsId"); + + b.HasIndex("TelevisionShowsId"); + + b.ToTable("SimpleMediaCollectionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalLibrary", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.Library"); + + b.ToTable("LocalLibrary"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexLibrary", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.Library"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("ShouldSyncItems") + .HasColumnType("INTEGER"); + + b.ToTable("PlexLibrary"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.SimpleMediaCollection", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaCollection"); + + b.ToTable("SimpleMediaCollection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Episode", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("SeasonId") + .HasColumnType("INTEGER"); + + b.HasIndex("SeasonId"); + + b.ToTable("Episode"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Movie", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("MetadataId") + .HasColumnType("INTEGER"); + + b.ToTable("Movie"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Season", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("ShowId") + .HasColumnType("INTEGER"); + + b.HasIndex("ShowId"); + + b.ToTable("Season"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Show", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.ToTable("Show"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("SeasonId") + .HasColumnType("INTEGER"); + + b.HasIndex("SeasonId"); + + b.ToTable("TelevisionEpisode"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaSource"); + + b.ToTable("LocalMediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaSource"); + + b.Property("ClientIdentifier") + .HasColumnType("TEXT"); + + b.Property("ProductVersion") + .HasColumnType("TEXT"); + + b.Property("ServerName") + .HasColumnType("TEXT"); + + b.ToTable("PlexMediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.Property("OfflineTail") + .HasColumnType("INTEGER"); + + b.Property("PlayoutDuration") + .HasColumnType("TEXT"); + + b.ToTable("ProgramScheduleDurationItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.ToTable("ProgramScheduleFloodItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.ToTable("ProgramScheduleMultipleItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemOne", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.ToTable("ProgramScheduleOneItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalTelevisionShowSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.TelevisionShowSource"); + + b.Property("MediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasIndex("MediaSourceId"); + + b.HasDiscriminator().HasValue("LocalTelevisionShowSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMovie", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.Movie"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("PartId") + .HasColumnType("INTEGER"); + + b.HasIndex("PartId"); + + b.ToTable("PlexMovie"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.HasOne("ErsatzTV.Core.Domain.FFmpegProfile", "FFmpegProfile") + .WithMany() + .HasForeignKey("FFmpegProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FFmpegProfile"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.CollectionItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.Collection", "Collection") + .WithMany("CollectionItems") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") + .WithMany("CollectionItems") + .HasForeignKey("MediaItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("MediaItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.FFmpegProfile", b => + { + b.HasOne("ErsatzTV.Core.Domain.Resolution", "Resolution") + .WithMany() + .HasForeignKey("ResolutionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Resolution"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaSource", "MediaSource") + .WithMany("Libraries") + .HasForeignKey("MediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", "Library") + .WithMany("Paths") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.LibraryPath", "LibraryPath") + .WithMany("MediaItems") + .HasForeignKey("LibraryPathId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.MediaItemStatistics", "Statistics", b1 => + { + b1.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b1.Property("AudioCodec") + .HasColumnType("TEXT"); + + b1.Property("DisplayAspectRatio") + .HasColumnType("TEXT"); + + b1.Property("Duration") + .HasColumnType("TEXT"); + + b1.Property("Height") + .HasColumnType("INTEGER"); + + b1.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b1.Property("SampleAspectRatio") + .HasColumnType("TEXT"); + + b1.Property("VideoCodec") + .HasColumnType("TEXT"); + + b1.Property("VideoScanType") + .HasColumnType("INTEGER"); + + b1.Property("Width") + .HasColumnType("INTEGER"); + + b1.HasKey("MediaItemId"); + + b1.ToTable("MediaItem"); + + b1.WithOwner() + .HasForeignKey("MediaItemId"); + }); + + b.Navigation("LibraryPath"); + + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.Movie", "Movie") + .WithOne("Metadata") + .HasForeignKey("ErsatzTV.Core.Domain.MovieMetadata", "MovieId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Movie"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => + { + b.HasOne("ErsatzTV.Core.Domain.Channel", "Channel") + .WithMany("Playouts") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") + .WithMany("Playouts") + .HasForeignKey("ProgramScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.PlayoutAnchor", "Anchor", b1 => + { + b1.Property("PlayoutId") + .HasColumnType("INTEGER"); + + b1.Property("NextScheduleItemId") + .HasColumnType("INTEGER"); + + b1.Property("NextStart") + .HasColumnType("TEXT"); + + b1.HasKey("PlayoutId"); + + b1.HasIndex("NextScheduleItemId"); + + b1.ToTable("Playout"); + + b1.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", "NextScheduleItem") + .WithMany() + .HasForeignKey("NextScheduleItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.WithOwner() + .HasForeignKey("PlayoutId"); + + b1.Navigation("NextScheduleItem"); + }); + + b.Navigation("Anchor"); + + b.Navigation("Channel"); + + b.Navigation("ProgramSchedule"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") + .WithMany() + .HasForeignKey("MediaItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout") + .WithMany("Items") + .HasForeignKey("PlayoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaItem"); + + b.Navigation("Playout"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") + .WithMany() + .HasForeignKey("MediaItemId"); + + b.HasOne("ErsatzTV.Core.Domain.Collection", "Collection") + .WithMany() + .HasForeignKey("NewCollectionId"); + + b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout") + .WithMany("ProgramScheduleAnchors") + .HasForeignKey("PlayoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") + .WithMany() + .HasForeignKey("ProgramScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.MediaCollectionEnumeratorState", "EnumeratorState", b1 => + { + b1.Property("PlayoutProgramScheduleAnchorId") + .HasColumnType("INTEGER"); + + b1.Property("Index") + .HasColumnType("INTEGER"); + + b1.Property("Seed") + .HasColumnType("INTEGER"); + + b1.HasKey("PlayoutProgramScheduleAnchorId"); + + b1.ToTable("PlayoutProgramScheduleAnchor"); + + b1.WithOwner() + .HasForeignKey("PlayoutProgramScheduleAnchorId"); + }); + + b.Navigation("Collection"); + + b.Navigation("EnumeratorState"); + + b.Navigation("MediaItem"); + + b.Navigation("Playout"); + + b.Navigation("ProgramSchedule"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexConnection", b => + { + b.HasOne("ErsatzTV.Core.Domain.PlexMediaSource", "PlexMediaSource") + .WithMany("Connections") + .HasForeignKey("PlexMediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlexMediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.Collection", "Collection") + .WithMany() + .HasForeignKey("CollectionId"); + + b.HasOne("ErsatzTV.Core.Domain.MediaCollection", "MediaCollection") + .WithMany() + .HasForeignKey("MediaCollectionId"); + + b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") + .WithMany() + .HasForeignKey("MediaItemId"); + + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") + .WithMany("Items") + .HasForeignKey("ProgramScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionSeason", "TelevisionSeason") + .WithMany() + .HasForeignKey("TelevisionSeasonId"); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithMany() + .HasForeignKey("TelevisionShowId"); + + b.Navigation("Collection"); + + b.Navigation("MediaCollection"); + + b.Navigation("MediaItem"); + + b.Navigation("ProgramSchedule"); + + b.Navigation("TelevisionSeason"); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", "TelevisionEpisode") + .WithOne("Metadata") + .HasForeignKey("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", "TelevisionEpisodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionEpisode"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithMany("Seasons") + .HasForeignKey("TelevisionShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithOne("Metadata") + .HasForeignKey("ErsatzTV.Core.Domain.TelevisionShowMetadata", "TelevisionShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithMany("Sources") + .HasForeignKey("TelevisionShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("MovieSimpleMediaCollection", b => + { + b.HasOne("ErsatzTV.Core.Domain.Movie", null) + .WithMany() + .HasForeignKey("MoviesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionEpisodeMediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", null) + .WithMany() + .HasForeignKey("TelevisionEpisodesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionSeason", b => + { + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionSeason", null) + .WithMany() + .HasForeignKey("TelevisionSeasonsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionShow", b => + { + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", null) + .WithMany() + .HasForeignKey("TelevisionShowsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalLibrary", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.LocalLibrary", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexLibrary", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexLibrary", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.SimpleMediaCollection", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaCollection", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.SimpleMediaCollection", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Episode", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.Episode", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.Season", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Movie", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.Movie", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Season", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.Season", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.Show", "Show") + .WithMany("Seasons") + .HasForeignKey("ShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Show", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.Show", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionSeason", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaSource", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.LocalMediaSource", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaSource", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexMediaSource", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemOne", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemOne", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalTelevisionShowSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.LocalMediaSource", "MediaSource") + .WithMany() + .HasForeignKey("MediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMovie", b => + { + b.HasOne("ErsatzTV.Core.Domain.Movie", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexMovie", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.PlexMediaItemPart", "Part") + .WithMany() + .HasForeignKey("PartId"); + + b.Navigation("Part"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.Navigation("Playouts"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Collection", b => + { + b.Navigation("CollectionItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.Navigation("Paths"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.Navigation("MediaItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => + { + b.Navigation("CollectionItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaSource", b => + { + b.Navigation("Libraries"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => + { + b.Navigation("Items"); + + b.Navigation("ProgramScheduleAnchors"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b => + { + b.Navigation("Items"); + + b.Navigation("Playouts"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShow", b => + { + b.Navigation("Metadata"); + + b.Navigation("Seasons"); + + b.Navigation("Sources"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Movie", b => + { + b.Navigation("Metadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Season", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Show", b => + { + b.Navigation("Seasons"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.Navigation("Metadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.Navigation("Connections"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ErsatzTV.Infrastructure/Migrations/20210226110531_ProgramSchedule_UpdateCollectionAndMediaItem.cs b/ErsatzTV.Infrastructure/Migrations/20210226110531_ProgramSchedule_UpdateCollectionAndMediaItem.cs new file mode 100644 index 000000000..db187270c --- /dev/null +++ b/ErsatzTV.Infrastructure/Migrations/20210226110531_ProgramSchedule_UpdateCollectionAndMediaItem.cs @@ -0,0 +1,50 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace ErsatzTV.Infrastructure.Migrations +{ + public partial class ProgramSchedule_UpdateCollectionAndMediaItem : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + // update schedule items that reference collections + migrationBuilder.Sql( + @"UPDATE ProgramScheduleItem SET CollectionId = + (SELECT c.Id FROM Collection c INNER JOIN MediaCollection mc ON mc.Name = c.Name WHERE mc.Id = MediaCollectionId) + WHERE MediaCollectionId > 0"); + + // update schedule items that reference shows + migrationBuilder.Sql( + @"UPDATE ProgramScheduleItem SET MediaItemId = + (SELECT mi.Id FROM MediaItem mi WHERE mi.TelevisionShowId = ProgramScheduleItem.TelevisionShowId) + WHERE TelevisionShowId > 0"); + + // update schedule items that reference seasons + migrationBuilder.Sql( + @"UPDATE ProgramScheduleItem SET MediaItemId = + (SELECT mi.Id FROM MediaItem mi WHERE mi.TelevisionSeasonId = ProgramScheduleItem.TelevisionSeasonId) + WHERE TelevisionSeasonId > 0"); + + // update anchors that reference collections + migrationBuilder.Sql( + @"UPDATE PlayoutProgramScheduleAnchor SET NewCollectionId = + (SELECT c.Id FROM Collection c INNER JOIN MediaCollection mc on c.Name = mc.Name WHERE mc.Id = CollectionId) + WHERE CollectionType = 0"); + + // update anchors that reference shows + migrationBuilder.Sql( + @"UPDATE PlayoutProgramScheduleAnchor SET MediaItemId = + (SELECT mi.Id from MediaItem mi WHERE mi.TelevisionShowId = CollectionId) + WHERE CollectionType = 1"); + + // update anchors that reference seasons + migrationBuilder.Sql( + @"UPDATE PlayoutProgramScheduleAnchor SET MediaItemId = + (SELECT mi.Id FROM MediaItem mi WHERE mi.TelevisionSeasonId = CollectionId) + WHERE CollectionType = 2"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/ErsatzTV.Infrastructure/Migrations/20210226114450_Add_VersionsAndMetadata.Designer.cs b/ErsatzTV.Infrastructure/Migrations/20210226114450_Add_VersionsAndMetadata.Designer.cs new file mode 100644 index 000000000..3d2329bbd --- /dev/null +++ b/ErsatzTV.Infrastructure/Migrations/20210226114450_Add_VersionsAndMetadata.Designer.cs @@ -0,0 +1,1874 @@ +// +using System; +using ErsatzTV.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace ErsatzTV.Infrastructure.Migrations +{ + [DbContext(typeof(TvContext))] + [Migration("20210226114450_Add_VersionsAndMetadata")] + partial class Add_VersionsAndMetadata + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.3"); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FFmpegProfileId") + .HasColumnType("INTEGER"); + + b.Property("Logo") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("StreamingMode") + .HasColumnType("INTEGER"); + + b.Property("UniqueId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FFmpegProfileId"); + + b.HasIndex("Number") + .IsUnique(); + + b.ToTable("Channel"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Collection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Collection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.CollectionItem", b => + { + b.Property("CollectionId") + .HasColumnType("INTEGER"); + + b.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "MediaItemId"); + + b.HasIndex("MediaItemId"); + + b.ToTable("CollectionItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ConfigElement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("ConfigElement"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.EpisodeMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("DateUpdated") + .HasColumnType("TEXT"); + + b.Property("EpisodeId") + .HasColumnType("INTEGER"); + + b.Property("MetadataKind") + .HasColumnType("INTEGER"); + + b.Property("OriginalTitle") + .HasColumnType("TEXT"); + + b.Property("Outline") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Tagline") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EpisodeId"); + + b.ToTable("EpisodeMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.FFmpegProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AudioBitrate") + .HasColumnType("INTEGER"); + + b.Property("AudioBufferSize") + .HasColumnType("INTEGER"); + + b.Property("AudioChannels") + .HasColumnType("INTEGER"); + + b.Property("AudioCodec") + .HasColumnType("TEXT"); + + b.Property("AudioSampleRate") + .HasColumnType("INTEGER"); + + b.Property("AudioVolume") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizeAudio") + .HasColumnType("INTEGER"); + + b.Property("NormalizeAudioCodec") + .HasColumnType("INTEGER"); + + b.Property("NormalizeResolution") + .HasColumnType("INTEGER"); + + b.Property("NormalizeVideoCodec") + .HasColumnType("INTEGER"); + + b.Property("ResolutionId") + .HasColumnType("INTEGER"); + + b.Property("ThreadCount") + .HasColumnType("INTEGER"); + + b.Property("Transcode") + .HasColumnType("INTEGER"); + + b.Property("VideoBitrate") + .HasColumnType("INTEGER"); + + b.Property("VideoBufferSize") + .HasColumnType("INTEGER"); + + b.Property("VideoCodec") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ResolutionId"); + + b.ToTable("FFmpegProfile"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastScan") + .HasColumnType("TEXT"); + + b.Property("MediaKind") + .HasColumnType("INTEGER"); + + b.Property("MediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MediaSourceId"); + + b.ToTable("Library"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("LibraryPath"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaCollection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("MediaCollection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("MediaVersionId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MediaVersionId"); + + b.ToTable("MediaFile"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("LibraryPathId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("Poster") + .HasColumnType("TEXT"); + + b.Property("PosterLastWriteTime") + .HasColumnType("TEXT"); + + b.Property("TelevisionEpisodeId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionSeasonId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LibraryPathId"); + + b.ToTable("MediaItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("MediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AudioCodec") + .HasColumnType("TEXT"); + + b.Property("DisplayAspectRatio") + .HasColumnType("TEXT"); + + b.Property("Duration") + .HasColumnType("TEXT"); + + b.Property("EpisodeId") + .HasColumnType("INTEGER"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("IsInterlaced") + .HasColumnType("INTEGER"); + + b.Property("MovieId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("SampleAspectRatio") + .HasColumnType("TEXT"); + + b.Property("VideoCodec") + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("EpisodeId"); + + b.HasIndex("MovieId"); + + b.ToTable("MediaVersion"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ContentRating") + .HasColumnType("TEXT"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("MovieId") + .HasColumnType("INTEGER"); + + b.Property("Outline") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("Premiered") + .HasColumnType("TEXT"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("Tagline") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MovieId") + .IsUnique(); + + b.ToTable("MovieMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.NewMovieMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("DateUpdated") + .HasColumnType("TEXT"); + + b.Property("MetadataKind") + .HasColumnType("INTEGER"); + + b.Property("MovieId") + .HasColumnType("INTEGER"); + + b.Property("OriginalTitle") + .HasColumnType("TEXT"); + + b.Property("Outline") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Tagline") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MovieId"); + + b.ToTable("NewMovieMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("ProgramScheduleId") + .HasColumnType("INTEGER"); + + b.Property("ProgramSchedulePlayoutType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("ProgramScheduleId"); + + b.ToTable("Playout"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Finish") + .HasColumnType("TEXT"); + + b.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b.Property("PlayoutId") + .HasColumnType("INTEGER"); + + b.Property("Start") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MediaItemId"); + + b.HasIndex("PlayoutId"); + + b.ToTable("PlayoutItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CollectionId") + .HasColumnType("INTEGER"); + + b.Property("CollectionType") + .HasColumnType("INTEGER"); + + b.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b.Property("NewCollectionId") + .HasColumnType("INTEGER"); + + b.Property("PlayoutId") + .HasColumnType("INTEGER"); + + b.Property("ProgramScheduleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MediaItemId"); + + b.HasIndex("NewCollectionId"); + + b.HasIndex("PlayoutId"); + + b.HasIndex("ProgramScheduleId"); + + b.ToTable("PlayoutProgramScheduleAnchor"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("PlexMediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlexMediaSourceId"); + + b.ToTable("PlexConnection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaItemPart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Duration") + .HasColumnType("INTEGER"); + + b.Property("File") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("PlexId") + .HasColumnType("INTEGER"); + + b.Property("Size") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("PlexMediaItemPart"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("MediaCollectionPlaybackOrder") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ProgramSchedule"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CollectionId") + .HasColumnType("INTEGER"); + + b.Property("CollectionType") + .HasColumnType("INTEGER"); + + b.Property("Index") + .HasColumnType("INTEGER"); + + b.Property("MediaCollectionId") + .HasColumnType("INTEGER"); + + b.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b.Property("ProgramScheduleId") + .HasColumnType("INTEGER"); + + b.Property("StartTime") + .HasColumnType("TEXT"); + + b.Property("TelevisionSeasonId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CollectionId"); + + b.HasIndex("MediaCollectionId"); + + b.HasIndex("MediaItemId"); + + b.HasIndex("ProgramScheduleId"); + + b.HasIndex("TelevisionSeasonId"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("ProgramScheduleItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Resolution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Resolution"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ShowMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("DateUpdated") + .HasColumnType("TEXT"); + + b.Property("MetadataKind") + .HasColumnType("INTEGER"); + + b.Property("OriginalTitle") + .HasColumnType("TEXT"); + + b.Property("Outline") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("ShowId") + .HasColumnType("INTEGER"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Tagline") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ShowId"); + + b.ToTable("ShowMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Aired") + .HasColumnType("TEXT"); + + b.Property("Episode") + .HasColumnType("INTEGER"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("Season") + .HasColumnType("INTEGER"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("TelevisionEpisodeId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionEpisodeId") + .IsUnique(); + + b.ToTable("TelevisionEpisodeMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("Poster") + .HasColumnType("TEXT"); + + b.Property("PosterLastWriteTime") + .HasColumnType("TEXT"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("TelevisionSeason"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Poster") + .HasColumnType("TEXT"); + + b.Property("PosterLastWriteTime") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionShowId") + .IsUnique(); + + b.ToTable("TelevisionShowMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("TelevisionShowSource"); + + b.HasDiscriminator("Discriminator").HasValue("TelevisionShowSource"); + }); + + modelBuilder.Entity("MovieSimpleMediaCollection", b => + { + b.Property("MoviesId") + .HasColumnType("INTEGER"); + + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.HasKey("MoviesId", "SimpleMediaCollectionsId"); + + b.HasIndex("SimpleMediaCollectionsId"); + + b.ToTable("SimpleMediaCollectionMovie"); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionEpisodeMediaItem", b => + { + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionEpisodesId") + .HasColumnType("INTEGER"); + + b.HasKey("SimpleMediaCollectionsId", "TelevisionEpisodesId"); + + b.HasIndex("TelevisionEpisodesId"); + + b.ToTable("SimpleMediaCollectionEpisode"); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionSeason", b => + { + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionSeasonsId") + .HasColumnType("INTEGER"); + + b.HasKey("SimpleMediaCollectionsId", "TelevisionSeasonsId"); + + b.HasIndex("TelevisionSeasonsId"); + + b.ToTable("SimpleMediaCollectionSeason"); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionShow", b => + { + b.Property("SimpleMediaCollectionsId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowsId") + .HasColumnType("INTEGER"); + + b.HasKey("SimpleMediaCollectionsId", "TelevisionShowsId"); + + b.HasIndex("TelevisionShowsId"); + + b.ToTable("SimpleMediaCollectionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalLibrary", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.Library"); + + b.ToTable("LocalLibrary"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexLibrary", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.Library"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("ShouldSyncItems") + .HasColumnType("INTEGER"); + + b.ToTable("PlexLibrary"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.SimpleMediaCollection", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaCollection"); + + b.ToTable("SimpleMediaCollection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Episode", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("SeasonId") + .HasColumnType("INTEGER"); + + b.HasIndex("SeasonId"); + + b.ToTable("Episode"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Movie", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("MetadataId") + .HasColumnType("INTEGER"); + + b.ToTable("Movie"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Season", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.Property("ShowId") + .HasColumnType("INTEGER"); + + b.HasIndex("ShowId"); + + b.ToTable("Season"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Show", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.ToTable("Show"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("SeasonId") + .HasColumnType("INTEGER"); + + b.HasIndex("SeasonId"); + + b.ToTable("TelevisionEpisode"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaSource"); + + b.ToTable("LocalMediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaSource"); + + b.Property("ClientIdentifier") + .HasColumnType("TEXT"); + + b.Property("ProductVersion") + .HasColumnType("TEXT"); + + b.Property("ServerName") + .HasColumnType("TEXT"); + + b.ToTable("PlexMediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.Property("OfflineTail") + .HasColumnType("INTEGER"); + + b.Property("PlayoutDuration") + .HasColumnType("TEXT"); + + b.ToTable("ProgramScheduleDurationItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.ToTable("ProgramScheduleFloodItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.ToTable("ProgramScheduleMultipleItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemOne", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); + + b.ToTable("ProgramScheduleOneItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalTelevisionShowSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.TelevisionShowSource"); + + b.Property("MediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasIndex("MediaSourceId"); + + b.HasDiscriminator().HasValue("LocalTelevisionShowSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMovie", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.Movie"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("PartId") + .HasColumnType("INTEGER"); + + b.HasIndex("PartId"); + + b.ToTable("PlexMovie"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.HasOne("ErsatzTV.Core.Domain.FFmpegProfile", "FFmpegProfile") + .WithMany() + .HasForeignKey("FFmpegProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FFmpegProfile"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.CollectionItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.Collection", "Collection") + .WithMany("CollectionItems") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") + .WithMany("CollectionItems") + .HasForeignKey("MediaItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("MediaItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.EpisodeMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.Episode", "Episode") + .WithMany("EpisodeMetadata") + .HasForeignKey("EpisodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Episode"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.FFmpegProfile", b => + { + b.HasOne("ErsatzTV.Core.Domain.Resolution", "Resolution") + .WithMany() + .HasForeignKey("ResolutionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Resolution"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaSource", "MediaSource") + .WithMany("Libraries") + .HasForeignKey("MediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", "Library") + .WithMany("Paths") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaFile", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaVersion", "MediaVersion") + .WithMany("MediaFiles") + .HasForeignKey("MediaVersionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaVersion"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.LibraryPath", "LibraryPath") + .WithMany("MediaItems") + .HasForeignKey("LibraryPathId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.MediaItemStatistics", "Statistics", b1 => + { + b1.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b1.Property("AudioCodec") + .HasColumnType("TEXT"); + + b1.Property("DisplayAspectRatio") + .HasColumnType("TEXT"); + + b1.Property("Duration") + .HasColumnType("TEXT"); + + b1.Property("Height") + .HasColumnType("INTEGER"); + + b1.Property("LastWriteTime") + .HasColumnType("TEXT"); + + b1.Property("SampleAspectRatio") + .HasColumnType("TEXT"); + + b1.Property("VideoCodec") + .HasColumnType("TEXT"); + + b1.Property("VideoScanType") + .HasColumnType("INTEGER"); + + b1.Property("Width") + .HasColumnType("INTEGER"); + + b1.HasKey("MediaItemId"); + + b1.ToTable("MediaItem"); + + b1.WithOwner() + .HasForeignKey("MediaItemId"); + }); + + b.Navigation("LibraryPath"); + + b.Navigation("Statistics"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaVersion", b => + { + b.HasOne("ErsatzTV.Core.Domain.Episode", null) + .WithMany("MediaVersions") + .HasForeignKey("EpisodeId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ErsatzTV.Core.Domain.Movie", null) + .WithMany("MediaVersions") + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.Movie", "Movie") + .WithOne("Metadata") + .HasForeignKey("ErsatzTV.Core.Domain.MovieMetadata", "MovieId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Movie"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.NewMovieMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.Movie", "Movie") + .WithMany("MovieMetadata") + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Movie"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => + { + b.HasOne("ErsatzTV.Core.Domain.Channel", "Channel") + .WithMany("Playouts") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") + .WithMany("Playouts") + .HasForeignKey("ProgramScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.PlayoutAnchor", "Anchor", b1 => + { + b1.Property("PlayoutId") + .HasColumnType("INTEGER"); + + b1.Property("NextScheduleItemId") + .HasColumnType("INTEGER"); + + b1.Property("NextStart") + .HasColumnType("TEXT"); + + b1.HasKey("PlayoutId"); + + b1.HasIndex("NextScheduleItemId"); + + b1.ToTable("Playout"); + + b1.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", "NextScheduleItem") + .WithMany() + .HasForeignKey("NextScheduleItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.WithOwner() + .HasForeignKey("PlayoutId"); + + b1.Navigation("NextScheduleItem"); + }); + + b.Navigation("Anchor"); + + b.Navigation("Channel"); + + b.Navigation("ProgramSchedule"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") + .WithMany() + .HasForeignKey("MediaItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout") + .WithMany("Items") + .HasForeignKey("PlayoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaItem"); + + b.Navigation("Playout"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") + .WithMany() + .HasForeignKey("MediaItemId"); + + b.HasOne("ErsatzTV.Core.Domain.Collection", "Collection") + .WithMany() + .HasForeignKey("NewCollectionId"); + + b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout") + .WithMany("ProgramScheduleAnchors") + .HasForeignKey("PlayoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") + .WithMany() + .HasForeignKey("ProgramScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.MediaCollectionEnumeratorState", "EnumeratorState", b1 => + { + b1.Property("PlayoutProgramScheduleAnchorId") + .HasColumnType("INTEGER"); + + b1.Property("Index") + .HasColumnType("INTEGER"); + + b1.Property("Seed") + .HasColumnType("INTEGER"); + + b1.HasKey("PlayoutProgramScheduleAnchorId"); + + b1.ToTable("PlayoutProgramScheduleAnchor"); + + b1.WithOwner() + .HasForeignKey("PlayoutProgramScheduleAnchorId"); + }); + + b.Navigation("Collection"); + + b.Navigation("EnumeratorState"); + + b.Navigation("MediaItem"); + + b.Navigation("Playout"); + + b.Navigation("ProgramSchedule"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexConnection", b => + { + b.HasOne("ErsatzTV.Core.Domain.PlexMediaSource", "PlexMediaSource") + .WithMany("Connections") + .HasForeignKey("PlexMediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlexMediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.Collection", "Collection") + .WithMany() + .HasForeignKey("CollectionId"); + + b.HasOne("ErsatzTV.Core.Domain.MediaCollection", "MediaCollection") + .WithMany() + .HasForeignKey("MediaCollectionId"); + + b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") + .WithMany() + .HasForeignKey("MediaItemId"); + + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") + .WithMany("Items") + .HasForeignKey("ProgramScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionSeason", "TelevisionSeason") + .WithMany() + .HasForeignKey("TelevisionSeasonId"); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithMany() + .HasForeignKey("TelevisionShowId"); + + b.Navigation("Collection"); + + b.Navigation("MediaCollection"); + + b.Navigation("MediaItem"); + + b.Navigation("ProgramSchedule"); + + b.Navigation("TelevisionSeason"); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ShowMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.Show", "Show") + .WithMany("ShowMetadata") + .HasForeignKey("ShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", "TelevisionEpisode") + .WithOne("Metadata") + .HasForeignKey("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", "TelevisionEpisodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionEpisode"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithMany("Seasons") + .HasForeignKey("TelevisionShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithOne("Metadata") + .HasForeignKey("ErsatzTV.Core.Domain.TelevisionShowMetadata", "TelevisionShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") + .WithMany("Sources") + .HasForeignKey("TelevisionShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TelevisionShow"); + }); + + modelBuilder.Entity("MovieSimpleMediaCollection", b => + { + b.HasOne("ErsatzTV.Core.Domain.Movie", null) + .WithMany() + .HasForeignKey("MoviesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionEpisodeMediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", null) + .WithMany() + .HasForeignKey("TelevisionEpisodesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionSeason", b => + { + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionSeason", null) + .WithMany() + .HasForeignKey("TelevisionSeasonsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SimpleMediaCollectionTelevisionShow", b => + { + b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) + .WithMany() + .HasForeignKey("SimpleMediaCollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", null) + .WithMany() + .HasForeignKey("TelevisionShowsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalLibrary", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.LocalLibrary", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexLibrary", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexLibrary", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.SimpleMediaCollection", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaCollection", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.SimpleMediaCollection", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Episode", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.Episode", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.Season", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Movie", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.Movie", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Season", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.Season", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.Show", "Show") + .WithMany("Seasons") + .HasForeignKey("ShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Show", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.Show", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.TelevisionSeason", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaSource", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.LocalMediaSource", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaSource", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexMediaSource", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemOne", b => + { + b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.ProgramScheduleItemOne", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalTelevisionShowSource", b => + { + b.HasOne("ErsatzTV.Core.Domain.LocalMediaSource", "MediaSource") + .WithMany() + .HasForeignKey("MediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMovie", b => + { + b.HasOne("ErsatzTV.Core.Domain.Movie", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexMovie", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.PlexMediaItemPart", "Part") + .WithMany() + .HasForeignKey("PartId"); + + b.Navigation("Part"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.Navigation("Playouts"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Collection", b => + { + b.Navigation("CollectionItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.Navigation("Paths"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.Navigation("MediaItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => + { + b.Navigation("CollectionItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaSource", b => + { + b.Navigation("Libraries"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaVersion", b => + { + b.Navigation("MediaFiles"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => + { + b.Navigation("Items"); + + b.Navigation("ProgramScheduleAnchors"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b => + { + b.Navigation("Items"); + + b.Navigation("Playouts"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShow", b => + { + b.Navigation("Metadata"); + + b.Navigation("Seasons"); + + b.Navigation("Sources"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Episode", b => + { + b.Navigation("EpisodeMetadata"); + + b.Navigation("MediaVersions"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Movie", b => + { + b.Navigation("MediaVersions"); + + b.Navigation("Metadata"); + + b.Navigation("MovieMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Season", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Show", b => + { + b.Navigation("Seasons"); + + b.Navigation("ShowMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.Navigation("Metadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.Navigation("Connections"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ErsatzTV.Infrastructure/Migrations/20210226114450_Add_VersionsAndMetadata.cs b/ErsatzTV.Infrastructure/Migrations/20210226114450_Add_VersionsAndMetadata.cs new file mode 100644 index 000000000..c31897710 --- /dev/null +++ b/ErsatzTV.Infrastructure/Migrations/20210226114450_Add_VersionsAndMetadata.cs @@ -0,0 +1,223 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace ErsatzTV.Infrastructure.Migrations +{ + public partial class Add_VersionsAndMetadata : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "SeasonNumber", + table: "Season", + type: "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "EpisodeNumber", + table: "Episode", + type: "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.CreateTable( + name: "EpisodeMetadata", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Outline = table.Column(type: "TEXT", nullable: true), + Plot = table.Column(type: "TEXT", nullable: true), + Tagline = table.Column(type: "TEXT", nullable: true), + EpisodeId = table.Column(type: "INTEGER", nullable: false), + MetadataKind = table.Column(type: "INTEGER", nullable: false), + Title = table.Column(type: "TEXT", nullable: true), + OriginalTitle = table.Column(type: "TEXT", nullable: true), + SortTitle = table.Column(type: "TEXT", nullable: true), + ReleaseDate = table.Column(type: "TEXT", nullable: true), + DateAdded = table.Column(type: "TEXT", nullable: false), + DateUpdated = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_EpisodeMetadata", x => x.Id); + table.ForeignKey( + name: "FK_EpisodeMetadata_Episode_EpisodeId", + column: x => x.EpisodeId, + principalTable: "Episode", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "MediaVersion", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", nullable: true), + Duration = table.Column(type: "TEXT", nullable: false), + SampleAspectRatio = table.Column(type: "TEXT", nullable: true), + DisplayAspectRatio = table.Column(type: "TEXT", nullable: true), + VideoCodec = table.Column(type: "TEXT", nullable: true), + AudioCodec = table.Column(type: "TEXT", nullable: true), + IsInterlaced = table.Column(type: "INTEGER", nullable: false), + Width = table.Column(type: "INTEGER", nullable: false), + Height = table.Column(type: "INTEGER", nullable: false), + EpisodeId = table.Column(type: "INTEGER", nullable: true), + MovieId = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_MediaVersion", x => x.Id); + table.ForeignKey( + name: "FK_MediaVersion_Episode_EpisodeId", + column: x => x.EpisodeId, + principalTable: "Episode", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_MediaVersion_Movie_MovieId", + column: x => x.MovieId, + principalTable: "Movie", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "NewMovieMetadata", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Outline = table.Column(type: "TEXT", nullable: true), + Plot = table.Column(type: "TEXT", nullable: true), + Tagline = table.Column(type: "TEXT", nullable: true), + MovieId = table.Column(type: "INTEGER", nullable: false), + MetadataKind = table.Column(type: "INTEGER", nullable: false), + Title = table.Column(type: "TEXT", nullable: true), + OriginalTitle = table.Column(type: "TEXT", nullable: true), + SortTitle = table.Column(type: "TEXT", nullable: true), + ReleaseDate = table.Column(type: "TEXT", nullable: true), + DateAdded = table.Column(type: "TEXT", nullable: false), + DateUpdated = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_NewMovieMetadata", x => x.Id); + table.ForeignKey( + name: "FK_NewMovieMetadata_Movie_MovieId", + column: x => x.MovieId, + principalTable: "Movie", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ShowMetadata", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Outline = table.Column(type: "TEXT", nullable: true), + Plot = table.Column(type: "TEXT", nullable: true), + Tagline = table.Column(type: "TEXT", nullable: true), + ShowId = table.Column(type: "INTEGER", nullable: false), + MetadataKind = table.Column(type: "INTEGER", nullable: false), + Title = table.Column(type: "TEXT", nullable: true), + OriginalTitle = table.Column(type: "TEXT", nullable: true), + SortTitle = table.Column(type: "TEXT", nullable: true), + ReleaseDate = table.Column(type: "TEXT", nullable: true), + DateAdded = table.Column(type: "TEXT", nullable: false), + DateUpdated = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ShowMetadata", x => x.Id); + table.ForeignKey( + name: "FK_ShowMetadata_Show_ShowId", + column: x => x.ShowId, + principalTable: "Show", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "MediaFile", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Path = table.Column(type: "TEXT", nullable: true), + MediaVersionId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MediaFile", x => x.Id); + table.ForeignKey( + name: "FK_MediaFile_MediaVersion_MediaVersionId", + column: x => x.MediaVersionId, + principalTable: "MediaVersion", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_EpisodeMetadata_EpisodeId", + table: "EpisodeMetadata", + column: "EpisodeId"); + + migrationBuilder.CreateIndex( + name: "IX_MediaFile_MediaVersionId", + table: "MediaFile", + column: "MediaVersionId"); + + migrationBuilder.CreateIndex( + name: "IX_MediaVersion_EpisodeId", + table: "MediaVersion", + column: "EpisodeId"); + + migrationBuilder.CreateIndex( + name: "IX_MediaVersion_MovieId", + table: "MediaVersion", + column: "MovieId"); + + migrationBuilder.CreateIndex( + name: "IX_NewMovieMetadata_MovieId", + table: "NewMovieMetadata", + column: "MovieId"); + + migrationBuilder.CreateIndex( + name: "IX_ShowMetadata_ShowId", + table: "ShowMetadata", + column: "ShowId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "EpisodeMetadata"); + + migrationBuilder.DropTable( + name: "MediaFile"); + + migrationBuilder.DropTable( + name: "NewMovieMetadata"); + + migrationBuilder.DropTable( + name: "ShowMetadata"); + + migrationBuilder.DropTable( + name: "MediaVersion"); + + migrationBuilder.DropColumn( + name: "SeasonNumber", + table: "Season"); + + migrationBuilder.DropColumn( + name: "EpisodeNumber", + table: "Episode"); + } + } +} diff --git a/ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs b/ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs index c0b9613ae..4e41489a5 100644 --- a/ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs +++ b/ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs @@ -1,14 +1,14 @@ // - using System; using ErsatzTV.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace ErsatzTV.Infrastructure.Migrations { [DbContext(typeof(TvContext))] - internal class TvContextModelSnapshot : ModelSnapshot + partial class TvContextModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) { @@ -16,118 +16,136 @@ namespace ErsatzTV.Infrastructure.Migrations modelBuilder .HasAnnotation("ProductVersion", "5.0.3"); - modelBuilder.Entity( - "ErsatzTV.Core.AggregateModels.GenericIntegerId", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => { b.Property("Id") + .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.ToView("No table or view exists for GenericIntegerId"); - }); + b.Property("FFmpegProfileId") + .HasColumnType("INTEGER"); - modelBuilder.Entity( - "ErsatzTV.Core.AggregateModels.MediaCollectionSummary", - b => - { - b.Property("Id") + b.Property("Logo") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Number") .HasColumnType("INTEGER"); - b.Property("IsSimple") + b.Property("StreamingMode") .HasColumnType("INTEGER"); - b.Property("ItemCount") + b.Property("UniqueId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FFmpegProfileId"); + + b.HasIndex("Number") + .IsUnique(); + + b.ToTable("Channel"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Collection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); b.Property("Name") .HasColumnType("TEXT"); - b.ToView("No table or view exists for MediaCollectionSummary"); + b.HasKey("Id"); + + b.ToTable("Collection"); }); - modelBuilder.Entity( - "ErsatzTV.Core.AggregateModels.MediaItemSummary", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.CollectionItem", b => { + b.Property("CollectionId") + .HasColumnType("INTEGER"); + b.Property("MediaItemId") .HasColumnType("INTEGER"); - b.Property("Poster") - .HasColumnType("TEXT"); + b.HasKey("CollectionId", "MediaItemId"); - b.Property("SortTitle") - .HasColumnType("TEXT"); + b.HasIndex("MediaItemId"); + + b.ToTable("CollectionItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ConfigElement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.Property("Subtitle") + b.Property("Key") .HasColumnType("TEXT"); - b.Property("Title") + b.Property("Value") .HasColumnType("TEXT"); - b.ToView("No table or view exists for MediaItemSummary"); + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("ConfigElement"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.Channel", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.EpisodeMetadata", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("FFmpegProfileId") - .HasColumnType("INTEGER"); - - b.Property("Logo") + b.Property("DateAdded") .HasColumnType("TEXT"); - b.Property("Name") + b.Property("DateUpdated") .HasColumnType("TEXT"); - b.Property("Number") + b.Property("EpisodeId") .HasColumnType("INTEGER"); - b.Property("StreamingMode") + b.Property("MetadataKind") .HasColumnType("INTEGER"); - b.Property("UniqueId") + b.Property("OriginalTitle") .HasColumnType("TEXT"); - b.HasKey("Id"); - - b.HasIndex("FFmpegProfileId"); + b.Property("Outline") + .HasColumnType("TEXT"); - b.HasIndex("Number") - .IsUnique(); + b.Property("Plot") + .HasColumnType("TEXT"); - b.ToTable("Channels"); - }); + b.Property("ReleaseDate") + .HasColumnType("TEXT"); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.ConfigElement", - b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + b.Property("SortTitle") + .HasColumnType("TEXT"); - b.Property("Key") + b.Property("Tagline") .HasColumnType("TEXT"); - b.Property("Value") + b.Property("Title") .HasColumnType("TEXT"); b.HasKey("Id"); - b.HasIndex("Key") - .IsUnique(); + b.HasIndex("EpisodeId"); - b.ToTable("ConfigElements"); + b.ToTable("EpisodeMetadata"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.FFmpegProfile", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.FFmpegProfile", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -188,12 +206,54 @@ namespace ErsatzTV.Infrastructure.Migrations b.HasIndex("ResolutionId"); - b.ToTable("FFmpegProfiles"); + b.ToTable("FFmpegProfile"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastScan") + .HasColumnType("TEXT"); + + b.Property("MediaKind") + .HasColumnType("INTEGER"); + + b.Property("MediaSourceId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MediaSourceId"); + + b.ToTable("Library"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("LibraryPath"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.MediaCollection", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaCollection", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -207,12 +267,29 @@ namespace ErsatzTV.Infrastructure.Migrations b.HasIndex("Name") .IsUnique(); - b.ToTable("MediaCollections"); + b.ToTable("MediaCollection"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.MediaItem", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("MediaVersionId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MediaVersionId"); + + b.ToTable("MediaFile"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -221,7 +298,7 @@ namespace ErsatzTV.Infrastructure.Migrations b.Property("LastWriteTime") .HasColumnType("TEXT"); - b.Property("MediaSourceId") + b.Property("LibraryPathId") .HasColumnType("INTEGER"); b.Property("Path") @@ -233,41 +310,82 @@ namespace ErsatzTV.Infrastructure.Migrations b.Property("PosterLastWriteTime") .HasColumnType("TEXT"); + b.Property("TelevisionEpisodeId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionSeasonId") + .HasColumnType("INTEGER"); + + b.Property("TelevisionShowId") + .HasColumnType("INTEGER"); + b.HasKey("Id"); - b.HasIndex("MediaSourceId"); + b.HasIndex("LibraryPathId"); - b.ToTable("MediaItems"); + b.ToTable("MediaItem"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.MediaSource", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaSource", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("LastScan") + b.HasKey("Id"); + + b.ToTable("MediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AudioCodec") + .HasColumnType("TEXT"); + + b.Property("DisplayAspectRatio") + .HasColumnType("TEXT"); + + b.Property("Duration") .HasColumnType("TEXT"); + b.Property("EpisodeId") + .HasColumnType("INTEGER"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("IsInterlaced") + .HasColumnType("INTEGER"); + + b.Property("MovieId") + .HasColumnType("INTEGER"); + b.Property("Name") .HasColumnType("TEXT"); - b.Property("SourceType") + b.Property("SampleAspectRatio") + .HasColumnType("TEXT"); + + b.Property("VideoCodec") + .HasColumnType("TEXT"); + + b.Property("Width") .HasColumnType("INTEGER"); b.HasKey("Id"); - b.HasIndex("Name") - .IsUnique(); + b.HasIndex("EpisodeId"); - b.ToTable("MediaSources"); + b.HasIndex("MovieId"); + + b.ToTable("MediaVersion"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.MovieMetadata", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMetadata", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -314,9 +432,53 @@ namespace ErsatzTV.Infrastructure.Migrations b.ToTable("MovieMetadata"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.Playout", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.NewMovieMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("DateUpdated") + .HasColumnType("TEXT"); + + b.Property("MetadataKind") + .HasColumnType("INTEGER"); + + b.Property("MovieId") + .HasColumnType("INTEGER"); + + b.Property("OriginalTitle") + .HasColumnType("TEXT"); + + b.Property("Outline") + .HasColumnType("TEXT"); + + b.Property("Plot") + .HasColumnType("TEXT"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Tagline") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MovieId"); + + b.ToTable("NewMovieMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -337,12 +499,10 @@ namespace ErsatzTV.Infrastructure.Migrations b.HasIndex("ProgramScheduleId"); - b.ToTable("Playouts"); + b.ToTable("Playout"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.PlayoutItem", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutItem", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -366,12 +526,10 @@ namespace ErsatzTV.Infrastructure.Migrations b.HasIndex("PlayoutId"); - b.ToTable("PlayoutItems"); + b.ToTable("PlayoutItem"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -383,6 +541,12 @@ namespace ErsatzTV.Infrastructure.Migrations b.Property("CollectionType") .HasColumnType("INTEGER"); + b.Property("MediaItemId") + .HasColumnType("INTEGER"); + + b.Property("NewCollectionId") + .HasColumnType("INTEGER"); + b.Property("PlayoutId") .HasColumnType("INTEGER"); @@ -391,16 +555,18 @@ namespace ErsatzTV.Infrastructure.Migrations b.HasKey("Id"); + b.HasIndex("MediaItemId"); + + b.HasIndex("NewCollectionId"); + b.HasIndex("PlayoutId"); b.HasIndex("ProgramScheduleId"); - b.ToTable("PlayoutProgramScheduleItemAnchors"); + b.ToTable("PlayoutProgramScheduleAnchor"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.PlexMediaSourceConnection", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexConnection", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -409,7 +575,7 @@ namespace ErsatzTV.Infrastructure.Migrations b.Property("IsActive") .HasColumnType("INTEGER"); - b.Property("PlexMediaSourceId") + b.Property("PlexMediaSourceId") .HasColumnType("INTEGER"); b.Property("Uri") @@ -419,39 +585,36 @@ namespace ErsatzTV.Infrastructure.Migrations b.HasIndex("PlexMediaSourceId"); - b.ToTable("PlexMediaSourceConnections"); + b.ToTable("PlexConnection"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.PlexMediaSourceLibrary", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaItemPart", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("Duration") + .HasColumnType("INTEGER"); + + b.Property("File") + .HasColumnType("TEXT"); + b.Property("Key") .HasColumnType("TEXT"); - b.Property("MediaType") + b.Property("PlexId") .HasColumnType("INTEGER"); - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("PlexMediaSourceId") + b.Property("Size") .HasColumnType("INTEGER"); b.HasKey("Id"); - b.HasIndex("PlexMediaSourceId"); - - b.ToTable("PlexMediaSourceLibraries"); + b.ToTable("PlexMediaItemPart"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.ProgramSchedule", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -468,17 +631,18 @@ namespace ErsatzTV.Infrastructure.Migrations b.HasIndex("Name") .IsUnique(); - b.ToTable("ProgramSchedules"); + b.ToTable("ProgramSchedule"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.ProgramScheduleItem", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("CollectionId") + .HasColumnType("INTEGER"); + b.Property("CollectionType") .HasColumnType("INTEGER"); @@ -488,6 +652,9 @@ namespace ErsatzTV.Infrastructure.Migrations b.Property("MediaCollectionId") .HasColumnType("INTEGER"); + b.Property("MediaItemId") + .HasColumnType("INTEGER"); + b.Property("ProgramScheduleId") .HasColumnType("INTEGER"); @@ -502,42 +669,88 @@ namespace ErsatzTV.Infrastructure.Migrations b.HasKey("Id"); + b.HasIndex("CollectionId"); + b.HasIndex("MediaCollectionId"); + b.HasIndex("MediaItemId"); + b.HasIndex("ProgramScheduleId"); - b.HasIndex("TelevisionSeasonId"); + b.HasIndex("TelevisionSeasonId"); + + b.HasIndex("TelevisionShowId"); + + b.ToTable("ProgramScheduleItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Resolution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Resolution"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.ShowMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("DateUpdated") + .HasColumnType("TEXT"); + + b.Property("MetadataKind") + .HasColumnType("INTEGER"); + + b.Property("OriginalTitle") + .HasColumnType("TEXT"); + + b.Property("Outline") + .HasColumnType("TEXT"); - b.HasIndex("TelevisionShowId"); + b.Property("Plot") + .HasColumnType("TEXT"); - b.ToTable("ProgramScheduleItems"); - }); + b.Property("ReleaseDate") + .HasColumnType("TEXT"); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.Resolution", - b => - { - b.Property("Id") - .ValueGeneratedOnAdd() + b.Property("ShowId") .HasColumnType("INTEGER"); - b.Property("Height") - .HasColumnType("INTEGER"); + b.Property("SortTitle") + .HasColumnType("TEXT"); - b.Property("Name") + b.Property("Tagline") .HasColumnType("TEXT"); - b.Property("Width") - .HasColumnType("INTEGER"); + b.Property("Title") + .HasColumnType("TEXT"); b.HasKey("Id"); - b.ToTable("Resolutions"); + b.HasIndex("ShowId"); + + b.ToTable("ShowMetadata"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -578,9 +791,7 @@ namespace ErsatzTV.Infrastructure.Migrations b.ToTable("TelevisionEpisodeMetadata"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.TelevisionSeason", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -605,12 +816,10 @@ namespace ErsatzTV.Infrastructure.Migrations b.HasIndex("TelevisionShowId"); - b.ToTable("TelevisionSeasons"); + b.ToTable("TelevisionSeason"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.TelevisionShow", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShow", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -624,12 +833,10 @@ namespace ErsatzTV.Infrastructure.Migrations b.HasKey("Id"); - b.ToTable("TelevisionShows"); + b.ToTable("TelevisionShow"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.TelevisionShowMetadata", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowMetadata", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -664,9 +871,7 @@ namespace ErsatzTV.Infrastructure.Migrations b.ToTable("TelevisionShowMetadata"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.TelevisionShowSource", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowSource", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -688,9 +893,7 @@ namespace ErsatzTV.Infrastructure.Migrations b.HasDiscriminator("Discriminator").HasValue("TelevisionShowSource"); }); - modelBuilder.Entity( - "MovieMediaItemSimpleMediaCollection", - b => + modelBuilder.Entity("MovieSimpleMediaCollection", b => { b.Property("MoviesId") .HasColumnType("INTEGER"); @@ -702,12 +905,10 @@ namespace ErsatzTV.Infrastructure.Migrations b.HasIndex("SimpleMediaCollectionsId"); - b.ToTable("SimpleMediaCollectionMovies"); + b.ToTable("SimpleMediaCollectionMovie"); }); - modelBuilder.Entity( - "SimpleMediaCollectionTelevisionEpisodeMediaItem", - b => + modelBuilder.Entity("SimpleMediaCollectionTelevisionEpisodeMediaItem", b => { b.Property("SimpleMediaCollectionsId") .HasColumnType("INTEGER"); @@ -719,12 +920,10 @@ namespace ErsatzTV.Infrastructure.Migrations b.HasIndex("TelevisionEpisodesId"); - b.ToTable("SimpleMediaCollectionEpisodes"); + b.ToTable("SimpleMediaCollectionEpisode"); }); - modelBuilder.Entity( - "SimpleMediaCollectionTelevisionSeason", - b => + modelBuilder.Entity("SimpleMediaCollectionTelevisionSeason", b => { b.Property("SimpleMediaCollectionsId") .HasColumnType("INTEGER"); @@ -736,12 +935,10 @@ namespace ErsatzTV.Infrastructure.Migrations b.HasIndex("TelevisionSeasonsId"); - b.ToTable("SimpleMediaCollectionSeasons"); + b.ToTable("SimpleMediaCollectionSeason"); }); - modelBuilder.Entity( - "SimpleMediaCollectionTelevisionShow", - b => + modelBuilder.Entity("SimpleMediaCollectionTelevisionShow", b => { b.Property("SimpleMediaCollectionsId") .HasColumnType("INTEGER"); @@ -753,62 +950,103 @@ namespace ErsatzTV.Infrastructure.Migrations b.HasIndex("TelevisionShowsId"); - b.ToTable("SimpleMediaCollectionShows"); + b.ToTable("SimpleMediaCollectionShow"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalLibrary", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.Library"); + + b.ToTable("LocalLibrary"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexLibrary", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.Library"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("ShouldSyncItems") + .HasColumnType("INTEGER"); + + b.ToTable("PlexLibrary"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.SimpleMediaCollection", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.SimpleMediaCollection", b => { b.HasBaseType("ErsatzTV.Core.Domain.MediaCollection"); - b.ToTable("SimpleMediaCollections"); + b.ToTable("SimpleMediaCollection"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Episode", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("SeasonId") + .HasColumnType("INTEGER"); + + b.HasIndex("SeasonId"); + + b.ToTable("Episode"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.MovieMediaItem", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.Movie", b => { b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); b.Property("MetadataId") .HasColumnType("INTEGER"); - b.ToTable("Movies"); + b.ToTable("Movie"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.Season", b => { b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); - b.Property("SeasonId") + b.Property("SeasonNumber") .HasColumnType("INTEGER"); - b.HasIndex("SeasonId"); + b.Property("ShowId") + .HasColumnType("INTEGER"); - b.ToTable("TelevisionEpisodes"); + b.HasIndex("ShowId"); + + b.ToTable("Season"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.LocalMediaSource", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.Show", b => { - b.HasBaseType("ErsatzTV.Core.Domain.MediaSource"); + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); - b.Property("Folder") - .HasColumnType("TEXT"); + b.ToTable("Show"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaItem"); - b.Property("MediaType") + b.Property("SeasonId") .HasColumnType("INTEGER"); - b.ToTable("LocalMediaSources"); + b.HasIndex("SeasonId"); + + b.ToTable("TelevisionEpisode"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.MediaSource"); + + b.ToTable("LocalMediaSource"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.PlexMediaSource", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => { b.HasBaseType("ErsatzTV.Core.Domain.MediaSource"); @@ -818,12 +1056,13 @@ namespace ErsatzTV.Infrastructure.Migrations b.Property("ProductVersion") .HasColumnType("TEXT"); - b.ToTable("PlexMediaSources"); + b.Property("ServerName") + .HasColumnType("TEXT"); + + b.ToTable("PlexMediaSource"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.ProgramScheduleItemDuration", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", b => { b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); @@ -833,42 +1072,34 @@ namespace ErsatzTV.Infrastructure.Migrations b.Property("PlayoutDuration") .HasColumnType("TEXT"); - b.ToTable("ProgramScheduleDurationItems"); + b.ToTable("ProgramScheduleDurationItem"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.ProgramScheduleItemFlood", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", b => { b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); - b.ToTable("ProgramScheduleFloodItems"); + b.ToTable("ProgramScheduleFloodItem"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", b => { b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); b.Property("Count") .HasColumnType("INTEGER"); - b.ToTable("ProgramScheduleMultipleItems"); + b.ToTable("ProgramScheduleMultipleItem"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.ProgramScheduleItemOne", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemOne", b => { b.HasBaseType("ErsatzTV.Core.Domain.ProgramScheduleItem"); - b.ToTable("ProgramScheduleOneItems"); + b.ToTable("ProgramScheduleOneItem"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.LocalTelevisionShowSource", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalTelevisionShowSource", b => { b.HasBaseType("ErsatzTV.Core.Domain.TelevisionShowSource"); @@ -883,9 +1114,22 @@ namespace ErsatzTV.Infrastructure.Migrations b.HasDiscriminator().HasValue("LocalTelevisionShowSource"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.Channel", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMovie", b => + { + b.HasBaseType("ErsatzTV.Core.Domain.Movie"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("PartId") + .HasColumnType("INTEGER"); + + b.HasIndex("PartId"); + + b.ToTable("PlexMovie"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => { b.HasOne("ErsatzTV.Core.Domain.FFmpegProfile", "FFmpegProfile") .WithMany() @@ -896,9 +1140,37 @@ namespace ErsatzTV.Infrastructure.Migrations b.Navigation("FFmpegProfile"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.FFmpegProfile", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.CollectionItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.Collection", "Collection") + .WithMany("CollectionItems") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") + .WithMany("CollectionItems") + .HasForeignKey("MediaItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("MediaItem"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.EpisodeMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.Episode", "Episode") + .WithMany("EpisodeMetadata") + .HasForeignKey("EpisodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Episode"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.FFmpegProfile", b => { b.HasOne("ErsatzTV.Core.Domain.Resolution", "Resolution") .WithMany() @@ -909,20 +1181,48 @@ namespace ErsatzTV.Infrastructure.Migrations b.Navigation("Resolution"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.MediaItem", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => { - b.HasOne("ErsatzTV.Core.Domain.MediaSource", "Source") - .WithMany() + b.HasOne("ErsatzTV.Core.Domain.MediaSource", "MediaSource") + .WithMany("Libraries") .HasForeignKey("MediaSourceId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.OwnsOne( - "ErsatzTV.Core.Domain.MediaItemStatistics", - "Statistics", - b1 => + b.Navigation("MediaSource"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", "Library") + .WithMany("Paths") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaFile", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaVersion", "MediaVersion") + .WithMany("MediaFiles") + .HasForeignKey("MediaVersionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MediaVersion"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => + { + b.HasOne("ErsatzTV.Core.Domain.LibraryPath", "LibraryPath") + .WithMany("MediaItems") + .HasForeignKey("LibraryPathId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("ErsatzTV.Core.Domain.MediaItemStatistics", "Statistics", b1 => { b1.Property("MediaItemId") .HasColumnType("INTEGER"); @@ -956,22 +1256,33 @@ namespace ErsatzTV.Infrastructure.Migrations b1.HasKey("MediaItemId"); - b1.ToTable("MediaItems"); + b1.ToTable("MediaItem"); b1.WithOwner() .HasForeignKey("MediaItemId"); }); - b.Navigation("Source"); + b.Navigation("LibraryPath"); b.Navigation("Statistics"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.MovieMetadata", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaVersion", b => + { + b.HasOne("ErsatzTV.Core.Domain.Episode", null) + .WithMany("MediaVersions") + .HasForeignKey("EpisodeId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ErsatzTV.Core.Domain.Movie", null) + .WithMany("MediaVersions") + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMetadata", b => { - b.HasOne("ErsatzTV.Core.Domain.MovieMediaItem", "Movie") + b.HasOne("ErsatzTV.Core.Domain.Movie", "Movie") .WithOne("Metadata") .HasForeignKey("ErsatzTV.Core.Domain.MovieMetadata", "MovieId") .OnDelete(DeleteBehavior.Cascade) @@ -980,9 +1291,18 @@ namespace ErsatzTV.Infrastructure.Migrations b.Navigation("Movie"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.Playout", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.NewMovieMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.Movie", "Movie") + .WithMany("MovieMetadata") + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Movie"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => { b.HasOne("ErsatzTV.Core.Domain.Channel", "Channel") .WithMany("Playouts") @@ -996,10 +1316,7 @@ namespace ErsatzTV.Infrastructure.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.OwnsOne( - "ErsatzTV.Core.Domain.PlayoutAnchor", - "Anchor", - b1 => + b.OwnsOne("ErsatzTV.Core.Domain.PlayoutAnchor", "Anchor", b1 => { b1.Property("PlayoutId") .HasColumnType("INTEGER"); @@ -1014,7 +1331,7 @@ namespace ErsatzTV.Infrastructure.Migrations b1.HasIndex("NextScheduleItemId"); - b1.ToTable("Playouts"); + b1.ToTable("Playout"); b1.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", "NextScheduleItem") .WithMany() @@ -1035,9 +1352,7 @@ namespace ErsatzTV.Infrastructure.Migrations b.Navigation("ProgramSchedule"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.PlayoutItem", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutItem", b => { b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") .WithMany() @@ -1056,10 +1371,16 @@ namespace ErsatzTV.Infrastructure.Migrations b.Navigation("Playout"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor", b => { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") + .WithMany() + .HasForeignKey("MediaItemId"); + + b.HasOne("ErsatzTV.Core.Domain.Collection", "Collection") + .WithMany() + .HasForeignKey("NewCollectionId"); + b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout") .WithMany("ProgramScheduleAnchors") .HasForeignKey("PlayoutId") @@ -1072,10 +1393,7 @@ namespace ErsatzTV.Infrastructure.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.OwnsOne( - "ErsatzTV.Core.Domain.MediaCollectionEnumeratorState", - "EnumeratorState", - b1 => + b.OwnsOne("ErsatzTV.Core.Domain.MediaCollectionEnumeratorState", "EnumeratorState", b1 => { b1.Property("PlayoutProgramScheduleAnchorId") .HasColumnType("INTEGER"); @@ -1088,45 +1406,48 @@ namespace ErsatzTV.Infrastructure.Migrations b1.HasKey("PlayoutProgramScheduleAnchorId"); - b1.ToTable("PlayoutProgramScheduleItemAnchors"); + b1.ToTable("PlayoutProgramScheduleAnchor"); b1.WithOwner() .HasForeignKey("PlayoutProgramScheduleAnchorId"); }); + b.Navigation("Collection"); + b.Navigation("EnumeratorState"); + b.Navigation("MediaItem"); + b.Navigation("Playout"); b.Navigation("ProgramSchedule"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.PlexMediaSourceConnection", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexConnection", b => { - b.HasOne("ErsatzTV.Core.Domain.PlexMediaSource", null) + b.HasOne("ErsatzTV.Core.Domain.PlexMediaSource", "PlexMediaSource") .WithMany("Connections") - .HasForeignKey("PlexMediaSourceId"); - }); + .HasForeignKey("PlexMediaSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.PlexMediaSourceLibrary", - b => - { - b.HasOne("ErsatzTV.Core.Domain.PlexMediaSource", null) - .WithMany("Libraries") - .HasForeignKey("PlexMediaSourceId"); + b.Navigation("PlexMediaSource"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.ProgramScheduleItem", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItem", b => { + b.HasOne("ErsatzTV.Core.Domain.Collection", "Collection") + .WithMany() + .HasForeignKey("CollectionId"); + b.HasOne("ErsatzTV.Core.Domain.MediaCollection", "MediaCollection") .WithMany() .HasForeignKey("MediaCollectionId"); + b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem") + .WithMany() + .HasForeignKey("MediaItemId"); + b.HasOne("ErsatzTV.Core.Domain.ProgramSchedule", "ProgramSchedule") .WithMany("Items") .HasForeignKey("ProgramScheduleId") @@ -1141,8 +1462,12 @@ namespace ErsatzTV.Infrastructure.Migrations .WithMany() .HasForeignKey("TelevisionShowId"); + b.Navigation("Collection"); + b.Navigation("MediaCollection"); + b.Navigation("MediaItem"); + b.Navigation("ProgramSchedule"); b.Navigation("TelevisionSeason"); @@ -1150,9 +1475,18 @@ namespace ErsatzTV.Infrastructure.Migrations b.Navigation("TelevisionShow"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.ShowMetadata", b => + { + b.HasOne("ErsatzTV.Core.Domain.Show", "Show") + .WithMany("ShowMetadata") + .HasForeignKey("ShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMetadata", b => { b.HasOne("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", "TelevisionEpisode") .WithOne("Metadata") @@ -1163,9 +1497,7 @@ namespace ErsatzTV.Infrastructure.Migrations b.Navigation("TelevisionEpisode"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.TelevisionSeason", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => { b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") .WithMany("Seasons") @@ -1176,9 +1508,7 @@ namespace ErsatzTV.Infrastructure.Migrations b.Navigation("TelevisionShow"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.TelevisionShowMetadata", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowMetadata", b => { b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") .WithOne("Metadata") @@ -1189,9 +1519,7 @@ namespace ErsatzTV.Infrastructure.Migrations b.Navigation("TelevisionShow"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.TelevisionShowSource", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShowSource", b => { b.HasOne("ErsatzTV.Core.Domain.TelevisionShow", "TelevisionShow") .WithMany("Sources") @@ -1202,11 +1530,9 @@ namespace ErsatzTV.Infrastructure.Migrations b.Navigation("TelevisionShow"); }); - modelBuilder.Entity( - "MovieMediaItemSimpleMediaCollection", - b => + modelBuilder.Entity("MovieSimpleMediaCollection", b => { - b.HasOne("ErsatzTV.Core.Domain.MovieMediaItem", null) + b.HasOne("ErsatzTV.Core.Domain.Movie", null) .WithMany() .HasForeignKey("MoviesId") .OnDelete(DeleteBehavior.Cascade) @@ -1219,9 +1545,7 @@ namespace ErsatzTV.Infrastructure.Migrations .IsRequired(); }); - modelBuilder.Entity( - "SimpleMediaCollectionTelevisionEpisodeMediaItem", - b => + modelBuilder.Entity("SimpleMediaCollectionTelevisionEpisodeMediaItem", b => { b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) .WithMany() @@ -1236,9 +1560,7 @@ namespace ErsatzTV.Infrastructure.Migrations .IsRequired(); }); - modelBuilder.Entity( - "SimpleMediaCollectionTelevisionSeason", - b => + modelBuilder.Entity("SimpleMediaCollectionTelevisionSeason", b => { b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) .WithMany() @@ -1253,9 +1575,7 @@ namespace ErsatzTV.Infrastructure.Migrations .IsRequired(); }); - modelBuilder.Entity( - "SimpleMediaCollectionTelevisionShow", - b => + modelBuilder.Entity("SimpleMediaCollectionTelevisionShow", b => { b.HasOne("ErsatzTV.Core.Domain.SimpleMediaCollection", null) .WithMany() @@ -1270,9 +1590,25 @@ namespace ErsatzTV.Infrastructure.Migrations .IsRequired(); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.SimpleMediaCollection", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalLibrary", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.LocalLibrary", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexLibrary", b => + { + b.HasOne("ErsatzTV.Core.Domain.Library", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexLibrary", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.SimpleMediaCollection", b => { b.HasOne("ErsatzTV.Core.Domain.MediaCollection", null) .WithOne() @@ -1281,20 +1617,59 @@ namespace ErsatzTV.Infrastructure.Migrations .IsRequired(); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.MovieMediaItem", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.Episode", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.Episode", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.Season", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Movie", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.Movie", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Season", b => + { + b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.Season", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.Show", "Show") + .WithMany("Seasons") + .HasForeignKey("ShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Show", b => { b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) .WithOne() - .HasForeignKey("ErsatzTV.Core.Domain.MovieMediaItem", "Id") + .HasForeignKey("ErsatzTV.Core.Domain.Show", "Id") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => { b.HasOne("ErsatzTV.Core.Domain.MediaItem", null) .WithOne() @@ -1311,9 +1686,7 @@ namespace ErsatzTV.Infrastructure.Migrations b.Navigation("Season"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.LocalMediaSource", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalMediaSource", b => { b.HasOne("ErsatzTV.Core.Domain.MediaSource", null) .WithOne() @@ -1322,9 +1695,7 @@ namespace ErsatzTV.Infrastructure.Migrations .IsRequired(); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.PlexMediaSource", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => { b.HasOne("ErsatzTV.Core.Domain.MediaSource", null) .WithOne() @@ -1333,9 +1704,7 @@ namespace ErsatzTV.Infrastructure.Migrations .IsRequired(); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.ProgramScheduleItemDuration", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemDuration", b => { b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) .WithOne() @@ -1344,9 +1713,7 @@ namespace ErsatzTV.Infrastructure.Migrations .IsRequired(); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.ProgramScheduleItemFlood", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemFlood", b => { b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) .WithOne() @@ -1355,9 +1722,7 @@ namespace ErsatzTV.Infrastructure.Migrations .IsRequired(); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemMultiple", b => { b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) .WithOne() @@ -1366,9 +1731,7 @@ namespace ErsatzTV.Infrastructure.Migrations .IsRequired(); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.ProgramScheduleItemOne", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramScheduleItemOne", b => { b.HasOne("ErsatzTV.Core.Domain.ProgramScheduleItem", null) .WithOne() @@ -1377,9 +1740,7 @@ namespace ErsatzTV.Infrastructure.Migrations .IsRequired(); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.LocalTelevisionShowSource", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.LocalTelevisionShowSource", b => { b.HasOne("ErsatzTV.Core.Domain.LocalMediaSource", "MediaSource") .WithMany() @@ -1390,31 +1751,76 @@ namespace ErsatzTV.Infrastructure.Migrations b.Navigation("MediaSource"); }); - modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => { b.Navigation("Playouts"); }); + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMovie", b => + { + b.HasOne("ErsatzTV.Core.Domain.Movie", null) + .WithOne() + .HasForeignKey("ErsatzTV.Core.Domain.PlexMovie", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ErsatzTV.Core.Domain.PlexMediaItemPart", "Part") + .WithMany() + .HasForeignKey("PartId"); + + b.Navigation("Part"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Channel", b => + { + b.Navigation("Playouts"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Collection", b => + { + b.Navigation("CollectionItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => + { + b.Navigation("Paths"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.LibraryPath", b => + { + b.Navigation("MediaItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaItem", b => + { + b.Navigation("CollectionItems"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaSource", b => + { + b.Navigation("Libraries"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.MediaVersion", b => + { + b.Navigation("MediaFiles"); + }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.Playout", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.Playout", b => { b.Navigation("Items"); b.Navigation("ProgramScheduleAnchors"); }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.ProgramSchedule", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b => { b.Navigation("Items"); b.Navigation("Playouts"); }); - modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => { b.Navigation("Episodes"); }); + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionSeason", b => + { + b.Navigation("Episodes"); + }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.TelevisionShow", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionShow", b => { b.Navigation("Metadata"); @@ -1423,17 +1829,42 @@ namespace ErsatzTV.Infrastructure.Migrations b.Navigation("Sources"); }); - modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMediaItem", b => { b.Navigation("Metadata"); }); + modelBuilder.Entity("ErsatzTV.Core.Domain.Episode", b => + { + b.Navigation("EpisodeMetadata"); - modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => { b.Navigation("Metadata"); }); + b.Navigation("MediaVersions"); + }); - modelBuilder.Entity( - "ErsatzTV.Core.Domain.PlexMediaSource", - b => + modelBuilder.Entity("ErsatzTV.Core.Domain.Movie", b => { - b.Navigation("Connections"); + b.Navigation("MediaVersions"); - b.Navigation("Libraries"); + b.Navigation("Metadata"); + + b.Navigation("MovieMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Season", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.Show", b => + { + b.Navigation("Seasons"); + + b.Navigation("ShowMetadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.TelevisionEpisodeMediaItem", b => + { + b.Navigation("Metadata"); + }); + + modelBuilder.Entity("ErsatzTV.Core.Domain.PlexMediaSource", b => + { + b.Navigation("Connections"); }); #pragma warning restore 612, 618 } diff --git a/ErsatzTV.Infrastructure/Plex/IPlexServerApi.cs b/ErsatzTV.Infrastructure/Plex/IPlexServerApi.cs index edd2d1078..075c8fb5a 100644 --- a/ErsatzTV.Infrastructure/Plex/IPlexServerApi.cs +++ b/ErsatzTV.Infrastructure/Plex/IPlexServerApi.cs @@ -8,8 +8,15 @@ namespace ErsatzTV.Infrastructure.Plex public interface IPlexServerApi { [Get("/library/sections")] - public Task> GetLibraries( + public Task>> GetLibraries( [Query] [AliasAs("X-Plex-Token")] string token); + + [Get("/library/sections/{key}/all")] + public Task>> + GetLibrarySectionContents( + string key, + [Query] [AliasAs("X-Plex-Token")] + string token); } } diff --git a/ErsatzTV.Infrastructure/Plex/Models/PlexMediaContainerResponse.cs b/ErsatzTV.Infrastructure/Plex/Models/PlexMediaContainerResponse.cs index 37af14d68..c39eeca83 100644 --- a/ErsatzTV.Infrastructure/Plex/Models/PlexMediaContainerResponse.cs +++ b/ErsatzTV.Infrastructure/Plex/Models/PlexMediaContainerResponse.cs @@ -4,11 +4,16 @@ namespace ErsatzTV.Infrastructure.Plex.Models { public class PlexMediaContainerResponse { - public PlexMediaContainerContent MediaContainer { get; set; } + public T MediaContainer { get; set; } } - public class PlexMediaContainerContent + public class PlexMediaContainerDirectoryContent { public List Directory { get; set; } } + + public class PlexMediaContainerMetadataContent + { + public List Metadata { get; set; } + } } diff --git a/ErsatzTV.Infrastructure/Plex/Models/PlexMediaResponse.cs b/ErsatzTV.Infrastructure/Plex/Models/PlexMediaResponse.cs new file mode 100644 index 000000000..909482c10 --- /dev/null +++ b/ErsatzTV.Infrastructure/Plex/Models/PlexMediaResponse.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace ErsatzTV.Infrastructure.Plex.Models +{ + public class PlexMediaResponse + { + public int Id { get; set; } + public int Duration { get; set; } + public int Bitrate { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public double AspectRatio { get; set; } + public int AudioChannels { get; set; } + public string AudioCodec { get; set; } + public string VideoCodec { get; set; } + public string Container { get; set; } + public string VideoFrameRate { get; set; } + public List Part { get; set; } + } +} diff --git a/ErsatzTV.Infrastructure/Plex/Models/PlexMetadataResponse.cs b/ErsatzTV.Infrastructure/Plex/Models/PlexMetadataResponse.cs new file mode 100644 index 000000000..293505e36 --- /dev/null +++ b/ErsatzTV.Infrastructure/Plex/Models/PlexMetadataResponse.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace ErsatzTV.Infrastructure.Plex.Models +{ + public class PlexMetadataResponse + { + public string Key { get; set; } + public string Title { get; set; } + public string Summary { get; set; } + public int Year { get; set; } + public string Tagline { get; set; } + public string Thumb { get; set; } + public string Art { get; set; } + public string OriginallyAvailableAt { get; set; } + public int AddedAt { get; set; } + public int UpdatedAt { get; set; } + public List Media { get; set; } + } +} diff --git a/ErsatzTV.Infrastructure/Plex/Models/PlexPartResponse.cs b/ErsatzTV.Infrastructure/Plex/Models/PlexPartResponse.cs new file mode 100644 index 000000000..758de563a --- /dev/null +++ b/ErsatzTV.Infrastructure/Plex/Models/PlexPartResponse.cs @@ -0,0 +1,11 @@ +namespace ErsatzTV.Infrastructure.Plex.Models +{ + public class PlexPartResponse + { + public int Id { get; set; } + public string Key { get; set; } + public int Duration { get; set; } + public string File { get; set; } + public int Size { get; set; } + } +} diff --git a/ErsatzTV.Infrastructure/Plex/Models/PlexResourceConnection.cs b/ErsatzTV.Infrastructure/Plex/Models/PlexResourceConnection.cs index 4d494093b..6f1a8e1c4 100644 --- a/ErsatzTV.Infrastructure/Plex/Models/PlexResourceConnection.cs +++ b/ErsatzTV.Infrastructure/Plex/Models/PlexResourceConnection.cs @@ -4,8 +4,8 @@ { public string Protocol { get; set; } public string Address { get; set; } - public string Port { get; set; } + public int Port { get; set; } public string Uri { get; set; } - public string Local { get; set; } + public bool Local { get; set; } } } diff --git a/ErsatzTV.Infrastructure/Plex/PlexSecretStore.cs b/ErsatzTV.Infrastructure/Plex/PlexSecretStore.cs index 31844a29f..e811f5c10 100644 --- a/ErsatzTV.Infrastructure/Plex/PlexSecretStore.cs +++ b/ErsatzTV.Infrastructure/Plex/PlexSecretStore.cs @@ -64,7 +64,14 @@ namespace ErsatzTV.Infrastructure.Plex private static Task ReadSecrets() => File.ReadAllTextAsync(FileSystemLayout.PlexSecretsPath) .Map(JsonConvert.DeserializeObject) - .Map(s => Optional(s).IfNone(new PlexSecrets())); + .Map(s => Optional(s).IfNone(new PlexSecrets())) + .Map( + s => + { + s.ServerAuthTokens ??= new Dictionary(); + s.UserAuthTokens ??= new Dictionary(); + return s; + }); private static Task SaveSecrets(PlexSecrets plexSecrets) => Some(JsonConvert.SerializeObject(plexSecrets)).Match( diff --git a/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs b/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs index 90dc32008..25fcd3478 100644 --- a/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs +++ b/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs @@ -15,8 +15,8 @@ namespace ErsatzTV.Infrastructure.Plex { public class PlexServerApiClient : IPlexServerApiClient { - public async Task>> GetLibraries( - PlexMediaSourceConnection connection, + public async Task>> GetLibraries( + PlexConnection connection, PlexServerAuthToken token) { try @@ -36,23 +36,129 @@ namespace ErsatzTV.Infrastructure.Plex } } - private static Option Project(PlexLibraryResponse response) => + public async Task>> GetLibraryContents( + PlexLibrary library, + PlexConnection connection, + PlexServerAuthToken token) + { + try + { + IPlexServerApi service = RestService.For(connection.Uri); + return await service.GetLibrarySectionContents(library.Key, token.AuthToken) + .Map(r => r.MediaContainer.Metadata.Filter(m => m.Media.Count > 0 && m.Media[0].Part.Count > 0)) + .Map(list => list.Map(ProjectToMovie).ToList()); + } + catch (Exception ex) + { + return BaseError.New(ex.Message); + } + } + + private static Option Project(PlexLibraryResponse response) => response.Type switch { - "show" => new PlexMediaSourceLibrary + "show" => new PlexLibrary { Key = response.Key, Name = response.Title, - MediaType = MediaType.TvShow + MediaKind = LibraryMediaKind.Shows, + ShouldSyncItems = false }, - "movie" => new PlexMediaSourceLibrary + "movie" => new PlexLibrary { Key = response.Key, Name = response.Title, - MediaType = MediaType.Movie + MediaKind = LibraryMediaKind.Movies, + ShouldSyncItems = false }, // TODO: "artist" for music libraries _ => None }; + + private static PlexPartEntry Project(PlexPartResponse response) => + new() + { + Id = response.Id, + Key = response.Key, + Duration = response.Duration, + File = response.File, + Size = response.Size + }; + + private static PlexMediaEntry Project(PlexMediaResponse response) => + new() + { + Id = response.Id, + Duration = response.Duration, + Bitrate = response.Bitrate, + Width = response.Width, + Height = response.Height, + AspectRatio = response.AspectRatio, + AudioChannels = response.AudioChannels, + AudioCodec = response.AudioCodec, + VideoCodec = response.VideoCodec, + Container = response.Container, + VideoFrameRate = response.VideoFrameRate, + Part = response.Part.Map(Project).ToList() + }; + + private static PlexMetadataEntry Project(PlexMetadataResponse response) => + new() + { + Key = response.Key, + Title = response.Title, + Summary = response.Summary, + Year = response.Year, + Tagline = response.Tagline, + Thumb = response.Thumb, + Art = response.Art, + AddedAt = response.AddedAt, + UpdatedAt = response.UpdatedAt, + Media = response.Media.Map(Project).ToList() + }; + + private static PlexMovie ProjectToMovie(PlexMetadataResponse response) + { + PlexMediaResponse media = response.Media.Head(); + PlexPartResponse part = media.Part.Head(); + DateTime lastWriteTime = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime; + + var movie = new PlexMovie + { + Key = response.Key, + Poster = response.Thumb, + LastWriteTime = lastWriteTime, + PosterLastWriteTime = lastWriteTime, + Metadata = new MovieMetadata + { + Title = response.Title, + Plot = response.Summary, + Year = response.Year, + Tagline = response.Tagline, + LastWriteTime = lastWriteTime + }, + Statistics = new MediaItemStatistics + { + Duration = TimeSpan.FromMilliseconds(media.Duration), + Width = media.Width, + Height = media.Height, + // TODO: aspect ratio + AudioCodec = media.AudioCodec, + VideoCodec = media.VideoCodec, + LastWriteTime = lastWriteTime + }, + // TODO: multi-part movies? + Part = new PlexMediaItemPart + { + PlexId = part.Id, + Key = part.Key, + File = part.File, + Duration = part.Duration, + Size = part.Size + } + }; + + return movie; + } } } diff --git a/ErsatzTV.Infrastructure/Plex/PlexTvApiClient.cs b/ErsatzTV.Infrastructure/Plex/PlexTvApiClient.cs index 0ab5103bf..7f5f8ec1e 100644 --- a/ErsatzTV.Infrastructure/Plex/PlexTvApiClient.cs +++ b/ErsatzTV.Infrastructure/Plex/PlexTvApiClient.cs @@ -49,11 +49,11 @@ namespace ErsatzTV.Infrastructure.Plex var source = new PlexMediaSource { - Name = resource.Name, + ServerName = resource.Name, ProductVersion = resource.ProductVersion, ClientIdentifier = resource.ClientIdentifier, Connections = resource.Connections - .Map(c => new PlexMediaSourceConnection { Uri = c.Uri }).ToList() + .Map(c => new PlexConnection { Uri = c.Uri }).ToList() }; return source; diff --git a/ErsatzTV/Pages/LocalMediaSourceEditor.razor b/ErsatzTV/Pages/LocalMediaSourceEditor.razor index f4edf64a7..bf61d6bda 100644 --- a/ErsatzTV/Pages/LocalMediaSourceEditor.razor +++ b/ErsatzTV/Pages/LocalMediaSourceEditor.razor @@ -1,99 +1,99 @@ -@page "/media/sources/local/add" -@using ErsatzTV.Application.MediaSources.Commands -@using ErsatzTV.Application.MediaSources -@inject NavigationManager NavigationManager -@inject ILogger Logger -@inject ISnackbar Snackbar -@inject IMediator Mediator -@inject IEntityLocker Locker -@inject ChannelWriter Channel - -
- Add Local Media Source - - - - - - - @foreach (MediaType mediaType in new[] { MediaType.TvShow, MediaType.Movie }) - { - @mediaType - } - - @* TODO: replace this with a folder picker *@ - - - - - Add Local Media Source - - - - -
- -@code { - - [Parameter] - public int Id { get; set; } - - private readonly LocalMediaSourceEditViewModel _model = new(); - private EditContext _editContext; - private ValidationMessageStore _messageStore; - - private LocalMediaSource _mediaSource; - - protected override Task OnParametersSetAsync() - { - _mediaSource = new LocalMediaSource - { - MediaType = MediaType.TvShow - }; - - _model.MediaType = _mediaSource.MediaType; - _model.Folder = _mediaSource.Folder; - - return Task.CompletedTask; - } - - protected override void OnInitialized() - { - _editContext = new EditContext(_model); - _messageStore = new ValidationMessageStore(_editContext); - } - - private async Task HandleSubmitAsync() - { - _messageStore.Clear(); - if (_editContext.Validate()) - { - var command = new CreateLocalMediaSource( - Convert.ToBase64String(Guid.NewGuid().ToByteArray()) - .TrimEnd('=') - .Replace("/", "_") - .Replace("+", "-"), - _model.MediaType, - _model.Folder); - Either result = await Mediator.Send(command); - await result.Match( - Left: error => - { - Snackbar.Add(error.Value, Severity.Error); - Logger.LogError("Unexpected error saving simple media collection: {Error}", error.Value); - return Task.CompletedTask; - }, - Right: async vm => - { - if (Locker.LockMediaSource(vm.Id)) - { - await Channel.WriteAsync(new ForceScanLocalMediaSource(vm.Id)); - NavigationManager.NavigateTo("/media/sources"); - } - }); - } - } - - - -} \ No newline at end of file +@* @page "/media/sources/local/add" *@ +@* @using ErsatzTV.Application.MediaSources.Commands *@ +@* @using ErsatzTV.Application.MediaSources *@ +@* @inject NavigationManager NavigationManager *@ +@* @inject ILogger Logger *@ +@* @inject ISnackbar Snackbar *@ +@* @inject IMediator Mediator *@ +@* @inject IEntityLocker Locker *@ +@* @inject ChannelWriter Channel *@ +@* *@ +@*
*@ +@* Add Local Media Source *@ +@* *@ +@* *@ +@* *@ +@* *@ +@* *@ +@* *@ +@* @foreach (MediaType mediaType in new[] { MediaType.TvShow, MediaType.Movie }) *@ +@* { *@ +@* @mediaType *@ +@* } *@ +@* *@ +@* $1$ TODO: replace this with a folder picker #1# *@ +@* *@ +@* *@ +@* *@ +@* *@ +@* Add Local Media Source *@ +@* *@ +@* *@ +@* *@ +@* *@ +@*
*@ +@* *@ +@* @code { *@ +@* *@ +@* [Parameter] *@ +@* public int Id { get; set; } *@ +@* *@ +@* private readonly LocalMediaSourceEditViewModel _model = new(); *@ +@* private EditContext _editContext; *@ +@* private ValidationMessageStore _messageStore; *@ +@* *@ +@* private LocalMediaSource _mediaSource; *@ +@* *@ +@* protected override Task OnParametersSetAsync() *@ +@* { *@ +@* _mediaSource = new LocalMediaSource *@ +@* { *@ +@* MediaType = MediaType.TvShow *@ +@* }; *@ +@* *@ +@* _model.MediaType = _mediaSource.MediaType; *@ +@* _model.Folder = _mediaSource.Folder; *@ +@* *@ +@* return Task.CompletedTask; *@ +@* } *@ +@* *@ +@* protected override void OnInitialized() *@ +@* { *@ +@* _editContext = new EditContext(_model); *@ +@* _messageStore = new ValidationMessageStore(_editContext); *@ +@* } *@ +@* *@ +@* private async Task HandleSubmitAsync() *@ +@* { *@ +@* _messageStore.Clear(); *@ +@* if (_editContext.Validate()) *@ +@* { *@ +@* var command = new CreateLocalMediaSource( *@ +@* Convert.ToBase64String(Guid.NewGuid().ToByteArray()) *@ +@* .TrimEnd('=') *@ +@* .Replace("/", "_") *@ +@* .Replace("+", "-"), *@ +@* _model.MediaType, *@ +@* _model.Folder); *@ +@* Either result = await Mediator.Send(command); *@ +@* await result.Match( *@ +@* Left: error => *@ +@* { *@ +@* Snackbar.Add(error.Value, Severity.Error); *@ +@* Logger.LogError("Unexpected error saving simple media collection: {Error}", error.Value); *@ +@* return Task.CompletedTask; *@ +@* }, *@ +@* Right: async vm => *@ +@* { *@ +@* if (Locker.LockLibrary(vm.Id)) *@ +@* { *@ +@* await Channel.WriteAsync(new ForceScanLocalLibrary(vm.Id)); *@ +@* NavigationManager.NavigateTo("/media/sources"); *@ +@* } *@ +@* }); *@ +@* } *@ +@* } *@ +@* *@ +@* *@ +@* *@ +@* } *@ \ No newline at end of file diff --git a/ErsatzTV/Pages/MediaSources.razor b/ErsatzTV/Pages/MediaSources.razor index 0cdbee151..dc1216f59 100644 --- a/ErsatzTV/Pages/MediaSources.razor +++ b/ErsatzTV/Pages/MediaSources.razor @@ -1,16 +1,25 @@ @page "/media/sources" +@page "/media/sources/{PanelIndex:int}" +@inject NavigationManager NavigationManager - - + + - @* *@ - @* *@ - @* *@ + + + @code { + [Parameter] + public int PanelIndex { get; set; } + + private MudTabs _tabs; + + protected override void OnAfterRender(bool firstRender) => _tabs.ActivatePanel(PanelIndex); + } \ No newline at end of file diff --git a/ErsatzTV/Pages/MovieList.razor b/ErsatzTV/Pages/MovieList.razor index add940841..74d134695 100644 --- a/ErsatzTV/Pages/MovieList.razor +++ b/ErsatzTV/Pages/MovieList.razor @@ -1,20 +1,22 @@ @page "/media/movies" +@page "/media/movies/page/{PageNumber:int}" @using ErsatzTV.Application.MediaCards @using ErsatzTV.Application.MediaCards.Queries @inject IMediator Mediator +@inject NavigationManager NavigationManager + OnClick="@PrevPage" + Disabled="@(PageNumber <= 1)"> - @Math.Min((_pageNumber - 1) * PageSize + 1, _data.Count)-@Math.Min(_data.Count, _pageNumber * PageSize) of @_data.Count + @Math.Min((PageNumber - 1) * PageSize + 1, _data.Count)-@Math.Min(_data.Count, PageNumber * PageSize) of @_data.Count + OnClick="@NextPage" Disabled="@(PageNumber * PageSize >= _data.Count)"> @@ -22,33 +24,33 @@ @foreach (MovieCardViewModel card in _data.Cards.Where(d => !string.IsNullOrWhiteSpace(d.Title))) { - + } @code { - private int PageSize => 100; - private int _pageNumber = 1; + private static int PageSize => 100; + + [Parameter] + public int PageNumber { get; set; } private MovieCardResultsViewModel _data; - protected override Task OnParametersSetAsync() => RefreshData(); + protected override Task OnParametersSetAsync() + { + if (PageNumber == 0) + { + PageNumber = 1; + } + + return RefreshData(); + } private async Task RefreshData() => - _data = await Mediator.Send(new GetMovieCards(_pageNumber, PageSize)); + _data = await Mediator.Send(new GetMovieCards(PageNumber, PageSize)); - private async Task PrevPage() - { - _pageNumber -= 1; - await RefreshData(); - } + private void PrevPage() => NavigationManager.NavigateTo($"/media/movies/page/{PageNumber - 1}"); - private async Task NextPage() - { - _pageNumber += 1; - await RefreshData(); - } + private void NextPage() => NavigationManager.NavigateTo($"/media/movies/page/{PageNumber + 1}"); } \ No newline at end of file diff --git a/ErsatzTV/Pages/PlexLibrariesEditor.razor b/ErsatzTV/Pages/PlexLibrariesEditor.razor new file mode 100644 index 000000000..fd20f350d --- /dev/null +++ b/ErsatzTV/Pages/PlexLibrariesEditor.razor @@ -0,0 +1,98 @@ +@page "/media/sources/plex/{Id:int}/libraries" +@using ErsatzTV.Application.Plex +@using ErsatzTV.Application.Plex.Commands +@using ErsatzTV.Application.Plex.Queries +@inject IMediator Mediator +@inject NavigationManager NavigationManager +@inject ILogger Logger +@inject ISnackbar Snackbar + + + + + @_source.Name Libraries + + + + + + + + Name + Media Type + Synchronize + + + @context.Name + @context.MediaKind + +
+ @if (context.ShouldSyncItems) + { + Yes + } + else + { + No + } +
+
+
+
+ + Save Changes + +
+ +@code { + + [Parameter] + public int Id { get; set; } + + private PlexMediaSourceViewModel _source; + private List _libraries; + + protected override Task OnParametersSetAsync() => LoadData(); + + private async Task LoadData() + { + Option maybeSource = await Mediator.Send(new GetPlexMediaSourceById(Id)); + await maybeSource.Match( + async source => + { + _source = source; + _libraries = await Mediator.Send(new GetPlexLibrariesBySourceId(Id)) + .Map(list => list.Map(ProjectToEditViewModel).ToList()); + }, + () => + { + NavigationManager.NavigateTo("404"); + return Task.CompletedTask; + }); + } + + private PlexMediaSourceLibraryEditViewModel ProjectToEditViewModel(PlexLibraryViewModel library) => new() + { + Id = library.Id, + Name = library.Name, + MediaKind = library.MediaKind, + ShouldSyncItems = library.ShouldSyncItems + }; + + private async Task SaveChanges() + { + var request = new UpdatePlexLibraryPreferences( + _libraries.Map(l => new PlexLibraryPreference(l.Id, l.ShouldSyncItems)).ToList()); + + Seq errorMessages = await Mediator.Send(request).Map(e => e.LeftToSeq()); + + errorMessages.HeadOrNone().Match( + error => + { + Snackbar.Add($"Unexpected error saving plex libraries: {error.Value}", Severity.Error); + Logger.LogError("Unexpected error saving plex libraries: {Error}", error.Value); + }, + () => NavigationManager.NavigateTo("/media/sources/1")); + } + +} \ No newline at end of file diff --git a/ErsatzTV/Services/PlexService.cs b/ErsatzTV/Services/PlexService.cs index 7c1140027..afbd61999 100644 --- a/ErsatzTV/Services/PlexService.cs +++ b/ErsatzTV/Services/PlexService.cs @@ -6,7 +6,7 @@ using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using ErsatzTV.Application; -using ErsatzTV.Application.MediaSources.Commands; +using ErsatzTV.Application.Plex.Commands; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using LanguageExt; diff --git a/ErsatzTV/Services/SchedulerService.cs b/ErsatzTV/Services/SchedulerService.cs index 6d74f23d4..371a00039 100644 --- a/ErsatzTV/Services/SchedulerService.cs +++ b/ErsatzTV/Services/SchedulerService.cs @@ -1,11 +1,13 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using ErsatzTV.Application; using ErsatzTV.Application.MediaSources.Commands; using ErsatzTV.Application.Playouts.Commands; +using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Locking; using ErsatzTV.Infrastructure.Data; using Microsoft.EntityFrameworkCore; @@ -73,19 +75,35 @@ namespace ErsatzTV.Services using IServiceScope scope = _serviceScopeFactory.CreateScope(); TvContext dbContext = scope.ServiceProvider.GetRequiredService(); - List localMediaSourceIds = await dbContext.LocalMediaSources - .Map(ms => ms.Id) + List localLibraryIds = await dbContext.LocalMediaSources + .SelectMany(ms => ms.Libraries) + .Map(l => l.Id) .ToListAsync(cancellationToken); - foreach (int mediaSourceId in localMediaSourceIds) + foreach (int libraryId in localLibraryIds) { - if (_entityLocker.LockMediaSource(mediaSourceId)) + if (_entityLocker.LockLibrary(libraryId)) { await _channel.WriteAsync( - new ScanLocalMediaSourceIfNeeded(mediaSourceId), + new ScanLocalLibraryIfNeeded(libraryId), cancellationToken); } } + + List plexLibraries = await dbContext.PlexLibraries + .Filter(l => l.ShouldSyncItems) + .ToListAsync(cancellationToken); + + foreach (PlexLibrary library in plexLibraries) + { + // TODO: this locking won't work... + // if (_entityLocker.LockMediaSource(library.PlexMediaSourceId)) + // { + // await _channel.WriteAsync( + // new SynchronizePlexLibraryByIdIfNeeded(library.PlexMediaSourceId, library.Id), + // cancellationToken); + // } + } } } } diff --git a/ErsatzTV/Services/WorkerService.cs b/ErsatzTV/Services/WorkerService.cs index 809f41a52..57eaa870a 100644 --- a/ErsatzTV/Services/WorkerService.cs +++ b/ErsatzTV/Services/WorkerService.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using ErsatzTV.Application; using ErsatzTV.Application.MediaSources.Commands; using ErsatzTV.Application.Playouts.Commands; +using ErsatzTV.Application.Plex.Commands; using ErsatzTV.Core; using LanguageExt; using MediatR; @@ -55,17 +56,30 @@ namespace ErsatzTV.Services buildPlayout.PlayoutId, error.Value)); break; - case IScanLocalMediaSource scanLocalMediaSource: + case IScanLocalLibrary scanLocalLibrary: Either scanResult = await mediator.Send( - scanLocalMediaSource, + scanLocalLibrary, cancellationToken); scanResult.BiIter( name => _logger.LogDebug( - "Done scanning local media source {MediaSource}", + "Done scanning local library {Library}", name), error => _logger.LogWarning( - "Unable to scan local media source {MediaSourceId}: {Error}", - scanLocalMediaSource.MediaSourceId, + "Unable to scan local library {LibraryId}: {Error}", + scanLocalLibrary.LibraryId, + error.Value)); + break; + case ISynchronizePlexLibraryById synchronizePlexLibraryById: + Either result = await mediator.Send( + synchronizePlexLibraryById, + cancellationToken); + result.BiIter( + name => _logger.LogDebug( + "Done synchronizing plex library {Name}", + name), + error => _logger.LogWarning( + "Unable to synchronize plex library {LibraryId}: {Error}", + synchronizePlexLibraryById.PlexLibraryId, error.Value)); break; } diff --git a/ErsatzTV/Shared/LocalMediaSources.razor b/ErsatzTV/Shared/LocalMediaSources.razor index bdd88d1d1..ed6d2979f 100644 --- a/ErsatzTV/Shared/LocalMediaSources.razor +++ b/ErsatzTV/Shared/LocalMediaSources.razor @@ -23,7 +23,7 @@ @context.Folder
- @if (Locker.IsMediaSourceLocked(context.Id)) + @if (Locker.IsLibraryLocked(context.Id)) {
@@ -33,14 +33,14 @@ { } @@ -48,15 +48,15 @@ - - Add Local Media Source - +@* *@ +@* Add Local Media Source *@ +@* *@ @code { private IList _mediaSources; protected override void OnInitialized() => - Locker.OnMediaSourceChanged += LockChanged; + Locker.OnLibraryChanged += LockChanged; protected override async Task OnParametersSetAsync() => await LoadMediaSources(); @@ -87,9 +87,9 @@ private async Task ScanMediaSource(LocalMediaSourceViewModel mediaSource) { - if (Locker.LockMediaSource(mediaSource.Id)) + if (Locker.LockLibrary(mediaSource.Id)) { - await Channel.WriteAsync(new ForceScanLocalMediaSource(mediaSource.Id)); + await Channel.WriteAsync(new ForceScanLocalLibrary(mediaSource.Id)); StateHasChanged(); } } @@ -97,6 +97,6 @@ private void LockChanged(object sender, EventArgs e) => InvokeAsync(StateHasChanged); - void IDisposable.Dispose() => Locker.OnMediaSourceChanged -= LockChanged; + void IDisposable.Dispose() => Locker.OnLibraryChanged -= LockChanged; } \ No newline at end of file diff --git a/ErsatzTV/Shared/PlexMediaSources.razor b/ErsatzTV/Shared/PlexMediaSources.razor index 40e713791..cff646462 100644 --- a/ErsatzTV/Shared/PlexMediaSources.razor +++ b/ErsatzTV/Shared/PlexMediaSources.razor @@ -1,6 +1,6 @@ -@using ErsatzTV.Application.MediaSources -@using ErsatzTV.Application.MediaSources.Commands -@using ErsatzTV.Application.MediaSources.Queries +@using ErsatzTV.Application.Plex +@using ErsatzTV.Application.Plex.Commands +@using ErsatzTV.Application.Plex.Queries @inject IDialogService Dialog @inject IMediator Mediator @inject ISnackbar Snackbar @@ -25,7 +25,7 @@ @context.Name @context.Address - + Edit Libraries diff --git a/ErsatzTV/Startup.cs b/ErsatzTV/Startup.cs index 9e75db1e2..4e481882d 100644 --- a/ErsatzTV/Startup.cs +++ b/ErsatzTV/Startup.cs @@ -16,6 +16,7 @@ using ErsatzTV.Core.Interfaces.Plex; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Metadata; +using ErsatzTV.Core.Plex; using ErsatzTV.Core.Scheduling; using ErsatzTV.Formatters; using ErsatzTV.Infrastructure.Data; @@ -113,7 +114,7 @@ namespace ErsatzTV // Log.Logger.Information("XMLTV is at {XmltvPath}", xmltvPath); var connectionString = $"Data Source={FileSystemLayout.DatabasePath}"; - + services.AddDbContext( options => options.UseSqlite( connectionString, @@ -121,15 +122,26 @@ namespace ErsatzTV { o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); o.MigrationsAssembly("ErsatzTV.Infrastructure"); - })); + }), + ServiceLifetime.Scoped, + ServiceLifetime.Singleton); - services.AddDbContext(options => options.UseSqlite(connectionString)); + services.AddDbContextFactory( + options => options.UseSqlite( + connectionString, + o => + { + o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); + o.MigrationsAssembly("ErsatzTV.Infrastructure"); + })); services.AddTransient(_ => new SqliteConnection(connectionString)); SqlMapper.AddTypeHandler(new DateTimeOffsetHandler()); SqlMapper.AddTypeHandler(new GuidHandler()); SqlMapper.AddTypeHandler(new TimeSpanHandler()); + services.AddDbContext(options => options.UseSqlite(connectionString)); + services.AddMediatR(typeof(GetAllChannels).Assembly); services.AddRefitClient() @@ -182,6 +194,7 @@ namespace ErsatzTV services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -191,8 +204,9 @@ namespace ErsatzTV services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); - services.AddHostedService(); + // services.AddHostedService(); services.AddHostedService(); services.AddHostedService(); services.AddHostedService(); diff --git a/ErsatzTV/ViewModels/LocalMediaSourceEditViewModel.cs b/ErsatzTV/ViewModels/LocalMediaSourceEditViewModel.cs index 97a6fe572..e093179c9 100644 --- a/ErsatzTV/ViewModels/LocalMediaSourceEditViewModel.cs +++ b/ErsatzTV/ViewModels/LocalMediaSourceEditViewModel.cs @@ -1,10 +1,7 @@ -using ErsatzTV.Core.Domain; - -namespace ErsatzTV.ViewModels +namespace ErsatzTV.ViewModels { public class LocalMediaSourceEditViewModel { - public MediaType MediaType { get; set; } public string Folder { get; set; } } } diff --git a/ErsatzTV/ViewModels/PlexMediaSourceLibraryEditViewModel.cs b/ErsatzTV/ViewModels/PlexMediaSourceLibraryEditViewModel.cs new file mode 100644 index 000000000..45ba27e68 --- /dev/null +++ b/ErsatzTV/ViewModels/PlexMediaSourceLibraryEditViewModel.cs @@ -0,0 +1,12 @@ +using ErsatzTV.Core.Domain; + +namespace ErsatzTV.ViewModels +{ + public class PlexMediaSourceLibraryEditViewModel + { + public int Id { get; set; } + public string Name { get; set; } + public LibraryMediaKind MediaKind { get; init; } + public bool ShouldSyncItems { get; set; } + } +}