Browse Source

prevent unnecessary search index updates (#1592)

* add sub_language and sub_language_tag fields to search index

* prevent unnecessary search index updates

* update changelog

* update dependencies
pull/1594/head
Jason Dove 1 year ago committed by GitHub
parent
commit
abed22b560
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 2
      ErsatzTV.Application/ErsatzTV.Application.csproj
  3. 2
      ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj
  4. 2
      ErsatzTV.Core/ErsatzTV.Core.csproj
  5. 3
      ErsatzTV.Core/Interfaces/Repositories/ISearchRepository.cs
  6. 2
      ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj
  7. 2
      ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj
  8. 7
      ErsatzTV.Infrastructure/Data/Repositories/Caching/CachingSearchRepository.cs
  9. 25
      ErsatzTV.Infrastructure/Data/Repositories/EmbyMovieRepository.cs
  10. 47
      ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs
  11. 25
      ErsatzTV.Infrastructure/Data/Repositories/JellyfinMovieRepository.cs
  12. 47
      ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs
  13. 25
      ErsatzTV.Infrastructure/Data/Repositories/PlexMovieRepository.cs
  14. 41
      ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs
  15. 38
      ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs
  16. 6
      ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj
  17. 53
      ErsatzTV.Infrastructure/Search/ElasticSearchIndex.cs
  18. 48
      ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs
  19. 6
      ErsatzTV.Infrastructure/Search/Models/ElasticSearchItem.cs
  20. 2
      ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj
  21. 6
      ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs
  22. 6
      ErsatzTV.Scanner/Core/Metadata/MediaServerTelevisionLibraryScanner.cs
  23. 4
      ErsatzTV.Scanner/ErsatzTV.Scanner.csproj
  24. 6
      ErsatzTV/ErsatzTV.csproj

2
CHANGELOG.md

@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -13,6 +13,7 @@ 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
@ -26,6 +27,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -26,6 +27,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- 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

2
ErsatzTV.Application/ErsatzTV.Application.csproj

@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
<ItemGroup>
<PackageReference Include="Bugsnag" Version="3.1.0" />
<PackageReference Include="CliWrap" Version="3.6.4" />
<PackageReference Include="CliWrap" Version="3.6.6" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="MediatR" Version="12.2.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />

2
ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj

@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
<ItemGroup>
<PackageReference Include="Bugsnag" Version="3.1.0" />
<PackageReference Include="CliWrap" Version="3.6.4" />
<PackageReference Include="CliWrap" Version="3.6.6" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="LanguageExt.Core" Version="4.4.7" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />

2
ErsatzTV.Core/ErsatzTV.Core.csproj

@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
<ItemGroup>
<PackageReference Include="Bugsnag" Version="3.1.0" />
<PackageReference Include="Destructurama.Attributed" Version="3.1.0" />
<PackageReference Include="Destructurama.Attributed" Version="3.2.0" />
<PackageReference Include="Flurl" Version="4.0.0" />
<PackageReference Include="LanguageExt.Core" Version="4.4.7" />
<PackageReference Include="LanguageExt.Transformers" Version="4.4.7" />

3
ErsatzTV.Core/Interfaces/Repositories/ISearchRepository.cs

@ -6,8 +6,11 @@ public interface ISearchRepository @@ -6,8 +6,11 @@ public interface ISearchRepository
{
Task<Option<MediaItem>> GetItemToIndex(int id);
Task<List<string>> GetLanguagesForShow(Show show);
Task<List<string>> GetSubLanguagesForShow(Show show);
Task<List<string>> GetLanguagesForSeason(Season season);
Task<List<string>> GetSubLanguagesForSeason(Season season);
Task<List<string>> GetLanguagesForArtist(Artist artist);
Task<List<string>> GetSubLanguagesForArtist(Artist artist);
Task<List<string>> GetAllLanguageCodes(List<string> mediaCodes);
IAsyncEnumerable<MediaItem> GetAllMediaItems();
}

2
ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj

@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CliWrap" Version="3.6.4" />
<PackageReference Include="CliWrap" Version="3.6.6" />
<PackageReference Include="LanguageExt.Core" Version="4.4.7" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />

2
ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj

@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="NUnit" Version="4.0.1" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.10.0">
<PackageReference Include="NUnit.Analyzers" Version="4.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>

7
ErsatzTV.Infrastructure/Data/Repositories/Caching/CachingSearchRepository.cs

@ -17,11 +17,18 @@ public class CachingSearchRepository : ICachingSearchRepository @@ -17,11 +17,18 @@ public class CachingSearchRepository : ICachingSearchRepository
public Task<Option<MediaItem>> GetItemToIndex(int id) => _searchRepository.GetItemToIndex(id);
public Task<List<string>> GetLanguagesForShow(Show show) => _searchRepository.GetLanguagesForShow(show);
public Task<List<string>> GetSubLanguagesForShow(Show show) => _searchRepository.GetSubLanguagesForShow(show);
public Task<List<string>> GetLanguagesForSeason(Season season) => _searchRepository.GetLanguagesForSeason(season);
public Task<List<string>> GetSubLanguagesForSeason(Season season) =>
_searchRepository.GetSubLanguagesForSeason(season);
public Task<List<string>> GetLanguagesForArtist(Artist artist) => _searchRepository.GetLanguagesForArtist(artist);
public Task<List<string>> GetSubLanguagesForArtist(Artist artist) =>
_searchRepository.GetSubLanguagesForArtist(artist);
public async Task<List<string>> GetAllLanguageCodes(List<string> mediaCodes)
{
if (!_cache.ContainsKey(mediaCodes))

25
ErsatzTV.Infrastructure/Data/Repositories/EmbyMovieRepository.cs

@ -37,6 +37,11 @@ public class EmbyMovieRepository : IEmbyMovieRepository @@ -37,6 +37,11 @@ public class EmbyMovieRepository : IEmbyMovieRepository
public async Task<Option<int>> FlagNormal(EmbyLibrary library, EmbyMovie movie)
{
if (movie.State is MediaItemState.Normal)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
movie.State = MediaItemState.Normal;
@ -51,7 +56,7 @@ public class EmbyMovieRepository : IEmbyMovieRepository @@ -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 @@ -60,6 +65,11 @@ public class EmbyMovieRepository : IEmbyMovieRepository
public async Task<Option<int>> FlagUnavailable(EmbyLibrary library, EmbyMovie movie)
{
if (movie.State is MediaItemState.Unavailable)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
movie.State = MediaItemState.Unavailable;
@ -74,7 +84,7 @@ public class EmbyMovieRepository : IEmbyMovieRepository @@ -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 @@ -83,6 +93,11 @@ public class EmbyMovieRepository : IEmbyMovieRepository
public async Task<Option<int>> FlagRemoteOnly(EmbyLibrary library, EmbyMovie movie)
{
if (movie.State is MediaItemState.RemoteOnly)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
movie.State = MediaItemState.RemoteOnly;
@ -97,7 +112,7 @@ public class EmbyMovieRepository : IEmbyMovieRepository @@ -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 @@ -108,7 +123,7 @@ public class EmbyMovieRepository : IEmbyMovieRepository
{
if (movieItemIds.Count == 0)
{
return new List<int>();
return [];
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
@ -123,7 +138,7 @@ public class EmbyMovieRepository : IEmbyMovieRepository @@ -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;

47
ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs

@ -207,6 +207,11 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository @@ -207,6 +207,11 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository
public async Task<Option<int>> FlagNormal(EmbyLibrary library, EmbyEpisode episode)
{
if (episode.State is MediaItemState.Normal)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
episode.State = MediaItemState.Normal;
@ -221,7 +226,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository @@ -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 @@ -230,6 +235,11 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository
public async Task<Option<int>> FlagNormal(EmbyLibrary library, EmbySeason season)
{
if (season.State is MediaItemState.Normal)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
season.State = MediaItemState.Normal;
@ -244,7 +254,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository @@ -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 @@ -253,6 +263,11 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository
public async Task<Option<int>> FlagNormal(EmbyLibrary library, EmbyShow show)
{
if (show.State is MediaItemState.Normal)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
show.State = MediaItemState.Normal;
@ -267,7 +282,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository @@ -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 @@ -278,7 +293,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository
{
if (showItemIds.Count == 0)
{
return new List<int>();
return [];
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
@ -293,7 +308,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository @@ -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 @@ -303,7 +318,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository
{
if (seasonItemIds.Count == 0)
{
return new List<int>();
return [];
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
@ -318,7 +333,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository @@ -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 @@ -328,7 +343,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository
{
if (episodeItemIds.Count == 0)
{
return new List<int>();
return [];
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
@ -343,7 +358,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository @@ -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 @@ -351,6 +366,11 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository
public async Task<Option<int>> FlagUnavailable(EmbyLibrary library, EmbyEpisode episode)
{
if (episode.State is MediaItemState.Unavailable)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
episode.State = MediaItemState.Unavailable;
@ -365,7 +385,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository @@ -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 @@ -374,6 +394,11 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository
public async Task<Option<int>> FlagRemoteOnly(EmbyLibrary library, EmbyEpisode episode)
{
if (episode.State is MediaItemState.RemoteOnly)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
episode.State = MediaItemState.RemoteOnly;
@ -388,7 +413,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository @@ -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);
}

25
ErsatzTV.Infrastructure/Data/Repositories/JellyfinMovieRepository.cs

@ -39,6 +39,11 @@ public class JellyfinMovieRepository : IJellyfinMovieRepository @@ -39,6 +39,11 @@ public class JellyfinMovieRepository : IJellyfinMovieRepository
public async Task<Option<int>> FlagNormal(JellyfinLibrary library, JellyfinMovie movie)
{
if (movie.State is MediaItemState.Normal)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
movie.State = MediaItemState.Normal;
@ -53,7 +58,7 @@ public class JellyfinMovieRepository : IJellyfinMovieRepository @@ -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 @@ -62,6 +67,11 @@ public class JellyfinMovieRepository : IJellyfinMovieRepository
public async Task<Option<int>> FlagUnavailable(JellyfinLibrary library, JellyfinMovie movie)
{
if (movie.State is MediaItemState.Unavailable)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
movie.State = MediaItemState.Unavailable;
@ -76,7 +86,7 @@ public class JellyfinMovieRepository : IJellyfinMovieRepository @@ -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 @@ -85,6 +95,11 @@ public class JellyfinMovieRepository : IJellyfinMovieRepository
public async Task<Option<int>> FlagRemoteOnly(JellyfinLibrary library, JellyfinMovie movie)
{
if (movie.State is MediaItemState.RemoteOnly)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
movie.State = MediaItemState.RemoteOnly;
@ -99,7 +114,7 @@ public class JellyfinMovieRepository : IJellyfinMovieRepository @@ -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 @@ -110,7 +125,7 @@ public class JellyfinMovieRepository : IJellyfinMovieRepository
{
if (movieItemIds.Count == 0)
{
return new List<int>();
return [];
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
@ -125,7 +140,7 @@ public class JellyfinMovieRepository : IJellyfinMovieRepository @@ -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;

47
ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs

@ -211,6 +211,11 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository @@ -211,6 +211,11 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
public async Task<Option<int>> FlagNormal(JellyfinLibrary library, JellyfinEpisode episode)
{
if (episode.State is MediaItemState.Normal)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
episode.State = MediaItemState.Normal;
@ -225,7 +230,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository @@ -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 @@ -234,6 +239,11 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
public async Task<Option<int>> FlagNormal(JellyfinLibrary library, JellyfinSeason season)
{
if (season.State is MediaItemState.Normal)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
season.State = MediaItemState.Normal;
@ -248,7 +258,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository @@ -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 @@ -257,6 +267,11 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
public async Task<Option<int>> FlagNormal(JellyfinLibrary library, JellyfinShow show)
{
if (show.State is MediaItemState.Normal)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
show.State = MediaItemState.Normal;
@ -271,7 +286,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository @@ -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 @@ -282,7 +297,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
{
if (showItemIds.Count == 0)
{
return new List<int>();
return [];
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
@ -297,7 +312,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository @@ -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 @@ -307,7 +322,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
{
if (seasonItemIds.Count == 0)
{
return new List<int>();
return [];
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
@ -322,7 +337,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository @@ -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 @@ -332,7 +347,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
{
if (episodeItemIds.Count == 0)
{
return new List<int>();
return [];
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
@ -347,7 +362,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository @@ -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 @@ -355,6 +370,11 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
public async Task<Option<int>> FlagUnavailable(JellyfinLibrary library, JellyfinEpisode episode)
{
if (episode.State is MediaItemState.Unavailable)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
episode.State = MediaItemState.Unavailable;
@ -369,7 +389,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository @@ -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 @@ -378,6 +398,11 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
public async Task<Option<int>> FlagRemoteOnly(JellyfinLibrary library, JellyfinEpisode episode)
{
if (episode.State is MediaItemState.RemoteOnly)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
episode.State = MediaItemState.RemoteOnly;
@ -392,7 +417,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository @@ -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);
}

25
ErsatzTV.Infrastructure/Data/Repositories/PlexMovieRepository.cs

@ -37,6 +37,11 @@ public class PlexMovieRepository : IPlexMovieRepository @@ -37,6 +37,11 @@ public class PlexMovieRepository : IPlexMovieRepository
public async Task<Option<int>> FlagNormal(PlexLibrary library, PlexMovie movie)
{
if (movie.State is MediaItemState.Normal)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
movie.State = MediaItemState.Normal;
@ -51,7 +56,7 @@ public class PlexMovieRepository : IPlexMovieRepository @@ -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 @@ -60,6 +65,11 @@ public class PlexMovieRepository : IPlexMovieRepository
public async Task<Option<int>> FlagUnavailable(PlexLibrary library, PlexMovie movie)
{
if (movie.State is MediaItemState.Unavailable)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
movie.State = MediaItemState.Unavailable;
@ -74,7 +84,7 @@ public class PlexMovieRepository : IPlexMovieRepository @@ -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 @@ -83,6 +93,11 @@ public class PlexMovieRepository : IPlexMovieRepository
public async Task<Option<int>> FlagRemoteOnly(PlexLibrary library, PlexMovie movie)
{
if (movie.State is MediaItemState.RemoteOnly)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
movie.State = MediaItemState.RemoteOnly;
@ -97,7 +112,7 @@ public class PlexMovieRepository : IPlexMovieRepository @@ -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 @@ -108,7 +123,7 @@ public class PlexMovieRepository : IPlexMovieRepository
{
if (movieItemIds.Count == 0)
{
return new List<int>();
return [];
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
@ -123,7 +138,7 @@ public class PlexMovieRepository : IPlexMovieRepository @@ -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;

41
ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs

@ -26,6 +26,11 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository @@ -26,6 +26,11 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository
public async Task<Option<int>> FlagNormal(PlexLibrary library, PlexEpisode episode)
{
if (episode.State is MediaItemState.Normal)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
episode.State = MediaItemState.Normal;
@ -40,7 +45,7 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository @@ -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 @@ -49,6 +54,11 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository
public async Task<Option<int>> FlagNormal(PlexLibrary library, PlexSeason season)
{
if (season.State is MediaItemState.Normal)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
season.State = MediaItemState.Normal;
@ -63,7 +73,7 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository @@ -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 @@ -72,6 +82,11 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository
public async Task<Option<int>> FlagNormal(PlexLibrary library, PlexShow show)
{
if (show.State is MediaItemState.Normal)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
show.State = MediaItemState.Normal;
@ -86,7 +101,7 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository @@ -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 @@ -95,6 +110,11 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository
public async Task<Option<int>> FlagUnavailable(PlexLibrary library, PlexEpisode episode)
{
if (episode.State is MediaItemState.Unavailable)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
episode.State = MediaItemState.Unavailable;
@ -109,7 +129,7 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository @@ -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 @@ -118,6 +138,11 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository
public async Task<Option<int>> FlagRemoteOnly(PlexLibrary library, PlexEpisode episode)
{
if (episode.State is MediaItemState.RemoteOnly)
{
return Option<int>.None;
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
episode.State = MediaItemState.RemoteOnly;
@ -132,7 +157,7 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository @@ -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 @@ -321,7 +346,7 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository
{
if (showItemIds.Count == 0)
{
return new List<int>();
return [];
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
@ -346,7 +371,7 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository @@ -346,7 +371,7 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository
{
if (seasonItemIds.Count == 0)
{
return new List<int>();
return [];
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
@ -371,7 +396,7 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository @@ -371,7 +396,7 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository
{
if (episodeItemIds.Count == 0)
{
return new List<int>();
return [];
}
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();

38
ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs

@ -158,6 +158,19 @@ public class SearchRepository : ISearchRepository @@ -158,6 +158,19 @@ public class SearchRepository : ISearchRepository
new { ShowId = show.Id }).Map(result => result.ToList());
}
public async Task<List<string>> GetSubLanguagesForShow(Show show)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.Connection.QueryAsync<string>(
@"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<List<string>> GetLanguagesForSeason(Season season)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
@ -170,6 +183,18 @@ public class SearchRepository : ISearchRepository @@ -170,6 +183,18 @@ public class SearchRepository : ISearchRepository
new { SeasonId = season.Id }).Map(result => result.ToList());
}
public async Task<List<string>> GetSubLanguagesForSeason(Season season)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.Connection.QueryAsync<string>(
@"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<List<string>> GetLanguagesForArtist(Artist artist)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
@ -183,6 +208,19 @@ public class SearchRepository : ISearchRepository @@ -183,6 +208,19 @@ public class SearchRepository : ISearchRepository
new { ArtistId = artist.Id }).Map(result => result.ToList());
}
public async Task<List<string>> GetSubLanguagesForArtist(Artist artist)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.Connection.QueryAsync<string>(
@"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<List<string>> GetAllLanguageCodes(List<string> mediaCodes)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();

6
ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj

@ -11,10 +11,10 @@ @@ -11,10 +11,10 @@
<ItemGroup>
<PackageReference Include="Blurhash.ImageSharp" Version="3.0.0" />
<PackageReference Include="CliWrap" Version="3.6.4" />
<PackageReference Include="CliWrap" Version="3.6.6" />
<PackageReference Include="Dapper" Version="2.1.28" />
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="8.11.0" />
<PackageReference Include="Jint" Version="3.0.0-beta-2059" />
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="8.12.0" />
<PackageReference Include="Jint" Version="3.0.0" />
<PackageReference Include="Lucene.Net" Version="4.8.0-beta00016" />
<PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00016" />
<PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00016" />

53
ErsatzTV.Infrastructure/Search/ElasticSearchIndex.cs

@ -46,7 +46,7 @@ public class ElasticSearchIndex : ISearchIndex @@ -46,7 +46,7 @@ public class ElasticSearchIndex : ISearchIndex
return exists.IsValidResponse;
}
public int Version => 38;
public int Version => 39;
public async Task<bool> Initialize(
ILocalFileSystem localFileSystem,
@ -211,6 +211,9 @@ public class ElasticSearchIndex : ISearchIndex @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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<string> { metadata.Artist } : null,
@ -745,6 +768,26 @@ public class ElasticSearchIndex : ISearchIndex @@ -745,6 +768,26 @@ public class ElasticSearchIndex : ISearchIndex
return result;
}
private async Task<List<string>> GetSubLanguages(
ISearchRepository searchRepository,
IEnumerable<MediaVersion> mediaVersions)
{
var result = new List<string>();
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<List<string>> GetLanguages(ISearchRepository searchRepository, List<string> mediaCodes)
{
var englishNames = new System.Collections.Generic.HashSet<string>();
@ -769,6 +812,14 @@ public class ElasticSearchIndex : ISearchIndex @@ -769,6 +812,14 @@ public class ElasticSearchIndex : ISearchIndex
.Distinct()
.ToList();
private static List<string> GetSubLanguageTags(IEnumerable<MediaVersion> 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<MediaVersion> mediaVersions)
{
foreach (MediaVersion version in mediaVersions.HeadOrNone())

48
ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs

@ -46,6 +46,8 @@ public sealed class LuceneSearchIndex : ISearchIndex @@ -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 @@ -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<bool> Initialize(
ILocalFileSystem localFileSystem,
@ -501,7 +503,7 @@ public sealed class LuceneSearchIndex : ISearchIndex @@ -501,7 +503,7 @@ public sealed class LuceneSearchIndex : ISearchIndex
private async Task AddLanguages(
ISearchRepository searchRepository,
Document doc,
IEnumerable<MediaVersion> mediaVersions)
ICollection<MediaVersion> 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 @@ -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<string> mediaCodes)
@ -537,6 +548,30 @@ public sealed class LuceneSearchIndex : ISearchIndex @@ -537,6 +548,30 @@ public sealed class LuceneSearchIndex : ISearchIndex
}
}
private async Task AddSubLanguages(ISearchRepository searchRepository, Document doc, List<string> 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<string>();
foreach (string code in await searchRepository.GetAllLanguageCodes(mediaCodes))
{
Option<CultureInfo> 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)
{
Option<ShowMetadata> maybeMetadata = show.ShowMetadata.HeadOrNone();
@ -566,6 +601,9 @@ public sealed class LuceneSearchIndex : ISearchIndex @@ -566,6 +601,9 @@ public sealed class LuceneSearchIndex : ISearchIndex
List<string> languages = await searchRepository.GetLanguagesForShow(show);
await AddLanguages(searchRepository, doc, languages);
List<string> 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 @@ -690,6 +728,9 @@ public sealed class LuceneSearchIndex : ISearchIndex
List<string> languages = await searchRepository.GetLanguagesForSeason(season);
await AddLanguages(searchRepository, doc, languages);
List<string> 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 @@ -768,6 +809,9 @@ public sealed class LuceneSearchIndex : ISearchIndex
List<string> languages = await searchRepository.GetLanguagesForArtist(artist);
await AddLanguages(searchRepository, doc, languages);
List<string> subLanguages = await searchRepository.GetSubLanguagesForArtist(artist);
await AddSubLanguages(searchRepository, doc, subLanguages);
doc.Add(
new StringField(
AddedDateField,

6
ErsatzTV.Infrastructure/Search/Models/ElasticSearchItem.cs

@ -31,6 +31,12 @@ public class ElasticSearchItem : MinimalElasticSearchItem @@ -31,6 +31,12 @@ public class ElasticSearchItem : MinimalElasticSearchItem
[JsonPropertyName(LuceneSearchIndex.LanguageTagField)]
public List<string> LanguageTag { get; set; }
[JsonPropertyName(LuceneSearchIndex.SubLanguageField)]
public List<string> SubLanguage { get; set; }
[JsonPropertyName(LuceneSearchIndex.SubLanguageTagField)]
public List<string> SubLanguageTag { get; set; }
[JsonPropertyName(LuceneSearchIndex.MinutesField)]
public int Minutes { get; set; }

2
ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj

@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="NUnit" Version="4.0.1" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.10.0">
<PackageReference Include="NUnit.Analyzers" Version="4.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>

6
ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs

@ -306,6 +306,8 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib @@ -306,6 +306,8 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
if (!_localFileSystem.FileExists(localPath))
{
if (ServerSupportsRemoteStreaming)
{
if (existingState is not MediaItemState.RemoteOnly)
{
foreach (int id in await movieRepository.FlagRemoteOnly(library, incoming))
{
@ -314,7 +316,10 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib @@ -314,7 +316,10 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
CancellationToken.None);
}
}
}
else
{
if (existingState is not MediaItemState.Unavailable)
{
foreach (int id in await movieRepository.FlagUnavailable(library, incoming))
{
@ -324,6 +329,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib @@ -324,6 +329,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
}
}
}
}
return false;
}

6
ErsatzTV.Scanner/Core/Metadata/MediaServerTelevisionLibraryScanner.cs

@ -583,6 +583,8 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters, @@ -583,6 +583,8 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
if (!_localFileSystem.FileExists(localPath))
{
if (ServerSupportsRemoteStreaming)
{
if (existingState is not MediaItemState.RemoteOnly)
{
foreach (int id in await televisionRepository.FlagRemoteOnly(library, incoming))
{
@ -591,7 +593,10 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters, @@ -591,7 +593,10 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
CancellationToken.None);
}
}
}
else
{
if (existingState is not MediaItemState.Unavailable)
{
foreach (int id in await televisionRepository.FlagUnavailable(library, incoming))
{
@ -601,6 +606,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters, @@ -601,6 +606,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
}
}
}
}
return false;
}

4
ErsatzTV.Scanner/ErsatzTV.Scanner.csproj

@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CliWrap" Version="3.6.4" />
<PackageReference Include="CliWrap" Version="3.6.6" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="LanguageExt.Core" Version="4.4.7" />
<PackageReference Include="MediatR" Version="12.2.0" />
@ -28,7 +28,7 @@ @@ -28,7 +28,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Serilog.Formatting.Compact" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />

6
ErsatzTV/ErsatzTV.csproj

@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
<PackageReference Include="FluentValidation" Version="11.9.0" />
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
<PackageReference Include="Heron.MudCalendar" Version="1.1.0" />
<PackageReference Include="HtmlSanitizer" Version="8.0.811" />
<PackageReference Include="HtmlSanitizer" Version="8.0.838" />
<PackageReference Include="LanguageExt.Core" Version="4.4.7" />
<PackageReference Include="Markdig" Version="0.34.0" />
<PackageReference Include="MediatR.Courier.DependencyInjection" Version="5.0.0" />
@ -37,11 +37,11 @@ @@ -37,11 +37,11 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="MudBlazor" Version="6.12.0" />
<PackageReference Include="MudBlazor" Version="6.15.0" />
<PackageReference Include="NaturalSort.Extension" Version="4.2.0" />
<PackageReference Include="Refit.HttpClientFactory" Version="7.0.0" />
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.0" />
<PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
<PackageReference Include="System.Text.Encoding.Extensions" Version="4.3.0" />

Loading…
Cancel
Save