Browse Source

start to remove old television classes

pull/31/head
Jason Dove 6 years ago
parent
commit
15d22c4641
  1. 2
      ErsatzTV.Application/MediaCards/CollectionCardResultsViewModel.cs
  2. 57
      ErsatzTV.Application/MediaCards/Mapper.cs
  3. 9
      ErsatzTV.Application/MediaCards/Queries/GetCollectionCards.cs
  4. 26
      ErsatzTV.Application/MediaCards/Queries/GetCollectionCardsHandler.cs
  5. 9
      ErsatzTV.Application/MediaCards/Queries/GetSimpleMediaCollectionCards.cs
  6. 26
      ErsatzTV.Application/MediaCards/Queries/GetSimpleMediaCollectionCardsHandler.cs
  7. 7
      ErsatzTV.Application/MediaCollections/Commands/AddEpisodeToCollection.cs
  8. 67
      ErsatzTV.Application/MediaCollections/Commands/AddEpisodeToCollectionHandler.cs
  9. 7
      ErsatzTV.Application/MediaCollections/Commands/AddMovieCollection.cs
  10. 30
      ErsatzTV.Application/MediaCollections/Commands/AddMovieToCollectionHandler.cs
  11. 8
      ErsatzTV.Application/MediaCollections/Commands/AddMovieToSimpleMediaCollection.cs
  12. 7
      ErsatzTV.Application/MediaCollections/Commands/AddSeasonToCollection.cs
  13. 67
      ErsatzTV.Application/MediaCollections/Commands/AddSeasonToCollectionHandler.cs
  14. 7
      ErsatzTV.Application/MediaCollections/Commands/AddShowToCollection.cs
  15. 64
      ErsatzTV.Application/MediaCollections/Commands/AddShowToCollectionHandler.cs
  16. 8
      ErsatzTV.Application/MediaCollections/Commands/AddTelevisionEpisodeToSimpleMediaCollection.cs
  17. 68
      ErsatzTV.Application/MediaCollections/Commands/AddTelevisionEpisodeToSimpleMediaCollectionHandler.cs
  18. 8
      ErsatzTV.Application/MediaCollections/Commands/AddTelevisionSeasonToSimpleMediaCollection.cs
  19. 68
      ErsatzTV.Application/MediaCollections/Commands/AddTelevisionSeasonToSimpleMediaCollectionHandler.cs
  20. 8
      ErsatzTV.Application/MediaCollections/Commands/AddTelevisionShowToSimpleMediaCollection.cs
  21. 67
      ErsatzTV.Application/MediaCollections/Commands/AddTelevisionShowToSimpleMediaCollectionHandler.cs
  22. 8
      ErsatzTV.Application/MediaCollections/Commands/CreateCollection.cs
  23. 23
      ErsatzTV.Application/MediaCollections/Commands/CreateCollectionHandler.cs
  24. 9
      ErsatzTV.Application/MediaCollections/Commands/CreateSimpleMediaCollection.cs
  25. 2
      ErsatzTV.Application/MediaCollections/Commands/DeleteCollection.cs
  26. 17
      ErsatzTV.Application/MediaCollections/Commands/DeleteCollectionHandler.cs
  27. 11
      ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromCollection.cs
  28. 51
      ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromCollectionHandler.cs
  29. 15
      ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromSimpleMediaCollection.cs
  30. 74
      ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromSimpleMediaCollectionHandler.cs
  31. 8
      ErsatzTV.Application/MediaCollections/Commands/UpdateCollection.cs
  32. 45
      ErsatzTV.Application/MediaCollections/Commands/UpdateCollectionHandler.cs
  33. 8
      ErsatzTV.Application/MediaCollections/Commands/UpdateSimpleMediaCollection.cs
  34. 47
      ErsatzTV.Application/MediaCollections/Commands/UpdateSimpleMediaCollectionHandler.cs
  35. 4
      ErsatzTV.Application/MediaCollections/Mapper.cs
  36. 2
      ErsatzTV.Application/MediaCollections/Queries/GetAllCollections.cs
  37. 6
      ErsatzTV.Application/MediaCollections/Queries/GetAllCollectionsHandler.cs
  38. 7
      ErsatzTV.Application/MediaCollections/Queries/GetAllSimpleMediaCollections.cs
  39. 25
      ErsatzTV.Application/MediaCollections/Queries/GetAllSimpleMediaCollectionsHandler.cs
  40. 7
      ErsatzTV.Application/MediaCollections/Queries/GetCollectionById.cs
  41. 8
      ErsatzTV.Application/MediaCollections/Queries/GetCollectionByIdHandler.cs
  42. 2
      ErsatzTV.Application/MediaCollections/Queries/GetCollectionItems.cs
  43. 8
      ErsatzTV.Application/MediaCollections/Queries/GetCollectionItemsHandler.cs
  44. 7
      ErsatzTV.Application/MediaCollections/Queries/GetSimpleMediaCollectionById.cs
  45. 10
      ErsatzTV.Application/MediaItems/Mapper.cs
  46. 6
      ErsatzTV.Application/Playouts/Mapper.cs
  47. 5
      ErsatzTV.Application/ProgramSchedules/Commands/AddProgramScheduleItem.cs
  48. 5
      ErsatzTV.Application/ProgramSchedules/Commands/IProgramScheduleItemRequest.cs
  49. 32
      ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs
  50. 5
      ErsatzTV.Application/ProgramSchedules/Commands/ReplaceProgramScheduleItems.cs
  51. 48
      ErsatzTV.Application/ProgramSchedules/Mapper.cs
  52. 4
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemDurationViewModel.cs
  53. 4
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemFloodViewModel.cs
  54. 4
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemMultipleViewModel.cs
  55. 4
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemOneViewModel.cs
  56. 4
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemViewModel.cs
  57. 33
      ErsatzTV.Application/Television/Mapper.cs
  58. 31
      ErsatzTV.Core.Tests/Fakes/FakeMediaCollectionRepository.cs
  59. 58
      ErsatzTV.Core.Tests/Fakes/FakeTelevisionRepository.cs
  60. 9
      ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs
  61. 15
      ErsatzTV.Core.Tests/Scheduling/ChronologicalContentTests.cs
  62. 106
      ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs
  63. 15
      ErsatzTV.Core.Tests/Scheduling/RandomizedContentTests.cs
  64. 15
      ErsatzTV.Core.Tests/Scheduling/ShuffledContentTests.cs
  65. 2
      ErsatzTV.Core/Domain/CollectionEnumeratorState.cs
  66. 9
      ErsatzTV.Core/Domain/LocalTelevisionShowSource.cs
  67. 10
      ErsatzTV.Core/Domain/MediaCollection.cs
  68. 4
      ErsatzTV.Core/Domain/MediaItem/Movie.cs
  69. 2
      ErsatzTV.Core/Domain/PlayoutProgramScheduleAnchor.cs
  70. 10
      ErsatzTV.Core/Domain/ProgramScheduleItem.cs
  71. 12
      ErsatzTV.Core/Domain/SimpleMediaCollection.cs
  72. 12
      ErsatzTV.Core/Domain/TelevisionEpisodeMediaItem.cs
  73. 15
      ErsatzTV.Core/Domain/TelevisionEpisodeMetadata.cs
  74. 19
      ErsatzTV.Core/Domain/TelevisionSeason.cs
  75. 16
      ErsatzTV.Core/Domain/TelevisionShow.cs
  76. 21
      ErsatzTV.Core/Domain/TelevisionShowMetadata.cs
  77. 11
      ErsatzTV.Core/Domain/TelevisionShowSource.cs
  78. 2
      ErsatzTV.Core/Interfaces/Metadata/IFallbackMetadataProvider.cs
  79. 6
      ErsatzTV.Core/Interfaces/Metadata/ILocalMetadataProvider.cs
  80. 11
      ErsatzTV.Core/Interfaces/Repositories/ICollectionRepository.cs
  81. 17
      ErsatzTV.Core/Interfaces/Repositories/IMediaCollectionRepository.cs
  82. 42
      ErsatzTV.Core/Interfaces/Repositories/ITelevisionRepository.cs
  83. 2
      ErsatzTV.Core/Interfaces/Scheduling/IMediaCollectionEnumerator.cs
  84. 10
      ErsatzTV.Core/Iptv/ChannelGuide.cs
  85. 27
      ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs
  86. 109
      ErsatzTV.Core/Metadata/LocalMetadataProvider.cs
  87. 88
      ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs
  88. 22
      ErsatzTV.Core/Scheduling/ChronologicalMediaCollectionEnumerator.cs
  89. 20
      ErsatzTV.Core/Scheduling/PlayoutBuilder.cs
  90. 6
      ErsatzTV.Core/Scheduling/RandomizedMediaCollectionEnumerator.cs
  91. 6
      ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs
  92. 16
      ErsatzTV.Infrastructure/Data/Configurations/MediaCollectionConfiguration.cs
  93. 15
      ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemConfiguration.cs
  94. 30
      ErsatzTV.Infrastructure/Data/Configurations/SimpleMediaCollectionConfiguration.cs
  95. 19
      ErsatzTV.Infrastructure/Data/Configurations/TelevisionEpisodeMediaItemConfiguration.cs
  96. 12
      ErsatzTV.Infrastructure/Data/Configurations/TelevisionEpisodeMetadataConfiguration.cs
  97. 19
      ErsatzTV.Infrastructure/Data/Configurations/TelevisionSeasonConfiguration.cs
  98. 27
      ErsatzTV.Infrastructure/Data/Configurations/TelevisionShowConfiguration.cs
  99. 12
      ErsatzTV.Infrastructure/Data/Configurations/TelevisionShowMetadataConfiguration.cs
  100. 2
      ErsatzTV.Infrastructure/Data/Repositories/ChannelRepository.cs
  101. Some files were not shown because too many files have changed in this diff Show More

2
ErsatzTV.Application/MediaCards/SimpleMediaCollectionCardResultsViewModel.cs → ErsatzTV.Application/MediaCards/CollectionCardResultsViewModel.cs

@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
namespace ErsatzTV.Application.MediaCards
{
public record SimpleMediaCollectionCardResultsViewModel(
public record CollectionCardResultsViewModel(
string Name,
List<MovieCardViewModel> MovieCards,
List<TelevisionShowCardViewModel> ShowCards,

57
ErsatzTV.Application/MediaCards/Mapper.cs

@ -1,42 +1,41 @@ @@ -1,42 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.MediaCards
{
internal static class Mapper
{
internal static TelevisionShowCardViewModel ProjectToViewModel(TelevisionShow televisionShow) =>
internal static TelevisionShowCardViewModel ProjectToViewModel(ShowMetadata showMetadata) =>
new(
televisionShow.Id,
televisionShow.Metadata?.Title,
televisionShow.Metadata?.Year.ToString(),
televisionShow.Metadata?.SortTitle,
televisionShow.Poster);
showMetadata.ShowId,
showMetadata.Title,
showMetadata.ReleaseDate?.Year.ToString(),
showMetadata.SortTitle,
null); // TODO: artwork
internal static TelevisionSeasonCardViewModel ProjectToViewModel(TelevisionSeason televisionSeason) =>
internal static TelevisionSeasonCardViewModel ProjectToViewModel(Season season) =>
new(
televisionSeason.TelevisionShow.Metadata?.Title,
televisionSeason.Id,
televisionSeason.Number,
GetSeasonName(televisionSeason.Number),
season.Show.ShowMetadata.HeadOrNone().Map(m => m.Title).IfNone(string.Empty),
season.Id,
season.SeasonNumber,
GetSeasonName(season.SeasonNumber),
string.Empty,
GetSeasonName(televisionSeason.Number),
televisionSeason.Poster,
televisionSeason.Number == 0 ? "S" : televisionSeason.Number.ToString());
GetSeasonName(season.SeasonNumber),
season.Poster,
season.SeasonNumber == 0 ? "S" : season.SeasonNumber.ToString());
internal static TelevisionEpisodeCardViewModel ProjectToViewModel(
TelevisionEpisodeMediaItem televisionEpisode) =>
EpisodeMetadata episodeMetadata) =>
new(
televisionEpisode.Id,
televisionEpisode.Metadata?.Aired ?? DateTime.MinValue,
televisionEpisode.Season.TelevisionShow.Metadata.Title,
televisionEpisode.Metadata?.Title,
$"Episode {televisionEpisode.Metadata?.Episode}",
televisionEpisode.Metadata?.Episode.ToString(),
televisionEpisode.Poster,
televisionEpisode.Metadata?.Episode.ToString());
episodeMetadata.EpisodeId,
episodeMetadata.ReleaseDate ?? DateTime.MinValue,
episodeMetadata.Episode.Season.Show.ShowMetadata.HeadOrNone().Map(m => m.Title).IfNone(string.Empty),
episodeMetadata.Title,
$"Episode {episodeMetadata.Episode.EpisodeNumber}",
episodeMetadata.Episode.EpisodeNumber.ToString(),
null, // TODO: artwork
episodeMetadata.Episode.EpisodeNumber.ToString());
internal static MovieCardViewModel ProjectToViewModel(MovieMetadata movieMetadata) =>
new(
@ -46,15 +45,15 @@ namespace ErsatzTV.Application.MediaCards @@ -46,15 +45,15 @@ namespace ErsatzTV.Application.MediaCards
movieMetadata.SortTitle,
null); // TODO: artwork
internal static SimpleMediaCollectionCardResultsViewModel
ProjectToViewModel(SimpleMediaCollection collection) =>
internal static CollectionCardResultsViewModel
ProjectToViewModel(Collection collection) =>
new(
collection.Name,
// TODO: fix this
new List<MovieCardViewModel>(), // collection.Movies.Map(ProjectToViewModel).ToList(),
collection.TelevisionShows.Map(ProjectToViewModel).ToList(),
collection.TelevisionSeasons.Map(ProjectToViewModel).ToList(),
collection.TelevisionEpisodes.Map(ProjectToViewModel).ToList());
new List<TelevisionShowCardViewModel>(), // collection.TelevisionShows.Map(ProjectToViewModel).ToList(),
new List<TelevisionSeasonCardViewModel>(), // collection.TelevisionSeasons.Map(ProjectToViewModel).ToList(),
new List<TelevisionEpisodeCardViewModel>()); // collection.TelevisionEpisodes.Map(ProjectToViewModel).ToList());
private static string GetSeasonName(int number) =>
number == 0 ? "Specials" : $"Season {number}";

9
ErsatzTV.Application/MediaCards/Queries/GetCollectionCards.cs

@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
using ErsatzTV.Core;
using LanguageExt;
using MediatR;
namespace ErsatzTV.Application.MediaCards.Queries
{
public record GetCollectionCards
(int Id) : IRequest<Either<BaseError, CollectionCardResultsViewModel>>;
}

26
ErsatzTV.Application/MediaCards/Queries/GetCollectionCardsHandler.cs

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
using MediatR;
using static ErsatzTV.Application.MediaCards.Mapper;
namespace ErsatzTV.Application.MediaCards.Queries
{
public class GetCollectionCardsHandler : IRequestHandler<GetCollectionCards,
Either<BaseError, CollectionCardResultsViewModel>>
{
private readonly ICollectionRepository _collectionRepository;
public GetCollectionCardsHandler(ICollectionRepository collectionRepository) =>
_collectionRepository = collectionRepository;
public async Task<Either<BaseError, CollectionCardResultsViewModel>> Handle(
GetCollectionCards request,
CancellationToken cancellationToken) =>
(await _collectionRepository.GetWithItemsUntracked(request.Id))
.ToEither(BaseError.New("Unable to load collection"))
.Map(ProjectToViewModel);
}
}

9
ErsatzTV.Application/MediaCards/Queries/GetSimpleMediaCollectionCards.cs

@ -1,9 +0,0 @@ @@ -1,9 +0,0 @@
using ErsatzTV.Core;
using LanguageExt;
using MediatR;
namespace ErsatzTV.Application.MediaCards.Queries
{
public record GetSimpleMediaCollectionCards
(int Id) : IRequest<Either<BaseError, SimpleMediaCollectionCardResultsViewModel>>;
}

26
ErsatzTV.Application/MediaCards/Queries/GetSimpleMediaCollectionCardsHandler.cs

@ -1,26 +0,0 @@ @@ -1,26 +0,0 @@
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
using MediatR;
using static ErsatzTV.Application.MediaCards.Mapper;
namespace ErsatzTV.Application.MediaCards.Queries
{
public class GetSimpleMediaCollectionCardsHandler : IRequestHandler<GetSimpleMediaCollectionCards,
Either<BaseError, SimpleMediaCollectionCardResultsViewModel>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public GetSimpleMediaCollectionCardsHandler(IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public async Task<Either<BaseError, SimpleMediaCollectionCardResultsViewModel>> Handle(
GetSimpleMediaCollectionCards request,
CancellationToken cancellationToken) =>
(await _mediaCollectionRepository.GetSimpleMediaCollectionWithItemsUntracked(request.Id))
.ToEither(BaseError.New("Unable to load collection"))
.Map(ProjectToViewModel);
}
}

7
ErsatzTV.Application/MediaCollections/Commands/AddEpisodeToCollection.cs

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record AddEpisodeToCollection(int CollectionId, int EpisodeId) : MediatR.IRequest<Either<BaseError, Unit>>;
}

67
ErsatzTV.Application/MediaCollections/Commands/AddEpisodeToCollectionHandler.cs

@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class
AddEpisodeToCollectionHandler : MediatR.IRequestHandler<AddEpisodeToCollection,
Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
private readonly ITelevisionRepository _televisionRepository;
public AddEpisodeToCollectionHandler(
IMediaCollectionRepository mediaCollectionRepository,
ITelevisionRepository televisionRepository)
{
_mediaCollectionRepository = mediaCollectionRepository;
_televisionRepository = televisionRepository;
}
public Task<Either<BaseError, Unit>> Handle(
AddEpisodeToCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(ApplyAddTelevisionEpisodeRequest)
.Bind(v => v.ToEitherAsync());
private async Task<Unit> ApplyAddTelevisionEpisodeRequest(RequestParameters parameters)
{
if (parameters.Collection.MediaItems.All(s => s.Id != parameters.EpisodeToAdd.Id))
{
parameters.Collection.MediaItems.Add(parameters.EpisodeToAdd);
await _mediaCollectionRepository.Update(parameters.Collection);
}
return Unit.Default;
}
private async Task<Validation<BaseError, RequestParameters>>
Validate(AddEpisodeToCollection request) =>
(await SimpleMediaCollectionMustExist(request), await ValidateEpisode(request))
.Apply(
(collectionToUpdate, episode) =>
new RequestParameters(collectionToUpdate, episode));
private Task<Validation<BaseError, Collection>> SimpleMediaCollectionMustExist(
AddEpisodeToCollection updateCollection) =>
_mediaCollectionRepository.GetCollectionWithItems(updateCollection.CollectionId)
.Map(v => v.ToValidation<BaseError>("Collection does not exist."));
private Task<Validation<BaseError, Episode>> ValidateEpisode(
AddEpisodeToCollection request) =>
LoadTelevisionEpisode(request)
.Map(v => v.ToValidation<BaseError>("Episode does not exist"));
private Task<Option<Episode>> LoadTelevisionEpisode(
AddEpisodeToCollection request) =>
_televisionRepository.GetEpisode(request.EpisodeId);
private record RequestParameters(Collection Collection, Episode EpisodeToAdd);
}
}

