diff --git a/CHANGELOG.md b/CHANGELOG.md index e7f5291a..78915165 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,19 +13,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - `Extract and use embedded (text) subtitles` - Default value: `false` - When enabled, embedded text subtitles will be periodically extracted, and considered for playback +- Add `sub_language` and `sub_language_tag` fields to search index ### Fixed - Fix antiforgery error caused by reusing existing browser tabs across docker container restarts - Data protection keys will now be persisted under ErsatzTV's config folder instead of being recreated at startup - Fix bug updating/replacing Jellyfin movies - A deep scan can be used to fix all movies, otherwise any future updates made to JF movies will correctly sync to ETV -- Automatically generate JWT tokens to allow channel previews of protected streams +- Automatically generate JWT tokens to allow channel previews of protected streams ### Changed - Log search index updates under scanner category at debug level, to indicate a potential cause for the UI being out of date - Batch search index updates to keep pace with library scans - Previously, search index updates would slowly process over minutes/hours after library scans completed - Search index updates should now complete at the same time as library scans +- Do not unnecessarily update the search index during media server library scans ## [0.8.5-beta] - 2024-01-30 ### Added diff --git a/ErsatzTV.Application/ErsatzTV.Application.csproj b/ErsatzTV.Application/ErsatzTV.Application.csproj index 7238e638..10a1286d 100644 --- a/ErsatzTV.Application/ErsatzTV.Application.csproj +++ b/ErsatzTV.Application/ErsatzTV.Application.csproj @@ -10,7 +10,7 @@ - + diff --git a/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj b/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj index f26bc1a8..78fc33ff 100644 --- a/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj +++ b/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/ErsatzTV.Core/ErsatzTV.Core.csproj b/ErsatzTV.Core/ErsatzTV.Core.csproj index b09631c2..dbb5efd7 100644 --- a/ErsatzTV.Core/ErsatzTV.Core.csproj +++ b/ErsatzTV.Core/ErsatzTV.Core.csproj @@ -10,7 +10,7 @@ - + diff --git a/ErsatzTV.Core/Interfaces/Repositories/ISearchRepository.cs b/ErsatzTV.Core/Interfaces/Repositories/ISearchRepository.cs index 50c38625..97e9c200 100644 --- a/ErsatzTV.Core/Interfaces/Repositories/ISearchRepository.cs +++ b/ErsatzTV.Core/Interfaces/Repositories/ISearchRepository.cs @@ -6,8 +6,11 @@ public interface ISearchRepository { Task> GetItemToIndex(int id); Task> GetLanguagesForShow(Show show); + Task> GetSubLanguagesForShow(Show show); Task> GetLanguagesForSeason(Season season); + Task> GetSubLanguagesForSeason(Season season); Task> GetLanguagesForArtist(Artist artist); + Task> GetSubLanguagesForArtist(Artist artist); Task> GetAllLanguageCodes(List mediaCodes); IAsyncEnumerable GetAllMediaItems(); } diff --git a/ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj b/ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj index 406396f3..0ea11219 100644 --- a/ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj +++ b/ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj @@ -9,7 +9,7 @@ - + diff --git a/ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj b/ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj index fcacb040..8311982d 100644 --- a/ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj +++ b/ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj @@ -14,7 +14,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/ErsatzTV.Infrastructure/Data/Repositories/Caching/CachingSearchRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/Caching/CachingSearchRepository.cs index 062f1dbd..96abe50a 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/Caching/CachingSearchRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/Caching/CachingSearchRepository.cs @@ -17,11 +17,18 @@ public class CachingSearchRepository : ICachingSearchRepository public Task> GetItemToIndex(int id) => _searchRepository.GetItemToIndex(id); public Task> GetLanguagesForShow(Show show) => _searchRepository.GetLanguagesForShow(show); + public Task> GetSubLanguagesForShow(Show show) => _searchRepository.GetSubLanguagesForShow(show); public Task> GetLanguagesForSeason(Season season) => _searchRepository.GetLanguagesForSeason(season); + public Task> GetSubLanguagesForSeason(Season season) => + _searchRepository.GetSubLanguagesForSeason(season); + public Task> GetLanguagesForArtist(Artist artist) => _searchRepository.GetLanguagesForArtist(artist); + public Task> GetSubLanguagesForArtist(Artist artist) => + _searchRepository.GetSubLanguagesForArtist(artist); + public async Task> GetAllLanguageCodes(List mediaCodes) { if (!_cache.ContainsKey(mediaCodes)) diff --git a/ErsatzTV.Infrastructure/Data/Repositories/EmbyMovieRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/EmbyMovieRepository.cs index 8e0d3f34..1531acb5 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/EmbyMovieRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/EmbyMovieRepository.cs @@ -37,6 +37,11 @@ public class EmbyMovieRepository : IEmbyMovieRepository public async Task> FlagNormal(EmbyLibrary library, EmbyMovie movie) { + if (movie.State is MediaItemState.Normal) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); movie.State = MediaItemState.Normal; @@ -51,7 +56,7 @@ public class EmbyMovieRepository : IEmbyMovieRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 0 WHERE Id = @Id", + "UPDATE MediaItem SET State = 0 WHERE Id = @Id AND State != 0", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -60,6 +65,11 @@ public class EmbyMovieRepository : IEmbyMovieRepository public async Task> FlagUnavailable(EmbyLibrary library, EmbyMovie movie) { + if (movie.State is MediaItemState.Unavailable) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); movie.State = MediaItemState.Unavailable; @@ -74,7 +84,7 @@ public class EmbyMovieRepository : IEmbyMovieRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 2 WHERE Id = @Id", + "UPDATE MediaItem SET State = 2 WHERE Id = @Id AND State != 2", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -83,6 +93,11 @@ public class EmbyMovieRepository : IEmbyMovieRepository public async Task> FlagRemoteOnly(EmbyLibrary library, EmbyMovie movie) { + if (movie.State is MediaItemState.RemoteOnly) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); movie.State = MediaItemState.RemoteOnly; @@ -97,7 +112,7 @@ public class EmbyMovieRepository : IEmbyMovieRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 3 WHERE Id = @Id", + "UPDATE MediaItem SET State = 3 WHERE Id = @Id AND State != 3", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -108,7 +123,7 @@ public class EmbyMovieRepository : IEmbyMovieRepository { if (movieItemIds.Count == 0) { - return new List(); + return []; } await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); @@ -123,7 +138,7 @@ public class EmbyMovieRepository : IEmbyMovieRepository .Map(result => result.ToList()); await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 1 WHERE Id IN @Ids", + "UPDATE MediaItem SET State = 1 WHERE Id IN @Ids AND State != 1", new { Ids = ids }); return ids; diff --git a/ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs index 247f1478..e2917bca 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs @@ -207,6 +207,11 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository public async Task> FlagNormal(EmbyLibrary library, EmbyEpisode episode) { + if (episode.State is MediaItemState.Normal) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); episode.State = MediaItemState.Normal; @@ -221,7 +226,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 0 WHERE Id = @Id", + "UPDATE MediaItem SET State = 0 WHERE Id = @Id AND State != 0", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -230,6 +235,11 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository public async Task> FlagNormal(EmbyLibrary library, EmbySeason season) { + if (season.State is MediaItemState.Normal) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); season.State = MediaItemState.Normal; @@ -244,7 +254,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 0 WHERE Id = @Id", + "UPDATE MediaItem SET State = 0 WHERE Id = @Id AND State != 0", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -253,6 +263,11 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository public async Task> FlagNormal(EmbyLibrary library, EmbyShow show) { + if (show.State is MediaItemState.Normal) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); show.State = MediaItemState.Normal; @@ -267,7 +282,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 0 WHERE Id = @Id", + "UPDATE MediaItem SET State = 0 WHERE Id = @Id AND State != 0", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -278,7 +293,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository { if (showItemIds.Count == 0) { - return new List(); + return []; } await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); @@ -293,7 +308,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository .Map(result => result.ToList()); await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 1 WHERE Id IN @Ids", + "UPDATE MediaItem SET State = 1 WHERE Id IN @Ids AND State != 1", new { Ids = ids }); return ids; @@ -303,7 +318,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository { if (seasonItemIds.Count == 0) { - return new List(); + return []; } await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); @@ -318,7 +333,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository .Map(result => result.ToList()); await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 1 WHERE Id IN @Ids", + "UPDATE MediaItem SET State = 1 WHERE Id IN @Ids AND State != 1", new { Ids = ids }); return ids; @@ -328,7 +343,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository { if (episodeItemIds.Count == 0) { - return new List(); + return []; } await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); @@ -343,7 +358,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository .Map(result => result.ToList()); await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 1 WHERE Id IN @Ids", + "UPDATE MediaItem SET State = 1 WHERE Id IN @Ids AND State != 1", new { Ids = ids }); return ids; @@ -351,6 +366,11 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository public async Task> FlagUnavailable(EmbyLibrary library, EmbyEpisode episode) { + if (episode.State is MediaItemState.Unavailable) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); episode.State = MediaItemState.Unavailable; @@ -365,7 +385,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 2 WHERE Id = @Id", + "UPDATE MediaItem SET State = 2 WHERE Id = @Id AND State != 2", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -374,6 +394,11 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository public async Task> FlagRemoteOnly(EmbyLibrary library, EmbyEpisode episode) { + if (episode.State is MediaItemState.RemoteOnly) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); episode.State = MediaItemState.RemoteOnly; @@ -388,7 +413,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 3 WHERE Id = @Id", + "UPDATE MediaItem SET State = 3 WHERE Id = @Id AND State != 3", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } diff --git a/ErsatzTV.Infrastructure/Data/Repositories/JellyfinMovieRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/JellyfinMovieRepository.cs index d4c38e3d..8edf04ac 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/JellyfinMovieRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/JellyfinMovieRepository.cs @@ -39,6 +39,11 @@ public class JellyfinMovieRepository : IJellyfinMovieRepository public async Task> FlagNormal(JellyfinLibrary library, JellyfinMovie movie) { + if (movie.State is MediaItemState.Normal) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); movie.State = MediaItemState.Normal; @@ -53,7 +58,7 @@ public class JellyfinMovieRepository : IJellyfinMovieRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 0 WHERE Id = @Id", + "UPDATE MediaItem SET State = 0 WHERE Id = @Id AND State != 0", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -62,6 +67,11 @@ public class JellyfinMovieRepository : IJellyfinMovieRepository public async Task> FlagUnavailable(JellyfinLibrary library, JellyfinMovie movie) { + if (movie.State is MediaItemState.Unavailable) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); movie.State = MediaItemState.Unavailable; @@ -76,7 +86,7 @@ public class JellyfinMovieRepository : IJellyfinMovieRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 2 WHERE Id = @Id", + "UPDATE MediaItem SET State = 2 WHERE Id = @Id AND State != 2", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -85,6 +95,11 @@ public class JellyfinMovieRepository : IJellyfinMovieRepository public async Task> FlagRemoteOnly(JellyfinLibrary library, JellyfinMovie movie) { + if (movie.State is MediaItemState.RemoteOnly) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); movie.State = MediaItemState.RemoteOnly; @@ -99,7 +114,7 @@ public class JellyfinMovieRepository : IJellyfinMovieRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 3 WHERE Id = @Id", + "UPDATE MediaItem SET State = 3 WHERE Id = @Id AND State != 3", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -110,7 +125,7 @@ public class JellyfinMovieRepository : IJellyfinMovieRepository { if (movieItemIds.Count == 0) { - return new List(); + return []; } await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); @@ -125,7 +140,7 @@ public class JellyfinMovieRepository : IJellyfinMovieRepository .Map(result => result.ToList()); await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 1 WHERE Id IN @Ids", + "UPDATE MediaItem SET State = 1 WHERE Id IN @Ids AND State != 1", new { Ids = ids }); return ids; diff --git a/ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs index b4e17680..7e77b850 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs @@ -211,6 +211,11 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository public async Task> FlagNormal(JellyfinLibrary library, JellyfinEpisode episode) { + if (episode.State is MediaItemState.Normal) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); episode.State = MediaItemState.Normal; @@ -225,7 +230,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 0 WHERE Id = @Id", + "UPDATE MediaItem SET State = 0 WHERE Id = @Id AND State != 0", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -234,6 +239,11 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository public async Task> FlagNormal(JellyfinLibrary library, JellyfinSeason season) { + if (season.State is MediaItemState.Normal) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); season.State = MediaItemState.Normal; @@ -248,7 +258,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 0 WHERE Id = @Id", + "UPDATE MediaItem SET State = 0 WHERE Id = @Id AND State != 0", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -257,6 +267,11 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository public async Task> FlagNormal(JellyfinLibrary library, JellyfinShow show) { + if (show.State is MediaItemState.Normal) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); show.State = MediaItemState.Normal; @@ -271,7 +286,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 0 WHERE Id = @Id", + "UPDATE MediaItem SET State = 0 WHERE Id = @Id AND State != 0", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -282,7 +297,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository { if (showItemIds.Count == 0) { - return new List(); + return []; } await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); @@ -297,7 +312,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository .Map(result => result.ToList()); await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 1 WHERE Id IN @Ids", + "UPDATE MediaItem SET State = 1 WHERE Id IN @Ids AND State != 1", new { Ids = ids }); return ids; @@ -307,7 +322,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository { if (seasonItemIds.Count == 0) { - return new List(); + return []; } await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); @@ -322,7 +337,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository .Map(result => result.ToList()); await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 1 WHERE Id IN @Ids", + "UPDATE MediaItem SET State = 1 WHERE Id IN @Ids AND State != 1", new { Ids = ids }); return ids; @@ -332,7 +347,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository { if (episodeItemIds.Count == 0) { - return new List(); + return []; } await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); @@ -347,7 +362,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository .Map(result => result.ToList()); await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 1 WHERE Id IN @Ids", + "UPDATE MediaItem SET State = 1 WHERE Id IN @Ids AND State != 1", new { Ids = ids }); return ids; @@ -355,6 +370,11 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository public async Task> FlagUnavailable(JellyfinLibrary library, JellyfinEpisode episode) { + if (episode.State is MediaItemState.Unavailable) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); episode.State = MediaItemState.Unavailable; @@ -369,7 +389,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 2 WHERE Id = @Id", + "UPDATE MediaItem SET State = 2 WHERE Id = @Id AND State != 2", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -378,6 +398,11 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository public async Task> FlagRemoteOnly(JellyfinLibrary library, JellyfinEpisode episode) { + if (episode.State is MediaItemState.RemoteOnly) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); episode.State = MediaItemState.RemoteOnly; @@ -392,7 +417,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 3 WHERE Id = @Id", + "UPDATE MediaItem SET State = 3 WHERE Id = @Id AND State != 3", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } diff --git a/ErsatzTV.Infrastructure/Data/Repositories/PlexMovieRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/PlexMovieRepository.cs index 3068f70c..10c3e548 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/PlexMovieRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/PlexMovieRepository.cs @@ -37,6 +37,11 @@ public class PlexMovieRepository : IPlexMovieRepository public async Task> FlagNormal(PlexLibrary library, PlexMovie movie) { + if (movie.State is MediaItemState.Normal) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); movie.State = MediaItemState.Normal; @@ -51,7 +56,7 @@ public class PlexMovieRepository : IPlexMovieRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 0 WHERE Id = @Id", + "UPDATE MediaItem SET State = 0 WHERE Id = @Id AND State != 0", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -60,6 +65,11 @@ public class PlexMovieRepository : IPlexMovieRepository public async Task> FlagUnavailable(PlexLibrary library, PlexMovie movie) { + if (movie.State is MediaItemState.Unavailable) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); movie.State = MediaItemState.Unavailable; @@ -74,7 +84,7 @@ public class PlexMovieRepository : IPlexMovieRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 2 WHERE Id = @Id", + "UPDATE MediaItem SET State = 2 WHERE Id = @Id AND State != 2", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -83,6 +93,11 @@ public class PlexMovieRepository : IPlexMovieRepository public async Task> FlagRemoteOnly(PlexLibrary library, PlexMovie movie) { + if (movie.State is MediaItemState.RemoteOnly) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); movie.State = MediaItemState.RemoteOnly; @@ -97,7 +112,7 @@ public class PlexMovieRepository : IPlexMovieRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 3 WHERE Id = @Id", + "UPDATE MediaItem SET State = 3 WHERE Id = @Id AND State != 3", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -108,7 +123,7 @@ public class PlexMovieRepository : IPlexMovieRepository { if (movieItemIds.Count == 0) { - return new List(); + return []; } await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); @@ -123,7 +138,7 @@ public class PlexMovieRepository : IPlexMovieRepository .Map(result => result.ToList()); await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 1 WHERE Id IN @Ids", + "UPDATE MediaItem SET State = 1 WHERE Id IN @Ids AND State != 1", new { Ids = ids }); return ids; diff --git a/ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs index 1cd1cfec..10306c6b 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs @@ -26,6 +26,11 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository public async Task> FlagNormal(PlexLibrary library, PlexEpisode episode) { + if (episode.State is MediaItemState.Normal) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); episode.State = MediaItemState.Normal; @@ -40,7 +45,7 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 0 WHERE Id = @Id", + "UPDATE MediaItem SET State = 0 WHERE Id = @Id AND State != 0", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -49,6 +54,11 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository public async Task> FlagNormal(PlexLibrary library, PlexSeason season) { + if (season.State is MediaItemState.Normal) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); season.State = MediaItemState.Normal; @@ -63,7 +73,7 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 0 WHERE Id = @Id", + "UPDATE MediaItem SET State = 0 WHERE Id = @Id AND State != 0", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -72,6 +82,11 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository public async Task> FlagNormal(PlexLibrary library, PlexShow show) { + if (show.State is MediaItemState.Normal) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); show.State = MediaItemState.Normal; @@ -86,7 +101,7 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 0 WHERE Id = @Id", + "UPDATE MediaItem SET State = 0 WHERE Id = @Id AND State != 0", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -95,6 +110,11 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository public async Task> FlagUnavailable(PlexLibrary library, PlexEpisode episode) { + if (episode.State is MediaItemState.Unavailable) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); episode.State = MediaItemState.Unavailable; @@ -109,7 +129,7 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 2 WHERE Id = @Id", + "UPDATE MediaItem SET State = 2 WHERE Id = @Id AND State != 2", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -118,6 +138,11 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository public async Task> FlagRemoteOnly(PlexLibrary library, PlexEpisode episode) { + if (episode.State is MediaItemState.RemoteOnly) + { + return Option.None; + } + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); episode.State = MediaItemState.RemoteOnly; @@ -132,7 +157,7 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository foreach (int id in maybeId) { return await dbContext.Connection.ExecuteAsync( - @"UPDATE MediaItem SET State = 3 WHERE Id = @Id", + "UPDATE MediaItem SET State = 3 WHERE Id = @Id AND State != 3", new { Id = id }).Map(count => count > 0 ? Some(id) : None); } @@ -321,7 +346,7 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository { if (showItemIds.Count == 0) { - return new List(); + return []; } await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); @@ -346,7 +371,7 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository { if (seasonItemIds.Count == 0) { - return new List(); + return []; } await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); @@ -371,7 +396,7 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository { if (episodeItemIds.Count == 0) { - return new List(); + return []; } await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); diff --git a/ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs index db4db437..ac2e6892 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs @@ -157,6 +157,19 @@ public class SearchRepository : ISearchRepository WHERE MediaStreamKind = 2 AND S.ShowId = @ShowId", new { ShowId = show.Id }).Map(result => result.ToList()); } + + public async Task> GetSubLanguagesForShow(Show show) + { + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); + return await dbContext.Connection.QueryAsync( + @"SELECT DISTINCT Language + FROM MediaStream + INNER JOIN MediaVersion MV ON MediaStream.MediaVersionId = MV.Id + INNER JOIN Episode E ON MV.EpisodeId = E.Id + INNER JOIN Season S ON E.SeasonId = S.Id + WHERE MediaStreamKind = 3 AND S.ShowId = @ShowId", + new { ShowId = show.Id }).Map(result => result.ToList()); + } public async Task> GetLanguagesForSeason(Season season) { @@ -169,6 +182,18 @@ public class SearchRepository : ISearchRepository WHERE MediaStreamKind = 2 AND E.SeasonId = @SeasonId", new { SeasonId = season.Id }).Map(result => result.ToList()); } + + public async Task> GetSubLanguagesForSeason(Season season) + { + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); + return await dbContext.Connection.QueryAsync( + @"SELECT DISTINCT Language + FROM MediaStream + INNER JOIN MediaVersion MV ON MediaStream.MediaVersionId = MV.Id + INNER JOIN Episode E ON MV.EpisodeId = E.Id + WHERE MediaStreamKind = 3 AND E.SeasonId = @SeasonId", + new { SeasonId = season.Id }).Map(result => result.ToList()); + } public async Task> GetLanguagesForArtist(Artist artist) { @@ -182,6 +207,19 @@ public class SearchRepository : ISearchRepository WHERE MediaStreamKind = 2 AND A.Id = @ArtistId", new { ArtistId = artist.Id }).Map(result => result.ToList()); } + + public async Task> GetSubLanguagesForArtist(Artist artist) + { + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); + return await dbContext.Connection.QueryAsync( + @"SELECT DISTINCT Language + FROM MediaStream + INNER JOIN MediaVersion V ON MediaStream.MediaVersionId = V.Id + INNER JOIN MusicVideo MV ON V.MusicVideoId = MV.Id + INNER JOIN Artist A on MV.ArtistId = A.Id + WHERE MediaStreamKind = 3 AND A.Id = @ArtistId", + new { ArtistId = artist.Id }).Map(result => result.ToList()); + } public virtual async Task> GetAllLanguageCodes(List mediaCodes) { diff --git a/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj b/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj index a05bdb2e..278ea587 100644 --- a/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj +++ b/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj @@ -11,10 +11,10 @@ - + - - + + diff --git a/ErsatzTV.Infrastructure/Search/ElasticSearchIndex.cs b/ErsatzTV.Infrastructure/Search/ElasticSearchIndex.cs index e1f12f97..db934d19 100644 --- a/ErsatzTV.Infrastructure/Search/ElasticSearchIndex.cs +++ b/ErsatzTV.Infrastructure/Search/ElasticSearchIndex.cs @@ -46,7 +46,7 @@ public class ElasticSearchIndex : ISearchIndex return exists.IsValidResponse; } - public int Version => 38; + public int Version => 39; public async Task Initialize( ILocalFileSystem localFileSystem, @@ -211,6 +211,9 @@ public class ElasticSearchIndex : ISearchIndex .Keyword(t => t.State, t => t.Store(false)) .Text(t => t.MetadataKind, t => t.Store(false)) .Text(t => t.Language, t => t.Store(false)) + .Text(t => t.LanguageTag, t => t.Store(false)) + .Text(t => t.SubLanguage, t => t.Store(false)) + .Text(t => t.SubLanguageTag, t => t.Store(false)) .IntegerNumber(t => t.Height, t => t.Store(false)) .IntegerNumber(t => t.Width, t => t.Store(false)) .Keyword(t => t.VideoCodec, t => t.Store(false)) @@ -294,6 +297,8 @@ public class ElasticSearchIndex : ISearchIndex MetadataKind = metadata.MetadataKind.ToString(), Language = await GetLanguages(searchRepository, movie.MediaVersions), LanguageTag = GetLanguageTags(movie.MediaVersions), + SubLanguage = await GetSubLanguages(searchRepository, movie.MediaVersions), + SubLanguageTag = GetSubLanguageTags(movie.MediaVersions), ContentRating = GetContentRatings(metadata.ContentRating), ReleaseDate = GetReleaseDate(metadata.ReleaseDate), AddedDate = GetAddedDate(metadata.DateAdded), @@ -345,6 +350,8 @@ public class ElasticSearchIndex : ISearchIndex MetadataKind = metadata.MetadataKind.ToString(), Language = await GetLanguages(searchRepository, await searchRepository.GetLanguagesForShow(show)), LanguageTag = await searchRepository.GetLanguagesForShow(show), + SubLanguage = await GetLanguages(searchRepository, await searchRepository.GetSubLanguagesForShow(show)), + SubLanguageTag = await searchRepository.GetSubLanguagesForShow(show), ContentRating = GetContentRatings(metadata.ContentRating), ReleaseDate = GetReleaseDate(metadata.ReleaseDate), AddedDate = GetAddedDate(metadata.DateAdded), @@ -405,6 +412,10 @@ public class ElasticSearchIndex : ISearchIndex searchRepository, await searchRepository.GetLanguagesForSeason(season)), LanguageTag = await searchRepository.GetLanguagesForSeason(season), + SubLanguage = await GetLanguages( + searchRepository, + await searchRepository.GetSubLanguagesForSeason(season)), + SubLanguageTag = await searchRepository.GetSubLanguagesForSeason(season), ContentRating = GetContentRatings(showMetadata.ContentRating), ReleaseDate = GetReleaseDate(metadata.ReleaseDate), AddedDate = GetAddedDate(metadata.DateAdded), @@ -450,6 +461,10 @@ public class ElasticSearchIndex : ISearchIndex searchRepository, await searchRepository.GetLanguagesForArtist(artist)), LanguageTag = await searchRepository.GetLanguagesForArtist(artist), + SubLanguage = await GetLanguages( + searchRepository, + await searchRepository.GetSubLanguagesForArtist(artist)), + SubLanguageTag = await searchRepository.GetSubLanguagesForArtist(artist), AddedDate = GetAddedDate(metadata.DateAdded), Genre = metadata.Genres.Map(g => g.Name).ToList(), Style = metadata.Styles.Map(t => t.Name).ToList(), @@ -491,6 +506,8 @@ public class ElasticSearchIndex : ISearchIndex MetadataKind = metadata.MetadataKind.ToString(), Language = await GetLanguages(searchRepository, musicVideo.MediaVersions), LanguageTag = GetLanguageTags(musicVideo.MediaVersions), + SubLanguage = await GetSubLanguages(searchRepository, musicVideo.MediaVersions), + SubLanguageTag = GetSubLanguageTags(musicVideo.MediaVersions), ReleaseDate = GetReleaseDate(metadata.ReleaseDate), AddedDate = GetAddedDate(metadata.DateAdded), Album = metadata.Album ?? string.Empty, @@ -570,6 +587,8 @@ public class ElasticSearchIndex : ISearchIndex EpisodeNumber = metadata.EpisodeNumber, Language = await GetLanguages(searchRepository, episode.MediaVersions), LanguageTag = GetLanguageTags(episode.MediaVersions), + SubLanguage = await GetSubLanguages(searchRepository, episode.MediaVersions), + SubLanguageTag = GetSubLanguageTags(episode.MediaVersions), ReleaseDate = GetReleaseDate(metadata.ReleaseDate), AddedDate = GetAddedDate(metadata.DateAdded), Plot = metadata.Plot ?? string.Empty, @@ -629,6 +648,8 @@ public class ElasticSearchIndex : ISearchIndex MetadataKind = metadata.MetadataKind.ToString(), Language = await GetLanguages(searchRepository, otherVideo.MediaVersions), LanguageTag = GetLanguageTags(otherVideo.MediaVersions), + SubLanguage = await GetSubLanguages(searchRepository, otherVideo.MediaVersions), + SubLanguageTag = GetSubLanguageTags(otherVideo.MediaVersions), ContentRating = GetContentRatings(metadata.ContentRating), ReleaseDate = GetReleaseDate(metadata.ReleaseDate), AddedDate = GetAddedDate(metadata.DateAdded), @@ -678,6 +699,8 @@ public class ElasticSearchIndex : ISearchIndex MetadataKind = metadata.MetadataKind.ToString(), Language = await GetLanguages(searchRepository, song.MediaVersions), LanguageTag = GetLanguageTags(song.MediaVersions), + SubLanguage = await GetSubLanguages(searchRepository, song.MediaVersions), + SubLanguageTag = GetSubLanguageTags(song.MediaVersions), AddedDate = GetAddedDate(metadata.DateAdded), Album = metadata.Album ?? string.Empty, Artist = !string.IsNullOrWhiteSpace(metadata.Artist) ? new List { metadata.Artist } : null, @@ -744,6 +767,26 @@ public class ElasticSearchIndex : ISearchIndex return result; } + + private async Task> GetSubLanguages( + ISearchRepository searchRepository, + IEnumerable mediaVersions) + { + var result = new List(); + + foreach (MediaVersion version in mediaVersions.HeadOrNone()) + { + var mediaCodes = version.Streams + .Filter(ms => ms.MediaStreamKind == MediaStreamKind.Subtitle) + .Map(ms => ms.Language) + .Distinct() + .ToList(); + + result.AddRange(await GetLanguages(searchRepository, mediaCodes)); + } + + return result; + } private async Task> GetLanguages(ISearchRepository searchRepository, List mediaCodes) { @@ -769,6 +812,14 @@ public class ElasticSearchIndex : ISearchIndex .Distinct() .ToList(); + private static List GetSubLanguageTags(IEnumerable mediaVersions) => + mediaVersions + .Map(mv => mv.Streams.Filter(ms => ms.MediaStreamKind == MediaStreamKind.Subtitle).Map(ms => ms.Language)) + .Flatten() + .Filter(s => !string.IsNullOrWhiteSpace(s)) + .Distinct() + .ToList(); + private static void AddStatistics(ElasticSearchItem doc, IEnumerable mediaVersions) { foreach (MediaVersion version in mediaVersions.HeadOrNone()) diff --git a/ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs b/ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs index 98506393..83cf5839 100644 --- a/ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs +++ b/ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs @@ -46,6 +46,8 @@ public sealed class LuceneSearchIndex : ISearchIndex internal const string StudioField = "studio"; internal const string LanguageField = "language"; internal const string LanguageTagField = "language_tag"; + internal const string SubLanguageField = "sub_language"; + internal const string SubLanguageTagField = "sub_language_tag"; internal const string StyleField = "style"; internal const string MoodField = "mood"; internal const string ActorField = "actor"; @@ -109,7 +111,7 @@ public sealed class LuceneSearchIndex : ISearchIndex return Task.FromResult(directoryExists && fileExists); } - public int Version => 38; + public int Version => 39; public async Task Initialize( ILocalFileSystem localFileSystem, @@ -501,7 +503,7 @@ public sealed class LuceneSearchIndex : ISearchIndex private async Task AddLanguages( ISearchRepository searchRepository, Document doc, - IEnumerable mediaVersions) + ICollection mediaVersions) { var mediaCodes = mediaVersions .Map(mv => mv.Streams.Filter(ms => ms.MediaStreamKind == MediaStreamKind.Audio).Map(ms => ms.Language)) @@ -511,6 +513,15 @@ public sealed class LuceneSearchIndex : ISearchIndex .ToList(); await AddLanguages(searchRepository, doc, mediaCodes); + + var subMediaCodes = mediaVersions + .Map(mv => mv.Streams.Filter(ms => ms.MediaStreamKind == MediaStreamKind.Subtitle).Map(ms => ms.Language)) + .Flatten() + .Filter(c => !string.IsNullOrWhiteSpace(c)) + .Distinct() + .ToList(); + + await AddSubLanguages(searchRepository, doc, subMediaCodes); } private async Task AddLanguages(ISearchRepository searchRepository, Document doc, List mediaCodes) @@ -536,6 +547,30 @@ public sealed class LuceneSearchIndex : ISearchIndex doc.Add(new TextField(LanguageField, englishName, Field.Store.NO)); } } + + private async Task AddSubLanguages(ISearchRepository searchRepository, Document doc, List mediaCodes) + { + foreach (string code in mediaCodes.Where(c => !string.IsNullOrWhiteSpace(c)).Distinct()) + { + doc.Add(new TextField(SubLanguageTagField, code, Field.Store.NO)); + } + + var englishNames = new System.Collections.Generic.HashSet(); + foreach (string code in await searchRepository.GetAllLanguageCodes(mediaCodes)) + { + Option maybeCultureInfo = _cultureInfos.Find( + ci => string.Equals(ci.ThreeLetterISOLanguageName, code, StringComparison.OrdinalIgnoreCase)); + foreach (CultureInfo cultureInfo in maybeCultureInfo) + { + englishNames.Add(cultureInfo.EnglishName); + } + } + + foreach (string englishName in englishNames) + { + doc.Add(new TextField(SubLanguageField, englishName, Field.Store.NO)); + } + } private async Task UpdateShow(ISearchRepository searchRepository, Show show) { @@ -566,6 +601,9 @@ public sealed class LuceneSearchIndex : ISearchIndex List languages = await searchRepository.GetLanguagesForShow(show); await AddLanguages(searchRepository, doc, languages); + List subLanguages = await searchRepository.GetSubLanguagesForShow(show); + await AddSubLanguages(searchRepository, doc, subLanguages); + if (!string.IsNullOrWhiteSpace(metadata.ContentRating)) { foreach (string contentRating in (metadata.ContentRating ?? string.Empty).Split("/") @@ -690,6 +728,9 @@ public sealed class LuceneSearchIndex : ISearchIndex List languages = await searchRepository.GetLanguagesForSeason(season); await AddLanguages(searchRepository, doc, languages); + List subLanguages = await searchRepository.GetSubLanguagesForSeason(season); + await AddSubLanguages(searchRepository, doc, subLanguages); + if (!string.IsNullOrWhiteSpace(showMetadata.ContentRating)) { foreach (string contentRating in (showMetadata.ContentRating ?? string.Empty).Split("/") @@ -768,6 +809,9 @@ public sealed class LuceneSearchIndex : ISearchIndex List languages = await searchRepository.GetLanguagesForArtist(artist); await AddLanguages(searchRepository, doc, languages); + List subLanguages = await searchRepository.GetSubLanguagesForArtist(artist); + await AddSubLanguages(searchRepository, doc, subLanguages); + doc.Add( new StringField( AddedDateField, diff --git a/ErsatzTV.Infrastructure/Search/Models/ElasticSearchItem.cs b/ErsatzTV.Infrastructure/Search/Models/ElasticSearchItem.cs index 66aaee13..e70b49cf 100644 --- a/ErsatzTV.Infrastructure/Search/Models/ElasticSearchItem.cs +++ b/ErsatzTV.Infrastructure/Search/Models/ElasticSearchItem.cs @@ -31,6 +31,12 @@ public class ElasticSearchItem : MinimalElasticSearchItem [JsonPropertyName(LuceneSearchIndex.LanguageTagField)] public List LanguageTag { get; set; } + [JsonPropertyName(LuceneSearchIndex.SubLanguageField)] + public List SubLanguage { get; set; } + + [JsonPropertyName(LuceneSearchIndex.SubLanguageTagField)] + public List SubLanguageTag { get; set; } + [JsonPropertyName(LuceneSearchIndex.MinutesField)] public int Minutes { get; set; } diff --git a/ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj b/ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj index 09c2dc15..de874752 100644 --- a/ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj +++ b/ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj @@ -15,7 +15,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs b/ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs index 85bb35b3..82225681 100644 --- a/ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs @@ -307,20 +307,26 @@ public abstract class MediaServerMovieLibraryScanner()), - CancellationToken.None); + foreach (int id in await movieRepository.FlagRemoteOnly(library, incoming)) + { + await _mediator.Publish( + new ScannerProgressUpdate(library.Id, null, null, new[] { id }, Array.Empty()), + CancellationToken.None); + } } } else { - foreach (int id in await movieRepository.FlagUnavailable(library, incoming)) + if (existingState is not MediaItemState.Unavailable) { - await _mediator.Publish( - new ScannerProgressUpdate(library.Id, null, null, new[] { id }, Array.Empty()), - CancellationToken.None); + foreach (int id in await movieRepository.FlagUnavailable(library, incoming)) + { + await _mediator.Publish( + new ScannerProgressUpdate(library.Id, null, null, new[] { id }, Array.Empty()), + CancellationToken.None); + } } } } diff --git a/ErsatzTV.Scanner/Core/Metadata/MediaServerTelevisionLibraryScanner.cs b/ErsatzTV.Scanner/Core/Metadata/MediaServerTelevisionLibraryScanner.cs index d90c79c6..ea120bf3 100644 --- a/ErsatzTV.Scanner/Core/Metadata/MediaServerTelevisionLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/MediaServerTelevisionLibraryScanner.cs @@ -584,20 +584,26 @@ public abstract class MediaServerTelevisionLibraryScanner()), - CancellationToken.None); + foreach (int id in await televisionRepository.FlagRemoteOnly(library, incoming)) + { + await _mediator.Publish( + new ScannerProgressUpdate(library.Id, null, null, new[] { id }, Array.Empty()), + CancellationToken.None); + } } } else { - foreach (int id in await televisionRepository.FlagUnavailable(library, incoming)) + if (existingState is not MediaItemState.Unavailable) { - await _mediator.Publish( - new ScannerProgressUpdate(library.Id, null, null, new[] { id }, Array.Empty()), - CancellationToken.None); + foreach (int id in await televisionRepository.FlagUnavailable(library, incoming)) + { + await _mediator.Publish( + new ScannerProgressUpdate(library.Id, null, null, new[] { id }, Array.Empty()), + CancellationToken.None); + } } } } diff --git a/ErsatzTV.Scanner/ErsatzTV.Scanner.csproj b/ErsatzTV.Scanner/ErsatzTV.Scanner.csproj index 46345eb2..5248cafd 100644 --- a/ErsatzTV.Scanner/ErsatzTV.Scanner.csproj +++ b/ErsatzTV.Scanner/ErsatzTV.Scanner.csproj @@ -20,7 +20,7 @@ - + @@ -28,7 +28,7 @@ - + diff --git a/ErsatzTV/ErsatzTV.csproj b/ErsatzTV/ErsatzTV.csproj index fb8f504d..bc83a47a 100644 --- a/ErsatzTV/ErsatzTV.csproj +++ b/ErsatzTV/ErsatzTV.csproj @@ -21,7 +21,7 @@ - + @@ -37,11 +37,11 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - +