Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c70d61d48 | ||
|
|
00fdc272e9 |
@@ -1,8 +0,0 @@
|
||||
using ErsatzTV.Core;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCards.Queries
|
||||
{
|
||||
public record GetSearchCards(string Query) : IRequest<Either<BaseError, SearchCardResultsViewModel>>;
|
||||
}
|
||||
@@ -1,43 +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;
|
||||
using static LanguageExt.Prelude;
|
||||
|
||||
namespace ErsatzTV.Application.MediaCards.Queries
|
||||
{
|
||||
public class GetSearchCardsHandler : IRequestHandler<GetSearchCards, Either<BaseError, SearchCardResultsViewModel>>
|
||||
{
|
||||
private readonly ISearchRepository _searchRepository;
|
||||
|
||||
public GetSearchCardsHandler(ISearchRepository searchRepository) => _searchRepository = searchRepository;
|
||||
|
||||
public Task<Either<BaseError, SearchCardResultsViewModel>> Handle(
|
||||
GetSearchCards request,
|
||||
CancellationToken cancellationToken) =>
|
||||
request.Query.Split(":").Head() switch
|
||||
{
|
||||
"genre" => GenreSearch(request.Query.Replace("genre:", string.Empty)),
|
||||
"tag" => TagSearch(request.Query.Replace("tag:", string.Empty)),
|
||||
_ => TitleSearch(request.Query)
|
||||
};
|
||||
|
||||
private Task<Either<BaseError, SearchCardResultsViewModel>> TitleSearch(string query) =>
|
||||
Try(_searchRepository.SearchMediaItemsByTitle(query)).Sequence()
|
||||
.Map(ProjectToSearchResults)
|
||||
.Map(t => t.ToEither(ex => BaseError.New($"Failed to search: {ex.Message}")));
|
||||
|
||||
private Task<Either<BaseError, SearchCardResultsViewModel>> GenreSearch(string query) =>
|
||||
Try(_searchRepository.SearchMediaItemsByGenre(query)).Sequence()
|
||||
.Map(ProjectToSearchResults)
|
||||
.Map(t => t.ToEither(ex => BaseError.New($"Failed to search: {ex.Message}")));
|
||||
|
||||
private Task<Either<BaseError, SearchCardResultsViewModel>> TagSearch(string query) =>
|
||||
Try(_searchRepository.SearchMediaItemsByTag(query)).Sequence()
|
||||
.Map(ProjectToSearchResults)
|
||||
.Map(t => t.ToEither(ex => BaseError.New($"Failed to search: {ex.Message}")));
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,8 @@ namespace ErsatzTV.Application.Movies
|
||||
Artwork(metadata, ArtworkKind.Poster),
|
||||
Artwork(metadata, ArtworkKind.FanArt),
|
||||
metadata.Genres.Map(g => g.Name).ToList(),
|
||||
metadata.Tags.Map(t => t.Name).ToList());
|
||||
metadata.Tags.Map(t => t.Name).ToList(),
|
||||
metadata.Studios.Map(s => s.Name).ToList());
|
||||
}
|
||||
|
||||
private static string Artwork(Metadata metadata, ArtworkKind artworkKind) =>
|
||||
|
||||
@@ -9,5 +9,6 @@ namespace ErsatzTV.Application.Movies
|
||||
string Poster,
|
||||
string FanArt,
|
||||
List<string> Genres,
|
||||
List<string> Tags);
|
||||
List<string> Tags,
|
||||
List<string> Studios);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Interfaces.Locking;
|
||||
using ErsatzTV.Core.Interfaces.Plex;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Application.Plex.Commands
|
||||
@@ -13,20 +15,24 @@ namespace ErsatzTV.Application.Plex.Commands
|
||||
private readonly IEntityLocker _entityLocker;
|
||||
private readonly IMediaSourceRepository _mediaSourceRepository;
|
||||
private readonly IPlexSecretStore _plexSecretStore;
|
||||
private readonly ISearchIndex _searchIndex;
|
||||
|
||||
public SignOutOfPlexHandler(
|
||||
IMediaSourceRepository mediaSourceRepository,
|
||||
IPlexSecretStore plexSecretStore,
|
||||
IEntityLocker entityLocker)
|
||||
IEntityLocker entityLocker,
|
||||
ISearchIndex searchIndex)
|
||||
{
|
||||
_mediaSourceRepository = mediaSourceRepository;
|
||||
_plexSecretStore = plexSecretStore;
|
||||
_entityLocker = entityLocker;
|
||||
_searchIndex = searchIndex;
|
||||
}
|
||||
|
||||
public async Task<Either<BaseError, Unit>> Handle(SignOutOfPlex request, CancellationToken cancellationToken)
|
||||
{
|
||||
await _mediaSourceRepository.DeleteAllPlex();
|
||||
List<int> ids = await _mediaSourceRepository.DeleteAllPlex();
|
||||
await _searchIndex.RemoveItems(ids);
|
||||
await _plexSecretStore.DeleteAll();
|
||||
_entityLocker.UnlockPlex();
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ namespace ErsatzTV.Application.Television
|
||||
show.ShowMetadata.HeadOrNone().Map(GetPoster).IfNone(string.Empty),
|
||||
show.ShowMetadata.HeadOrNone().Map(GetFanArt).IfNone(string.Empty),
|
||||
show.ShowMetadata.HeadOrNone().Map(m => m.Genres.Map(g => g.Name).ToList()).IfNone(new List<string>()),
|
||||
show.ShowMetadata.HeadOrNone().Map(m => m.Tags.Map(g => g.Name).ToList()).IfNone(new List<string>()));
|
||||
show.ShowMetadata.HeadOrNone().Map(m => m.Tags.Map(g => g.Name).ToList()).IfNone(new List<string>()),
|
||||
show.ShowMetadata.HeadOrNone().Map(m => m.Studios.Map(s => s.Name).ToList()).IfNone(new List<string>()));
|
||||
|
||||
internal static TelevisionSeasonViewModel ProjectToViewModel(Season season) =>
|
||||
new(
|
||||
|
||||
@@ -10,5 +10,6 @@ namespace ErsatzTV.Application.Television
|
||||
string Poster,
|
||||
string FanArt,
|
||||
List<string> Genres,
|
||||
List<string> Tags);
|
||||
List<string> Tags,
|
||||
List<string> Studios);
|
||||
}
|
||||
|
||||
@@ -76,6 +76,9 @@ namespace ErsatzTV.Core.Tests.Fakes
|
||||
throw new NotSupportedException();
|
||||
|
||||
public Task<bool> AddGenre(ShowMetadata metadata, Genre genre) => throw new NotSupportedException();
|
||||
public Task<bool> AddTag(ShowMetadata metadata, Tag tag) => throw new NotSupportedException();
|
||||
|
||||
public Task<bool> AddStudio(ShowMetadata metadata, Studio studio) => throw new NotSupportedException();
|
||||
|
||||
public Task<List<int>> RemoveMissingPlexShows(PlexLibrary library, List<string> showKeys) =>
|
||||
throw new NotSupportedException();
|
||||
|
||||
@@ -17,5 +17,6 @@ namespace ErsatzTV.Core.Domain
|
||||
public List<Artwork> Artwork { get; set; }
|
||||
public List<Genre> Genres { get; set; }
|
||||
public List<Tag> Tags { get; set; }
|
||||
public List<Studio> Studios { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace ErsatzTV.Core.Domain
|
||||
{
|
||||
public class Studio
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ namespace ErsatzTV.Core.Interfaces.Repositories
|
||||
|
||||
Task Update(PlexLibrary plexMediaSourceLibrary);
|
||||
Task Delete(int mediaSourceId);
|
||||
Task<Unit> DeleteAllPlex();
|
||||
Task<List<int>> DeleteAllPlex();
|
||||
Task<List<int>> DisablePlexLibrarySync(List<int> libraryIds);
|
||||
Task EnablePlexLibrarySync(IEnumerable<int> libraryIds);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ namespace ErsatzTV.Core.Interfaces.Repositories
|
||||
public interface IMetadataRepository
|
||||
{
|
||||
Task<bool> RemoveGenre(Genre genre);
|
||||
Task<bool> RemoveTag(Tag tag);
|
||||
Task<bool> RemoveStudio(Studio studio);
|
||||
Task<bool> Update(Domain.Metadata metadata);
|
||||
Task<bool> Add(Domain.Metadata metadata);
|
||||
Task<bool> UpdateLocalStatistics(MediaVersion mediaVersion);
|
||||
|
||||
@@ -18,6 +18,9 @@ namespace ErsatzTV.Core.Interfaces.Repositories
|
||||
Task<IEnumerable<string>> FindMoviePaths(LibraryPath libraryPath);
|
||||
Task<List<int>> DeleteByPath(LibraryPath libraryPath, string path);
|
||||
Task<bool> AddGenre(MovieMetadata metadata, Genre genre);
|
||||
Task<bool> AddTag(MovieMetadata metadata, Tag tag);
|
||||
Task<bool> AddStudio(MovieMetadata metadata, Studio studio);
|
||||
Task<List<int>> RemoveMissingPlexMovies(PlexLibrary library, List<string> movieKeys);
|
||||
Task<bool> UpdateSortTitle(MovieMetadata movieMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ namespace ErsatzTV.Core.Interfaces.Repositories
|
||||
Task<Either<BaseError, PlexSeason>> GetOrAddPlexSeason(PlexLibrary library, PlexSeason item);
|
||||
Task<Either<BaseError, PlexEpisode>> GetOrAddPlexEpisode(PlexLibrary library, PlexEpisode item);
|
||||
Task<bool> AddGenre(ShowMetadata metadata, Genre genre);
|
||||
Task<bool> AddTag(ShowMetadata metadata, Tag tag);
|
||||
Task<bool> AddStudio(ShowMetadata metadata, Studio studio);
|
||||
Task<List<int>> RemoveMissingPlexShows(PlexLibrary library, List<string> showKeys);
|
||||
Task<Unit> RemoveMissingPlexSeasons(string showKey, List<string> seasonKeys);
|
||||
Task<Unit> RemoveMissingPlexEpisodes(string seasonKey, List<string> episodeKeys);
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
string path = episode.MediaVersions.Head().MediaFiles.Head().Path;
|
||||
string fileName = Path.GetFileName(path);
|
||||
var metadata = new EpisodeMetadata
|
||||
{ MetadataKind = MetadataKind.Fallback, Title = fileName ?? path };
|
||||
{ MetadataKind = MetadataKind.Fallback, Title = fileName ?? path, DateAdded = DateTime.UtcNow };
|
||||
return fileName != null ? GetEpisodeMetadata(fileName, metadata) : Tuple(metadata, 0);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,16 @@ namespace ErsatzTV.Core.Metadata
|
||||
return title.Substring(4);
|
||||
}
|
||||
|
||||
if (title.StartsWith("a ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return title.Substring(2);
|
||||
}
|
||||
|
||||
if (title.StartsWith("an ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return title.Substring(3);
|
||||
}
|
||||
|
||||
if (title.StartsWith("Æ"))
|
||||
{
|
||||
return title.Replace("Æ", "E");
|
||||
@@ -65,6 +75,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
if (match.Success)
|
||||
{
|
||||
metadata.Title = match.Groups[1].Value;
|
||||
metadata.DateUpdated = DateTime.UtcNow;
|
||||
return Tuple(metadata, int.Parse(match.Groups[3].Value));
|
||||
}
|
||||
}
|
||||
@@ -89,6 +100,8 @@ namespace ErsatzTV.Core.Metadata
|
||||
metadata.ReleaseDate = new DateTime(int.Parse(match.Groups[2].Value), 1, 1);
|
||||
metadata.Genres = new List<Genre>();
|
||||
metadata.Tags = new List<Tag>();
|
||||
metadata.Studios = new List<Studio>();
|
||||
metadata.DateUpdated = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -110,6 +123,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
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);
|
||||
metadata.DateUpdated = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
|
||||
@@ -23,16 +23,19 @@ namespace ErsatzTV.Core.Metadata
|
||||
private readonly ILogger<LocalMetadataProvider> _logger;
|
||||
|
||||
private readonly IMetadataRepository _metadataRepository;
|
||||
private readonly IMovieRepository _movieRepository;
|
||||
private readonly ITelevisionRepository _televisionRepository;
|
||||
|
||||
public LocalMetadataProvider(
|
||||
IMetadataRepository metadataRepository,
|
||||
IMovieRepository movieRepository,
|
||||
ITelevisionRepository televisionRepository,
|
||||
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||
ILocalFileSystem localFileSystem,
|
||||
ILogger<LocalMetadataProvider> logger)
|
||||
{
|
||||
_metadataRepository = metadataRepository;
|
||||
_movieRepository = movieRepository;
|
||||
_televisionRepository = televisionRepository;
|
||||
_fallbackMetadataProvider = fallbackMetadataProvider;
|
||||
_localFileSystem = localFileSystem;
|
||||
@@ -138,7 +141,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
|
||||
private Task<bool> ApplyMetadataUpdate(Movie movie, MovieMetadata metadata) =>
|
||||
Optional(movie.MovieMetadata).Flatten().HeadOrNone().Match(
|
||||
existing =>
|
||||
async existing =>
|
||||
{
|
||||
existing.Outline = metadata.Outline;
|
||||
existing.Plot = metadata.Plot;
|
||||
@@ -158,29 +161,49 @@ namespace ErsatzTV.Core.Metadata
|
||||
.ToList())
|
||||
{
|
||||
existing.Genres.Remove(genre);
|
||||
await _metadataRepository.RemoveGenre(genre);
|
||||
}
|
||||
|
||||
foreach (Genre genre in metadata.Genres.Filter(g => existing.Genres.All(g2 => g2.Name != g.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Genres.Add(genre);
|
||||
await _movieRepository.AddGenre(existing, genre);
|
||||
}
|
||||
|
||||
foreach (Tag tag in existing.Tags.Filter(t => metadata.Tags.All(t2 => t2.Name != t.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Tags.Remove(tag);
|
||||
await _metadataRepository.RemoveTag(tag);
|
||||
}
|
||||
|
||||
foreach (Tag tag in metadata.Tags.Filter(t => existing.Tags.All(t2 => t2.Name != t.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Tags.Add(tag);
|
||||
await _movieRepository.AddTag(existing, tag);
|
||||
}
|
||||
|
||||
return _metadataRepository.Update(existing);
|
||||
foreach (Studio studio in existing.Studios
|
||||
.Filter(s => metadata.Studios.All(s2 => s2.Name != s.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Studios.Remove(studio);
|
||||
await _metadataRepository.RemoveStudio(studio);
|
||||
}
|
||||
|
||||
foreach (Studio studio in metadata.Studios
|
||||
.Filter(s => existing.Studios.All(s2 => s2.Name != s.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Studios.Add(studio);
|
||||
await _movieRepository.AddStudio(existing, studio);
|
||||
}
|
||||
|
||||
return await _metadataRepository.Update(existing);
|
||||
},
|
||||
() =>
|
||||
async () =>
|
||||
{
|
||||
metadata.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle)
|
||||
? _fallbackMetadataProvider.GetSortTitle(metadata.Title)
|
||||
@@ -188,12 +211,12 @@ namespace ErsatzTV.Core.Metadata
|
||||
metadata.MovieId = movie.Id;
|
||||
movie.MovieMetadata = new List<MovieMetadata> { metadata };
|
||||
|
||||
return _metadataRepository.Add(metadata);
|
||||
return await _metadataRepository.Add(metadata);
|
||||
});
|
||||
|
||||
private Task<bool> ApplyMetadataUpdate(Show show, ShowMetadata metadata) =>
|
||||
Optional(show.ShowMetadata).Flatten().HeadOrNone().Match(
|
||||
existing =>
|
||||
async existing =>
|
||||
{
|
||||
existing.Outline = metadata.Outline;
|
||||
existing.Plot = metadata.Plot;
|
||||
@@ -213,29 +236,49 @@ namespace ErsatzTV.Core.Metadata
|
||||
.ToList())
|
||||
{
|
||||
existing.Genres.Remove(genre);
|
||||
await _metadataRepository.RemoveGenre(genre);
|
||||
}
|
||||
|
||||
foreach (Genre genre in metadata.Genres.Filter(g => existing.Genres.All(g2 => g2.Name != g.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Genres.Add(genre);
|
||||
await _televisionRepository.AddGenre(existing, genre);
|
||||
}
|
||||
|
||||
foreach (Tag tag in existing.Tags.Filter(t => metadata.Tags.All(t2 => t2.Name != t.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Tags.Remove(tag);
|
||||
await _metadataRepository.RemoveTag(tag);
|
||||
}
|
||||
|
||||
foreach (Tag tag in metadata.Tags.Filter(t => existing.Tags.All(t2 => t2.Name != t.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Tags.Add(tag);
|
||||
await _televisionRepository.AddTag(existing, tag);
|
||||
}
|
||||
|
||||
return _metadataRepository.Update(existing);
|
||||
foreach (Studio studio in existing.Studios
|
||||
.Filter(s => metadata.Studios.All(s2 => s2.Name != s.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Studios.Remove(studio);
|
||||
await _metadataRepository.RemoveStudio(studio);
|
||||
}
|
||||
|
||||
foreach (Studio studio in metadata.Studios
|
||||
.Filter(s => existing.Studios.All(s2 => s2.Name != s.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Studios.Add(studio);
|
||||
await _televisionRepository.AddStudio(existing, studio);
|
||||
}
|
||||
|
||||
return await _metadataRepository.Update(existing);
|
||||
},
|
||||
() =>
|
||||
async () =>
|
||||
{
|
||||
metadata.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle)
|
||||
? _fallbackMetadataProvider.GetSortTitle(metadata.Title)
|
||||
@@ -243,7 +286,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
metadata.ShowId = show.Id;
|
||||
show.ShowMetadata = new List<ShowMetadata> { metadata };
|
||||
|
||||
return _metadataRepository.Add(metadata);
|
||||
return await _metadataRepository.Add(metadata);
|
||||
});
|
||||
|
||||
private async Task<Option<MovieMetadata>> LoadMetadata(Movie mediaItem, string nfoFileName)
|
||||
@@ -294,10 +337,11 @@ namespace ErsatzTV.Core.Metadata
|
||||
Plot = nfo.Plot,
|
||||
Outline = nfo.Outline,
|
||||
Tagline = nfo.Tagline,
|
||||
Year = nfo.Year,
|
||||
ReleaseDate = GetAired(nfo.Premiered) ?? new DateTime(nfo.Year, 1, 1),
|
||||
Year = GetYear(nfo.Year, nfo.Premiered),
|
||||
ReleaseDate = GetAired(nfo.Year, nfo.Premiered),
|
||||
Genres = nfo.Genres.Map(g => new Genre { Name = g }).ToList(),
|
||||
Tags = nfo.Tags.Map(t => new Tag { Name = t }).ToList()
|
||||
Tags = nfo.Tags.Map(t => new Tag { Name = t }).ToList(),
|
||||
Studios = nfo.Studios.Map(s => new Studio { Name = s }).ToList()
|
||||
},
|
||||
None);
|
||||
}
|
||||
@@ -322,7 +366,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
MetadataKind = MetadataKind.Sidecar,
|
||||
DateUpdated = File.GetLastWriteTimeUtc(nfoFileName),
|
||||
Title = nfo.Title,
|
||||
ReleaseDate = GetAired(nfo.Aired),
|
||||
ReleaseDate = GetAired(0, nfo.Aired),
|
||||
Plot = nfo.Plot
|
||||
};
|
||||
return Tuple(metadata, nfo.Episode);
|
||||
@@ -354,7 +398,8 @@ namespace ErsatzTV.Core.Metadata
|
||||
Outline = nfo.Outline,
|
||||
Tagline = nfo.Tagline,
|
||||
Genres = nfo.Genres.Map(g => new Genre { Name = g }).ToList(),
|
||||
Tags = nfo.Tags.Map(t => new Tag { Name = t }).ToList()
|
||||
Tags = nfo.Tags.Map(t => new Tag { Name = t }).ToList(),
|
||||
Studios = nfo.Studios.Map(s => new Studio { Name = s }).ToList()
|
||||
},
|
||||
None);
|
||||
}
|
||||
@@ -365,21 +410,38 @@ namespace ErsatzTV.Core.Metadata
|
||||
}
|
||||
}
|
||||
|
||||
private static DateTime? GetAired(string aired)
|
||||
private static int? GetYear(int year, string premiered)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(aired))
|
||||
if (year > 1000)
|
||||
{
|
||||
return year;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(premiered))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (DateTime.TryParse(aired, out DateTime parsed))
|
||||
if (DateTime.TryParse(premiered, out DateTime parsed))
|
||||
{
|
||||
return parsed;
|
||||
return parsed.Year;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static DateTime? GetAired(int year, string aired)
|
||||
{
|
||||
DateTime? fallback = year > 1000 ? new DateTime(year, 1, 1) : null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(aired))
|
||||
{
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return DateTime.TryParse(aired, out DateTime parsed) ? parsed : fallback;
|
||||
}
|
||||
|
||||
[XmlRoot("movie")]
|
||||
public class MovieNfo
|
||||
{
|
||||
@@ -409,6 +471,9 @@ namespace ErsatzTV.Core.Metadata
|
||||
|
||||
[XmlElement("tag")]
|
||||
public List<string> Tags { get; set; }
|
||||
|
||||
[XmlElement("studio")]
|
||||
public List<string> Studios { get; set; }
|
||||
}
|
||||
|
||||
[XmlRoot("tvshow")]
|
||||
@@ -437,6 +502,9 @@ namespace ErsatzTV.Core.Metadata
|
||||
|
||||
[XmlElement("tag")]
|
||||
public List<string> Tags { get; set; }
|
||||
|
||||
[XmlElement("studio")]
|
||||
public List<string> Studios { get; set; }
|
||||
}
|
||||
|
||||
[XmlRoot("episodedetails")]
|
||||
|
||||
@@ -159,7 +159,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BaseError.New(ex.Message);
|
||||
return BaseError.New(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BaseError.New(ex.Message);
|
||||
return BaseError.New(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BaseError.New(ex.Message);
|
||||
return BaseError.New(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +225,11 @@ namespace ErsatzTV.Core.Metadata
|
||||
},
|
||||
async () =>
|
||||
{
|
||||
if (!Optional(episode.EpisodeMetadata).Flatten().Any())
|
||||
bool shouldUpdate = Optional(episode.EpisodeMetadata).Flatten().HeadOrNone().Match(
|
||||
m => m.DateUpdated == DateTime.MinValue,
|
||||
true);
|
||||
|
||||
if (shouldUpdate)
|
||||
{
|
||||
string path = episode.MediaVersions.Head().MediaFiles.Head().Path;
|
||||
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Fallback Metadata", path);
|
||||
@@ -237,7 +241,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BaseError.New(ex.Message);
|
||||
return BaseError.New(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +264,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BaseError.New(ex.Message);
|
||||
return BaseError.New(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,7 +283,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BaseError.New(ex.Message);
|
||||
return BaseError.New(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,7 +302,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BaseError.New(ex.Message);
|
||||
return BaseError.New(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -158,6 +158,37 @@ namespace ErsatzTV.Core.Plex
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Studio studio in existingMetadata.Studios
|
||||
.Filter(s => incomingMetadata.Studios.All(s2 => s2.Name != s.Name))
|
||||
.ToList())
|
||||
{
|
||||
existingMetadata.Studios.Remove(studio);
|
||||
if (await _metadataRepository.RemoveStudio(studio))
|
||||
{
|
||||
result.IsUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Studio studio in incomingMetadata.Studios
|
||||
.Filter(s => existingMetadata.Studios.All(s2 => s2.Name != s.Name))
|
||||
.ToList())
|
||||
{
|
||||
existingMetadata.Studios.Add(studio);
|
||||
if (await _movieRepository.AddStudio(existingMetadata, studio))
|
||||
{
|
||||
result.IsUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (incomingMetadata.SortTitle != existingMetadata.SortTitle)
|
||||
{
|
||||
existingMetadata.SortTitle = incomingMetadata.SortTitle;
|
||||
if (await _movieRepository.UpdateSortTitle(existingMetadata))
|
||||
{
|
||||
result.IsUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: update other metadata?
|
||||
}
|
||||
|
||||
|
||||
@@ -131,6 +131,28 @@ namespace ErsatzTV.Core.Plex
|
||||
result.IsUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Studio studio in existingMetadata.Studios
|
||||
.Filter(s => incomingMetadata.Studios.All(s2 => s2.Name != s.Name))
|
||||
.ToList())
|
||||
{
|
||||
existingMetadata.Studios.Remove(studio);
|
||||
if (await _metadataRepository.RemoveStudio(studio))
|
||||
{
|
||||
result.IsUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Studio studio in incomingMetadata.Studios
|
||||
.Filter(s => existingMetadata.Studios.All(s2 => s2.Name != s.Name))
|
||||
.ToList())
|
||||
{
|
||||
existingMetadata.Studios.Add(studio);
|
||||
if (await _televisionRepository.AddStudio(existingMetadata, studio))
|
||||
{
|
||||
result.IsUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -21,6 +21,10 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
builder.HasMany(mm => mm.Tags)
|
||||
.WithOne()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasMany(mm => mm.Studios)
|
||||
.WithOne()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,15 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
.WithOne()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasMany(mm => mm.Genres)
|
||||
builder.HasMany(sm => sm.Genres)
|
||||
.WithOne()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasMany(mm => mm.Tags)
|
||||
builder.HasMany(sm => sm.Tags)
|
||||
.WithOne()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasMany(sm => sm.Studios)
|
||||
.WithOne()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations
|
||||
{
|
||||
public class StudioConfiguration : IEntityTypeConfiguration<Studio>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Studio> builder) => builder.ToTable("Studio");
|
||||
}
|
||||
}
|
||||
@@ -257,7 +257,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<Unit> DeleteAllPlex()
|
||||
public async Task<List<int>> DeleteAllPlex()
|
||||
{
|
||||
await using TvContext context = _dbContextFactory.CreateDbContext();
|
||||
|
||||
@@ -267,8 +267,12 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
List<PlexLibrary> allPlexLibraries = await context.PlexLibraries.ToListAsync();
|
||||
context.PlexLibraries.RemoveRange(allPlexLibraries);
|
||||
|
||||
List<int> movieIds = await context.PlexMovies.Map(pm => pm.Id).ToListAsync();
|
||||
List<int> showIds = await context.PlexShows.Map(ps => ps.Id).ToListAsync();
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
return Unit.Default;
|
||||
|
||||
return movieIds.Append(showIds).ToList();
|
||||
}
|
||||
|
||||
public async Task<List<int>> DisablePlexLibrarySync(List<int> libraryIds)
|
||||
|
||||
@@ -112,5 +112,13 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
public Task<bool> RemoveGenre(Genre genre) =>
|
||||
_dbConnection.ExecuteAsync("DELETE FROM Genre WHERE Id = @GenreId", new { GenreId = genre.Id })
|
||||
.Map(result => result > 0);
|
||||
|
||||
public Task<bool> RemoveTag(Tag tag) =>
|
||||
_dbConnection.ExecuteAsync("DELETE FROM Tag WHERE Id = @TagId", new { TagId = tag.Id })
|
||||
.Map(result => result > 0);
|
||||
|
||||
public Task<bool> RemoveStudio(Studio studio) =>
|
||||
_dbConnection.ExecuteAsync("DELETE FROM Studio WHERE Id = @StudioId", new { StudioId = studio.Id })
|
||||
.Map(result => result > 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
.ThenInclude(m => m.Genres)
|
||||
.Include(m => m.MovieMetadata)
|
||||
.ThenInclude(m => m.Tags)
|
||||
.Include(m => m.MovieMetadata)
|
||||
.ThenInclude(m => m.Studios)
|
||||
.OrderBy(m => m.Id)
|
||||
.SingleOrDefaultAsync(m => m.Id == movieId)
|
||||
.Map(Optional);
|
||||
@@ -56,6 +58,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
.ThenInclude(mm => mm.Genres)
|
||||
.Include(i => i.MovieMetadata)
|
||||
.ThenInclude(mm => mm.Tags)
|
||||
.Include(i => i.MovieMetadata)
|
||||
.ThenInclude(mm => mm.Studios)
|
||||
.Include(i => i.LibraryPath)
|
||||
.ThenInclude(lp => lp.Library)
|
||||
.Include(i => i.MediaVersions)
|
||||
@@ -82,6 +86,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
.Include(i => i.MovieMetadata)
|
||||
.ThenInclude(mm => mm.Tags)
|
||||
.Include(i => i.MovieMetadata)
|
||||
.ThenInclude(mm => mm.Studios)
|
||||
.Include(i => i.MovieMetadata)
|
||||
.ThenInclude(mm => mm.Artwork)
|
||||
.Include(i => i.MediaVersions)
|
||||
.ThenInclude(mv => mv.MediaFiles)
|
||||
@@ -165,6 +171,16 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
"INSERT INTO Genre (Name, MovieMetadataId) VALUES (@Name, @MetadataId)",
|
||||
new { genre.Name, MetadataId = metadata.Id }).Map(result => result > 0);
|
||||
|
||||
public Task<bool> AddTag(MovieMetadata metadata, Tag tag) =>
|
||||
_dbConnection.ExecuteAsync(
|
||||
"INSERT INTO Tag (Name, MovieMetadataId) VALUES (@Name, @MetadataId)",
|
||||
new { tag.Name, MetadataId = metadata.Id }).Map(result => result > 0);
|
||||
|
||||
public Task<bool> AddStudio(MovieMetadata metadata, Studio studio) =>
|
||||
_dbConnection.ExecuteAsync(
|
||||
"INSERT INTO Studio (Name, MovieMetadataId) VALUES (@Name, @MetadataId)",
|
||||
new { studio.Name, MetadataId = metadata.Id }).Map(result => result > 0);
|
||||
|
||||
public async Task<List<int>> RemoveMissingPlexMovies(PlexLibrary library, List<string> movieKeys)
|
||||
{
|
||||
List<int> ids = await _dbConnection.QueryAsync<int>(
|
||||
@@ -185,6 +201,11 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
return ids;
|
||||
}
|
||||
|
||||
public Task<bool> UpdateSortTitle(MovieMetadata movieMetadata) =>
|
||||
_dbConnection.ExecuteAsync(
|
||||
@"UPDATE MovieMetadata SET SortTitle = @SortTitle WHERE Id = @Id",
|
||||
new { movieMetadata.SortTitle, movieMetadata.Id }).Map(result => result > 0);
|
||||
|
||||
private static async Task<Either<BaseError, MediaItemScanResult<Movie>>> AddMovie(
|
||||
TvContext dbContext,
|
||||
int libraryPathId,
|
||||
|
||||
@@ -37,10 +37,14 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
.ThenInclude(mm => mm.Genres)
|
||||
.Include(mi => (mi as Movie).MovieMetadata)
|
||||
.ThenInclude(mm => mm.Tags)
|
||||
.Include(mi => (mi as Movie).MovieMetadata)
|
||||
.ThenInclude(mm => mm.Studios)
|
||||
.Include(mi => (mi as Show).ShowMetadata)
|
||||
.ThenInclude(mm => mm.Genres)
|
||||
.Include(mi => (mi as Show).ShowMetadata)
|
||||
.ThenInclude(mm => mm.Tags)
|
||||
.Include(mi => (mi as Show).ShowMetadata)
|
||||
.ThenInclude(mm => mm.Studios)
|
||||
.OrderBy(mi => mi.Id)
|
||||
.SingleOrDefaultAsync(mi => mi.Id == id)
|
||||
.Map(Optional);
|
||||
|
||||
@@ -53,6 +53,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
.ThenInclude(sm => sm.Genres)
|
||||
.Include(s => s.ShowMetadata)
|
||||
.ThenInclude(sm => sm.Tags)
|
||||
.Include(s => s.ShowMetadata)
|
||||
.ThenInclude(sm => sm.Studios)
|
||||
.OrderBy(s => s.Id)
|
||||
.SingleOrDefaultAsync()
|
||||
.Map(Optional);
|
||||
@@ -354,11 +356,13 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
Option<PlexShow> maybeExisting = await dbContext.PlexShows
|
||||
.AsNoTracking()
|
||||
.Include(i => i.ShowMetadata)
|
||||
.ThenInclude(mm => mm.Genres)
|
||||
.ThenInclude(sm => sm.Genres)
|
||||
.Include(i => i.ShowMetadata)
|
||||
.ThenInclude(mm => mm.Tags)
|
||||
.ThenInclude(sm => sm.Tags)
|
||||
.Include(i => i.ShowMetadata)
|
||||
.ThenInclude(mm => mm.Artwork)
|
||||
.ThenInclude(sm => sm.Studios)
|
||||
.Include(i => i.ShowMetadata)
|
||||
.ThenInclude(sm => sm.Artwork)
|
||||
.Include(i => i.LibraryPath)
|
||||
.ThenInclude(lp => lp.Library)
|
||||
.OrderBy(i => i.Key)
|
||||
@@ -464,9 +468,19 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
|
||||
public Task<bool> AddGenre(ShowMetadata metadata, Genre genre) =>
|
||||
_dbConnection.ExecuteAsync(
|
||||
"INSERT INTO Genre (Name, SeasonMetadataId) VALUES (@Name, @MetadataId)",
|
||||
"INSERT INTO Genre (Name, ShowMetadataId) VALUES (@Name, @MetadataId)",
|
||||
new { genre.Name, MetadataId = metadata.Id }).Map(result => result > 0);
|
||||
|
||||
public Task<bool> AddTag(ShowMetadata metadata, Tag tag) =>
|
||||
_dbConnection.ExecuteAsync(
|
||||
"INSERT INTO Tag (Name, ShowMetadataId) VALUES (@Name, @MetadataId)",
|
||||
new { tag.Name, MetadataId = metadata.Id }).Map(result => result > 0);
|
||||
|
||||
public Task<bool> AddStudio(ShowMetadata metadata, Studio studio) =>
|
||||
_dbConnection.ExecuteAsync(
|
||||
"INSERT INTO Studio (Name, ShowMetadataId) VALUES (@Name, @MetadataId)",
|
||||
new { studio.Name, MetadataId = metadata.Id }).Map(result => result > 0);
|
||||
|
||||
public async Task<List<int>> RemoveMissingPlexShows(PlexLibrary library, List<string> showKeys)
|
||||
{
|
||||
List<int> ids = await _dbConnection.QueryAsync<int>(
|
||||
@@ -504,6 +518,9 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
SeasonMetadata = new List<SeasonMetadata>
|
||||
{
|
||||
new()
|
||||
{
|
||||
DateAdded = DateTime.UtcNow
|
||||
}
|
||||
}
|
||||
};
|
||||
await dbContext.Seasons.AddAsync(season);
|
||||
@@ -524,6 +541,11 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
{
|
||||
try
|
||||
{
|
||||
if (dbContext.MediaFiles.Any(mf => mf.Path == path))
|
||||
{
|
||||
return BaseError.New("Multi-episode files are not yet supported");
|
||||
}
|
||||
|
||||
var episode = new Episode
|
||||
{
|
||||
LibraryPathId = libraryPathId,
|
||||
@@ -532,6 +554,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
{
|
||||
new()
|
||||
{
|
||||
DateAdded = DateTime.UtcNow,
|
||||
DateUpdated = DateTime.MinValue,
|
||||
MetadataKind = MetadataKind.Fallback
|
||||
}
|
||||
@@ -605,6 +628,11 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
{
|
||||
try
|
||||
{
|
||||
if (dbContext.MediaFiles.Any(mf => mf.Path == item.MediaVersions.Head().MediaFiles.Head().Path))
|
||||
{
|
||||
return BaseError.New("Multi-episode files are not yet supported");
|
||||
}
|
||||
|
||||
item.LibraryPathId = library.Paths.Head().Id;
|
||||
|
||||
await dbContext.PlexEpisodes.AddAsync(item);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,75 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Migrations
|
||||
{
|
||||
public partial class Add_Studio : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
"Studio",
|
||||
table => new
|
||||
{
|
||||
Id = table.Column<int>("INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Name = table.Column<string>("TEXT", nullable: true),
|
||||
EpisodeMetadataId = table.Column<int>("INTEGER", nullable: true),
|
||||
MovieMetadataId = table.Column<int>("INTEGER", nullable: true),
|
||||
SeasonMetadataId = table.Column<int>("INTEGER", nullable: true),
|
||||
ShowMetadataId = table.Column<int>("INTEGER", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Studio", x => x.Id);
|
||||
table.ForeignKey(
|
||||
"FK_Studio_EpisodeMetadata_EpisodeMetadataId",
|
||||
x => x.EpisodeMetadataId,
|
||||
"EpisodeMetadata",
|
||||
"Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
"FK_Studio_MovieMetadata_MovieMetadataId",
|
||||
x => x.MovieMetadataId,
|
||||
"MovieMetadata",
|
||||
"Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
"FK_Studio_SeasonMetadata_SeasonMetadataId",
|
||||
x => x.SeasonMetadataId,
|
||||
"SeasonMetadata",
|
||||
"Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
"FK_Studio_ShowMetadata_ShowMetadataId",
|
||||
x => x.ShowMetadataId,
|
||||
"ShowMetadata",
|
||||
"Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
"IX_Studio_EpisodeMetadataId",
|
||||
"Studio",
|
||||
"EpisodeMetadataId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
"IX_Studio_MovieMetadataId",
|
||||
"Studio",
|
||||
"MovieMetadataId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
"IX_Studio_SeasonMetadataId",
|
||||
"Studio",
|
||||
"SeasonMetadataId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
"IX_Studio_ShowMetadataId",
|
||||
"Studio",
|
||||
"ShowMetadataId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder) =>
|
||||
migrationBuilder.DropTable(
|
||||
"Studio");
|
||||
}
|
||||
}
|
||||
Generated
+1749
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Migrations
|
||||
{
|
||||
public partial class Reset_MetadataDateUpdated_Studio : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(@"UPDATE MovieMetadata SET DateUpdated = '0001-01-01 00:00:00'");
|
||||
migrationBuilder.Sql(@"UPDATE ShowMetadata SET DateUpdated = '0001-01-01 00:00:00'");
|
||||
migrationBuilder.Sql(@"UPDATE Library SET LastScan = '0001-01-01 00:00:00'");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -845,6 +845,42 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.ToTable("ShowMetadata");
|
||||
});
|
||||
|
||||
modelBuilder.Entity(
|
||||
"ErsatzTV.Core.Domain.Studio",
|
||||
b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("EpisodeMetadataId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("MovieMetadataId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("SeasonMetadataId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("ShowMetadataId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("EpisodeMetadataId");
|
||||
|
||||
b.HasIndex("MovieMetadataId");
|
||||
|
||||
b.HasIndex("SeasonMetadataId");
|
||||
|
||||
b.HasIndex("ShowMetadataId");
|
||||
|
||||
b.ToTable("Studio");
|
||||
});
|
||||
|
||||
modelBuilder.Entity(
|
||||
"ErsatzTV.Core.Domain.Tag",
|
||||
b =>
|
||||
@@ -1499,6 +1535,29 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
b.Navigation("Show");
|
||||
});
|
||||
|
||||
modelBuilder.Entity(
|
||||
"ErsatzTV.Core.Domain.Studio",
|
||||
b =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.EpisodeMetadata", null)
|
||||
.WithMany("Studios")
|
||||
.HasForeignKey("EpisodeMetadataId");
|
||||
|
||||
b.HasOne("ErsatzTV.Core.Domain.MovieMetadata", null)
|
||||
.WithMany("Studios")
|
||||
.HasForeignKey("MovieMetadataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("ErsatzTV.Core.Domain.SeasonMetadata", null)
|
||||
.WithMany("Studios")
|
||||
.HasForeignKey("SeasonMetadataId");
|
||||
|
||||
b.HasOne("ErsatzTV.Core.Domain.ShowMetadata", null)
|
||||
.WithMany("Studios")
|
||||
.HasForeignKey("ShowMetadataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity(
|
||||
"ErsatzTV.Core.Domain.Tag",
|
||||
b =>
|
||||
@@ -1744,6 +1803,8 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
|
||||
b.Navigation("Genres");
|
||||
|
||||
b.Navigation("Studios");
|
||||
|
||||
b.Navigation("Tags");
|
||||
});
|
||||
|
||||
@@ -1765,6 +1826,8 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
|
||||
b.Navigation("Genres");
|
||||
|
||||
b.Navigation("Studios");
|
||||
|
||||
b.Navigation("Tags");
|
||||
});
|
||||
|
||||
@@ -1794,6 +1857,8 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
|
||||
b.Navigation("Genres");
|
||||
|
||||
b.Navigation("Studios");
|
||||
|
||||
b.Navigation("Tags");
|
||||
});
|
||||
|
||||
@@ -1805,6 +1870,8 @@ namespace ErsatzTV.Infrastructure.Migrations
|
||||
|
||||
b.Navigation("Genres");
|
||||
|
||||
b.Navigation("Studios");
|
||||
|
||||
b.Navigation("Tags");
|
||||
});
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace ErsatzTV.Infrastructure.Plex.Models
|
||||
public int AddedAt { get; set; }
|
||||
public int UpdatedAt { get; set; }
|
||||
public int Index { get; set; }
|
||||
public string Studio { get; set; }
|
||||
public List<PlexMediaResponse> Media { get; set; }
|
||||
public List<PlexGenreResponse> Genre { get; set; }
|
||||
}
|
||||
|
||||
@@ -177,9 +177,15 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
DateAdded = dateAdded,
|
||||
DateUpdated = lastWriteTime,
|
||||
Genres = Optional(response.Genre).Flatten().Map(g => new Genre { Name = g.Tag }).ToList(),
|
||||
Tags = new List<Tag>()
|
||||
Tags = new List<Tag>(),
|
||||
Studios = new List<Studio>()
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(response.Studio))
|
||||
{
|
||||
metadata.Studios.Add(new Studio { Name = response.Studio });
|
||||
}
|
||||
|
||||
if (DateTime.TryParse(response.OriginallyAvailableAt, out DateTime releaseDate))
|
||||
{
|
||||
metadata.ReleaseDate = releaseDate;
|
||||
@@ -279,9 +285,15 @@ namespace ErsatzTV.Infrastructure.Plex
|
||||
DateAdded = dateAdded,
|
||||
DateUpdated = lastWriteTime,
|
||||
Genres = Optional(response.Genre).Flatten().Map(g => new Genre { Name = g.Tag }).ToList(),
|
||||
Tags = new List<Tag>()
|
||||
Tags = new List<Tag>(),
|
||||
Studios = new List<Studio>()
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(response.Studio))
|
||||
{
|
||||
metadata.Studios.Add(new Studio { Name = response.Studio });
|
||||
}
|
||||
|
||||
if (DateTime.TryParse(response.OriginallyAvailableAt, out DateTime releaseDate))
|
||||
{
|
||||
metadata.ReleaseDate = releaseDate;
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace ErsatzTV.Infrastructure.Search
|
||||
private const string TitleAndYearField = "title_and_year";
|
||||
private const string JumpLetterField = "jump_letter";
|
||||
private const string ReleaseDateField = "release_date";
|
||||
private const string StudioField = "studio";
|
||||
|
||||
private const string MovieType = "movie";
|
||||
private const string ShowType = "show";
|
||||
@@ -48,7 +49,7 @@ namespace ErsatzTV.Infrastructure.Search
|
||||
private readonly ILogger<SearchIndex> _logger;
|
||||
|
||||
private readonly ISearchRepository _searchRepository;
|
||||
|
||||
|
||||
public SearchIndex(
|
||||
ILocalFileSystem localFileSystem,
|
||||
ISearchRepository searchRepository,
|
||||
@@ -263,10 +264,16 @@ namespace ErsatzTV.Infrastructure.Search
|
||||
doc.Add(new TextField(TagField, tag.Name, Field.Store.NO));
|
||||
}
|
||||
|
||||
foreach (Studio studio in metadata.Studios)
|
||||
{
|
||||
doc.Add(new TextField(StudioField, studio.Name, Field.Store.NO));
|
||||
}
|
||||
|
||||
writer.UpdateDocument(new Term(IdField, movie.Id.ToString()), doc);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
metadata.Movie = null;
|
||||
_logger.LogWarning(ex, "Error indexing movie with metadata {@Metadata}", metadata);
|
||||
}
|
||||
}
|
||||
@@ -316,10 +323,16 @@ namespace ErsatzTV.Infrastructure.Search
|
||||
doc.Add(new TextField(TagField, tag.Name, Field.Store.NO));
|
||||
}
|
||||
|
||||
foreach (Studio studio in metadata.Studios)
|
||||
{
|
||||
doc.Add(new TextField(StudioField, studio.Name, Field.Store.NO));
|
||||
}
|
||||
|
||||
writer.UpdateDocument(new Term(IdField, show.Id.ToString()), doc);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
metadata.Show = null;
|
||||
_logger.LogWarning(ex, "Error indexing show with metadata {@Metadata}", metadata);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,13 +43,23 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (_movie.Studios.Any())
|
||||
{
|
||||
<MudText GutterBottom="true">Studios</MudText>
|
||||
<div class="mb-2">
|
||||
@foreach (string studio in _movie.Studios.OrderBy(s => s))
|
||||
{
|
||||
<MudFab Color="Color.Info" Size="Size.Small" Label="@studio" Class="mr-2 mb-2" Link="@($"/search?query=studio%3a%22{Uri.EscapeDataString(studio.ToLowerInvariant())}%22")"/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (_movie.Genres.Any())
|
||||
{
|
||||
<MudText GutterBottom="true">Genres</MudText>
|
||||
<div class="mb-2">
|
||||
@foreach (string genre in _movie.Genres.OrderBy(g => g))
|
||||
{
|
||||
<MudFab Color="Color.Info" Size="Size.Small" Label="@genre" Class="mr-2 mb-2" Link="@($"/search?query=genre%3a%22{genre.ToLowerInvariant()}%22")"/>
|
||||
<MudFab Color="Color.Info" Size="Size.Small" Label="@genre" Class="mr-2 mb-2" Link="@($"/search?query=genre%3a%22{Uri.EscapeDataString(genre.ToLowerInvariant())}%22")"/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -59,7 +69,7 @@
|
||||
<div>
|
||||
@foreach (string tag in _movie.Tags.OrderBy(t => t))
|
||||
{
|
||||
<MudFab Color="Color.Info" Size="Size.Small" Label="@tag" Class="mr-2 mb-2" Link="@($"/search?query=tag%3a%22{tag.ToLowerInvariant()}%22")"/>
|
||||
<MudFab Color="Color.Info" Size="Size.Small" Label="@tag" Class="mr-2 mb-2" Link="@($"/search?query=tag%3a%22{Uri.EscapeDataString(tag.ToLowerInvariant())}%22")"/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -58,13 +58,23 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (_show.Studios.Any())
|
||||
{
|
||||
<MudText GutterBottom="true">Studios</MudText>
|
||||
<div class="mb-2">
|
||||
@foreach (string studio in _show.Studios.OrderBy(g => g))
|
||||
{
|
||||
<MudFab Color="Color.Info" Size="Size.Small" Label="@studio" Class="mr-2 mb-2" Link="@($"/search?query=studio%3a%22{Uri.EscapeDataString(studio.ToLowerInvariant())}%22")"/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (_show.Genres.Any())
|
||||
{
|
||||
<MudText GutterBottom="true">Genres</MudText>
|
||||
<div class="mb-2">
|
||||
@foreach (string genre in _show.Genres.OrderBy(g => g))
|
||||
{
|
||||
<MudFab Color="Color.Info" Size="Size.Small" Label="@genre" Class="mr-2 mb-2" Link="@($"/search?query=genre%3a%22{genre.ToLowerInvariant()}%22")"/>
|
||||
<MudFab Color="Color.Info" Size="Size.Small" Label="@genre" Class="mr-2 mb-2" Link="@($"/search?query=genre%3a%22{Uri.EscapeDataString(genre.ToLowerInvariant())}%22")"/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -74,7 +84,7 @@
|
||||
<div>
|
||||
@foreach (string tag in _show.Tags.OrderBy(t => t))
|
||||
{
|
||||
<MudFab Color="Color.Info" Size="Size.Small" Label="@tag" Class="mr-2 mb-2" Link="@($"/search?query=tag%3a%22{tag.ToLowerInvariant()}%22")"/>
|
||||
<MudFab Color="Color.Info" Size="Size.Small" Label="@tag" Class="mr-2 mb-2" Link="@($"/search?query=tag%3a%22{Uri.EscapeDataString(tag.ToLowerInvariant())}%22")"/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user