7
ErsatzTV.Application/MediaCollections/Commands/AddMovieCollection.cs

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record AddMovieToCollection(int CollectionId, int MovieId) : MediatR.IRequest<Either<BaseError, Unit>>;
}

30
ErsatzTV.Application/MediaCollections/Commands/AddMovieToSimpleMediaCollectionHandler.cs → ErsatzTV.Application/MediaCollections/Commands/AddMovieToCollectionHandler.cs

@ -8,13 +8,13 @@ using LanguageExt; @@ -8,13 +8,13 @@ using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class
AddMovieToSimpleMediaCollectionHandler : MediatR.IRequestHandler<AddMovieToSimpleMediaCollection,
AddMovieToCollectionHandler : MediatR.IRequestHandler<AddMovieToCollection,
Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
private readonly IMovieRepository _movieRepository;
public AddMovieToSimpleMediaCollectionHandler(
public AddMovieToCollectionHandler(
IMediaCollectionRepository mediaCollectionRepository,
IMovieRepository movieRepository)
{
@ -23,7 +23,7 @@ namespace ErsatzTV.Application.MediaCollections.Commands @@ -23,7 +23,7 @@ namespace ErsatzTV.Application.MediaCollections.Commands
}
public Task<Either<BaseError, Unit>> Handle(
AddMovieToSimpleMediaCollection request,
AddMovieToCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(ApplyAddMoviesRequest)
@ -31,32 +31,32 @@ namespace ErsatzTV.Application.MediaCollections.Commands @@ -31,32 +31,32 @@ namespace ErsatzTV.Application.MediaCollections.Commands
private async Task<Unit> ApplyAddMoviesRequest(RequestParameters parameters)
{
parameters.Collection.Movies.Add(parameters.MovieToAdd);
parameters.Collection.MediaItems.Add(parameters.MovieToAdd);
await _mediaCollectionRepository.Update(parameters.Collection);
return Unit.Default;
}
private async Task<Validation<BaseError, RequestParameters>>
Validate(AddMovieToSimpleMediaCollection request) =>
Validate(AddMovieToCollection request) =>
(await SimpleMediaCollectionMustExist(request), await ValidateMovies(request))
.Apply(
(simpleMediaCollectionToUpdate, movieToAdd) =>
new RequestParameters(simpleMediaCollectionToUpdate, movieToAdd));
(collectionToUpdate, movieToAdd) =>
new RequestParameters(collectionToUpdate, movieToAdd));
private Task<Validation<BaseError, SimpleMediaCollection>> SimpleMediaCollectionMustExist(
AddMovieToSimpleMediaCollection updateSimpleMediaCollection) =>
_mediaCollectionRepository.GetSimpleMediaCollectionWithItems(updateSimpleMediaCollection.MediaCollectionId)
.Map(v => v.ToValidation<BaseError>("SimpleMediaCollection does not exist."));
private Task<Validation<BaseError, Collection>> SimpleMediaCollectionMustExist(
AddMovieToCollection updateCollection) =>
_mediaCollectionRepository.GetCollectionWithItems(updateCollection.CollectionId)
.Map(v => v.ToValidation<BaseError>("Collection does not exist."));
private Task<Validation<BaseError, Movie>> ValidateMovies(
AddMovieToSimpleMediaCollection request) =>
AddMovieToCollection request) =>
LoadMovie(request)
.Map(v => v.ToValidation<BaseError>("MovieMediaItem does not exist"));
.Map(v => v.ToValidation<BaseError>("Movie does not exist"));
private Task<Option<Movie>> LoadMovie(AddMovieToSimpleMediaCollection request) =>
private Task<Option<Movie>> LoadMovie(AddMovieToCollection request) =>
_movieRepository.GetMovie(request.MovieId);
private record RequestParameters(SimpleMediaCollection Collection, Movie MovieToAdd);
private record RequestParameters(Collection Collection, Movie MovieToAdd);
}
}

8
ErsatzTV.Application/MediaCollections/Commands/AddMovieToSimpleMediaCollection.cs

@ -1,8 +0,0 @@ @@ -1,8 +0,0 @@
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record AddMovieToSimpleMediaCollection
(int MediaCollectionId, int MovieId) : MediatR.IRequest<Either<BaseError, Unit>>;
}

7
ErsatzTV.Application/MediaCollections/Commands/AddSeasonToCollection.cs

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record AddSeasonToCollection(int CollectionId, int SeasonId) : MediatR.IRequest<Either<BaseError, Unit>>;
}

67
ErsatzTV.Application/MediaCollections/Commands/AddSeasonToCollectionHandler.cs

@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class
AddSeasonToCollectionHandler : MediatR.IRequestHandler<AddSeasonToCollection,
Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
private readonly ITelevisionRepository _televisionRepository;
public AddSeasonToCollectionHandler(
IMediaCollectionRepository mediaCollectionRepository,
ITelevisionRepository televisionRepository)
{
_mediaCollectionRepository = mediaCollectionRepository;
_televisionRepository = televisionRepository;
}
public Task<Either<BaseError, Unit>> Handle(
AddSeasonToCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(ApplyAddTelevisionSeasonRequest)
.Bind(v => v.ToEitherAsync());
private async Task<Unit> ApplyAddTelevisionSeasonRequest(RequestParameters parameters)
{
if (parameters.Collection.MediaItems.All(s => s.Id != parameters.SeasonToAdd.Id))
{
parameters.Collection.MediaItems.Add(parameters.SeasonToAdd);
await _mediaCollectionRepository.Update(parameters.Collection);
}
return Unit.Default;
}
private async Task<Validation<BaseError, RequestParameters>>
Validate(AddSeasonToCollection request) =>
(await SimpleMediaCollectionMustExist(request), await ValidateSeason(request))
.Apply(
(collectionToUpdate, season) =>
new RequestParameters(collectionToUpdate, season));
private Task<Validation<BaseError, Collection>> SimpleMediaCollectionMustExist(
AddSeasonToCollection updateCollection) =>
_mediaCollectionRepository.GetCollectionWithItems(updateCollection.CollectionId)
.Map(v => v.ToValidation<BaseError>("Collection does not exist."));
private Task<Validation<BaseError, Season>> ValidateSeason(
AddSeasonToCollection request) =>
LoadTelevisionSeason(request)
.Map(v => v.ToValidation<BaseError>("Season does not exist"));
private Task<Option<Season>> LoadTelevisionSeason(
AddSeasonToCollection request) =>
_televisionRepository.GetSeason(request.SeasonId);
private record RequestParameters(Collection Collection, Season SeasonToAdd);
}
}

7
ErsatzTV.Application/MediaCollections/Commands/AddShowToCollection.cs

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record AddShowToCollection(int CollectionId, int ShowId) : MediatR.IRequest<Either<BaseError, Unit>>;
}

64
ErsatzTV.Application/MediaCollections/Commands/AddShowToCollectionHandler.cs

@ -0,0 +1,64 @@ @@ -0,0 +1,64 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class AddShowToCollectionHandler : MediatR.IRequestHandler<AddShowToCollection, Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
private readonly ITelevisionRepository _televisionRepository;
public AddShowToCollectionHandler(
IMediaCollectionRepository mediaCollectionRepository,
ITelevisionRepository televisionRepository)
{
_mediaCollectionRepository = mediaCollectionRepository;
_televisionRepository = televisionRepository;
}
public Task<Either<BaseError, Unit>> Handle(
AddShowToCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(ApplyAddTelevisionShowRequest)
.Bind(v => v.ToEitherAsync());
private async Task<Unit> ApplyAddTelevisionShowRequest(RequestParameters parameters)
{
if (parameters.Collection.MediaItems.All(s => s.Id != parameters.ShowToAdd.Id))
{
parameters.Collection.MediaItems.Add(parameters.ShowToAdd);
await _mediaCollectionRepository.Update(parameters.Collection);
}
return Unit.Default;
}
private async Task<Validation<BaseError, RequestParameters>>
Validate(AddShowToCollection request) =>
(await SimpleMediaCollectionMustExist(request), await ValidateShow(request))
.Apply(
(collectionToUpdate, show) =>
new RequestParameters(collectionToUpdate, show));
private Task<Validation<BaseError, Collection>> SimpleMediaCollectionMustExist(
AddShowToCollection updateCollection) =>
_mediaCollectionRepository.GetCollectionWithItems(updateCollection.CollectionId)
.Map(v => v.ToValidation<BaseError>("Collection does not exist."));
private Task<Validation<BaseError, Show>> ValidateShow(
AddShowToCollection request) =>
LoadTelevisionShow(request)
.Map(v => v.ToValidation<BaseError>("Show does not exist"));
private Task<Option<Show>> LoadTelevisionShow(AddShowToCollection request) =>
_televisionRepository.GetShow(request.ShowId);
private record RequestParameters(Collection Collection, Show ShowToAdd);
}
}

8
ErsatzTV.Application/MediaCollections/Commands/AddTelevisionEpisodeToSimpleMediaCollection.cs

@ -1,8 +0,0 @@ @@ -1,8 +0,0 @@
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record AddTelevisionEpisodeToSimpleMediaCollection
(int MediaCollectionId, int TelevisionEpisodeId) : MediatR.IRequest<Either<BaseError, Unit>>;
}

68
ErsatzTV.Application/MediaCollections/Commands/AddTelevisionEpisodeToSimpleMediaCollectionHandler.cs

@ -1,68 +0,0 @@ @@ -1,68 +0,0 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class
AddTelevisionEpisodeToSimpleMediaCollectionHandler : MediatR.IRequestHandler<
AddTelevisionEpisodeToSimpleMediaCollection,
Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
private readonly ITelevisionRepository _televisionRepository;
public AddTelevisionEpisodeToSimpleMediaCollectionHandler(
IMediaCollectionRepository mediaCollectionRepository,
ITelevisionRepository televisionRepository)
{
_mediaCollectionRepository = mediaCollectionRepository;
_televisionRepository = televisionRepository;
}
public Task<Either<BaseError, Unit>> Handle(
AddTelevisionEpisodeToSimpleMediaCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(ApplyAddTelevisionEpisodeRequest)
.Bind(v => v.ToEitherAsync());
private async Task<Unit> ApplyAddTelevisionEpisodeRequest(RequestParameters parameters)
{
if (parameters.Collection.TelevisionEpisodes.All(s => s.Id != parameters.EpisodeToAdd.Id))
{
parameters.Collection.TelevisionEpisodes.Add(parameters.EpisodeToAdd);
await _mediaCollectionRepository.Update(parameters.Collection);
}
return Unit.Default;
}
private async Task<Validation<BaseError, RequestParameters>>
Validate(AddTelevisionEpisodeToSimpleMediaCollection request) =>
(await SimpleMediaCollectionMustExist(request), await ValidateEpisode(request))
.Apply(
(simpleMediaCollectionToUpdate, episode) =>
new RequestParameters(simpleMediaCollectionToUpdate, episode));
private Task<Validation<BaseError, SimpleMediaCollection>> SimpleMediaCollectionMustExist(
AddTelevisionEpisodeToSimpleMediaCollection updateSimpleMediaCollection) =>
_mediaCollectionRepository.GetSimpleMediaCollectionWithItems(updateSimpleMediaCollection.MediaCollectionId)
.Map(v => v.ToValidation<BaseError>("SimpleMediaCollection does not exist."));
private Task<Validation<BaseError, TelevisionEpisodeMediaItem>> ValidateEpisode(
AddTelevisionEpisodeToSimpleMediaCollection request) =>
LoadTelevisionEpisode(request)
.Map(v => v.ToValidation<BaseError>("TelevisionEpisode does not exist"));
private Task<Option<TelevisionEpisodeMediaItem>> LoadTelevisionEpisode(
AddTelevisionEpisodeToSimpleMediaCollection request) =>
_televisionRepository.GetEpisode(request.TelevisionEpisodeId);
private record RequestParameters(SimpleMediaCollection Collection, TelevisionEpisodeMediaItem EpisodeToAdd);
}
}

8
ErsatzTV.Application/MediaCollections/Commands/AddTelevisionSeasonToSimpleMediaCollection.cs

@ -1,8 +0,0 @@ @@ -1,8 +0,0 @@
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record AddTelevisionSeasonToSimpleMediaCollection
(int MediaCollectionId, int TelevisionSeasonId) : MediatR.IRequest<Either<BaseError, Unit>>;
}

68
ErsatzTV.Application/MediaCollections/Commands/AddTelevisionSeasonToSimpleMediaCollectionHandler.cs

@ -1,68 +0,0 @@ @@ -1,68 +0,0 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class
AddTelevisionSeasonToSimpleMediaCollectionHandler : MediatR.IRequestHandler<
AddTelevisionSeasonToSimpleMediaCollection,
Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
private readonly ITelevisionRepository _televisionRepository;
public AddTelevisionSeasonToSimpleMediaCollectionHandler(
IMediaCollectionRepository mediaCollectionRepository,
ITelevisionRepository televisionRepository)
{
_mediaCollectionRepository = mediaCollectionRepository;
_televisionRepository = televisionRepository;
}
public Task<Either<BaseError, Unit>> Handle(
AddTelevisionSeasonToSimpleMediaCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(ApplyAddTelevisionSeasonRequest)
.Bind(v => v.ToEitherAsync());
private async Task<Unit> ApplyAddTelevisionSeasonRequest(RequestParameters parameters)
{
if (parameters.Collection.TelevisionSeasons.All(s => s.Id != parameters.SeasonToAdd.Id))
{
parameters.Collection.TelevisionSeasons.Add(parameters.SeasonToAdd);
await _mediaCollectionRepository.Update(parameters.Collection);
}
return Unit.Default;
}
private async Task<Validation<BaseError, RequestParameters>>
Validate(AddTelevisionSeasonToSimpleMediaCollection request) =>
(await SimpleMediaCollectionMustExist(request), await ValidateSeason(request))
.Apply(
(simpleMediaCollectionToUpdate, season) =>
new RequestParameters(simpleMediaCollectionToUpdate, season));
private Task<Validation<BaseError, SimpleMediaCollection>> SimpleMediaCollectionMustExist(
AddTelevisionSeasonToSimpleMediaCollection updateSimpleMediaCollection) =>
_mediaCollectionRepository.GetSimpleMediaCollectionWithItems(updateSimpleMediaCollection.MediaCollectionId)
.Map(v => v.ToValidation<BaseError>("SimpleMediaCollection does not exist."));
private Task<Validation<BaseError, TelevisionSeason>> ValidateSeason(
AddTelevisionSeasonToSimpleMediaCollection request) =>
LoadTelevisionSeason(request)
.Map(v => v.ToValidation<BaseError>("TelevisionSeason does not exist"));
private Task<Option<TelevisionSeason>> LoadTelevisionSeason(
AddTelevisionSeasonToSimpleMediaCollection request) =>
_televisionRepository.GetSeason(request.TelevisionSeasonId);
private record RequestParameters(SimpleMediaCollection Collection, TelevisionSeason SeasonToAdd);
}
}

8
ErsatzTV.Application/MediaCollections/Commands/AddTelevisionShowToSimpleMediaCollection.cs

@ -1,8 +0,0 @@ @@ -1,8 +0,0 @@
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record AddTelevisionShowToSimpleMediaCollection
(int MediaCollectionId, int TelevisionShowId) : MediatR.IRequest<Either<BaseError, Unit>>;
}

67
ErsatzTV.Application/MediaCollections/Commands/AddTelevisionShowToSimpleMediaCollectionHandler.cs

@ -1,67 +0,0 @@ @@ -1,67 +0,0 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class
AddTelevisionShowToSimpleMediaCollectionHandler : MediatR.IRequestHandler<
AddTelevisionShowToSimpleMediaCollection,
Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
private readonly ITelevisionRepository _televisionRepository;
public AddTelevisionShowToSimpleMediaCollectionHandler(
IMediaCollectionRepository mediaCollectionRepository,
ITelevisionRepository televisionRepository)
{
_mediaCollectionRepository = mediaCollectionRepository;
_televisionRepository = televisionRepository;
}
public Task<Either<BaseError, Unit>> Handle(
AddTelevisionShowToSimpleMediaCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(ApplyAddTelevisionShowRequest)
.Bind(v => v.ToEitherAsync());
private async Task<Unit> ApplyAddTelevisionShowRequest(RequestParameters parameters)
{
if (parameters.Collection.TelevisionShows.All(s => s.Id != parameters.ShowToAdd.Id))
{
parameters.Collection.TelevisionShows.Add(parameters.ShowToAdd);
await _mediaCollectionRepository.Update(parameters.Collection);
}
return Unit.Default;
}
private async Task<Validation<BaseError, RequestParameters>>
Validate(AddTelevisionShowToSimpleMediaCollection request) =>
(await SimpleMediaCollectionMustExist(request), await ValidateShow(request))
.Apply(
(simpleMediaCollectionToUpdate, show) =>
new RequestParameters(simpleMediaCollectionToUpdate, show));
private Task<Validation<BaseError, SimpleMediaCollection>> SimpleMediaCollectionMustExist(
AddTelevisionShowToSimpleMediaCollection updateSimpleMediaCollection) =>
_mediaCollectionRepository.GetSimpleMediaCollectionWithItems(updateSimpleMediaCollection.MediaCollectionId)
.Map(v => v.ToValidation<BaseError>("SimpleMediaCollection does not exist."));
private Task<Validation<BaseError, TelevisionShow>> ValidateShow(
AddTelevisionShowToSimpleMediaCollection request) =>
LoadTelevisionShow(request)
.Map(v => v.ToValidation<BaseError>("TelevisionShow does not exist"));
private Task<Option<TelevisionShow>> LoadTelevisionShow(AddTelevisionShowToSimpleMediaCollection request) =>
_televisionRepository.GetShow(request.TelevisionShowId);
private record RequestParameters(SimpleMediaCollection Collection, TelevisionShow ShowToAdd);
}
}

8
ErsatzTV.Application/MediaCollections/Commands/CreateCollection.cs

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
using ErsatzTV.Core;
using LanguageExt;
using MediatR;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record CreateCollection(string Name) : IRequest<Either<BaseError, MediaCollectionViewModel>>;
}

23
ErsatzTV.Application/MediaCollections/Commands/CreateSimpleMediaCollectionHandler.cs → ErsatzTV.Application/MediaCollections/Commands/CreateCollectionHandler.cs

