Browse Source

add more search fields (#769)

pull/770/head
Jason Dove 4 years ago committed by GitHub
parent
commit
17f540dc99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      CHANGELOG.md
  2. 8
      ErsatzTV.Application/MediaCollections/Commands/DeleteTraktListHandler.cs
  3. 10
      ErsatzTV.Application/MediaCollections/Commands/TraktCommandBase.cs
  4. 1
      ErsatzTV.Core/Interfaces/Search/ISearchIndex.cs
  5. 8
      ErsatzTV.Core/Metadata/MediaServerMovieLibraryScanner.cs
  6. 8
      ErsatzTV.Core/Metadata/MovieFolderScanner.cs
  7. 16
      ErsatzTV.Core/Metadata/MusicVideoFolderScanner.cs
  8. 8
      ErsatzTV.Core/Metadata/OtherVideoFolderScanner.cs
  9. 8
      ErsatzTV.Core/Metadata/SongFolderScanner.cs
  10. 12
      ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs
  11. 4
      ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs
  12. 17
      ErsatzTV.Infrastructure/Search/SearchIndex.cs
  13. 11
      docs/user-guide/search.md

2
CHANGELOG.md

@ -20,6 +20,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Add `unavailable` state for Emby movie libraries - Add `unavailable` state for Emby movie libraries
- Add `height` and `width` to search index for all videos - Add `height` and `width` to search index for all videos
- Add `season_number` and `episode_number` to search index for all episodes - Add `season_number` and `episode_number` to search index for all episodes
- Add `season_number` to search index for seasons
- Add `show_title` to search index for seasons and episodes
## [0.5.3-beta] - 2022-04-24 ## [0.5.3-beta] - 2022-04-24
### Fixed ### Fixed

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

@ -56,13 +56,7 @@ public class DeleteTraktListHandler : TraktCommandBase, IRequestHandler<DeleteTr
dbContext.TraktLists.Remove(traktList); dbContext.TraktLists.Remove(traktList);
if (await dbContext.SaveChangesAsync() > 0) if (await dbContext.SaveChangesAsync() > 0)
{ {
foreach (int mediaItemId in mediaItemIds) await _searchIndex.RebuildItems(_searchRepository, mediaItemIds);
{
foreach (MediaItem mediaItem in await _searchRepository.GetItemToIndex(mediaItemId))
{
await _searchIndex.UpdateItems(_searchRepository, new[] { mediaItem }.ToList());
}
}
} }
_searchIndex.Commit(); _searchIndex.Commit();

10
ErsatzTV.Application/MediaCollections/Commands/TraktCommandBase.cs

@ -156,15 +156,9 @@ public abstract class TraktCommandBase
} }
} }
await dbContext.SaveChangesAsync(); if (await dbContext.SaveChangesAsync() > 0)
foreach (int mediaItemId in ids)
{ {
Option<MediaItem> maybeItem = await _searchRepository.GetItemToIndex(mediaItemId); await _searchIndex.RebuildItems(_searchRepository, ids.ToList());
foreach (MediaItem item in maybeItem)
{
await _searchIndex.UpdateItems(_searchRepository, new[] { item }.ToList());
}
} }
_searchIndex.Commit(); _searchIndex.Commit();

1
ErsatzTV.Core/Interfaces/Search/ISearchIndex.cs

@ -11,7 +11,6 @@ public interface ISearchIndex : IDisposable
Task<bool> Initialize(ILocalFileSystem localFileSystem); Task<bool> Initialize(ILocalFileSystem localFileSystem);
Task<Unit> Rebuild(ISearchRepository searchRepository); Task<Unit> Rebuild(ISearchRepository searchRepository);
Task<Unit> RebuildItems(ISearchRepository searchRepository, List<int> itemIds); Task<Unit> RebuildItems(ISearchRepository searchRepository, List<int> itemIds);
Task<Unit> AddItems(ISearchRepository searchRepository, List<MediaItem> items);
Task<Unit> UpdateItems(ISearchRepository searchRepository, List<MediaItem> items); Task<Unit> UpdateItems(ISearchRepository searchRepository, List<MediaItem> items);
Task<Unit> RemoveItems(List<int> ids); Task<Unit> RemoveItems(List<int> ids);
Task<SearchResult> Search(string query, int skip, int limit, string searchField = ""); Task<SearchResult> Search(string query, int skip, int limit, string searchField = "");

8
ErsatzTV.Core/Metadata/MediaServerMovieLibraryScanner.cs

@ -157,13 +157,9 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
} }
} }
if (result.IsAdded) if (result.IsAdded || result.IsUpdated)
{ {
await _searchIndex.AddItems(_searchRepository, new List<MediaItem> { result.Item }); await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
}
else if (result.IsUpdated)
{
await _searchIndex.UpdateItems(_searchRepository, new List<MediaItem> { result.Item });
} }
} }
} }

8
ErsatzTV.Core/Metadata/MovieFolderScanner.cs

@ -158,13 +158,9 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner
foreach (MediaItemScanResult<Movie> result in maybeMovie.RightToSeq()) foreach (MediaItemScanResult<Movie> result in maybeMovie.RightToSeq())
{ {
if (result.IsAdded) if (result.IsAdded || result.IsUpdated)
{ {
await _searchIndex.AddItems(_searchRepository, new List<MediaItem> { result.Item }); await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
}
else if (result.IsUpdated)
{
await _searchIndex.UpdateItems(_searchRepository, new List<MediaItem> { result.Item });
} }
await _libraryRepository.SetEtag(libraryPath, knownFolder, movieFolder, etag); await _libraryRepository.SetEtag(libraryPath, knownFolder, movieFolder, etag);

16
ErsatzTV.Core/Metadata/MusicVideoFolderScanner.cs

@ -135,13 +135,9 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
return error; return error;
} }
if (result.IsAdded) if (result.IsAdded || result.IsUpdated)
{ {
await _searchIndex.AddItems(_searchRepository, new List<MediaItem> { result.Item }); await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
}
else if (result.IsUpdated)
{
await _searchIndex.UpdateItems(_searchRepository, new List<MediaItem> { result.Item });
} }
} }
} }
@ -329,13 +325,9 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
foreach (MediaItemScanResult<MusicVideo> result in maybeMusicVideo.RightToSeq()) foreach (MediaItemScanResult<MusicVideo> result in maybeMusicVideo.RightToSeq())
{ {
if (result.IsAdded) if (result.IsAdded || result.IsUpdated)
{
await _searchIndex.AddItems(_searchRepository, new List<MediaItem> { result.Item });
}
else if (result.IsUpdated)
{ {
await _searchIndex.UpdateItems(_searchRepository, new List<MediaItem> { result.Item }); await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
} }
await _libraryRepository.SetEtag(libraryPath, knownFolder, musicVideoFolder, etag); await _libraryRepository.SetEtag(libraryPath, knownFolder, musicVideoFolder, etag);

8
ErsatzTV.Core/Metadata/OtherVideoFolderScanner.cs

@ -152,13 +152,9 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
foreach (MediaItemScanResult<OtherVideo> result in maybeVideo.RightToSeq()) foreach (MediaItemScanResult<OtherVideo> result in maybeVideo.RightToSeq())
{ {
if (result.IsAdded) if (result.IsAdded || result.IsUpdated)
{ {
await _searchIndex.AddItems(_searchRepository, new List<MediaItem> { result.Item }); await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
}
else if (result.IsUpdated)
{
await _searchIndex.UpdateItems(_searchRepository, new List<MediaItem> { result.Item });
} }
await _libraryRepository.SetEtag(libraryPath, knownFolder, otherVideoFolder, etag); await _libraryRepository.SetEtag(libraryPath, knownFolder, otherVideoFolder, etag);

8
ErsatzTV.Core/Metadata/SongFolderScanner.cs

@ -150,13 +150,9 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
foreach (MediaItemScanResult<Song> result in maybeSong.RightToSeq()) foreach (MediaItemScanResult<Song> result in maybeSong.RightToSeq())
{ {
if (result.IsAdded) if (result.IsAdded || result.IsUpdated)
{ {
await _searchIndex.AddItems(_searchRepository, new List<MediaItem> { result.Item }); await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
}
else if (result.IsUpdated)
{
await _searchIndex.UpdateItems(_searchRepository, new List<MediaItem> { result.Item });
} }
await _libraryRepository.SetEtag(libraryPath, knownFolder, songFolder, etag); await _libraryRepository.SetEtag(libraryPath, knownFolder, songFolder, etag);

12
ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs

@ -125,13 +125,9 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
return error; return error;
} }
if (result.IsAdded) if (result.IsAdded || result.IsUpdated)
{ {
await _searchIndex.AddItems(_searchRepository, new List<MediaItem> { result.Item }); await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
}
else if (result.IsUpdated)
{
await _searchIndex.UpdateItems(_searchRepository, new List<MediaItem> { result.Item });
} }
} }
} }
@ -245,7 +241,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
await _libraryRepository.SetEtag(libraryPath, knownFolder, seasonFolder, etag); await _libraryRepository.SetEtag(libraryPath, knownFolder, seasonFolder, etag);
season.Show = show; season.Show = show;
await _searchIndex.UpdateItems(_searchRepository, new List<MediaItem> { season }); await _searchIndex.RebuildItems(_searchRepository, new List<int> { season.Id });
} }
} }
} }
@ -291,7 +287,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
foreach (Episode episode in maybeEpisode.RightToSeq()) foreach (Episode episode in maybeEpisode.RightToSeq())
{ {
await _searchIndex.UpdateItems(_searchRepository, new List<MediaItem> { episode }); await _searchIndex.RebuildItems(_searchRepository, new List<int> { episode.Id });
} }
} }

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

@ -50,6 +50,8 @@ public class SearchRepository : ISearchRepository
.Include(mi => (mi as Episode).MediaVersions) .Include(mi => (mi as Episode).MediaVersions)
.ThenInclude(em => em.Streams) .ThenInclude(em => em.Streams)
.Include(mi => (mi as Episode).Season) .Include(mi => (mi as Episode).Season)
.ThenInclude(s => s.Show)
.ThenInclude(s => s.ShowMetadata)
.Include(mi => (mi as Season).SeasonMetadata) .Include(mi => (mi as Season).SeasonMetadata)
.ThenInclude(sm => sm.Genres) .ThenInclude(sm => sm.Genres)
.Include(mi => (mi as Season).SeasonMetadata) .Include(mi => (mi as Season).SeasonMetadata)
@ -181,6 +183,8 @@ public class SearchRepository : ISearchRepository
.Include(mi => (mi as Episode).MediaVersions) .Include(mi => (mi as Episode).MediaVersions)
.ThenInclude(em => em.Streams) .ThenInclude(em => em.Streams)
.Include(mi => (mi as Episode).Season) .Include(mi => (mi as Episode).Season)
.ThenInclude(s => s.Show)
.ThenInclude(s => s.ShowMetadata)
.Include(mi => (mi as Season).SeasonMetadata) .Include(mi => (mi as Season).SeasonMetadata)
.ThenInclude(sm => sm.Genres) .ThenInclude(sm => sm.Genres)
.Include(mi => (mi as Season).SeasonMetadata) .Include(mi => (mi as Season).SeasonMetadata)

17
ErsatzTV.Infrastructure/Search/SearchIndex.cs