@ -12,36 +12,33 @@ using static LanguageExt.Prelude; @@ -12,36 +12,33 @@ using static LanguageExt.Prelude;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class CreateSimpleMediaCollectionHandler : IRequestHandler<CreateSimpleMediaCollection,
Either<BaseError, MediaCollectionViewModel>>
public class
CreateCollectionHandler : IRequestHandler<CreateCollection, Either<BaseError, MediaCollectionViewModel>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public CreateSimpleMediaCollectionHandler(IMediaCollectionRepository mediaCollectionRepository) =>
public CreateCollectionHandler(IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public Task<Either<BaseError, MediaCollectionViewModel>> Handle(
CreateSimpleMediaCollection request,
CreateCollection request,
CancellationToken cancellationToken) =>
Validate(request).MapT(PersistCollection).Bind(v => v.ToEitherAsync());
private Task<MediaCollectionViewModel> PersistCollection(SimpleMediaCollection c) =>
private Task<MediaCollectionViewModel> PersistCollection(Collection c) =>
_mediaCollectionRepository.Add(c).Map(ProjectToViewModel);
private Task<Validation<BaseError, SimpleMediaCollection>> Validate(CreateSimpleMediaCollection request) =>
private Task<Validation<BaseError, Collection>> Validate(CreateCollection request) =>
ValidateName(request).MapT(
name => new SimpleMediaCollection
name => new Collection
{
Name = name,
Movies = new List<Movie>(),
TelevisionShows = new List<TelevisionShow>(),
TelevisionEpisodes = new List<TelevisionEpisodeMediaItem>(),
TelevisionSeasons = new List<TelevisionSeason>()
MediaItems = new List<MediaItem>()
});
private async Task<Validation<BaseError, string>> ValidateName(CreateSimpleMediaCollection createCollection)
private async Task<Validation<BaseError, string>> ValidateName(CreateCollection createCollection)
{
List<string> allNames = await _mediaCollectionRepository.GetSimpleMediaCollections()
List<string> allNames = await _mediaCollectionRepository.GetAll()
.Map(list => list.Map(c => c.Name).ToList());
Validation<BaseError, string> result1 = createCollection.NotEmpty(c => c.Name)

9
ErsatzTV.Application/MediaCollections/Commands/CreateSimpleMediaCollection.cs

@ -1,9 +0,0 @@ @@ -1,9 +0,0 @@
using ErsatzTV.Core;
using LanguageExt;
using MediatR;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record CreateSimpleMediaCollection
(string Name) : IRequest<Either<BaseError, MediaCollectionViewModel>>;
}

2
ErsatzTV.Application/MediaCollections/Commands/DeleteSimpleMediaCollection.cs → ErsatzTV.Application/MediaCollections/Commands/DeleteCollection.cs

@ -5,5 +5,5 @@ using MediatR; @@ -5,5 +5,5 @@ using MediatR;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record DeleteSimpleMediaCollection(int SimpleMediaCollectionId) : IRequest<Either<BaseError, Task>>;
public record DeleteCollection(int CollectionId) : IRequest<Either<BaseError, Task>>;
}

17
ErsatzTV.Application/MediaCollections/Commands/DeleteSimpleMediaCollectionHandler.cs → ErsatzTV.Application/MediaCollections/Commands/DeleteCollectionHandler.cs

@ -7,28 +7,27 @@ using MediatR; @@ -7,28 +7,27 @@ using MediatR;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class
DeleteSimpleMediaCollectionHandler : IRequestHandler<DeleteSimpleMediaCollection, Either<BaseError, Task>>
public class DeleteCollectionHandler : IRequestHandler<DeleteCollection, Either<BaseError, Task>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public DeleteSimpleMediaCollectionHandler(IMediaCollectionRepository mediaCollectionRepository) =>
public DeleteCollectionHandler(IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public async Task<Either<BaseError, Task>> Handle(
DeleteSimpleMediaCollection request,
DeleteCollection request,
CancellationToken cancellationToken) =>
(await SimpleMediaCollectionMustExist(request))
(await CollectionMustExist(request))
.Map(DoDeletion)
.ToEither<Task>();
private Task DoDeletion(int mediaCollectionId) => _mediaCollectionRepository.Delete(mediaCollectionId);
private async Task<Validation<BaseError, int>> SimpleMediaCollectionMustExist(
DeleteSimpleMediaCollection deleteMediaCollection) =>
(await _mediaCollectionRepository.GetSimpleMediaCollection(deleteMediaCollection.SimpleMediaCollectionId))
private async Task<Validation<BaseError, int>> CollectionMustExist(
DeleteCollection deleteMediaCollection) =>
(await _mediaCollectionRepository.Get(deleteMediaCollection.CollectionId))
.ToValidation<BaseError>(
$"SimpleMediaCollection {deleteMediaCollection.SimpleMediaCollectionId} does not exist.")
$"Collection {deleteMediaCollection.CollectionId} does not exist.")
.Map(c => c.Id);
}
}

11
ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromCollection.cs

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
using System.Collections.Generic;
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record RemoveItemsFromCollection(int MediaCollectionId) : MediatR.IRequest<Either<BaseError, Unit>>
{
public List<int> MediaItemIds { get; set; } = new();
}
}

51
ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromCollectionHandler.cs

@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class
RemoveItemsFromCollectionHandler : MediatR.IRequestHandler<RemoveItemsFromCollection, Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public RemoveItemsFromCollectionHandler(
IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public Task<Either<BaseError, Unit>> Handle(
RemoveItemsFromCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(collection => ApplyAddTelevisionEpisodeRequest(request, collection))
.Bind(v => v.ToEitherAsync());
private Task<Unit> ApplyAddTelevisionEpisodeRequest(
RemoveItemsFromCollection request,
Collection collection)
{
var itemsToRemove = collection.MediaItems
.Filter(m => request.MediaItemIds.Contains(m.Id))
.ToList();
itemsToRemove.ForEach(m => collection.MediaItems.Remove(m));
return itemsToRemove.Any()
? _mediaCollectionRepository.Update(collection).ToUnit()
: Task.FromResult(Unit.Default);
}
private Task<Validation<BaseError, Collection>> Validate(
RemoveItemsFromCollection request) =>
CollectionMustExist(request);
private Task<Validation<BaseError, Collection>> CollectionMustExist(
RemoveItemsFromCollection updateCollection) =>
_mediaCollectionRepository.GetCollectionWithItems(updateCollection.MediaCollectionId)
.Map(v => v.ToValidation<BaseError>("Collection does not exist."));
}
}

15
ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromSimpleMediaCollection.cs

@ -1,15 +0,0 @@ @@ -1,15 +0,0 @@
using System.Collections.Generic;
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record RemoveItemsFromSimpleMediaCollection
(int MediaCollectionId) : MediatR.IRequest<Either<BaseError, Unit>>
{
public List<int> MovieIds { get; set; } = new();
public List<int> TelevisionShowIds { get; set; } = new();
public List<int> TelevisionSeasonIds { get; set; } = new();
public List<int> TelevisionEpisodeIds { get; set; } = new();
}
}

74
ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromSimpleMediaCollectionHandler.cs

@ -1,74 +0,0 @@ @@ -1,74 +0,0 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class
RemoveItemsFromSimpleMediaCollectionHandler : MediatR.IRequestHandler<
RemoveItemsFromSimpleMediaCollection,
Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public RemoveItemsFromSimpleMediaCollectionHandler(
IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public Task<Either<BaseError, Unit>> Handle(
RemoveItemsFromSimpleMediaCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(collection => ApplyAddTelevisionEpisodeRequest(request, collection))
.Bind(v => v.ToEitherAsync());
private Task<Unit> ApplyAddTelevisionEpisodeRequest(
RemoveItemsFromSimpleMediaCollection request,
SimpleMediaCollection collection)
{
var moviesToRemove = collection.Movies
.Filter(m => request.MovieIds.Contains(m.Id))
.ToList();
moviesToRemove.ForEach(m => collection.Movies.Remove(m));
var showsToRemove = collection.TelevisionShows
.Filter(s => request.TelevisionShowIds.Contains(s.Id))
.ToList();
showsToRemove.ForEach(s => collection.TelevisionShows.Remove(s));
var seasonsToRemove = collection.TelevisionSeasons
.Filter(s => request.TelevisionSeasonIds.Contains(s.Id))
.ToList();
seasonsToRemove.ForEach(s => collection.TelevisionSeasons.Remove(s));
var episodesToRemove = collection.TelevisionEpisodes
.Filter(e => request.TelevisionEpisodeIds.Contains(e.Id))
.ToList();
episodesToRemove.ForEach(e => collection.TelevisionEpisodes.Remove(e));
if (moviesToRemove.Any() || showsToRemove.Any() || seasonsToRemove.Any() || episodesToRemove.Any())
{
return _mediaCollectionRepository.Update(collection).ToUnit();
}
return Task.FromResult(Unit.Default);
}
private Task<Validation<BaseError, SimpleMediaCollection>> Validate(
RemoveItemsFromSimpleMediaCollection request) =>
SimpleMediaCollectionMustExist(request);
private Task<Validation<BaseError, SimpleMediaCollection>> SimpleMediaCollectionMustExist(
RemoveItemsFromSimpleMediaCollection updateSimpleMediaCollection) =>
_mediaCollectionRepository.GetSimpleMediaCollectionWithItems(updateSimpleMediaCollection.MediaCollectionId)
.Map(v => v.ToValidation<BaseError>("SimpleMediaCollection does not exist."));
}
}

8
ErsatzTV.Application/MediaCollections/Commands/UpdateCollection.cs

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record UpdateCollection
(int CollectionId, string Name) : MediatR.IRequest<Either<BaseError, Unit>>;
}

45
ErsatzTV.Application/MediaCollections/Commands/UpdateCollectionHandler.cs

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class UpdateCollectionHandler : MediatR.IRequestHandler<UpdateCollection, Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public UpdateCollectionHandler(IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public Task<Either<BaseError, Unit>> Handle(
UpdateCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(c => ApplyUpdateRequest(c, request))
.Bind(v => v.ToEitherAsync());
private async Task<Unit> ApplyUpdateRequest(Collection c, UpdateCollection update)
{
c.Name = update.Name;
await _mediaCollectionRepository.Update(c);
return Unit.Default;
}
private async Task<Validation<BaseError, Collection>>
Validate(UpdateCollection request) =>
(await CollectionMustExist(request), ValidateName(request))
.Apply((collectionToUpdate, _) => collectionToUpdate);
private Task<Validation<BaseError, Collection>> CollectionMustExist(
UpdateCollection updateCollection) =>
_mediaCollectionRepository.Get(updateCollection.CollectionId)
.Map(v => v.ToValidation<BaseError>("Collection does not exist."));
private Validation<BaseError, string> ValidateName(UpdateCollection updateSimpleMediaCollection) =>
updateSimpleMediaCollection.NotEmpty(c => c.Name)
.Bind(_ => updateSimpleMediaCollection.NotLongerThan(50)(c => c.Name));
}
}

8
ErsatzTV.Application/MediaCollections/Commands/UpdateSimpleMediaCollection.cs

@ -1,8 +0,0 @@ @@ -1,8 +0,0 @@
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record UpdateSimpleMediaCollection
(int MediaCollectionId, string Name) : MediatR.IRequest<Either<BaseError, Unit>>;
}

47
ErsatzTV.Application/MediaCollections/Commands/UpdateSimpleMediaCollectionHandler.cs

@ -1,47 +0,0 @@ @@ -1,47 +0,0 @@
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class
UpdateSimpleMediaCollectionHandler : MediatR.IRequestHandler<UpdateSimpleMediaCollection,
Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public UpdateSimpleMediaCollectionHandler(IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public Task<Either<BaseError, Unit>> Handle(
UpdateSimpleMediaCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(c => ApplyUpdateRequest(c, request))
.Bind(v => v.ToEitherAsync());
private async Task<Unit> ApplyUpdateRequest(SimpleMediaCollection c, UpdateSimpleMediaCollection update)
{
c.Name = update.Name;
await _mediaCollectionRepository.Update(c);
return Unit.Default;
}
private async Task<Validation<BaseError, SimpleMediaCollection>>
Validate(UpdateSimpleMediaCollection request) =>
(await SimpleMediaCollectionMustExist(request), ValidateName(request))
.Apply((simpleMediaCollectionToUpdate, _) => simpleMediaCollectionToUpdate);
private Task<Validation<BaseError, SimpleMediaCollection>> SimpleMediaCollectionMustExist(
UpdateSimpleMediaCollection updateSimpleMediaCollection) =>
_mediaCollectionRepository.GetSimpleMediaCollection(updateSimpleMediaCollection.MediaCollectionId)
.Map(v => v.ToValidation<BaseError>("SimpleMediaCollection does not exist."));
private Validation<BaseError, string> ValidateName(UpdateSimpleMediaCollection updateSimpleMediaCollection) =>
updateSimpleMediaCollection.NotEmpty(c => c.Name)
.Bind(_ => updateSimpleMediaCollection.NotLongerThan(50)(c => c.Name));
}
}

4
ErsatzTV.Application/MediaCollections/Mapper.cs

@ -4,7 +4,7 @@ namespace ErsatzTV.Application.MediaCollections @@ -4,7 +4,7 @@ namespace ErsatzTV.Application.MediaCollections
{
internal static class Mapper
{
internal static MediaCollectionViewModel ProjectToViewModel(MediaCollection mediaCollection) =>
new(mediaCollection.Id, mediaCollection.Name);
internal static MediaCollectionViewModel ProjectToViewModel(Collection collection) =>
new(collection.Id, collection.Name);
}
}

2
ErsatzTV.Application/MediaCollections/Queries/GetAllMediaCollections.cs → ErsatzTV.Application/MediaCollections/Queries/GetAllCollections.cs

@ -3,5 +3,5 @@ using MediatR; @@ -3,5 +3,5 @@ using MediatR;
namespace ErsatzTV.Application.MediaCollections.Queries
{
public record GetAllMediaCollections : IRequest<List<MediaCollectionViewModel>>;
public record GetAllCollections : IRequest<List<MediaCollectionViewModel>>;
}

6
ErsatzTV.Application/MediaCollections/Queries/GetAllMediaCollectionsHandler.cs → ErsatzTV.Application/MediaCollections/Queries/GetAllCollectionsHandler.cs

@ -9,15 +9,15 @@ using static ErsatzTV.Application.MediaCollections.Mapper; @@ -9,15 +9,15 @@ using static ErsatzTV.Application.MediaCollections.Mapper;
namespace ErsatzTV.Application.MediaCollections.Queries
{
public class GetAllMediaCollectionsHandler : IRequestHandler<GetAllMediaCollections, List<MediaCollectionViewModel>>
public class GetAllCollectionsHandler : IRequestHandler<GetAllCollections, List<MediaCollectionViewModel>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public GetAllMediaCollectionsHandler(IMediaCollectionRepository mediaCollectionRepository) =>
public GetAllCollectionsHandler(IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public Task<List<MediaCollectionViewModel>> Handle(
GetAllMediaCollections request,
GetAllCollections request,
CancellationToken cancellationToken) =>
_mediaCollectionRepository.GetAll().Map(list => list.Map(ProjectToViewModel).ToList());
}

7
ErsatzTV.Application/MediaCollections/Queries/GetAllSimpleMediaCollections.cs

@ -1,7 +0,0 @@ @@ -1,7 +0,0 @@
using System.Collections.Generic;
using MediatR;
namespace ErsatzTV.Application.MediaCollections.Queries
{
public record GetAllSimpleMediaCollections : IRequest<List<MediaCollectionViewModel>>;
}

25
ErsatzTV.Application/MediaCollections/Queries/GetAllSimpleMediaCollectionsHandler.cs

@ -1,25 +0,0 @@ @@ -1,25 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core.Interfaces.Repositories;
using MediatR;
using static ErsatzTV.Application.MediaCollections.Mapper;
namespace ErsatzTV.Application.MediaCollections.Queries
{
public class
GetAllSimpleMediaCollectionsHandler : IRequestHandler<GetAllSimpleMediaCollections,
List<MediaCollectionViewModel>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public GetAllSimpleMediaCollectionsHandler(IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public async Task<List<MediaCollectionViewModel>> Handle(
GetAllSimpleMediaCollections request,
CancellationToken cancellationToken) =>
(await _mediaCollectionRepository.GetSimpleMediaCollections()).Map(ProjectToViewModel).ToList();
}
}

7
ErsatzTV.Application/MediaCollections/Queries/GetCollectionById.cs

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
using LanguageExt;
using MediatR;
namespace ErsatzTV.Application.MediaCollections.Queries
{
public record GetCollectionById(int Id) : IRequest<Option<MediaCollectionViewModel>>;
}

8
ErsatzTV.Application/MediaCollections/Queries/GetSimpleMediaCollectionByIdHandler.cs → ErsatzTV.Application/MediaCollections/Queries/GetCollectionByIdHandler.cs

@ -8,18 +8,18 @@ using static ErsatzTV.Application.MediaCollections.Mapper; @@ -8,18 +8,18 @@ using static ErsatzTV.Application.MediaCollections.Mapper;
namespace ErsatzTV.Application.MediaCollections.Queries
{
public class
GetSimpleMediaCollectionByIdHandler : IRequestHandler<GetSimpleMediaCollectionById,
GetCollectionByIdHandler : IRequestHandler<GetCollectionById,
Option<MediaCollectionViewModel>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public GetSimpleMediaCollectionByIdHandler(IMediaCollectionRepository mediaCollectionRepository) =>
public GetCollectionByIdHandler(IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public Task<Option<MediaCollectionViewModel>> Handle(
GetSimpleMediaCollectionById request,
GetCollectionById request,
CancellationToken cancellationToken) =>
_mediaCollectionRepository.GetSimpleMediaCollection(request.Id)
_mediaCollectionRepository.Get(request.Id)
.MapT(ProjectToViewModel);
}
}

2
ErsatzTV.Application/MediaCollections/Queries/GetSimpleMediaCollectionItems.cs → ErsatzTV.Application/MediaCollections/Queries/GetCollectionItems.cs

@ -5,5 +5,5 @@ using MediatR; @@ -5,5 +5,5 @@ using MediatR;
namespace ErsatzTV.Application.MediaCollections.Queries
{
public record GetSimpleMediaCollectionItems(int Id) : IRequest<Option<IEnumerable<MediaItemViewModel>>>;
public record GetCollectionItems(int Id) : IRequest<Option<IEnumerable<MediaItemViewModel>>>;
}

8
ErsatzTV.Application/MediaCollections/Queries/GetSimpleMediaCollectionItemsHandler.cs → ErsatzTV.Application/MediaCollections/Queries/GetCollectionItemsHandler.cs

@ -9,18 +9,18 @@ using static ErsatzTV.Application.MediaItems.Mapper; @@ -9,18 +9,18 @@ using static ErsatzTV.Application.MediaItems.Mapper;
namespace ErsatzTV.Application.MediaCollections.Queries
{
public class GetSimpleMediaCollectionItemsHandler : IRequestHandler<GetSimpleMediaCollectionItems,
public class GetCollectionItemsHandler : IRequestHandler<GetCollectionItems,
Option<IEnumerable<MediaItemViewModel>>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public GetSimpleMediaCollectionItemsHandler(IMediaCollectionRepository mediaCollectionRepository) =>
public GetCollectionItemsHandler(IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public Task<Option<IEnumerable<MediaItemViewModel>>> Handle(
GetSimpleMediaCollectionItems request,
GetCollectionItems request,
CancellationToken cancellationToken) =>
_mediaCollectionRepository.GetSimpleMediaCollectionItems(request.Id)
_mediaCollectionRepository.GetItems(request.Id)
.MapT(mediaItems => mediaItems.Map(ProjectToViewModel));
}
}

7
ErsatzTV.Application/MediaCollections/Queries/GetSimpleMediaCollectionById.cs

@ -1,7 +0,0 @@ @@ -1,7 +0,0 @@
using LanguageExt;
using MediatR;
namespace ErsatzTV.Application.MediaCollections.Queries
{
public record GetSimpleMediaCollectionById(int Id) : IRequest<Option<MediaCollectionViewModel>>;
}

10
ErsatzTV.Application/MediaItems/Mapper.cs

@ -15,12 +15,12 @@ namespace ErsatzTV.Application.MediaItems @@ -15,12 +15,12 @@ namespace ErsatzTV.Application.MediaItems
internal static MediaItemSearchResultViewModel ProjectToSearchViewModel(MediaItem mediaItem) =>
mediaItem switch
{
TelevisionEpisodeMediaItem e => ProjectToSearchViewModel(e),
Episode e => ProjectToSearchViewModel(e),
Movie m => ProjectToSearchViewModel(m),
_ => throw new ArgumentOutOfRangeException()
};
private static MediaItemSearchResultViewModel ProjectToSearchViewModel(TelevisionEpisodeMediaItem mediaItem) =>
private static MediaItemSearchResultViewModel ProjectToSearchViewModel(Episode mediaItem) =>
new(
mediaItem.Id,
GetLibraryName(mediaItem),
@ -40,9 +40,9 @@ namespace ErsatzTV.Application.MediaItems @@ -40,9 +40,9 @@ namespace ErsatzTV.Application.MediaItems
private static string GetDisplayTitle(MediaItem mediaItem) =>
mediaItem switch
{
TelevisionEpisodeMediaItem e => e.Metadata != null
? $"{e.Metadata.Title} - s{e.Metadata.Season:00}e{e.Metadata.Episode:00}"
: Path.GetFileName(e.Path),
Episode e => e.EpisodeMetadata.HeadOrNone()
.Map(em => $"{em.Title} - s{e.Season.SeasonNumber:00}e{e.EpisodeNumber:00}")
.IfNone(Path.GetFileName(e.Path)),
Movie m => m.MovieMetadata.HeadOrNone().Map(mm => mm.Title).IfNone(Path.GetFileName(m.Path)),
_ => string.Empty
};

6
ErsatzTV.Application/Playouts/Mapper.cs

@ -24,9 +24,9 @@ namespace ErsatzTV.Application.Playouts @@ -24,9 +24,9 @@ namespace ErsatzTV.Application.Playouts
private static string GetDisplayTitle(MediaItem mediaItem) =>
mediaItem switch
{
TelevisionEpisodeMediaItem e => e.Metadata != null
? $"{e.Metadata.Title} - s{e.Metadata.Season:00}e{e.Metadata.Episode:00}"
: Path.GetFileName(e.Path),
Episode e => e.EpisodeMetadata.HeadOrNone()
.Map(em => $"{em.Title} - s{e.Season.SeasonNumber:00}e{e.EpisodeNumber:00}")
.IfNone(Path.GetFileName(e.Path)),
Movie m => m.MovieMetadata.HeadOrNone().Map(mm => mm.Title).IfNone(Path.GetFileName(m.Path)),
_ => string.Empty
};

5
ErsatzTV.Application/ProgramSchedules/Commands/AddProgramScheduleItem.cs

@ -12,9 +12,8 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands @@ -12,9 +12,8 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands
TimeSpan? StartTime,
PlayoutMode PlayoutMode,
ProgramScheduleItemCollectionType CollectionType,
int? MediaCollectionId,
int? TelevisionShowId,
int? TelevisionSeasonId,
int? CollectionId,
int? MediaItemId,
int? MultipleCount,
TimeSpan? PlayoutDuration,
bool? OfflineTail) : IRequest<Either<BaseError, ProgramScheduleItemViewModel>>, IProgramScheduleItemRequest;

5
ErsatzTV.Application/ProgramSchedules/Commands/IProgramScheduleItemRequest.cs

@ -7,9 +7,8 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands @@ -7,9 +7,8 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands
{
TimeSpan? StartTime { get; }
ProgramScheduleItemCollectionType CollectionType { get; }
int? MediaCollectionId { get; }
int? TelevisionShowId { get; }
int? TelevisionSeasonId { get; }
int? CollectionId { get; }
int? MediaItemId { get; }
PlayoutMode PlayoutMode { get; }
int? MultipleCount { get; }
TimeSpan? PlayoutDuration { get; }

32
ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs

@ -60,23 +60,23 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands @@ -60,23 +60,23 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands
switch (item.CollectionType)
{
case ProgramScheduleItemCollectionType.Collection:
if (item.MediaCollectionId is null)
if (item.CollectionId is null)
{
return BaseError.New("[MediaCollection] is required for collection type 'Collection'");
return BaseError.New("[Collection] is required for collection type 'Collection'");
}
break;
case ProgramScheduleItemCollectionType.TelevisionShow:
if (item.TelevisionShowId is null)
if (item.MediaItemId is null)
{
return BaseError.New("[TelevisionShow] is required for collection type 'TelevisionShow'");
return BaseError.New("[MediaItem] is required for collection type 'TelevisionShow'");
}
break;
case ProgramScheduleItemCollectionType.TelevisionSeason:
if (item.TelevisionSeasonId is null)
if (item.MediaItemId is null)
{
return BaseError.New("[TelevisionSeason] is required for collection type 'TelevisionSeason'");
return BaseError.New("[MediaItem] is required for collection type 'TelevisionSeason'");
}
break;
@ -99,9 +99,8 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands @@ -99,9 +99,8 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands
Index = index,
StartTime = item.StartTime,
CollectionType = item.CollectionType,
MediaCollectionId = item.MediaCollectionId,
TelevisionShowId = item.TelevisionShowId,
TelevisionSeasonId = item.TelevisionSeasonId
CollectionId = item.CollectionId,
MediaItemId = item.MediaItemId
},
PlayoutMode.One => new ProgramScheduleItemOne
{
@ -109,9 +108,8 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands @@ -109,9 +108,8 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands
Index = index,
StartTime = item.StartTime,
CollectionType = item.CollectionType,
MediaCollectionId = item.MediaCollectionId,
TelevisionShowId = item.TelevisionShowId,
TelevisionSeasonId = item.TelevisionSeasonId
CollectionId = item.CollectionId,
MediaItemId = item.MediaItemId
},
PlayoutMode.Multiple => new ProgramScheduleItemMultiple
{
@ -119,9 +117,8 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands @@ -119,9 +117,8 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands
Index = index,
StartTime = item.StartTime,
CollectionType = item.CollectionType,
MediaCollectionId = item.MediaCollectionId,
TelevisionShowId = item.TelevisionShowId,
TelevisionSeasonId = item.TelevisionSeasonId,
CollectionId = item.CollectionId,
MediaItemId = item.MediaItemId,
Count = item.MultipleCount.GetValueOrDefault()
},
PlayoutMode.Duration => new ProgramScheduleItemDuration
@ -130,9 +127,8 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands @@ -130,9 +127,8 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands
Index = index,
StartTime = item.StartTime,
CollectionType = item.CollectionType,
MediaCollectionId = item.MediaCollectionId,
TelevisionShowId = item.TelevisionShowId,
TelevisionSeasonId = item.TelevisionSeasonId,
CollectionId = item.CollectionId,
MediaItemId = item.MediaItemId,
PlayoutDuration = item.PlayoutDuration.GetValueOrDefault(),
OfflineTail = item.OfflineTail.GetValueOrDefault()
},

5
ErsatzTV.Application/ProgramSchedules/Commands/ReplaceProgramScheduleItems.cs

@ -13,9 +13,8 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands @@ -13,9 +13,8 @@ namespace ErsatzTV.Application.ProgramSchedules.Commands
TimeSpan? StartTime,
PlayoutMode PlayoutMode,
ProgramScheduleItemCollectionType CollectionType,
int? MediaCollectionId,
int? TelevisionShowId,
int? TelevisionSeasonId,
int? CollectionId,
int? MediaItemId,
int? MultipleCount,
TimeSpan? PlayoutDuration,
bool? OfflineTail) : IProgramScheduleItemRequest;

48
ErsatzTV.Application/ProgramSchedules/Mapper.cs

@ -18,14 +18,14 @@ namespace ErsatzTV.Application.ProgramSchedules @@ -18,14 +18,14 @@ namespace ErsatzTV.Application.ProgramSchedules
duration.StartType,
duration.StartTime,
duration.CollectionType,
duration.MediaCollection != null
? MediaCollections.Mapper.ProjectToViewModel(duration.MediaCollection)
duration.Collection != null
? MediaCollections.Mapper.ProjectToViewModel(duration.Collection)
: null,
duration.TelevisionShow != null
? Television.Mapper.ProjectToViewModel(duration.TelevisionShow)
duration.MediaItem is Show show
? Television.Mapper.ProjectToViewModel(show)
: null,
duration.TelevisionSeason != null
? Television.Mapper.ProjectToViewModel(duration.TelevisionSeason)
duration.MediaItem is Season season
? Television.Mapper.ProjectToViewModel(season)
: null,
duration.PlayoutDuration,
duration.OfflineTail),
@ -36,14 +36,14 @@ namespace ErsatzTV.Application.ProgramSchedules @@ -36,14 +36,14 @@ namespace ErsatzTV.Application.ProgramSchedules
flood.StartType,
flood.StartTime,
flood.CollectionType,
flood.MediaCollection != null
? MediaCollections.Mapper.ProjectToViewModel(flood.MediaCollection)
flood.Collection != null
? MediaCollections.Mapper.ProjectToViewModel(flood.Collection)
: null,
flood.TelevisionShow != null
? Television.Mapper.ProjectToViewModel(flood.TelevisionShow)
flood.MediaItem is Show show
? Television.Mapper.ProjectToViewModel(show)
: null,
flood.TelevisionSeason != null
? Television.Mapper.ProjectToViewModel(flood.TelevisionSeason)
flood.MediaItem is Season season
? Television.Mapper.ProjectToViewModel(season)
: null),
ProgramScheduleItemMultiple multiple =>
new ProgramScheduleItemMultipleViewModel(
@ -52,14 +52,14 @@ namespace ErsatzTV.Application.ProgramSchedules @@ -52,14 +52,14 @@ namespace ErsatzTV.Application.ProgramSchedules
multiple.StartType,
multiple.StartTime,
multiple.CollectionType,
multiple.MediaCollection != null
? MediaCollections.Mapper.ProjectToViewModel(multiple.MediaCollection)
multiple.Collection != null
? MediaCollections.Mapper.ProjectToViewModel(multiple.Collection)
: null,
multiple.TelevisionShow != null
? Television.Mapper.ProjectToViewModel(multiple.TelevisionShow)
multiple.MediaItem is Show show
? Television.Mapper.ProjectToViewModel(show)
: null,
multiple.TelevisionSeason != null
? Television.Mapper.ProjectToViewModel(multiple.TelevisionSeason)
multiple.MediaItem is Season season
? Television.Mapper.ProjectToViewModel(season)
: null,
multiple.Count),
ProgramScheduleItemOne one =>
@ -69,12 +69,14 @@ namespace ErsatzTV.Application.ProgramSchedules @@ -69,12 +69,14 @@ namespace ErsatzTV.Application.ProgramSchedules
one.StartType,
one.StartTime,
one.CollectionType,
one.MediaCollection != null
? MediaCollections.Mapper.ProjectToViewModel(one.MediaCollection)
one.Collection != null
? MediaCollections.Mapper.ProjectToViewModel(one.Collection)
: null,
one.TelevisionShow != null ? Television.Mapper.ProjectToViewModel(one.TelevisionShow) : null,
one.TelevisionSeason != null
? Television.Mapper.ProjectToViewModel(one.TelevisionSeason)
one.MediaItem is Show show
? Television.Mapper.ProjectToViewModel(show)
: null,
one.MediaItem is Season season
? Television.Mapper.ProjectToViewModel(season)
: null),
_ => throw new NotSupportedException(
$"Unsupported program schedule item type {programScheduleItem.GetType().Name}")

4
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemDurationViewModel.cs

@ -13,7 +13,7 @@ namespace ErsatzTV.Application.ProgramSchedules @@ -13,7 +13,7 @@ namespace ErsatzTV.Application.ProgramSchedules
StartType startType,
TimeSpan? startTime,
ProgramScheduleItemCollectionType collectionType,
MediaCollectionViewModel mediaCollection,
MediaCollectionViewModel collection,
TelevisionShowViewModel televisionShow,
TelevisionSeasonViewModel televisionSeason,
TimeSpan playoutDuration,
@ -24,7 +24,7 @@ namespace ErsatzTV.Application.ProgramSchedules @@ -24,7 +24,7 @@ namespace ErsatzTV.Application.ProgramSchedules
startTime,
PlayoutMode.Duration,
collectionType,
mediaCollection,
collection,
televisionShow,
televisionSeason)
{

4
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemFloodViewModel.cs

@ -13,7 +13,7 @@ namespace ErsatzTV.Application.ProgramSchedules @@ -13,7 +13,7 @@ namespace ErsatzTV.Application.ProgramSchedules
StartType startType,
TimeSpan? startTime,
ProgramScheduleItemCollectionType collectionType,
MediaCollectionViewModel mediaCollection,
MediaCollectionViewModel collection,
TelevisionShowViewModel televisionShow,
TelevisionSeasonViewModel televisionSeason) : base(
id,
@ -22,7 +22,7 @@ namespace ErsatzTV.Application.ProgramSchedules @@ -22,7 +22,7 @@ namespace ErsatzTV.Application.ProgramSchedules
startTime,
PlayoutMode.Flood,
collectionType,
mediaCollection,
collection,
televisionShow,
televisionSeason)
{

4
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemMultipleViewModel.cs

@ -13,7 +13,7 @@ namespace ErsatzTV.Application.ProgramSchedules @@ -13,7 +13,7 @@ namespace ErsatzTV.Application.ProgramSchedules
StartType startType,
TimeSpan? startTime,
ProgramScheduleItemCollectionType collectionType,
MediaCollectionViewModel mediaCollection,
MediaCollectionViewModel collection,
TelevisionShowViewModel televisionShow,
TelevisionSeasonViewModel televisionSeason,
int count) : base(
@ -23,7 +23,7 @@ namespace ErsatzTV.Application.ProgramSchedules @@ -23,7 +23,7 @@ namespace ErsatzTV.Application.ProgramSchedules
startTime,
PlayoutMode.Multiple,
collectionType,
mediaCollection,
collection,
televisionShow,
televisionSeason) =>
Count = count;

4
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemOneViewModel.cs

@ -13,7 +13,7 @@ namespace ErsatzTV.Application.ProgramSchedules @@ -13,7 +13,7 @@ namespace ErsatzTV.Application.ProgramSchedules
StartType startType,
TimeSpan? startTime,
ProgramScheduleItemCollectionType collectionType,
MediaCollectionViewModel mediaCollection,
MediaCollectionViewModel collection,
TelevisionShowViewModel televisionShow,
TelevisionSeasonViewModel televisionSeason) : base(
id,
@ -22,7 +22,7 @@ namespace ErsatzTV.Application.ProgramSchedules @@ -22,7 +22,7 @@ namespace ErsatzTV.Application.ProgramSchedules
startTime,
PlayoutMode.One,
collectionType,
mediaCollection,
collection,
televisionShow,
televisionSeason)
{

4
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemViewModel.cs

@ -12,13 +12,13 @@ namespace ErsatzTV.Application.ProgramSchedules @@ -12,13 +12,13 @@ namespace ErsatzTV.Application.ProgramSchedules
TimeSpan? StartTime,
PlayoutMode PlayoutMode,
ProgramScheduleItemCollectionType CollectionType,
MediaCollectionViewModel MediaCollection,
MediaCollectionViewModel Collection,
TelevisionShowViewModel TelevisionShow,
TelevisionSeasonViewModel TelevisionSeason)
{
public string Name => CollectionType switch
{
ProgramScheduleItemCollectionType.Collection => MediaCollection?.Name,
ProgramScheduleItemCollectionType.Collection => Collection?.Name,
ProgramScheduleItemCollectionType.TelevisionShow => $"{TelevisionShow?.Title} ({TelevisionShow?.Year})",
ProgramScheduleItemCollectionType.TelevisionSeason =>
$"{TelevisionSeason?.Title} ({TelevisionSeason?.Plot})",

33
ErsatzTV.Application/Television/Mapper.cs

@ -4,25 +4,30 @@ namespace ErsatzTV.Application.Television @@ -4,25 +4,30 @@ namespace ErsatzTV.Application.Television
{
internal static class Mapper
{
internal static TelevisionShowViewModel ProjectToViewModel(TelevisionShow show) =>
new(show.Id, show.Metadata.Title, show.Metadata.Year?.ToString(), show.Metadata.Plot, show.Poster);
internal static TelevisionShowViewModel ProjectToViewModel(Show show) =>
new(
show.Id,
show.ShowMetadata.HeadOrNone().Map(m => m.Title).IfNone(string.Empty),
show.ShowMetadata.HeadOrNone().Map(m => m.ReleaseDate?.Year.ToString()).IfNone(string.Empty),
show.ShowMetadata.HeadOrNone().Map(m => m.Plot).IfNone(string.Empty),
null); // TODO: artwork
internal static TelevisionSeasonViewModel ProjectToViewModel(TelevisionSeason season) =>
internal static TelevisionSeasonViewModel ProjectToViewModel(Season season) =>
new(
season.Id,
season.TelevisionShowId,
season.TelevisionShow.Metadata.Title,
season.TelevisionShow.Metadata.Year?.ToString(),
season.Number == 0 ? "Specials" : $"Season {season.Number}",
season.Poster);
season.ShowId,
season.Show.ShowMetadata.HeadOrNone().Map(m => m.Title).IfNone(string.Empty),
season.Show.ShowMetadata.HeadOrNone().Map(m => m.ReleaseDate?.Year.ToString()).IfNone(string.Empty),
season.SeasonNumber == 0 ? "Specials" : $"Season {season.SeasonNumber}",
null); // TODO: artwork
internal static TelevisionEpisodeViewModel ProjectToViewModel(TelevisionEpisodeMediaItem episode) =>
internal static TelevisionEpisodeViewModel ProjectToViewModel(Episode episode) =>
new(
episode.Season.TelevisionShowId,
episode.Season.ShowId,
episode.SeasonId,
episode.Metadata.Episode,
episode.Metadata.Title,
episode.Metadata.Plot,
episode.Poster);
episode.EpisodeNumber,
episode.EpisodeMetadata.HeadOrNone().Map(m => m.Title).IfNone(string.Empty),
episode.EpisodeMetadata.HeadOrNone().Map(m => m.Plot).IfNone(string.Empty),
null); // TODO: artwork
}
}

31
ErsatzTV.Core.Tests/Fakes/FakeMediaCollectionRepository.cs