@ -49,10 +49,12 @@ public sealed class SearchIndex : ISearchIndex
private const string WriterField = "writer"; private const string WriterField = "writer";
private const string TraktListField = "trakt_list"; private const string TraktListField = "trakt_list";
private const string AlbumField = "album"; private const string AlbumField = "album";
internal const string MinutesField = "minutes";
private const string ArtistField = "artist"; private const string ArtistField = "artist";
private const string StateField = "state"; private const string StateField = "state";
private const string AlbumArtistField = "album_artist"; private const string AlbumArtistField = "album_artist";
private const string ShowTitleField = "show_title";
internal const string MinutesField = "minutes";
internal const string HeightField = "height"; internal const string HeightField = "height";
internal const string WidthField = "width"; internal const string WidthField = "width";
internal const string SeasonNumberField = "season_number"; internal const string SeasonNumberField = "season_number";
@ -66,6 +68,7 @@ public sealed class SearchIndex : ISearchIndex
public const string EpisodeType = "episode"; public const string EpisodeType = "episode";
public const string OtherVideoType = "other_video"; public const string OtherVideoType = "other_video";
public const string SongType = "song"; public const string SongType = "song";
private readonly List<CultureInfo> _cultureInfos; private readonly List<CultureInfo> _cultureInfos;
private readonly ILogger<SearchIndex> _logger; private readonly ILogger<SearchIndex> _logger;
@ -81,7 +84,7 @@ public sealed class SearchIndex : ISearchIndex
_initialized = false; _initialized = false;
} }
public int Version => 21; public int Version => 22;
public Task<bool> Initialize(ILocalFileSystem localFileSystem) public Task<bool> Initialize(ILocalFileSystem localFileSystem)
{ {
@ -100,9 +103,6 @@ public sealed class SearchIndex : ISearchIndex
return Task.FromResult(_initialized); return Task.FromResult(_initialized);
} }
public Task<Unit> AddItems(ISearchRepository searchRepository, List<MediaItem> items) =>
UpdateItems(searchRepository, items);
public async Task<Unit> UpdateItems(ISearchRepository searchRepository, List<MediaItem> items) public async Task<Unit> UpdateItems(ISearchRepository searchRepository, List<MediaItem> items)
{ {
foreach (MediaItem item in items) foreach (MediaItem item in items)
@ -554,7 +554,9 @@ public sealed class SearchIndex : ISearchIndex
new StringField(LibraryIdField, season.LibraryPath.Library.Id.ToString(), Field.Store.NO), new StringField(LibraryIdField, season.LibraryPath.Library.Id.ToString(), Field.Store.NO),
new StringField(TitleAndYearField, titleAndYear, Field.Store.NO), new StringField(TitleAndYearField, titleAndYear, Field.Store.NO),
new StringField(JumpLetterField, GetJumpLetter(showMetadata), Field.Store.YES), new StringField(JumpLetterField, GetJumpLetter(showMetadata), Field.Store.YES),
new StringField(StateField, season.State.ToString(), Field.Store.NO) new StringField(StateField, season.State.ToString(), Field.Store.NO),
new Int32Field(SeasonNumberField, season.SeasonNumber, Field.Store.NO),
new TextField(ShowTitleField, season.Show?.ShowMetadata.Head().Title, Field.Store.NO)
}; };
List<string> languages = await searchRepository.GetLanguagesForSeason(season); List<string> languages = await searchRepository.GetLanguagesForSeason(season);
@ -767,7 +769,8 @@ public sealed class SearchIndex : ISearchIndex
new StringField(JumpLetterField, GetJumpLetter(metadata), Field.Store.YES), new StringField(JumpLetterField, GetJumpLetter(metadata), Field.Store.YES),
new StringField(StateField, episode.State.ToString(), Field.Store.NO), new StringField(StateField, episode.State.ToString(), Field.Store.NO),
new Int32Field(SeasonNumberField, episode.Season?.SeasonNumber ?? 0, Field.Store.NO), new Int32Field(SeasonNumberField, episode.Season?.SeasonNumber ?? 0, Field.Store.NO),
new Int32Field(EpisodeNumberField, metadata.EpisodeNumber, Field.Store.NO) new Int32Field(EpisodeNumberField, metadata.EpisodeNumber, Field.Store.NO),
new TextField(ShowTitleField, episode.Season?.Show?.ShowMetadata.Head().Title, Field.Store.NO)
}; };
await AddLanguages(searchRepository, doc, episode.MediaVersions); await AddLanguages(searchRepository, doc, episode.MediaVersions);

11
docs/user-guide/search.md

@ -47,6 +47,16 @@ The following fields are available for searching shows:
- `added_date`: The date the show was added to ErsatzTV (YYYYMMDD) - `added_date`: The date the show was added to ErsatzTV (YYYYMMDD)
- `type`: Always `show` - `type`: Always `show`
### Seasons
The following fields are available for searching seasons:
- `title`: The season title
- `library_name`: The name of the library that contains the season
- `season_number`: The season number
- `show_title`: The title of the show that contains the season
- `type`: Always `season`
### Episodes ### Episodes
The following fields are available for searching episodes: The following fields are available for searching episodes:
@ -64,6 +74,7 @@ The following fields are available for searching episodes:
- `width`: The episode width - `width`: The episode width
- `season_number`: The episode season number - `season_number`: The episode season number
- `episode_number`: The episode number - `episode_number`: The episode number
- `show_title`: The title of the show that contains the episode
- `type`: Always `episode` - `type`: Always `episode`
### Artists ### Artists

Loading…
Cancel
Save