@ -11,34 +11,25 @@ namespace ErsatzTV.Core.Tests.Fakes @@ -11,34 +11,25 @@ namespace ErsatzTV.Core.Tests.Fakes
{
public class FakeMediaCollectionRepository : IMediaCollectionRepository
{
private readonly Map<int, List<Movie>> _data;
private readonly Map<int, List<MediaItem>> _data;
public FakeMediaCollectionRepository(Map<int, List<Movie>> data) => _data = data;
public FakeMediaCollectionRepository(Map<int, List<MediaItem>> data) => _data = data;
public Task<SimpleMediaCollection> Add(SimpleMediaCollection collection) => throw new NotSupportedException();
public Task<Collection> Add(Collection collection) => throw new NotSupportedException();
public Task<Option<MediaCollection>> Get(int id) => throw new NotSupportedException();
public Task<Option<Collection>> Get(int id) => throw new NotSupportedException();
public Task<Option<SimpleMediaCollection>> GetSimpleMediaCollection(int id) =>
throw new NotSupportedException();
public Task<Option<Collection>> GetCollectionWithItems(int id) => throw new NotSupportedException();
public Task<Option<SimpleMediaCollection>> GetSimpleMediaCollectionWithItems(int id) =>
throw new NotSupportedException();
public Task<Option<Collection>> GetCollectionWithItemsUntracked(int id) => throw new NotSupportedException();
public Task<Option<SimpleMediaCollection>> GetSimpleMediaCollectionWithItemsUntracked(int id) =>
throw new NotSupportedException();
public Task<List<Collection>> GetAll() => throw new NotSupportedException();
public Task<List<SimpleMediaCollection>> GetSimpleMediaCollections() => throw new NotSupportedException();
public Task<Option<List<MediaItem>>> GetItems(int id) => Some(_data[id].ToList()).AsTask();
public Task<Option<List<MediaItem>>> GetSimpleMediaCollectionItems(int id) => throw new NotSupportedException();
public Task<List<MediaCollection>> GetAll() => throw new NotSupportedException();
public Task Update(Collection collection) => throw new NotSupportedException();
public Task<Option<List<MediaItem>>> GetItems(int id) => Some(_data[id].OfType<MediaItem>().ToList()).AsTask();
public Task<Option<List<MediaItem>>> GetSimpleMediaCollectionItems(int id) =>
throw new NotSupportedException();
public Task Update(SimpleMediaCollection collection) => throw new NotSupportedException();
public Task Delete(int mediaCollectionId) => throw new NotSupportedException();
public Task Delete(int collectionId) => throw new NotSupportedException();
}
}

58
ErsatzTV.Core.Tests/Fakes/FakeTelevisionRepository.cs

@ -9,66 +9,54 @@ namespace ErsatzTV.Core.Tests.Fakes @@ -9,66 +9,54 @@ namespace ErsatzTV.Core.Tests.Fakes
{
public class FakeTelevisionRepository : ITelevisionRepository
{
public Task<bool> Update(TelevisionShow show) => throw new NotSupportedException();
public Task<bool> Update(Show show) => throw new NotSupportedException();
public Task<bool> Update(TelevisionSeason season) => throw new NotSupportedException();
public Task<bool> Update(Season season) => throw new NotSupportedException();
public Task<bool> Update(TelevisionEpisodeMediaItem episode) => throw new NotSupportedException();
public Task<bool> Update(Episode episode) => throw new NotSupportedException();
public Task<List<TelevisionShow>> GetAllShows() => throw new NotSupportedException();
public Task<List<Show>> GetAllShows() => throw new NotSupportedException();
public Task<Option<TelevisionShow>> GetShow(int televisionShowId) => throw new NotSupportedException();
public Task<Option<Show>> GetShow(int televisionShowId) => throw new NotSupportedException();
public Task<int> GetShowCount() => throw new NotSupportedException();
public Task<List<TelevisionShow>> GetPagedShows(int pageNumber, int pageSize) =>
public Task<List<ShowMetadata>> GetPagedShows(int pageNumber, int pageSize) =>
throw new NotSupportedException();
public Task<List<TelevisionEpisodeMediaItem>> GetShowItems(int televisionShowId) =>
throw new NotSupportedException();
public Task<List<Episode>> GetShowItems(int televisionShowId) => throw new NotSupportedException();
public Task<List<TelevisionSeason>> GetAllSeasons() => throw new NotSupportedException();
public Task<List<Season>> GetAllSeasons() => throw new NotSupportedException();
public Task<Option<TelevisionSeason>> GetSeason(int televisionSeasonId) => throw new NotSupportedException();
public Task<Option<Season>> GetSeason(int televisionSeasonId) => throw new NotSupportedException();
public Task<int> GetSeasonCount(int televisionShowId) => throw new NotSupportedException();
public Task<List<TelevisionSeason>> GetPagedSeasons(int televisionShowId, int pageNumber, int pageSize) =>
public Task<List<Season>> GetPagedSeasons(int televisionShowId, int pageNumber, int pageSize) =>
throw new NotSupportedException();
public Task<List<TelevisionEpisodeMediaItem>> GetSeasonItems(int televisionSeasonId) =>
throw new NotSupportedException();
public Task<List<Episode>> GetSeasonItems(int televisionSeasonId) => throw new NotSupportedException();
public Task<Option<TelevisionEpisodeMediaItem>> GetEpisode(int televisionEpisodeId) =>
throw new NotSupportedException();
public Task<Option<Episode>> GetEpisode(int televisionEpisodeId) => throw new NotSupportedException();
public Task<int> GetEpisodeCount(int televisionSeasonId) => throw new NotSupportedException();
public Task<List<TelevisionEpisodeMediaItem>> GetPagedEpisodes(
int televisionSeasonId,
int pageNumber,
int pageSize) => throw new NotSupportedException();
public Task<Option<TelevisionShow>> GetShowByPath(int mediaSourceId, string path) =>
public Task<List<EpisodeMetadata>> GetPagedEpisodes(int televisionSeasonId, int pageNumber, int pageSize) =>
throw new NotSupportedException();
public Task<Option<TelevisionShow>> GetShowByMetadata(TelevisionShowMetadata metadata) =>
throw new NotSupportedException();
public Task<Option<Show>> GetShowByPath(int mediaSourceId, string path) => throw new NotSupportedException();
public Task<Either<BaseError, TelevisionShow>> AddShow(
int localMediaSourceId,
string showFolder,
TelevisionShowMetadata metadata) => throw new NotSupportedException();
public Task<Option<Show>> GetShowByMetadata(ShowMetadata metadata) => throw new NotSupportedException();
public Task<Either<BaseError, TelevisionSeason>> GetOrAddSeason(
TelevisionShow show,
string path,
int seasonNumber) => throw new NotSupportedException();
public Task<Either<BaseError, Show>>
AddShow(int localMediaSourceId, string showFolder, ShowMetadata metadata) =>
throw new NotSupportedException();
public Task<Either<BaseError, Season>> GetOrAddSeason(Show show, string path, int seasonNumber) =>
throw new NotSupportedException();
public Task<Either<BaseError, TelevisionEpisodeMediaItem>> GetOrAddEpisode(
TelevisionSeason season,
LibraryPath libraryPath,
string path) => throw new NotSupportedException();
public Task<Either<BaseError, Episode>> GetOrAddEpisode(Season season, LibraryPath libraryPath, string path) =>
throw new NotSupportedException();
public Task<Unit> DeleteMissingSources(int localMediaSourceId, List<string> allFolders) =>
throw new NotSupportedException();

9
ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs

@ -37,13 +37,14 @@ namespace ErsatzTV.Core.Tests.Metadata @@ -37,13 +37,14 @@ namespace ErsatzTV.Core.Tests.Metadata
2)]
public void GetFallbackMetadata_ShouldHandleVariousFormats(string path, string title, int season, int episode)
{
TelevisionEpisodeMetadata metadata = FallbackMetadataProvider.GetFallbackMetadata(
new TelevisionEpisodeMediaItem
EpisodeMetadata metadata = FallbackMetadataProvider.GetFallbackMetadata(
new Episode
{ Path = path, LibraryPath = new LibraryPath() });
metadata.Title.Should().Be(title);
metadata.Season.Should().Be(season);
metadata.Episode.Should().Be(episode);
// metadata.Season.Should().Be(season);
// metadata.Episode.Should().Be(episode);
// TODO: how do we test season and episode number?
}
}
}

15
ErsatzTV.Core.Tests/Scheduling/ChronologicalContentTests.cs

@ -16,7 +16,7 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -16,7 +16,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
public void Episodes_Should_Sort_By_Aired()
{
List<MediaItem> contents = Episodes(10);
var state = new MediaCollectionEnumeratorState();
var state = new CollectionEnumeratorState();
var chronologicalContent = new ChronologicalMediaCollectionEnumerator(contents, state);
@ -32,7 +32,7 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -32,7 +32,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
public void State_Index_Should_Increment()
{
List<MediaItem> contents = Episodes(10);
var state = new MediaCollectionEnumeratorState();
var state = new CollectionEnumeratorState();
var chronologicalContent = new ChronologicalMediaCollectionEnumerator(contents, state);
@ -47,7 +47,7 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -47,7 +47,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
public void State_Should_Impact_Iterator_Start()
{
List<MediaItem> contents = Episodes(10);
var state = new MediaCollectionEnumeratorState { Index = 5 };
var state = new CollectionEnumeratorState { Index = 5 };
var chronologicalContent = new ChronologicalMediaCollectionEnumerator(contents, state);
@ -62,12 +62,15 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -62,12 +62,15 @@ namespace ErsatzTV.Core.Tests.Scheduling
private static List<MediaItem> Episodes(int count) =>
Range(1, count).Map(
i => (MediaItem) new TelevisionEpisodeMediaItem
i => (MediaItem) new Episode
{
Id = i,
Metadata = new TelevisionEpisodeMetadata
EpisodeMetadata = new List<EpisodeMetadata>
{
Aired = new DateTime(2020, 1, i)
new()
{
ReleaseDate = new DateTime(2020, 1, i)
}
}
})
.Reverse()

106
ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs

@ -40,7 +40,7 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -40,7 +40,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
[Test]
public async Task InitialFlood_Should_StartAtMidnight()
{
var mediaItems = new List<Movie>
var mediaItems = new List<MediaItem>
{
TestMovie(1, TimeSpan.FromHours(6), DateTime.Today)
};
@ -59,7 +59,7 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -59,7 +59,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
[Test]
public async Task InitialFlood_Should_StartAtMidnight_With_LateStart()
{
var mediaItems = new List<Movie>
var mediaItems = new List<MediaItem>
{
TestMovie(1, TimeSpan.FromHours(6), DateTime.Today)
};
@ -79,7 +79,7 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -79,7 +79,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
[Test]
public async Task ChronologicalContent_Should_CreateChronologicalItems()
{
var mediaItems = new List<Movie>
var mediaItems = new List<MediaItem>
{
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 @@ -105,7 +105,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
[Test]
public async Task ChronologicalFlood_Should_AnchorAndMaintainExistingPlayout()
{
var mediaItems = new List<Movie>
var mediaItems = new List<MediaItem>
{
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 @@ -142,7 +142,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
[Test]
public async Task ChronologicalFlood_Should_AnchorAndReturnNewPlayoutItems()
{
var mediaItems = new List<Movie>
var mediaItems = new List<MediaItem>
{
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 @@ -180,7 +180,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
[Test]
public async Task ShuffleFloodRebuild_Should_IgnoreAnchors()
{
var mediaItems = new List<Movie>
var mediaItems = new List<MediaItem>
{
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 @@ -223,7 +223,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
[Test]
public async Task ShuffleFlood_Should_MaintainRandomSeed()
{
var mediaItems = new List<Movie>
var mediaItems = new List<MediaItem>
{
TestMovie(1, TimeSpan.FromHours(1), DateTime.Today),
TestMovie(2, TimeSpan.FromHours(1), DateTime.Today.AddHours(1)),
@ -255,22 +255,22 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -255,22 +255,22 @@ namespace ErsatzTV.Core.Tests.Scheduling
[Test]
public async Task FloodContent_Should_FloodAroundFixedContent_One()
{
var floodCollection = new SimpleMediaCollection
var floodCollection = new Collection
{
Id = 1,
Name = "Flood Items",
Movies = new List<Movie>
MediaItems = new List<MediaItem>
{
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)),
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1))
}
};
var fixedCollection = new SimpleMediaCollection
var fixedCollection = new Collection
{
Id = 2,
Name = "Fixed Items",
Movies = new List<Movie>
MediaItems = new List<MediaItem>
{
TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1))
}
@ -278,23 +278,23 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -278,23 +278,23 @@ namespace ErsatzTV.Core.Tests.Scheduling
var fakeRepository = new FakeMediaCollectionRepository(
Map(
(floodCollection.Id, floodCollection.Movies.ToList()),
(fixedCollection.Id, fixedCollection.Movies.ToList())));
(floodCollection.Id, floodCollection.MediaItems.ToList()),
(fixedCollection.Id, fixedCollection.MediaItems.ToList())));
var items = new List<ProgramScheduleItem>
{
new ProgramScheduleItemFlood
{
Index = 1,
MediaCollection = floodCollection,
MediaCollectionId = floodCollection.Id,
Collection = floodCollection,
CollectionId = floodCollection.Id,
StartTime = null
},
new ProgramScheduleItemOne
{
Index = 2,
MediaCollection = fixedCollection,
MediaCollectionId = fixedCollection.Id,
Collection = fixedCollection,
CollectionId = fixedCollection.Id,
StartTime = TimeSpan.FromHours(3)
}
};
@ -333,22 +333,22 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -333,22 +333,22 @@ namespace ErsatzTV.Core.Tests.Scheduling
[Test]
public async Task FloodContent_Should_FloodAroundFixedContent_Multiple()
{
var floodCollection = new SimpleMediaCollection
var floodCollection = new Collection
{
Id = 1,
Name = "Flood Items",
Movies = new List<Movie>
MediaItems = new List<MediaItem>
{
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)),
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1))
}
};
var fixedCollection = new SimpleMediaCollection
var fixedCollection = new Collection
{
Id = 2,
Name = "Fixed Items",
Movies = new List<Movie>
MediaItems = new List<MediaItem>
{
TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)),
TestMovie(4, TimeSpan.FromHours(1), new DateTime(2020, 1, 2))
@ -357,23 +357,23 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -357,23 +357,23 @@ namespace ErsatzTV.Core.Tests.Scheduling
var fakeRepository = new FakeMediaCollectionRepository(
Map(
(floodCollection.Id, floodCollection.Movies.ToList()),
(fixedCollection.Id, fixedCollection.Movies.ToList())));
(floodCollection.Id, floodCollection.MediaItems.ToList()),
(fixedCollection.Id, fixedCollection.MediaItems.ToList())));
var items = new List<ProgramScheduleItem>
{
new ProgramScheduleItemFlood
{
Index = 1,
MediaCollection = floodCollection,
MediaCollectionId = floodCollection.Id,
Collection = floodCollection,
CollectionId = floodCollection.Id,
StartTime = null
},
new ProgramScheduleItemMultiple
{
Index = 2,
MediaCollection = fixedCollection,
MediaCollectionId = fixedCollection.Id,
Collection = fixedCollection,
CollectionId = fixedCollection.Id,
StartTime = TimeSpan.FromHours(3),
Count = 2
}
@ -418,22 +418,22 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -418,22 +418,22 @@ namespace ErsatzTV.Core.Tests.Scheduling
[Test]
public async Task FloodContent_Should_FloodAroundFixedContent_DurationWithoutOfflineTail()
{
var floodCollection = new SimpleMediaCollection
var floodCollection = new Collection
{
Id = 1,
Name = "Flood Items",
Movies = new List<Movie>
MediaItems = new List<MediaItem>
{
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)),
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1))
}
};
var fixedCollection = new SimpleMediaCollection
var fixedCollection = new Collection
{
Id = 2,
Name = "Fixed Items",
Movies = new List<Movie>
MediaItems = new List<MediaItem>
{
TestMovie(3, TimeSpan.FromHours(0.75), new DateTime(2020, 1, 1)),
TestMovie(4, TimeSpan.FromHours(1.5), new DateTime(2020, 1, 2))
@ -442,23 +442,23 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -442,23 +442,23 @@ namespace ErsatzTV.Core.Tests.Scheduling
var fakeRepository = new FakeMediaCollectionRepository(
Map(
(floodCollection.Id, floodCollection.Movies.ToList()),
(fixedCollection.Id, fixedCollection.Movies.ToList())));
(floodCollection.Id, floodCollection.MediaItems.ToList()),
(fixedCollection.Id, fixedCollection.MediaItems.ToList())));
var items = new List<ProgramScheduleItem>
{
new ProgramScheduleItemFlood
{
Index = 1,
MediaCollection = floodCollection,
MediaCollectionId = floodCollection.Id,
Collection = floodCollection,
CollectionId = floodCollection.Id,
StartTime = null
},
new ProgramScheduleItemDuration
{
Index = 2,
MediaCollection = fixedCollection,
MediaCollectionId = fixedCollection.Id,
Collection = fixedCollection,
CollectionId = fixedCollection.Id,
StartTime = TimeSpan.FromHours(2),
PlayoutDuration = TimeSpan.FromHours(2),
OfflineTail = false // immediately continue
@ -507,22 +507,22 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -507,22 +507,22 @@ namespace ErsatzTV.Core.Tests.Scheduling
[Test]
public async Task MultipleContent_Should_WrapAroundDynamicContent_DurationWithoutOfflineTail()
{
var multipleCollection = new SimpleMediaCollection
var multipleCollection = new Collection
{
Id = 1,
Name = "Multiple Items",
Movies = new List<Movie>
MediaItems = new List<MediaItem>
{
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)),
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1))
}
};
var dynamicCollection = new SimpleMediaCollection
var dynamicCollection = new Collection
{
Id = 2,
Name = "Dynamic Items",
Movies = new List<Movie>
MediaItems = new List<MediaItem>
{
TestMovie(3, TimeSpan.FromHours(0.75), new DateTime(2020, 1, 1)),
TestMovie(4, TimeSpan.FromHours(1.5), new DateTime(2020, 1, 2))
@ -531,24 +531,24 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -531,24 +531,24 @@ namespace ErsatzTV.Core.Tests.Scheduling
var fakeRepository = new FakeMediaCollectionRepository(
Map(
(multipleCollection.Id, multipleCollection.Movies.ToList()),
(dynamicCollection.Id, dynamicCollection.Movies.ToList())));
(multipleCollection.Id, multipleCollection.MediaItems.ToList()),
(dynamicCollection.Id, dynamicCollection.MediaItems.ToList())));
var items = new List<ProgramScheduleItem>
{
new ProgramScheduleItemMultiple
{
Index = 1,
MediaCollection = multipleCollection,
MediaCollectionId = multipleCollection.Id,
Collection = multipleCollection,
CollectionId = multipleCollection.Id,
StartTime = null,
Count = 2
},
new ProgramScheduleItemDuration
{
Index = 2,
MediaCollection = dynamicCollection,
MediaCollectionId = dynamicCollection.Id,
Collection = dynamicCollection,
CollectionId = dynamicCollection.Id,
StartTime = null,
PlayoutDuration = TimeSpan.FromHours(2),
OfflineTail = false // immediately continue
@ -598,12 +598,12 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -598,12 +598,12 @@ namespace ErsatzTV.Core.Tests.Scheduling
return now - now.TimeOfDay + TimeSpan.FromHours(hours);
}
private static ProgramScheduleItem Flood(MediaCollection mediaCollection) =>
private static ProgramScheduleItem Flood(Collection mediaCollection) =>
new ProgramScheduleItemFlood
{
Index = 1,
MediaCollection = mediaCollection,
MediaCollectionId = mediaCollection.Id,
Collection = mediaCollection,
CollectionId = mediaCollection.Id,
StartTime = null
};
@ -615,12 +615,12 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -615,12 +615,12 @@ namespace ErsatzTV.Core.Tests.Scheduling
Statistics = new MediaItemStatistics { Duration = duration }
};
private TestData TestDataFloodForItems(List<Movie> mediaItems, PlaybackOrder playbackOrder)
private TestData TestDataFloodForItems(List<MediaItem> mediaItems, PlaybackOrder playbackOrder)
{
var mediaCollection = new SimpleMediaCollection
var mediaCollection = new Collection
{
Id = 1,
Movies = mediaItems
MediaItems = mediaItems
};
var collectionRepo = new FakeMediaCollectionRepository(Map((mediaCollection.Id, mediaItems)));

15
ErsatzTV.Core.Tests/Scheduling/RandomizedContentTests.cs

@ -26,7 +26,7 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -26,7 +26,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
{
List<MediaItem> contents = Episodes(10);
var state = new MediaCollectionEnumeratorState();
var state = new CollectionEnumeratorState();
var randomizedContent = new RandomizedMediaCollectionEnumerator(contents, state);
@ -47,7 +47,7 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -47,7 +47,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
public void State_Index_Should_Increment()
{
List<MediaItem> contents = Episodes(10);
var state = new MediaCollectionEnumeratorState();
var state = new CollectionEnumeratorState();
var randomizedContent = new RandomizedMediaCollectionEnumerator(contents, state);
@ -63,7 +63,7 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -63,7 +63,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
public void State_Should_Impact_Iterator_Start()
{
List<MediaItem> contents = Episodes(10);
var state = new MediaCollectionEnumeratorState { Index = 5, Seed = KnownSeed };
var state = new CollectionEnumeratorState { Index = 5, Seed = KnownSeed };
var randomizedContent = new RandomizedMediaCollectionEnumerator(contents, state);
@ -80,12 +80,15 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -80,12 +80,15 @@ namespace ErsatzTV.Core.Tests.Scheduling
private static List<MediaItem> Episodes(int count) =>
Range(1, count).Map(
i => (MediaItem) new TelevisionEpisodeMediaItem
i => (MediaItem) new Episode
{
Id = i,
Metadata = new TelevisionEpisodeMetadata
EpisodeMetadata = new List<EpisodeMetadata>
{
Aired = new DateTime(2020, 1, i)
new()
{
ReleaseDate = new DateTime(2020, 1, i)
}
}
})
.Reverse()

15
ErsatzTV.Core.Tests/Scheduling/ShuffledContentTests.cs

@ -20,7 +20,7 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -20,7 +20,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
{
List<MediaItem> contents = Episodes(10);
var state = new MediaCollectionEnumeratorState();
var state = new CollectionEnumeratorState();
var shuffledContent = new ShuffledMediaCollectionEnumerator(contents, state);
@ -40,7 +40,7 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -40,7 +40,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
public void State_Index_Should_Increment()
{
List<MediaItem> contents = Episodes(10);
var state = new MediaCollectionEnumeratorState();
var state = new CollectionEnumeratorState();
var shuffledContent = new ShuffledMediaCollectionEnumerator(contents, state);
@ -55,7 +55,7 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -55,7 +55,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
public void State_Should_Impact_Iterator_Start()
{
List<MediaItem> contents = Episodes(10);
var state = new MediaCollectionEnumeratorState { Index = 5, Seed = MagicSeed };
var state = new CollectionEnumeratorState { Index = 5, Seed = MagicSeed };
var shuffledContent = new ShuffledMediaCollectionEnumerator(contents, state);
@ -70,12 +70,15 @@ namespace ErsatzTV.Core.Tests.Scheduling @@ -70,12 +70,15 @@ namespace ErsatzTV.Core.Tests.Scheduling
private static List<MediaItem> Episodes(int count) =>
Range(1, count).Map(
i => (MediaItem) new TelevisionEpisodeMediaItem
i => (MediaItem) new Episode
{
Id = i,
Metadata = new TelevisionEpisodeMetadata
EpisodeMetadata = new List<EpisodeMetadata>
{
Aired = new DateTime(2020, 1, i)
new()
{
ReleaseDate = new DateTime(2020, 1, i)
}
}
})
.Reverse()

2
ErsatzTV.Core/Domain/MediaCollectionEnumeratorState.cs → ErsatzTV.Core/Domain/CollectionEnumeratorState.cs

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
namespace ErsatzTV.Core.Domain
{
public class MediaCollectionEnumeratorState
public class CollectionEnumeratorState
{
public int Seed { get; set; }
public int Index { get; set; }

9
ErsatzTV.Core/Domain/LocalTelevisionShowSource.cs

@ -1,9 +0,0 @@ @@ -1,9 +0,0 @@
namespace ErsatzTV.Core.Domain
{
public class LocalTelevisionShowSource : TelevisionShowSource
{
public int MediaSourceId { get; set; }
public LocalMediaSource MediaSource { get; set; }
public string Path { get; set; }
}
}

10
ErsatzTV.Core/Domain/MediaCollection.cs

@ -1,10 +0,0 @@ @@ -1,10 +0,0 @@
using LanguageExt;
namespace ErsatzTV.Core.Domain
{
public abstract class MediaCollection : Record<MediaCollection>
{
public int Id { get; set; }
public string Name { get; set; }
}
}

4
ErsatzTV.Core/Domain/MediaItem/Movie.cs

@ -5,10 +5,6 @@ namespace ErsatzTV.Core.Domain @@ -5,10 +5,6 @@ namespace ErsatzTV.Core.Domain
public class Movie : MediaItem
{
public List<MovieMetadata> MovieMetadata { get; set; }
public List<MediaVersion> MediaVersions { get; set; }
// TODO: remove SimpleMediaCollections
public List<SimpleMediaCollection> SimpleMediaCollections { get; set; }
}
}

2
ErsatzTV.Core/Domain/PlayoutProgramScheduleAnchor.cs

@ -18,6 +18,6 @@ @@ -18,6 +18,6 @@
public Collection Collection { get; set; }
public int? MediaItemId { get; set; }
public MediaItem MediaItem { get; set; }
public MediaCollectionEnumeratorState EnumeratorState { get; set; }
public CollectionEnumeratorState EnumeratorState { get; set; }
}
}

10
ErsatzTV.Core/Domain/ProgramScheduleItem.cs

@ -8,19 +8,9 @@ namespace ErsatzTV.Core.Domain @@ -8,19 +8,9 @@ namespace ErsatzTV.Core.Domain
public int Index { get; set; }
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; }
public TelevisionShow TelevisionShow { get; set; }
public int? TelevisionSeasonId { get; set; }
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; }

12
ErsatzTV.Core/Domain/SimpleMediaCollection.cs

@ -1,12 +0,0 @@ @@ -1,12 +0,0 @@
using System.Collections.Generic;
namespace ErsatzTV.Core.Domain
{
public class SimpleMediaCollection : MediaCollection
{
public List<Movie> Movies { get; set; }
public List<TelevisionShow> TelevisionShows { get; set; }
public List<TelevisionSeason> TelevisionSeasons { get; set; }
public List<TelevisionEpisodeMediaItem> TelevisionEpisodes { get; set; }
}
}

12
ErsatzTV.Core/Domain/TelevisionEpisodeMediaItem.cs

@ -1,12 +0,0 @@ @@ -1,12 +0,0 @@
using System.Collections.Generic;
namespace ErsatzTV.Core.Domain
{
public class TelevisionEpisodeMediaItem : MediaItem
{
public int SeasonId { get; set; }
public TelevisionSeason Season { get; set; }
public TelevisionEpisodeMetadata Metadata { get; set; }
public List<SimpleMediaCollection> SimpleMediaCollections { get; set; }
}
}

15
ErsatzTV.Core/Domain/TelevisionEpisodeMetadata.cs

@ -1,15 +0,0 @@ @@ -1,15 +0,0 @@
using System;
namespace ErsatzTV.Core.Domain
{
public class TelevisionEpisodeMetadata : MediaItemMetadata
{
public int Id { get; set; }
public int TelevisionEpisodeId { get; set; }
public TelevisionEpisodeMediaItem TelevisionEpisode { get; set; }
public int Season { get; set; }
public int Episode { get; set; }
public string Plot { get; set; }
public DateTime? Aired { get; set; }
}
}

19
ErsatzTV.Core/Domain/TelevisionSeason.cs

@ -1,19 +0,0 @@ @@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
using ErsatzTV.Core.Interfaces.Domain;
namespace ErsatzTV.Core.Domain
{
public class TelevisionSeason : IHasAPoster
{
public int Id { get; set; }
public int TelevisionShowId { get; set; }
public TelevisionShow TelevisionShow { get; set; }
public int Number { get; set; }
public List<TelevisionEpisodeMediaItem> Episodes { get; set; }
public List<SimpleMediaCollection> SimpleMediaCollections { get; set; }
public string Path { get; set; }
public string Poster { get; set; }
public DateTime? PosterLastWriteTime { get; set; }
}
}

16
ErsatzTV.Core/Domain/TelevisionShow.cs

@ -1,16 +0,0 @@ @@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
namespace ErsatzTV.Core.Domain
{
public class TelevisionShow
{
public int Id { get; set; }
public List<TelevisionShowSource> Sources { get; set; }
public TelevisionShowMetadata Metadata { get; set; }
public List<TelevisionSeason> Seasons { get; set; }
public string Poster { get; set; }
public DateTime? PosterLastWriteTime { get; set; }
public List<SimpleMediaCollection> SimpleMediaCollections { get; set; }
}
}

21
ErsatzTV.Core/Domain/TelevisionShowMetadata.cs

@ -1,21 +0,0 @@ @@ -1,21 +0,0 @@
using System;
namespace ErsatzTV.Core.Domain
{
public class TelevisionShowMetadata
{
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; }
public string SortTitle { get; set; }
public int? Year { get; set; }
public string Plot { get; set; }
}
}

11
ErsatzTV.Core/Domain/TelevisionShowSource.cs

@ -1,11 +0,0 @@ @@ -1,11 +0,0 @@
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; }
public int TelevisionShowId { get; set; }
public TelevisionShow TelevisionShow { get; set; }
}
}

2
ErsatzTV.Core/Interfaces/Metadata/IFallbackMetadataProvider.cs

@ -4,6 +4,6 @@ namespace ErsatzTV.Core.Interfaces.Metadata @@ -4,6 +4,6 @@ namespace ErsatzTV.Core.Interfaces.Metadata
{
public interface IFallbackMetadataProvider
{
TelevisionShowMetadata GetFallbackMetadataForShow(string showFolder);
ShowMetadata GetFallbackMetadataForShow(string showFolder);
}
}

6
ErsatzTV.Core/Interfaces/Metadata/ILocalMetadataProvider.cs

@ -6,10 +6,10 @@ namespace ErsatzTV.Core.Interfaces.Metadata @@ -6,10 +6,10 @@ namespace ErsatzTV.Core.Interfaces.Metadata
{
public interface ILocalMetadataProvider
{
Task<TelevisionShowMetadata> GetMetadataForShow(string showFolder);
Task<ShowMetadata> GetMetadataForShow(string showFolder);
Task<Unit> RefreshSidecarMetadata(MediaItem mediaItem, string path);
Task<Unit> RefreshSidecarMetadata(TelevisionShow televisionShow, string showFolder);
Task<Unit> RefreshSidecarMetadata(Show televisionShow, string showFolder);
Task<Unit> RefreshFallbackMetadata(MediaItem mediaItem);
Task<Unit> RefreshFallbackMetadata(TelevisionShow televisionShow, string showFolder);
Task<Unit> RefreshFallbackMetadata(Show televisionShow, string showFolder);
}
}

11
ErsatzTV.Core/Interfaces/Repositories/ICollectionRepository.cs

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
using LanguageExt;
namespace ErsatzTV.Core.Interfaces.Repositories
{
public interface ICollectionRepository
{
public Task<Option<Collection>> GetWithItemsUntracked(int collectionId);
}
}

17
ErsatzTV.Core/Interfaces/Repositories/IMediaCollectionRepository.cs

@ -7,16 +7,13 @@ namespace ErsatzTV.Core.Interfaces.Repositories @@ -7,16 +7,13 @@ namespace ErsatzTV.Core.Interfaces.Repositories
{
public interface IMediaCollectionRepository
{
Task<SimpleMediaCollection> Add(SimpleMediaCollection collection);
Task<Option<MediaCollection>> Get(int id);
Task<Option<SimpleMediaCollection>> GetSimpleMediaCollection(int id);
Task<Option<SimpleMediaCollection>> GetSimpleMediaCollectionWithItems(int id);
Task<Option<SimpleMediaCollection>> GetSimpleMediaCollectionWithItemsUntracked(int id);
Task<List<SimpleMediaCollection>> GetSimpleMediaCollections();
Task<List<MediaCollection>> GetAll();
Task<Collection> Add(Collection collection);
Task<Option<Collection>> Get(int id);
Task<Option<Collection>> GetCollectionWithItems(int id);
Task<Option<Collection>> GetCollectionWithItemsUntracked(int id);
Task<List<Collection>> GetAll();
Task<Option<List<MediaItem>>> GetItems(int id);
Task<Option<List<MediaItem>>> GetSimpleMediaCollectionItems(int id);
Task Update(SimpleMediaCollection collection);
Task Delete(int mediaCollectionId);
Task Update(Collection collection);
Task Delete(int collectionId);
}
}

42
ErsatzTV.Core/Interfaces/Repositories/ITelevisionRepository.cs

@ -7,41 +7,39 @@ namespace ErsatzTV.Core.Interfaces.Repositories @@ -7,41 +7,39 @@ namespace ErsatzTV.Core.Interfaces.Repositories
{
public interface ITelevisionRepository
{
Task<bool> Update(TelevisionShow show);
Task<bool> Update(TelevisionSeason season);
Task<bool> Update(TelevisionEpisodeMediaItem episode);
Task<List<TelevisionShow>> GetAllShows();
Task<Option<TelevisionShow>> GetShow(int televisionShowId);
Task<bool> Update(Show show);
Task<bool> Update(Season season);
Task<bool> Update(Episode episode);
Task<List<Show>> GetAllShows();
Task<Option<Show>> GetShow(int televisionShowId);
Task<int> GetShowCount();
Task<List<TelevisionShow>> GetPagedShows(int pageNumber, int pageSize);
Task<List<TelevisionEpisodeMediaItem>> GetShowItems(int televisionShowId);
Task<List<TelevisionSeason>> GetAllSeasons();
Task<Option<TelevisionSeason>> GetSeason(int televisionSeasonId);
Task<List<ShowMetadata>> GetPagedShows(int pageNumber, int pageSize);
Task<List<Episode>> GetShowItems(int televisionShowId);
Task<List<Season>> GetAllSeasons();
Task<Option<Season>> GetSeason(int televisionSeasonId);
Task<int> GetSeasonCount(int televisionShowId);
Task<List<TelevisionSeason>> GetPagedSeasons(int televisionShowId, int pageNumber, int pageSize);
Task<List<TelevisionEpisodeMediaItem>> GetSeasonItems(int televisionSeasonId);
Task<Option<TelevisionEpisodeMediaItem>> GetEpisode(int televisionEpisodeId);
Task<List<Season>> GetPagedSeasons(int televisionShowId, int pageNumber, int pageSize);
Task<List<Episode>> GetSeasonItems(int televisionSeasonId);
Task<Option<Episode>> GetEpisode(int televisionEpisodeId);
Task<int> GetEpisodeCount(int televisionSeasonId);
Task<List<TelevisionEpisodeMediaItem>> GetPagedEpisodes(int televisionSeasonId, int pageNumber, int pageSize);
Task<Option<TelevisionShow>> GetShowByPath(int mediaSourceId, string path);
Task<Option<TelevisionShow>> GetShowByMetadata(TelevisionShowMetadata metadata);
Task<List<EpisodeMetadata>> GetPagedEpisodes(int televisionSeasonId, int pageNumber, int pageSize);
Task<Option<Show>> GetShowByMetadata(ShowMetadata metadata);
Task<Either<BaseError, TelevisionShow>> AddShow(
Task<Either<BaseError, Show>> AddShow(
int localMediaSourceId,
string showFolder,
TelevisionShowMetadata metadata);
ShowMetadata metadata);
Task<Either<BaseError, TelevisionSeason>> GetOrAddSeason(
TelevisionShow show,
Task<Either<BaseError, Season>> GetOrAddSeason(
Show show,
string path,
int seasonNumber);
Task<Either<BaseError, TelevisionEpisodeMediaItem>> GetOrAddEpisode(
TelevisionSeason season,
Task<Either<BaseError, Episode>> GetOrAddEpisode(
Season season,
LibraryPath libraryPath,
string path);
Task<Unit> DeleteMissingSources(int localMediaSourceId, List<string> allFolders);
Task<Unit> DeleteEmptyShows();
}
}

2
ErsatzTV.Core/Interfaces/Scheduling/IMediaCollectionEnumerator.cs

@ -5,7 +5,7 @@ namespace ErsatzTV.Core.Interfaces.Scheduling @@ -5,7 +5,7 @@ namespace ErsatzTV.Core.Interfaces.Scheduling
{
public interface IMediaCollectionEnumerator
{
MediaCollectionEnumeratorState State { get; }
CollectionEnumeratorState State { get; }
Option<MediaItem> Current { get; }
void MoveNext();
}

10
ErsatzTV.Core/Iptv/ChannelGuide.cs

@ -61,14 +61,14 @@ namespace ErsatzTV.Core.Iptv @@ -61,14 +61,14 @@ namespace ErsatzTV.Core.Iptv
string title = playoutItem.MediaItem switch
{
Movie m => m.MovieMetadata.HeadOrNone().Match(mm => mm.Title, () => m.Path),
TelevisionEpisodeMediaItem e => e.Metadata?.Title ?? e.Path,
Episode e => e.EpisodeMetadata.HeadOrNone().Match(em => em.Title, () => e.Path),
_ => "[unknown]"
};
string description = playoutItem.MediaItem switch
{
Movie m => m.MovieMetadata.HeadOrNone().Map(mm => mm.Plot).IfNone(string.Empty),
TelevisionEpisodeMediaItem e => e.Metadata?.Plot,
Episode e => e.EpisodeMetadata.HeadOrNone().Map(em => em.Plot).IfNone(string.Empty),
_ => string.Empty
};
@ -96,10 +96,10 @@ namespace ErsatzTV.Core.Iptv @@ -96,10 +96,10 @@ namespace ErsatzTV.Core.Iptv
xml.WriteAttributeString("lang", "en");
xml.WriteEndElement(); // sub-title
if (playoutItem.MediaItem is TelevisionEpisodeMediaItem episode)
if (playoutItem.MediaItem is Episode episode)
{
int s = Optional(episode.Metadata?.Season).IfNone(0);
int e = Optional(episode.Metadata?.Episode).IfNone(0);
int s = Optional(episode.Season?.SeasonNumber).IfNone(0);
int e = episode.EpisodeNumber;
if (s > 0 && e > 0)
{
xml.WriteStartElement("episode-num");

27
ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs

@ -8,19 +8,19 @@ namespace ErsatzTV.Core.Metadata @@ -8,19 +8,19 @@ namespace ErsatzTV.Core.Metadata
{
public class FallbackMetadataProvider : IFallbackMetadataProvider
{
public TelevisionShowMetadata GetFallbackMetadataForShow(string showFolder)
public ShowMetadata GetFallbackMetadataForShow(string showFolder)
{
string fileName = Path.GetFileName(showFolder);
var metadata = new TelevisionShowMetadata
{ Source = MetadataSource.Fallback, Title = fileName ?? showFolder };
var metadata = new ShowMetadata
{ MetadataKind = MetadataKind.Fallback, Title = fileName ?? showFolder };
return GetTelevisionShowMetadata(fileName, metadata);
}
public static TelevisionEpisodeMetadata GetFallbackMetadata(TelevisionEpisodeMediaItem mediaItem)
public static EpisodeMetadata GetFallbackMetadata(Episode episode)
{
string fileName = Path.GetFileName(mediaItem.Path);
var metadata = new TelevisionEpisodeMetadata
{ Source = MetadataSource.Fallback, Title = fileName ?? mediaItem.Path };
string fileName = Path.GetFileName(episode.Path);
var metadata = new EpisodeMetadata
{ MetadataKind = MetadataKind.Fallback, Title = fileName ?? episode.Path };
if (fileName != null)
{
@ -55,7 +55,7 @@ namespace ErsatzTV.Core.Metadata @@ -55,7 +55,7 @@ namespace ErsatzTV.Core.Metadata
return metadata;
}
private static TelevisionEpisodeMetadata GetEpisodeMetadata(string fileName, TelevisionEpisodeMetadata metadata)
private static EpisodeMetadata GetEpisodeMetadata(string fileName, EpisodeMetadata metadata)
{
try
{
@ -64,8 +64,9 @@ namespace ErsatzTV.Core.Metadata @@ -64,8 +64,9 @@ namespace ErsatzTV.Core.Metadata
if (match.Success)
{
metadata.Title = match.Groups[1].Value;
metadata.Season = int.Parse(match.Groups[2].Value);
metadata.Episode = int.Parse(match.Groups[3].Value);
// TODO: set episode number?
// metadata.Season = int.Parse(match.Groups[2].Value);
// metadata.Episode = int.Parse(match.Groups[3].Value);
}
}
catch (Exception)
@ -96,9 +97,9 @@ namespace ErsatzTV.Core.Metadata @@ -96,9 +97,9 @@ namespace ErsatzTV.Core.Metadata
return metadata;
}
private static TelevisionShowMetadata GetTelevisionShowMetadata(
private static ShowMetadata GetTelevisionShowMetadata(
string fileName,
TelevisionShowMetadata metadata)
ShowMetadata metadata)
{
try
{
@ -107,7 +108,7 @@ namespace ErsatzTV.Core.Metadata @@ -107,7 +108,7 @@ namespace ErsatzTV.Core.Metadata
if (match.Success)
{
metadata.Title = match.Groups[1].Value;
metadata.Year = int.Parse(match.Groups[2].Value);
metadata.ReleaseDate = new DateTime(int.Parse(match.Groups[2].Value), 1, 1);
}
}
catch (Exception)

109
ErsatzTV.Core/Metadata/LocalMetadataProvider.cs

@ -38,7 +38,7 @@ namespace ErsatzTV.Core.Metadata @@ -38,7 +38,7 @@ namespace ErsatzTV.Core.Metadata
_logger = logger;
}
public Task<TelevisionShowMetadata> GetMetadataForShow(string showFolder)
public Task<ShowMetadata> GetMetadataForShow(string showFolder)
{
string nfoFileName = Path.Combine(showFolder, "tvshow.nfo");
return Optional(_localFileSystem.FileExists(nfoFileName))
@ -56,43 +56,49 @@ namespace ErsatzTV.Core.Metadata @@ -56,43 +56,49 @@ namespace ErsatzTV.Core.Metadata
public Task<Unit> RefreshSidecarMetadata(MediaItem mediaItem, string path) =>
mediaItem switch
{
TelevisionEpisodeMediaItem e => LoadMetadata(e, path)
Episode e => LoadMetadata(e, path)
.Bind(maybeMetadata => maybeMetadata.IfSomeAsync(metadata => ApplyMetadataUpdate(e, metadata))),
Movie m => LoadMetadata(m, path)
.Bind(maybeMetadata => maybeMetadata.IfSomeAsync(metadata => ApplyMetadataUpdate(m, metadata))),
_ => Task.FromResult(Unit.Default)
};
public Task<Unit> RefreshSidecarMetadata(TelevisionShow televisionShow, string showFolder) =>
public Task<Unit> RefreshSidecarMetadata(Show televisionShow, string showFolder) =>
LoadMetadata(televisionShow, showFolder).Bind(
maybeMetadata => maybeMetadata.IfSomeAsync(metadata => ApplyMetadataUpdate(televisionShow, metadata)));
public Task<Unit> RefreshFallbackMetadata(MediaItem mediaItem) =>
mediaItem switch
{
TelevisionEpisodeMediaItem e => ApplyMetadataUpdate(e, FallbackMetadataProvider.GetFallbackMetadata(e))
Episode e => ApplyMetadataUpdate(e, FallbackMetadataProvider.GetFallbackMetadata(e))
.ToUnit(),
Movie m => ApplyMetadataUpdate(m, FallbackMetadataProvider.GetFallbackMetadata(m)).ToUnit(),
_ => Task.FromResult(Unit.Default)
};
public Task<Unit> RefreshFallbackMetadata(TelevisionShow televisionShow, string showFolder) =>
public Task<Unit> RefreshFallbackMetadata(Show televisionShow, string showFolder) =>
ApplyMetadataUpdate(televisionShow, _fallbackMetadataProvider.GetFallbackMetadataForShow(showFolder))
.ToUnit();
private async Task ApplyMetadataUpdate(TelevisionEpisodeMediaItem mediaItem, TelevisionEpisodeMetadata metadata)
private async Task ApplyMetadataUpdate(Episode episode, EpisodeMetadata metadata)
{
mediaItem.Metadata ??= new TelevisionEpisodeMetadata { TelevisionEpisodeId = mediaItem.Id };
mediaItem.Metadata.Source = metadata.Source;
mediaItem.Metadata.LastWriteTime = metadata.LastWriteTime;
mediaItem.Metadata.Title = metadata.Title;
mediaItem.Metadata.SortTitle = GetSortTitle(metadata.Title);
mediaItem.Metadata.Season = metadata.Season;
mediaItem.Metadata.Episode = metadata.Episode;
mediaItem.Metadata.Plot = metadata.Plot;
mediaItem.Metadata.Aired = metadata.Aired;
await _televisionRepository.Update(mediaItem);
episode.EpisodeMetadata.HeadOrNone().Match(
existing =>
{
existing.Outline = metadata.Outline;
existing.Plot = metadata.Plot;
existing.Tagline = metadata.Tagline;
existing.Title = metadata.Title;
existing.DateAdded = metadata.DateAdded;
existing.DateUpdated = metadata.DateUpdated;
existing.MetadataKind = metadata.MetadataKind;
existing.OriginalTitle = metadata.OriginalTitle;
existing.ReleaseDate = metadata.ReleaseDate;
existing.SortTitle = metadata.SortTitle ?? GetSortTitle(metadata.Title);
},
() => episode.EpisodeMetadata = new List<EpisodeMetadata> { metadata });
await _televisionRepository.Update(episode);
}
private async Task ApplyMetadataUpdate(Movie movie, MovieMetadata metadata)
@ -109,24 +115,32 @@ namespace ErsatzTV.Core.Metadata @@ -109,24 +115,32 @@ namespace ErsatzTV.Core.Metadata
existing.MetadataKind = metadata.MetadataKind;
existing.OriginalTitle = metadata.OriginalTitle;
existing.ReleaseDate = metadata.ReleaseDate;
existing.SortTitle = metadata.SortTitle;
existing.SortTitle = metadata.SortTitle ?? GetSortTitle(metadata.Title);
},
() => movie.MovieMetadata = new List<MovieMetadata> { metadata });
await _mediaItemRepository.Update(movie);
}
private async Task ApplyMetadataUpdate(TelevisionShow televisionShow, TelevisionShowMetadata metadata)
private async Task ApplyMetadataUpdate(Show show, ShowMetadata metadata)
{
televisionShow.Metadata ??= new TelevisionShowMetadata();
televisionShow.Metadata.Source = metadata.Source;
televisionShow.Metadata.LastWriteTime = metadata.LastWriteTime;
televisionShow.Metadata.Title = metadata.Title;
televisionShow.Metadata.Plot = metadata.Plot;
televisionShow.Metadata.Year = metadata.Year;
televisionShow.Metadata.SortTitle = GetSortTitle(metadata.Title);
await _televisionRepository.Update(televisionShow);
show.ShowMetadata.HeadOrNone().Match(
existing =>
{
existing.Outline = metadata.Outline;
existing.Plot = metadata.Plot;
existing.Tagline = metadata.Tagline;
existing.Title = metadata.Title;
existing.DateAdded = metadata.DateAdded;
existing.DateUpdated = metadata.DateUpdated;
existing.MetadataKind = metadata.MetadataKind;
existing.OriginalTitle = metadata.OriginalTitle;
existing.ReleaseDate = metadata.ReleaseDate;
existing.SortTitle = metadata.SortTitle ?? GetSortTitle(metadata.Title);
},
() => show.ShowMetadata = new List<ShowMetadata> { metadata });
await _televisionRepository.Update(show);
}
private async Task<Option<MovieMetadata>> LoadMetadata(Movie mediaItem, string nfoFileName)
@ -146,9 +160,7 @@ namespace ErsatzTV.Core.Metadata @@ -146,9 +160,7 @@ namespace ErsatzTV.Core.Metadata
return await LoadMovieMetadata(mediaItem, nfoFileName);
}
private async Task<Option<TelevisionEpisodeMetadata>> LoadMetadata(
TelevisionEpisodeMediaItem mediaItem,
string nfoFileName)
private async Task<Option<EpisodeMetadata>> LoadMetadata(Episode mediaItem, string nfoFileName)
{
if (nfoFileName == null || !File.Exists(nfoFileName))
{
@ -165,8 +177,8 @@ namespace ErsatzTV.Core.Metadata @@ -165,8 +177,8 @@ namespace ErsatzTV.Core.Metadata
return await LoadEpisodeMetadata(mediaItem, nfoFileName);
}
private async Task<Option<TelevisionShowMetadata>> LoadMetadata(
TelevisionShow televisionShow,
private async Task<Option<ShowMetadata>> LoadMetadata(
Show televisionShow,
string nfoFileName)
{
if (nfoFileName == null || !File.Exists(nfoFileName))
@ -178,20 +190,20 @@ namespace ErsatzTV.Core.Metadata @@ -178,20 +190,20 @@ namespace ErsatzTV.Core.Metadata
return await LoadTelevisionShowMetadata(nfoFileName);
}
private async Task<Option<TelevisionShowMetadata>> LoadTelevisionShowMetadata(string nfoFileName)
private async Task<Option<ShowMetadata>> LoadTelevisionShowMetadata(string nfoFileName)
{
try
{
await using FileStream fileStream = File.Open(nfoFileName, FileMode.Open, FileAccess.Read);
Option<TvShowNfo> maybeNfo = TvShowSerializer.Deserialize(fileStream) as TvShowNfo;
return maybeNfo.Match<Option<TelevisionShowMetadata>>(
nfo => new TelevisionShowMetadata
return maybeNfo.Match<Option<ShowMetadata>>(
nfo => new ShowMetadata
{
Source = MetadataSource.Sidecar,
LastWriteTime = File.GetLastWriteTimeUtc(nfoFileName),
MetadataKind = MetadataKind.Sidecar,
DateUpdated = File.GetLastWriteTimeUtc(nfoFileName),
Title = nfo.Title,
Plot = nfo.Plot,
Year = nfo.Year
ReleaseDate = new DateTime(nfo.Year, 1, 1)
},
None);
}
@ -202,23 +214,24 @@ namespace ErsatzTV.Core.Metadata @@ -202,23 +214,24 @@ namespace ErsatzTV.Core.Metadata
}
}
private async Task<Option<TelevisionEpisodeMetadata>> LoadEpisodeMetadata(
TelevisionEpisodeMediaItem mediaItem,
private async Task<Option<EpisodeMetadata>> LoadEpisodeMetadata(
Episode mediaItem,
string nfoFileName)
{
try
{
await using FileStream fileStream = File.Open(nfoFileName, FileMode.Open, FileAccess.Read);
Option<TvShowEpisodeNfo> maybeNfo = EpisodeSerializer.Deserialize(fileStream) as TvShowEpisodeNfo;
return maybeNfo.Match<Option<TelevisionEpisodeMetadata>>(
nfo => new TelevisionEpisodeMetadata
return maybeNfo.Match<Option<EpisodeMetadata>>(
nfo => new EpisodeMetadata
{
Source = MetadataSource.Sidecar,
LastWriteTime = File.GetLastWriteTimeUtc(nfoFileName),
MetadataKind = MetadataKind.Sidecar,
DateUpdated = File.GetLastWriteTimeUtc(nfoFileName),
Title = nfo.Title,
Aired = GetAired(nfo.Aired),
Episode = nfo.Episode,
Season = nfo.Season,
ReleaseDate = GetAired(nfo.Aired),
// Episode = nfo.Episode,
// Season = nfo.Season,
// TODO: save episode number somewhere?
Plot = nfo.Plot
},
None);

88
ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs

@ -53,7 +53,7 @@ namespace ErsatzTV.Core.Metadata @@ -53,7 +53,7 @@ namespace ErsatzTV.Core.Metadata
foreach (string showFolder in allShowFolders)
{
// TODO: check all sources for latest metadata?
Either<BaseError, TelevisionShow> maybeShow =
Either<BaseError, Show> maybeShow =
await FindOrCreateShow(libraryPath.Id, showFolder)
.BindT(show => UpdateMetadataForShow(show, showFolder))
.BindT(show => UpdatePosterForShow(show, showFolder));
@ -70,39 +70,21 @@ namespace ErsatzTV.Core.Metadata @@ -70,39 +70,21 @@ namespace ErsatzTV.Core.Metadata
return Unit.Default;
}
private async Task<Either<BaseError, TelevisionShow>> FindOrCreateShow(
private async Task<Either<BaseError, Show>> FindOrCreateShow(
int libraryPathId,
string showFolder)
{
Option<TelevisionShow> maybeShowByPath =
await _televisionRepository.GetShowByPath(libraryPathId, showFolder);
return await maybeShowByPath.Match(
show => Right<BaseError, TelevisionShow>(show).AsTask(),
async () =>
{
TelevisionShowMetadata metadata = await _localMetadataProvider.GetMetadataForShow(showFolder);
Option<TelevisionShow> maybeShow = await _televisionRepository.GetShowByMetadata(metadata);
return await maybeShow.Match(
async show =>
{
show.Sources.Add(
new LocalTelevisionShowSource
{
MediaSourceId = libraryPathId,
Path = showFolder,
TelevisionShow = show
});
await _televisionRepository.Update(show);
return Right<BaseError, TelevisionShow>(show);
},
async () => await _televisionRepository.AddShow(libraryPathId, showFolder, metadata));
});
ShowMetadata metadata = await _localMetadataProvider.GetMetadataForShow(showFolder);
Option<Show> maybeShow = await _televisionRepository.GetShowByMetadata(metadata);
return await maybeShow.Match(
show => Right<BaseError, Show>(show).AsTask(),
async () => await _televisionRepository.AddShow(libraryPathId, showFolder, metadata));
}
private async Task<Unit> ScanSeasons(
LibraryPath libraryPath,
string ffprobePath,
TelevisionShow show,
Show show,
string showPath)
{
foreach (string seasonFolder in _localFileSystem.ListSubdirectories(showPath).Filter(ShouldIncludeFolder)
@ -112,7 +94,7 @@ namespace ErsatzTV.Core.Metadata @@ -112,7 +94,7 @@ namespace ErsatzTV.Core.Metadata
await maybeSeasonNumber.IfSomeAsync(
async seasonNumber =>
{
Either<BaseError, TelevisionSeason> maybeSeason = await _televisionRepository
Either<BaseError, Season> maybeSeason = await _televisionRepository
.GetOrAddSeason(show, seasonFolder, seasonNumber)
.BindT(UpdatePoster);
@ -128,13 +110,13 @@ namespace ErsatzTV.Core.Metadata @@ -128,13 +110,13 @@ namespace ErsatzTV.Core.Metadata
private async Task<Unit> ScanEpisodes(
LibraryPath libraryPath,
string ffprobePath,
TelevisionSeason season)
Season season)
{
foreach (string file in _localFileSystem.ListFiles(season.Path)
.Filter(f => VideoFileExtensions.Contains(Path.GetExtension(f))).OrderBy(identity))
{
// TODO: figure out how to rebuild playlists
Either<BaseError, TelevisionEpisodeMediaItem> maybeEpisode = await _televisionRepository
Either<BaseError, Episode> maybeEpisode = await _televisionRepository
.GetOrAddEpisode(season, libraryPath, file)
.BindT(episode => UpdateStatistics(episode, ffprobePath).MapT(_ => episode))
.BindT(UpdateMetadata)
@ -147,8 +129,8 @@ namespace ErsatzTV.Core.Metadata @@ -147,8 +129,8 @@ namespace ErsatzTV.Core.Metadata
return Unit.Default;
}
private async Task<Either<BaseError, TelevisionShow>> UpdateMetadataForShow(
TelevisionShow show,
private async Task<Either<BaseError, Show>> UpdateMetadataForShow(
Show show,
string showFolder)
{
try
@ -156,9 +138,12 @@ namespace ErsatzTV.Core.Metadata @@ -156,9 +138,12 @@ namespace ErsatzTV.Core.Metadata
await LocateNfoFileForShow(showFolder).Match(
async nfoFile =>
{
if (show.Metadata == null || show.Metadata.Source == MetadataSource.Fallback ||
(show.Metadata.LastWriteTime ?? DateTime.MinValue) <
_localFileSystem.GetLastWriteTime(nfoFile))
bool shouldUpdate = show.ShowMetadata.HeadOrNone().Match(
m => m.MetadataKind == MetadataKind.Fallback ||
m.DateUpdated < _localFileSystem.GetLastWriteTime(nfoFile),
true);
if (shouldUpdate)
{
_logger.LogDebug("Refreshing {Attribute} from {Path}", "Sidecar Metadata", nfoFile);
await _localMetadataProvider.RefreshSidecarMetadata(show, nfoFile);
@ -166,7 +151,7 @@ namespace ErsatzTV.Core.Metadata @@ -166,7 +151,7 @@ namespace ErsatzTV.Core.Metadata
},
async () =>
{
if (show.Metadata == null)
if (!show.ShowMetadata.Any())
{
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Fallback Metadata", showFolder);
await _localMetadataProvider.RefreshFallbackMetadata(show, showFolder);
@ -181,17 +166,20 @@ namespace ErsatzTV.Core.Metadata @@ -181,17 +166,20 @@ namespace ErsatzTV.Core.Metadata
}
}
private async Task<Either<BaseError, TelevisionEpisodeMediaItem>> UpdateMetadata(
TelevisionEpisodeMediaItem episode)
private async Task<Either<BaseError, Episode>> UpdateMetadata(
Episode episode)
{
try
{
await LocateNfoFile(episode).Match(
async nfoFile =>
{
if (episode.Metadata == null || episode.Metadata.Source == MetadataSource.Fallback ||
(episode.Metadata.LastWriteTime ?? DateTime.MinValue) <
_localFileSystem.GetLastWriteTime(nfoFile))
bool shouldUpdate = episode.EpisodeMetadata.HeadOrNone().Match(
m => m.MetadataKind == MetadataKind.Fallback ||
m.DateUpdated < _localFileSystem.GetLastWriteTime(nfoFile),
true);
if (shouldUpdate)
{
_logger.LogDebug("Refreshing {Attribute} from {Path}", "Sidecar Metadata", nfoFile);
await _localMetadataProvider.RefreshSidecarMetadata(episode, nfoFile);
@ -199,7 +187,7 @@ namespace ErsatzTV.Core.Metadata @@ -199,7 +187,7 @@ namespace ErsatzTV.Core.Metadata
},
async () =>
{
if (episode.Metadata == null)
if (!episode.EpisodeMetadata.Any())
{
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Fallback Metadata", episode.Path);
await _localMetadataProvider.RefreshFallbackMetadata(episode);
@ -214,8 +202,8 @@ namespace ErsatzTV.Core.Metadata @@ -214,8 +202,8 @@ namespace ErsatzTV.Core.Metadata
}
}
private async Task<Either<BaseError, TelevisionShow>> UpdatePosterForShow(
TelevisionShow show,
private async Task<Either<BaseError, Show>> UpdatePosterForShow(
Show show,
string showFolder)
{
try
@ -255,7 +243,7 @@ namespace ErsatzTV.Core.Metadata @@ -255,7 +243,7 @@ namespace ErsatzTV.Core.Metadata
}
}
private async Task<Either<BaseError, TelevisionSeason>> UpdatePoster(TelevisionSeason season)
private async Task<Either<BaseError, Season>> UpdatePoster(Season season)
{
try
{
@ -279,8 +267,8 @@ namespace ErsatzTV.Core.Metadata @@ -279,8 +267,8 @@ namespace ErsatzTV.Core.Metadata
}
}
private async Task<Either<BaseError, TelevisionEpisodeMediaItem>> UpdateThumbnail(
TelevisionEpisodeMediaItem episode)
private async Task<Either<BaseError, Episode>> UpdateThumbnail(
Episode episode)
{
try
{
@ -308,7 +296,7 @@ namespace ErsatzTV.Core.Metadata @@ -308,7 +296,7 @@ namespace ErsatzTV.Core.Metadata
Optional(Path.Combine(showFolder, "tvshow.nfo"))
.Filter(s => _localFileSystem.FileExists(s));
private Option<string> LocateNfoFile(TelevisionEpisodeMediaItem episode) =>
private Option<string> LocateNfoFile(Episode episode) =>
Optional(Path.ChangeExtension(episode.Path, "nfo"))
.Filter(s => _localFileSystem.FileExists(s));
@ -319,16 +307,16 @@ namespace ErsatzTV.Core.Metadata @@ -319,16 +307,16 @@ namespace ErsatzTV.Core.Metadata
.Filter(s => _localFileSystem.FileExists(s))
.HeadOrNone();
private Option<string> LocatePoster(TelevisionSeason season)
private Option<string> LocatePoster(Season season)
{
string folder = Path.GetDirectoryName(season.Path) ?? string.Empty;
return ImageFileExtensions
.Map(ext => Path.Combine(folder, $"season{season.Number:00}-poster.{ext}"))
.Map(ext => Path.Combine(folder, $"season{season.SeasonNumber:00}-poster.{ext}"))
.Filter(s => _localFileSystem.FileExists(s))
.HeadOrNone();
}
private Option<string> LocateThumbnail(TelevisionEpisodeMediaItem episode)
private Option<string> LocateThumbnail(Episode episode)
{
string folder = Path.GetDirectoryName(episode.Path) ?? string.Empty;
return ImageFileExtensions

22
ErsatzTV.Core/Scheduling/ChronologicalMediaCollectionEnumerator.cs

@ -14,18 +14,18 @@ namespace ErsatzTV.Core.Scheduling @@ -14,18 +14,18 @@ namespace ErsatzTV.Core.Scheduling
public ChronologicalMediaCollectionEnumerator(
IEnumerable<MediaItem> mediaItems,
MediaCollectionEnumeratorState state)
CollectionEnumeratorState state)
{
_sortedMediaItems = mediaItems.OrderBy(identity, new ChronologicalComparer()).ToList();
State = new MediaCollectionEnumeratorState { Seed = state.Seed };
State = new CollectionEnumeratorState { Seed = state.Seed };
while (State.Index < state.Index)
{
MoveNext();
}
}
public MediaCollectionEnumeratorState State { get; }
public CollectionEnumeratorState State { get; }
public Option<MediaItem> Current => _sortedMediaItems.Any() ? _sortedMediaItems[State.Index] : None;
@ -42,7 +42,9 @@ namespace ErsatzTV.Core.Scheduling @@ -42,7 +42,9 @@ namespace ErsatzTV.Core.Scheduling
DateTime date1 = x switch
{
TelevisionEpisodeMediaItem e => e.Metadata?.Aired ?? DateTime.MaxValue,
Episode e => e.EpisodeMetadata.HeadOrNone().Match(
em => em.ReleaseDate ?? DateTime.MaxValue,
() => DateTime.MaxValue),
Movie m => m.MovieMetadata.HeadOrNone().Match(
mm => mm.ReleaseDate ?? DateTime.MaxValue,
() => DateTime.MaxValue),
@ -51,7 +53,9 @@ namespace ErsatzTV.Core.Scheduling @@ -51,7 +53,9 @@ namespace ErsatzTV.Core.Scheduling
DateTime date2 = y switch
{
TelevisionEpisodeMediaItem e => e.Metadata?.Aired ?? DateTime.MaxValue,
Episode e => e.EpisodeMetadata.HeadOrNone().Match(
em => em.ReleaseDate ?? DateTime.MaxValue,
() => DateTime.MaxValue),
Movie m => m.MovieMetadata.HeadOrNone().Match(
mm => mm.ReleaseDate ?? DateTime.MaxValue,
() => DateTime.MaxValue),
@ -65,13 +69,13 @@ namespace ErsatzTV.Core.Scheduling @@ -65,13 +69,13 @@ namespace ErsatzTV.Core.Scheduling
int season1 = x switch
{
TelevisionEpisodeMediaItem e => e.Metadata?.Season ?? int.MaxValue,
Episode e => e.Season?.SeasonNumber ?? int.MaxValue,
_ => int.MaxValue
};
int season2 = y switch
{
TelevisionEpisodeMediaItem e => e.Metadata?.Season ?? int.MaxValue,
Episode e => e.Season?.SeasonNumber ?? int.MaxValue,
_ => int.MaxValue
};
@ -82,13 +86,13 @@ namespace ErsatzTV.Core.Scheduling @@ -82,13 +86,13 @@ namespace ErsatzTV.Core.Scheduling
int episode1 = x switch
{
TelevisionEpisodeMediaItem e => e.Metadata?.Episode ?? int.MaxValue,
Episode e => e.EpisodeNumber,
_ => int.MaxValue
};
int episode2 = y switch
{
TelevisionEpisodeMediaItem e => e.Metadata?.Episode ?? int.MaxValue,
Episode e => e.EpisodeNumber,
_ => int.MaxValue
};

20
ErsatzTV.Core/Scheduling/PlayoutBuilder.cs

@ -58,11 +58,11 @@ namespace ErsatzTV.Core.Scheduling @@ -58,11 +58,11 @@ namespace ErsatzTV.Core.Scheduling
await _mediaCollectionRepository.GetItems(collectionKey.Id);
return Tuple(collectionKey, maybeItems.IfNone(new List<MediaItem>()));
case ProgramScheduleItemCollectionType.TelevisionShow:
List<TelevisionEpisodeMediaItem> showItems =
List<Episode> showItems =
await _televisionRepository.GetShowItems(collectionKey.Id);
return Tuple(collectionKey, showItems.Cast<MediaItem>().ToList());
case ProgramScheduleItemCollectionType.TelevisionSeason:
List<TelevisionEpisodeMediaItem> seasonItems =
List<Episode> seasonItems =
await _televisionRepository.GetSeasonItems(collectionKey.Id);
return Tuple(collectionKey, seasonItems.Cast<MediaItem>().ToList());
default:
@ -364,9 +364,9 @@ namespace ErsatzTV.Core.Scheduling @@ -364,9 +364,9 @@ namespace ErsatzTV.Core.Scheduling
collectionKey.CollectionType
&& a.CollectionId == collectionKey.Id);
MediaCollectionEnumeratorState state = maybeAnchor.Match(
CollectionEnumeratorState state = maybeAnchor.Match(
anchor => anchor.EnumeratorState,
() => new MediaCollectionEnumeratorState { Seed = Random.Next(), Index = 0 });
() => new CollectionEnumeratorState { Seed = Random.Next(), Index = 0 });
switch (playout.ProgramSchedule.MediaCollectionPlaybackOrder)
{
@ -385,11 +385,11 @@ namespace ErsatzTV.Core.Scheduling @@ -385,11 +385,11 @@ namespace ErsatzTV.Core.Scheduling
private static string DisplayTitle(MediaItem mediaItem) =>
mediaItem switch
{
TelevisionEpisodeMediaItem e => e.Metadata != null
? $"{e.Metadata.Title} - s{e.Metadata.Season:00}e{e.Metadata.Episode:00}"
Episode e => e.EpisodeMetadata.Any() && e.Season != null
? $"{e.EpisodeMetadata.Head().Title} - s{e.Season.SeasonNumber:00}e{e.EpisodeNumber:00}"
: Path.GetFileName(e.Path),
Movie m => m.MovieMetadata.HeadOrNone().Match(
mm => mm.Title,
mm => mm.Title ?? string.Empty,
() => Path.GetFileName(m.Path)),
_ => string.Empty
};
@ -400,17 +400,17 @@ namespace ErsatzTV.Core.Scheduling @@ -400,17 +400,17 @@ namespace ErsatzTV.Core.Scheduling
ProgramScheduleItemCollectionType.Collection => new CollectionKey
{
CollectionType = item.CollectionType,
Id = item.MediaCollectionId.Value
Id = item.CollectionId.Value
},
ProgramScheduleItemCollectionType.TelevisionShow => new CollectionKey
{
CollectionType = item.CollectionType,
Id = item.TelevisionShowId.Value
Id = item.MediaItemId.Value
},
ProgramScheduleItemCollectionType.TelevisionSeason => new CollectionKey
{
CollectionType = item.CollectionType,
Id = item.TelevisionSeasonId.Value
Id = item.MediaItemId.Value
},
_ => throw new ArgumentOutOfRangeException(nameof(item))
};

6
ErsatzTV.Core/Scheduling/RandomizedMediaCollectionEnumerator.cs

@ -14,12 +14,12 @@ namespace ErsatzTV.Core.Scheduling @@ -14,12 +14,12 @@ namespace ErsatzTV.Core.Scheduling
private readonly Random _random;
private int _index;
public RandomizedMediaCollectionEnumerator(IList<MediaItem> mediaItems, MediaCollectionEnumeratorState state)
public RandomizedMediaCollectionEnumerator(IList<MediaItem> mediaItems, CollectionEnumeratorState state)
{
_mediaItems = mediaItems;
_random = new Random(state.Seed);
State = new MediaCollectionEnumeratorState { Seed = state.Seed };
State = new CollectionEnumeratorState { Seed = state.Seed };
// we want to move at least once so we start with a random item and not the first
// because _index defaults to 0
while (State.Index <= state.Index)
@ -28,7 +28,7 @@ namespace ErsatzTV.Core.Scheduling @@ -28,7 +28,7 @@ namespace ErsatzTV.Core.Scheduling
}
}
public MediaCollectionEnumeratorState State { get; }
public CollectionEnumeratorState State { get; }
public Option<MediaItem> Current => _mediaItems.Any() ? _mediaItems[_index] : None;

6
ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs

@ -15,20 +15,20 @@ namespace ErsatzTV.Core.Scheduling @@ -15,20 +15,20 @@ namespace ErsatzTV.Core.Scheduling
private IList<MediaItem> _shuffled;
public ShuffledMediaCollectionEnumerator(IList<MediaItem> mediaItems, MediaCollectionEnumeratorState state)
public ShuffledMediaCollectionEnumerator(IList<MediaItem> mediaItems, CollectionEnumeratorState state)
{
_mediaItems = mediaItems;
_random = new Random(state.Seed);
_shuffled = Shuffle(_mediaItems, _random);
State = new MediaCollectionEnumeratorState { Seed = state.Seed };
State = new CollectionEnumeratorState { Seed = state.Seed };
while (State.Index < state.Index)
{
MoveNext();
}
}
public MediaCollectionEnumeratorState State { get; }
public CollectionEnumeratorState State { get; }
public Option<MediaItem> Current => _shuffled.Any() ? _shuffled[State.Index % _mediaItems.Count] : None;

16
ErsatzTV.Infrastructure/Data/Configurations/MediaCollectionConfiguration.cs

@ -1,16 +0,0 @@ @@ -1,16 +0,0 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class MediaCollectionConfiguration : IEntityTypeConfiguration<MediaCollection>
{
public void Configure(EntityTypeBuilder<MediaCollection> builder)
{
builder.ToTable("MediaCollection");
builder.HasIndex(c => c.Name).IsUnique();
}
}
}

15
ErsatzTV.Infrastructure/Data/Configurations/ProgramScheduleItemConfiguration.cs

@ -10,21 +10,6 @@ namespace ErsatzTV.Infrastructure.Data.Configurations @@ -10,21 +10,6 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
{
builder.ToTable("ProgramScheduleItem");
builder.HasOne(i => i.MediaCollection)
.WithMany()
.HasForeignKey(i => i.MediaCollectionId)
.IsRequired(false);
builder.HasOne(i => i.TelevisionShow)
.WithMany()
.HasForeignKey(i => i.TelevisionShowId)
.IsRequired(false);
builder.HasOne(i => i.TelevisionSeason)
.WithMany()
.HasForeignKey(i => i.TelevisionSeasonId)
.IsRequired(false);
builder.HasOne(i => i.Collection)
.WithMany()
.HasForeignKey(i => i.CollectionId)

30
ErsatzTV.Infrastructure/Data/Configurations/SimpleMediaCollectionConfiguration.cs

@ -1,30 +0,0 @@ @@ -1,30 +0,0 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class SimpleMediaCollectionConfiguration : IEntityTypeConfiguration<SimpleMediaCollection>
{
public void Configure(EntityTypeBuilder<SimpleMediaCollection> builder)
{
builder.ToTable("SimpleMediaCollection");
builder.HasMany(c => c.Movies)
.WithMany(m => m.SimpleMediaCollections)
.UsingEntity(join => join.ToTable("SimpleMediaCollectionMovie"));
builder.HasMany(c => c.TelevisionShows)
.WithMany(s => s.SimpleMediaCollections)
.UsingEntity(join => join.ToTable("SimpleMediaCollectionShow"));
builder.HasMany(c => c.TelevisionSeasons)
.WithMany(s => s.SimpleMediaCollections)
.UsingEntity(join => join.ToTable("SimpleMediaCollectionSeason"));
builder.HasMany(c => c.TelevisionEpisodes)
.WithMany(e => e.SimpleMediaCollections)
.UsingEntity(join => join.ToTable("SimpleMediaCollectionEpisode"));
}
}
}

19
ErsatzTV.Infrastructure/Data/Configurations/TelevisionEpisodeMediaItemConfiguration.cs

@ -1,19 +0,0 @@ @@ -1,19 +0,0 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class TelevisionEpisodeMediaItemConfiguration : IEntityTypeConfiguration<TelevisionEpisodeMediaItem>
{
public void Configure(EntityTypeBuilder<TelevisionEpisodeMediaItem> builder)
{
builder.ToTable("TelevisionEpisode");
builder.HasOne(i => i.Metadata)
.WithOne(m => m.TelevisionEpisode)
.HasForeignKey<TelevisionEpisodeMetadata>(m => m.TelevisionEpisodeId)
.OnDelete(DeleteBehavior.Cascade);
}
}
}

12
ErsatzTV.Infrastructure/Data/Configurations/TelevisionEpisodeMetadataConfiguration.cs

@ -1,12 +0,0 @@ @@ -1,12 +0,0 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class TelevisionEpisodeMetadataConfiguration : IEntityTypeConfiguration<TelevisionEpisodeMetadata>
{
public void Configure(EntityTypeBuilder<TelevisionEpisodeMetadata> builder) =>
builder.ToTable("TelevisionEpisodeMetadata");
}
}

19
ErsatzTV.Infrastructure/Data/Configurations/TelevisionSeasonConfiguration.cs

@ -1,19 +0,0 @@ @@ -1,19 +0,0 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class TelevisionSeasonConfiguration : IEntityTypeConfiguration<TelevisionSeason>
{
public void Configure(EntityTypeBuilder<TelevisionSeason> builder)
{
builder.ToTable("TelevisionSeason");
builder.HasMany(season => season.Episodes)
.WithOne(episode => episode.Season)
.HasForeignKey(episode => episode.SeasonId)
.OnDelete(DeleteBehavior.Cascade);
}
}
}

27
ErsatzTV.Infrastructure/Data/Configurations/TelevisionShowConfiguration.cs

@ -1,27 +0,0 @@ @@ -1,27 +0,0 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class TelevisionShowConfiguration : IEntityTypeConfiguration<TelevisionShow>
{
public void Configure(EntityTypeBuilder<TelevisionShow> builder)
{
builder.ToTable("TelevisionShow");
builder.HasOne(show => show.Metadata)
.WithOne(metadata => metadata.TelevisionShow)
.HasForeignKey<TelevisionShowMetadata>(metadata => metadata.TelevisionShowId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasMany(show => show.Seasons)
.WithOne(season => season.TelevisionShow);
builder.HasMany(show => show.Sources)
.WithOne(source => source.TelevisionShow)
.HasForeignKey(source => source.TelevisionShowId)
.OnDelete(DeleteBehavior.Cascade);
}
}
}

12
ErsatzTV.Infrastructure/Data/Configurations/TelevisionShowMetadataConfiguration.cs

@ -1,12 +0,0 @@ @@ -1,12 +0,0 @@
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class TelevisionShowMetadataConfiguration : IEntityTypeConfiguration<TelevisionShowMetadata>
{
public void Configure(EntityTypeBuilder<TelevisionShowMetadata> builder) =>
builder.ToTable("TelevisionShowMetadata");
}
}

2
ErsatzTV.Infrastructure/Data/Repositories/ChannelRepository.cs

@ -38,7 +38,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -38,7 +38,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.Include(c => c.Playouts)
.ThenInclude(p => p.Items)
.ThenInclude(i => i.MediaItem)
.ThenInclude(i => (i as TelevisionEpisodeMediaItem).Metadata)
.ThenInclude(i => (i as Episode).EpisodeMetadata)
.Include(c => c.Playouts)
.ThenInclude(p => p.Items)
.ThenInclude(i => i.MediaItem)

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save