Browse Source

fix search index threading (#141)

* fix search index threading

* code cleanup
pull/142/head
Jason Dove 5 years ago committed by GitHub
parent
commit
3fb6da0754
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      ErsatzTV.Application/ISearchBackgroundServiceRequest.cs
  2. 26
      ErsatzTV.Application/MediaSources/Commands/ScanLocalLibraryHandler.cs
  3. 22
      ErsatzTV.Application/Plex/Commands/SynchronizePlexLibraryByIdHandler.cs
  4. 9
      ErsatzTV.Application/Search/Commands/AddItemsToSearchIndex.cs
  5. 17
      ErsatzTV.Application/Search/Commands/AddItemsToSearchIndexHandler.cs
  6. 2
      ErsatzTV.Application/Search/Commands/RebuildSearchIndex.cs
  7. 8
      ErsatzTV.Application/Search/Commands/RemoveItemsFromSearchIndex.cs
  8. 17
      ErsatzTV.Application/Search/Commands/RemoveItemsFromSearchIndexHandler.cs
  9. 59
      ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs
  10. 5
      ErsatzTV.Core/Interfaces/Metadata/IMovieFolderScanner.cs
  11. 5
      ErsatzTV.Core/Interfaces/Metadata/IMusicVideoFolderScanner.cs
  12. 5
      ErsatzTV.Core/Interfaces/Metadata/ITelevisionFolderScanner.cs
  13. 8
      ErsatzTV.Core/Interfaces/Plex/IPlexMovieLibraryScanner.cs
  14. 8
      ErsatzTV.Core/Interfaces/Plex/IPlexTelevisionLibraryScanner.cs
  15. 22
      ErsatzTV.Core/Metadata/MovieFolderScanner.cs
  16. 22
      ErsatzTV.Core/Metadata/MusicVideoFolderScanner.cs
  17. 20
      ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs
  18. 25
      ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs
  19. 23
      ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs
  20. 2
      ErsatzTV/Pages/Movie.razor
  21. 5
      ErsatzTV/Pages/MovieList.razor
  22. 5
      ErsatzTV/Pages/MusicVideoList.razor
  23. 2
      ErsatzTV/Pages/TelevisionEpisodeList.razor
  24. 2
      ErsatzTV/Pages/TelevisionSeasonList.razor
  25. 5
      ErsatzTV/Pages/TelevisionShowList.razor
  26. 5
      ErsatzTV/Services/SchedulerService.cs
  27. 61
      ErsatzTV/Services/SearchIndexService.cs
  28. 5
      ErsatzTV/Shared/FragmentLetterAnchor.razor
  29. 1
      ErsatzTV/Startup.cs

6
ErsatzTV.Application/ISearchBackgroundServiceRequest.cs

@ -0,0 +1,6 @@
namespace ErsatzTV.Application
{
public interface ISearchBackgroundServiceRequest
{
}
}

26
ErsatzTV.Application/MediaSources/Commands/ScanLocalLibraryHandler.cs

@ -1,8 +1,11 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks; using System.Threading.Tasks;
using ErsatzTV.Application.Search.Commands;
using ErsatzTV.Core; using ErsatzTV.Core;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Locking; using ErsatzTV.Core.Interfaces.Locking;
@ -26,6 +29,7 @@ namespace ErsatzTV.Application.MediaSources.Commands
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly IMovieFolderScanner _movieFolderScanner; private readonly IMovieFolderScanner _movieFolderScanner;
private readonly IMusicVideoFolderScanner _musicVideoFolderScanner; private readonly IMusicVideoFolderScanner _musicVideoFolderScanner;
private readonly ChannelWriter<ISearchBackgroundServiceRequest> _searchChannel;
private readonly ITelevisionFolderScanner _televisionFolderScanner; private readonly ITelevisionFolderScanner _televisionFolderScanner;
public ScanLocalLibraryHandler( public ScanLocalLibraryHandler(
@ -36,7 +40,8 @@ namespace ErsatzTV.Application.MediaSources.Commands
IMusicVideoFolderScanner musicVideoFolderScanner, IMusicVideoFolderScanner musicVideoFolderScanner,
IEntityLocker entityLocker, IEntityLocker entityLocker,
IMediator mediator, IMediator mediator,
ILogger<ScanLocalLibraryHandler> logger) ILogger<ScanLocalLibraryHandler> logger,
ChannelWriter<ISearchBackgroundServiceRequest> searchChannel)
{ {
_libraryRepository = libraryRepository; _libraryRepository = libraryRepository;
_configElementRepository = configElementRepository; _configElementRepository = configElementRepository;
@ -46,6 +51,7 @@ namespace ErsatzTV.Application.MediaSources.Commands
_entityLocker = entityLocker; _entityLocker = entityLocker;
_mediator = mediator; _mediator = mediator;
_logger = logger; _logger = logger;
_searchChannel = searchChannel;
} }
public Task<Either<BaseError, string>> Handle( public Task<Either<BaseError, string>> Handle(
@ -87,7 +93,9 @@ namespace ErsatzTV.Application.MediaSources.Commands
ffprobePath, ffprobePath,
lastScan, lastScan,
progressMin, progressMin,
progressMax); progressMax,
AddToSearchIndex,
RemoveFromSearchIndex);
break; break;
case LibraryMediaKind.Shows: case LibraryMediaKind.Shows:
await _televisionFolderScanner.ScanFolder( await _televisionFolderScanner.ScanFolder(
@ -95,7 +103,9 @@ namespace ErsatzTV.Application.MediaSources.Commands
ffprobePath, ffprobePath,
lastScan, lastScan,
progressMin, progressMin,
progressMax); progressMax,
AddToSearchIndex,
RemoveFromSearchIndex);
break; break;
case LibraryMediaKind.MusicVideos: case LibraryMediaKind.MusicVideos:
await _musicVideoFolderScanner.ScanFolder( await _musicVideoFolderScanner.ScanFolder(
@ -103,7 +113,9 @@ namespace ErsatzTV.Application.MediaSources.Commands
ffprobePath, ffprobePath,
lastScan, lastScan,
progressMin, progressMin,
progressMax); progressMax,
AddToSearchIndex,
RemoveFromSearchIndex);
break; break;
} }
@ -126,6 +138,12 @@ namespace ErsatzTV.Application.MediaSources.Commands
return Unit.Default; return Unit.Default;
} }
private ValueTask AddToSearchIndex(List<MediaItem> mediaItems) =>
_searchChannel.WriteAsync(new AddItemsToSearchIndex(mediaItems));
private ValueTask RemoveFromSearchIndex(List<int> mediaItemIds) =>
_searchChannel.WriteAsync(new RemoveItemsFromSearchIndex(mediaItemIds));
private async Task<Validation<BaseError, RequestParameters>> Validate(IScanLocalLibrary request) => private async Task<Validation<BaseError, RequestParameters>> Validate(IScanLocalLibrary request) =>
(await LocalLibraryMustExist(request), await ValidateFFprobePath()) (await LocalLibraryMustExist(request), await ValidateFFprobePath())
.Apply( .Apply(

22
ErsatzTV.Application/Plex/Commands/SynchronizePlexLibraryByIdHandler.cs

@ -1,7 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks; using System.Threading.Tasks;
using ErsatzTV.Application.Search.Commands;
using ErsatzTV.Core; using ErsatzTV.Core;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Locking; using ErsatzTV.Core.Interfaces.Locking;
@ -26,6 +29,7 @@ namespace ErsatzTV.Application.Plex.Commands
private readonly IPlexMovieLibraryScanner _plexMovieLibraryScanner; private readonly IPlexMovieLibraryScanner _plexMovieLibraryScanner;
private readonly IPlexSecretStore _plexSecretStore; private readonly IPlexSecretStore _plexSecretStore;
private readonly IPlexTelevisionLibraryScanner _plexTelevisionLibraryScanner; private readonly IPlexTelevisionLibraryScanner _plexTelevisionLibraryScanner;
private readonly ChannelWriter<ISearchBackgroundServiceRequest> _searchChannel;
public SynchronizePlexLibraryByIdHandler( public SynchronizePlexLibraryByIdHandler(
IMediaSourceRepository mediaSourceRepository, IMediaSourceRepository mediaSourceRepository,
@ -34,7 +38,8 @@ namespace ErsatzTV.Application.Plex.Commands
IPlexTelevisionLibraryScanner plexTelevisionLibraryScanner, IPlexTelevisionLibraryScanner plexTelevisionLibraryScanner,
ILibraryRepository libraryRepository, ILibraryRepository libraryRepository,
IEntityLocker entityLocker, IEntityLocker entityLocker,
ILogger<SynchronizePlexLibraryByIdHandler> logger) ILogger<SynchronizePlexLibraryByIdHandler> logger,
ChannelWriter<ISearchBackgroundServiceRequest> searchChannel)
{ {
_mediaSourceRepository = mediaSourceRepository; _mediaSourceRepository = mediaSourceRepository;
_plexSecretStore = plexSecretStore; _plexSecretStore = plexSecretStore;
@ -43,6 +48,7 @@ namespace ErsatzTV.Application.Plex.Commands
_libraryRepository = libraryRepository; _libraryRepository = libraryRepository;
_entityLocker = entityLocker; _entityLocker = entityLocker;
_logger = logger; _logger = logger;
_searchChannel = searchChannel;
} }
public Task<Either<BaseError, string>> Handle( public Task<Either<BaseError, string>> Handle(
@ -70,13 +76,17 @@ namespace ErsatzTV.Application.Plex.Commands
await _plexMovieLibraryScanner.ScanLibrary( await _plexMovieLibraryScanner.ScanLibrary(
parameters.ConnectionParameters.ActiveConnection, parameters.ConnectionParameters.ActiveConnection,
parameters.ConnectionParameters.PlexServerAuthToken, parameters.ConnectionParameters.PlexServerAuthToken,
parameters.Library); parameters.Library,
AddToSearchIndex,
RemoveFromSearchIndex);
break; break;
case LibraryMediaKind.Shows: case LibraryMediaKind.Shows:
await _plexTelevisionLibraryScanner.ScanLibrary( await _plexTelevisionLibraryScanner.ScanLibrary(
parameters.ConnectionParameters.ActiveConnection, parameters.ConnectionParameters.ActiveConnection,
parameters.ConnectionParameters.PlexServerAuthToken, parameters.ConnectionParameters.PlexServerAuthToken,
parameters.Library); parameters.Library,
AddToSearchIndex,
RemoveFromSearchIndex);
break; break;
} }
@ -94,6 +104,12 @@ namespace ErsatzTV.Application.Plex.Commands
return Unit.Default; return Unit.Default;
} }
private ValueTask AddToSearchIndex(List<MediaItem> mediaItems) =>
_searchChannel.WriteAsync(new AddItemsToSearchIndex(mediaItems));
private ValueTask RemoveFromSearchIndex(List<int> mediaItemIds) =>
_searchChannel.WriteAsync(new RemoveItemsFromSearchIndex(mediaItemIds));
private async Task<Validation<BaseError, RequestParameters>> Validate(ISynchronizePlexLibraryById request) => private async Task<Validation<BaseError, RequestParameters>> Validate(ISynchronizePlexLibraryById request) =>
(await ValidateConnection(request), await PlexLibraryMustExist(request)) (await ValidateConnection(request), await PlexLibraryMustExist(request))
.Apply( .Apply(

9
ErsatzTV.Application/Search/Commands/AddItemsToSearchIndex.cs

@ -0,0 +1,9 @@
using System.Collections.Generic;
using ErsatzTV.Core.Domain;
using LanguageExt;
namespace ErsatzTV.Application.Search.Commands
{
public record AddItemsToSearchIndex(List<MediaItem> MediaItems) : MediatR.IRequest<Unit>,
ISearchBackgroundServiceRequest;
}

17
ErsatzTV.Application/Search/Commands/AddItemsToSearchIndexHandler.cs

@ -0,0 +1,17 @@
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core.Interfaces.Search;
using LanguageExt;
namespace ErsatzTV.Application.Search.Commands
{
public class AddItemsToSearchIndexHandler : MediatR.IRequestHandler<AddItemsToSearchIndex, Unit>
{
private readonly ISearchIndex _searchIndex;
public AddItemsToSearchIndexHandler(ISearchIndex searchIndex) => _searchIndex = searchIndex;
public Task<Unit> Handle(AddItemsToSearchIndex request, CancellationToken cancellationToken) =>
_searchIndex.AddItems(request.MediaItems);
}
}

2
ErsatzTV.Application/Search/Commands/RebuildSearchIndex.cs

@ -2,5 +2,5 @@
namespace ErsatzTV.Application.Search.Commands namespace ErsatzTV.Application.Search.Commands
{ {
public record RebuildSearchIndex : MediatR.IRequest<Unit>, IBackgroundServiceRequest; public record RebuildSearchIndex : MediatR.IRequest<Unit>, ISearchBackgroundServiceRequest;
} }

8
ErsatzTV.Application/Search/Commands/RemoveItemsFromSearchIndex.cs

@ -0,0 +1,8 @@
using System.Collections.Generic;
using LanguageExt;
namespace ErsatzTV.Application.Search.Commands
{
public record RemoveItemsFromSearchIndex(List<int> MediaItemIds) : MediatR.IRequest<Unit>,
ISearchBackgroundServiceRequest;
}

17
ErsatzTV.Application/Search/Commands/RemoveItemsFromSearchIndexHandler.cs

@ -0,0 +1,17 @@
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core.Interfaces.Search;
using LanguageExt;
namespace ErsatzTV.Application.Search.Commands
{
public class RemoveItemsFromSearchIndexHandler : MediatR.IRequestHandler<RemoveItemsFromSearchIndex, Unit>
{
private readonly ISearchIndex _searchIndex;
public RemoveItemsFromSearchIndexHandler(ISearchIndex searchIndex) => _searchIndex = searchIndex;
public Task<Unit> Handle(RemoveItemsFromSearchIndex request, CancellationToken cancellationToken) =>
_searchIndex.RemoveItems(request.MediaItemIds);
}
}

59
ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs

@ -9,7 +9,6 @@ using ErsatzTV.Core.Errors;
using ErsatzTV.Core.Interfaces.Images; using ErsatzTV.Core.Interfaces.Images;
using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Search;
using ErsatzTV.Core.Metadata; using ErsatzTV.Core.Metadata;
using ErsatzTV.Core.Tests.Fakes; using ErsatzTV.Core.Tests.Fakes;
using FluentAssertions; using FluentAssertions;
@ -88,7 +87,9 @@ namespace ErsatzTV.Core.Tests.Metadata
FFprobePath, FFprobePath,
DateTimeOffset.MinValue, DateTimeOffset.MinValue,
0, 0,
1); 1,
_ => ValueTask.CompletedTask,
_ => ValueTask.CompletedTask);
result.IsLeft.Should().BeTrue(); result.IsLeft.Should().BeTrue();
result.IfLeft(error => error.Should().BeOfType<MediaSourceInaccessible>()); result.IfLeft(error => error.Should().BeOfType<MediaSourceInaccessible>());
@ -113,7 +114,9 @@ namespace ErsatzTV.Core.Tests.Metadata
FFprobePath, FFprobePath,
DateTimeOffset.MinValue, DateTimeOffset.MinValue,
0, 0,
1); 1,
_ => ValueTask.CompletedTask,
_ => ValueTask.CompletedTask);
result.IsRight.Should().BeTrue(); result.IsRight.Should().BeTrue();
@ -154,7 +157,10 @@ namespace ErsatzTV.Core.Tests.Metadata
FFprobePath, FFprobePath,
DateTimeOffset.MinValue, DateTimeOffset.MinValue,
0, 0,
1); 1,
_ => ValueTask.CompletedTask,
_ => ValueTask.CompletedTask);
result.IsRight.Should().BeTrue(); result.IsRight.Should().BeTrue();
@ -196,7 +202,10 @@ namespace ErsatzTV.Core.Tests.Metadata
FFprobePath, FFprobePath,
DateTimeOffset.MinValue, DateTimeOffset.MinValue,
0, 0,
1); 1,
_ => ValueTask.CompletedTask,
_ => ValueTask.CompletedTask);
result.IsRight.Should().BeTrue(); result.IsRight.Should().BeTrue();
@ -242,7 +251,10 @@ namespace ErsatzTV.Core.Tests.Metadata
FFprobePath, FFprobePath,
DateTimeOffset.MinValue, DateTimeOffset.MinValue,
0, 0,
1); 1,
_ => ValueTask.CompletedTask,
_ => ValueTask.CompletedTask);
result.IsRight.Should().BeTrue(); result.IsRight.Should().BeTrue();
@ -291,7 +303,10 @@ namespace ErsatzTV.Core.Tests.Metadata
FFprobePath, FFprobePath,
DateTimeOffset.MinValue, DateTimeOffset.MinValue,
0, 0,
1); 1,
_ => ValueTask.CompletedTask,
_ => ValueTask.CompletedTask);
result.IsRight.Should().BeTrue(); result.IsRight.Should().BeTrue();
@ -340,7 +355,10 @@ namespace ErsatzTV.Core.Tests.Metadata
FFprobePath, FFprobePath,
DateTimeOffset.MinValue, DateTimeOffset.MinValue,
0, 0,
1); 1,
_ => ValueTask.CompletedTask,
_ => ValueTask.CompletedTask);
result.IsRight.Should().BeTrue(); result.IsRight.Should().BeTrue();
@ -388,7 +406,10 @@ namespace ErsatzTV.Core.Tests.Metadata
FFprobePath, FFprobePath,
DateTimeOffset.MinValue, DateTimeOffset.MinValue,
0, 0,
1); 1,
_ => ValueTask.CompletedTask,
_ => ValueTask.CompletedTask);
result.IsRight.Should().BeTrue(); result.IsRight.Should().BeTrue();
@ -432,7 +453,10 @@ namespace ErsatzTV.Core.Tests.Metadata
FFprobePath, FFprobePath,
DateTimeOffset.MinValue, DateTimeOffset.MinValue,
0, 0,
1); 1,
_ => ValueTask.CompletedTask,
_ => ValueTask.CompletedTask);
result.IsRight.Should().BeTrue(); result.IsRight.Should().BeTrue();
@ -470,7 +494,10 @@ namespace ErsatzTV.Core.Tests.Metadata
FFprobePath, FFprobePath,
DateTimeOffset.MinValue, DateTimeOffset.MinValue,
0, 0,
1); 1,
_ => ValueTask.CompletedTask,
_ => ValueTask.CompletedTask);
result.IsRight.Should().BeTrue(); result.IsRight.Should().BeTrue();
@ -510,7 +537,9 @@ namespace ErsatzTV.Core.Tests.Metadata
FFprobePath, FFprobePath,
DateTimeOffset.MinValue, DateTimeOffset.MinValue,
0, 0,
1); 1,
_ => ValueTask.CompletedTask,
_ => ValueTask.CompletedTask);
result.IsRight.Should().BeTrue(); result.IsRight.Should().BeTrue();
@ -539,7 +568,10 @@ namespace ErsatzTV.Core.Tests.Metadata
FFprobePath, FFprobePath,
DateTimeOffset.MinValue, DateTimeOffset.MinValue,
0, 0,
1); 1,
_ => ValueTask.CompletedTask,
_ => ValueTask.CompletedTask);
result.IsRight.Should().BeTrue(); result.IsRight.Should().BeTrue();
@ -556,7 +588,6 @@ namespace ErsatzTV.Core.Tests.Metadata
_localMetadataProvider.Object, _localMetadataProvider.Object,
new Mock<IMetadataRepository>().Object, new Mock<IMetadataRepository>().Object,
_imageCache.Object, _imageCache.Object,
new Mock<ISearchIndex>().Object,
new Mock<IMediator>().Object, new Mock<IMediator>().Object,
new Mock<ILogger<MovieFolderScanner>>().Object new Mock<ILogger<MovieFolderScanner>>().Object
); );

5
ErsatzTV.Core/Interfaces/Metadata/IMovieFolderScanner.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using LanguageExt; using LanguageExt;
@ -12,6 +13,8 @@ namespace ErsatzTV.Core.Interfaces.Metadata
string ffprobePath, string ffprobePath,
DateTimeOffset lastScan, DateTimeOffset lastScan,
decimal progressMin, decimal progressMin,
decimal progressMax); decimal progressMax,
Func<List<MediaItem>, ValueTask> addToSearchIndex,
Func<List<int>, ValueTask> removeFromSearchIndex);
} }
} }

5
ErsatzTV.Core/Interfaces/Metadata/IMusicVideoFolderScanner.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using LanguageExt; using LanguageExt;
@ -12,6 +13,8 @@ namespace ErsatzTV.Core.Interfaces.Metadata
string ffprobePath, string ffprobePath,
DateTimeOffset lastScan, DateTimeOffset lastScan,
decimal progressMin, decimal progressMin,
decimal progressMax); decimal progressMax,
Func<List<MediaItem>, ValueTask> addToSearchIndex,
Func<List<int>, ValueTask> removeFromSearchIndex);
} }
} }

5
ErsatzTV.Core/Interfaces/Metadata/ITelevisionFolderScanner.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using LanguageExt; using LanguageExt;
@ -12,6 +13,8 @@ namespace ErsatzTV.Core.Interfaces.Metadata
string ffprobePath, string ffprobePath,
DateTimeOffset lastScan, DateTimeOffset lastScan,
decimal progressMin, decimal progressMin,
decimal progressMax); decimal progressMax,
Func<List<MediaItem>, ValueTask> addToSearchIndex,
Func<List<int>, ValueTask> removeFromSearchIndex);
} }
} }

8
ErsatzTV.Core/Interfaces/Plex/IPlexMovieLibraryScanner.cs

@ -1,4 +1,6 @@
using System.Threading.Tasks; using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Plex; using ErsatzTV.Core.Plex;
using LanguageExt; using LanguageExt;
@ -10,6 +12,8 @@ namespace ErsatzTV.Core.Interfaces.Plex
Task<Either<BaseError, Unit>> ScanLibrary( Task<Either<BaseError, Unit>> ScanLibrary(
PlexConnection connection, PlexConnection connection,
PlexServerAuthToken token, PlexServerAuthToken token,
PlexLibrary plexMediaSourceLibrary); PlexLibrary plexMediaSourceLibrary,
Func<List<MediaItem>, ValueTask> addToSearchIndex,
Func<List<int>, ValueTask> removeFromSearchIndex);
} }
} }

8
ErsatzTV.Core/Interfaces/Plex/IPlexTelevisionLibraryScanner.cs

@ -1,4 +1,6 @@
using System.Threading.Tasks; using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Plex; using ErsatzTV.Core.Plex;
using LanguageExt; using LanguageExt;
@ -10,6 +12,8 @@ namespace ErsatzTV.Core.Interfaces.Plex
Task<Either<BaseError, Unit>> ScanLibrary( Task<Either<BaseError, Unit>> ScanLibrary(
PlexConnection connection, PlexConnection connection,
PlexServerAuthToken token, PlexServerAuthToken token,
PlexLibrary plexMediaSourceLibrary); PlexLibrary plexMediaSourceLibrary,
Func<List<MediaItem>, ValueTask> addToSearchIndex,
Func<List<int>, ValueTask> removeFromSearchIndex);
} }
} }

22
ErsatzTV.Core/Metadata/MovieFolderScanner.cs

@ -8,7 +8,6 @@ using ErsatzTV.Core.Errors;
using ErsatzTV.Core.Interfaces.Images; using ErsatzTV.Core.Interfaces.Images;
using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Search;
using LanguageExt; using LanguageExt;
using MediatR; using MediatR;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -25,7 +24,6 @@ namespace ErsatzTV.Core.Metadata
private readonly ILogger<MovieFolderScanner> _logger; private readonly ILogger<MovieFolderScanner> _logger;
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly IMovieRepository _movieRepository; private readonly IMovieRepository _movieRepository;
private readonly ISearchIndex _searchIndex;
public MovieFolderScanner( public MovieFolderScanner(
ILocalFileSystem localFileSystem, ILocalFileSystem localFileSystem,
@ -34,7 +32,6 @@ namespace ErsatzTV.Core.Metadata
ILocalMetadataProvider localMetadataProvider, ILocalMetadataProvider localMetadataProvider,
IMetadataRepository metadataRepository, IMetadataRepository metadataRepository,
IImageCache imageCache, IImageCache imageCache,
ISearchIndex searchIndex,
IMediator mediator, IMediator mediator,
ILogger<MovieFolderScanner> logger) ILogger<MovieFolderScanner> logger)
: base(localFileSystem, localStatisticsProvider, metadataRepository, imageCache, logger) : base(localFileSystem, localStatisticsProvider, metadataRepository, imageCache, logger)
@ -42,7 +39,6 @@ namespace ErsatzTV.Core.Metadata
_localFileSystem = localFileSystem; _localFileSystem = localFileSystem;
_movieRepository = movieRepository; _movieRepository = movieRepository;
_localMetadataProvider = localMetadataProvider; _localMetadataProvider = localMetadataProvider;
_searchIndex = searchIndex;
_mediator = mediator; _mediator = mediator;
_logger = logger; _logger = logger;
} }
@ -52,7 +48,9 @@ namespace ErsatzTV.Core.Metadata
string ffprobePath, string ffprobePath,
DateTimeOffset lastScan, DateTimeOffset lastScan,
decimal progressMin, decimal progressMin,
decimal progressMax) decimal progressMax,
Func<List<MediaItem>, ValueTask> addToSearchIndex,
Func<List<int>, ValueTask> removeFromSearchIndex)
{ {
decimal progressSpread = progressMax - progressMin; decimal progressSpread = progressMax - progressMin;
@ -111,17 +109,7 @@ namespace ErsatzTV.Core.Metadata
.BindT(movie => UpdateArtwork(movie, ArtworkKind.FanArt)); .BindT(movie => UpdateArtwork(movie, ArtworkKind.FanArt));
await maybeMovie.Match( await maybeMovie.Match(
async result => async result => await addToSearchIndex(new List<MediaItem> { result.Item }),
{
if (result.IsAdded)
{
await _searchIndex.AddItems(new List<MediaItem> { result.Item });
}
else if (result.IsUpdated)
{
await _searchIndex.UpdateItems(new List<MediaItem> { result.Item });
}
},
error => error =>
{ {
_logger.LogWarning("Error processing movie at {Path}: {Error}", file, error.Value); _logger.LogWarning("Error processing movie at {Path}: {Error}", file, error.Value);
@ -136,7 +124,7 @@ namespace ErsatzTV.Core.Metadata
{ {
_logger.LogInformation("Removing missing movie at {Path}", path); _logger.LogInformation("Removing missing movie at {Path}", path);
List<int> ids = await _movieRepository.DeleteByPath(libraryPath, path); List<int> ids = await _movieRepository.DeleteByPath(libraryPath, path);
await _searchIndex.RemoveItems(ids); await removeFromSearchIndex(ids);
} }
} }

22
ErsatzTV.Core/Metadata/MusicVideoFolderScanner.cs

@ -8,7 +8,6 @@ using ErsatzTV.Core.Errors;
using ErsatzTV.Core.Interfaces.Images; using ErsatzTV.Core.Interfaces.Images;
using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Search;
using LanguageExt; using LanguageExt;
using MediatR; using MediatR;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -24,7 +23,6 @@ namespace ErsatzTV.Core.Metadata
private readonly ILogger<MusicVideoFolderScanner> _logger; private readonly ILogger<MusicVideoFolderScanner> _logger;
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly IMusicVideoRepository _musicVideoRepository; private readonly IMusicVideoRepository _musicVideoRepository;
private readonly ISearchIndex _searchIndex;
public MusicVideoFolderScanner( public MusicVideoFolderScanner(
ILocalFileSystem localFileSystem, ILocalFileSystem localFileSystem,
@ -32,7 +30,6 @@ namespace ErsatzTV.Core.Metadata
ILocalMetadataProvider localMetadataProvider, ILocalMetadataProvider localMetadataProvider,
IMetadataRepository metadataRepository, IMetadataRepository metadataRepository,
IImageCache imageCache, IImageCache imageCache,
ISearchIndex searchIndex,
IMusicVideoRepository musicVideoRepository, IMusicVideoRepository musicVideoRepository,
IMediator mediator, IMediator mediator,
ILogger<MusicVideoFolderScanner> logger) : base( ILogger<MusicVideoFolderScanner> logger) : base(
@ -44,7 +41,6 @@ namespace ErsatzTV.Core.Metadata
{ {
_localFileSystem = localFileSystem; _localFileSystem = localFileSystem;
_localMetadataProvider = localMetadataProvider; _localMetadataProvider = localMetadataProvider;
_searchIndex = searchIndex;
_musicVideoRepository = musicVideoRepository; _musicVideoRepository = musicVideoRepository;
_mediator = mediator; _mediator = mediator;
_logger = logger; _logger = logger;
@ -55,7 +51,9 @@ namespace ErsatzTV.Core.Metadata
string ffprobePath, string ffprobePath,
DateTimeOffset lastScan, DateTimeOffset lastScan,
decimal progressMin, decimal progressMin,
decimal progressMax) decimal progressMax,
Func<List<MediaItem>, ValueTask> addToSearchIndex,
Func<List<int>, ValueTask> removeFromSearchIndex)
{ {
decimal progressSpread = progressMax - progressMin; decimal progressSpread = progressMax - progressMin;
@ -104,17 +102,7 @@ namespace ErsatzTV.Core.Metadata
.BindT(UpdateThumbnail); .BindT(UpdateThumbnail);
await maybeMusicVideo.Match( await maybeMusicVideo.Match(
async result => async result => await addToSearchIndex(new List<MediaItem> { result.Item }),
{
if (result.IsAdded)
{
await _searchIndex.AddItems(new List<MediaItem> { result.Item });
}
else if (result.IsUpdated)
{
await _searchIndex.UpdateItems(new List<MediaItem> { result.Item });
}
},
error => error =>
{ {
_logger.LogWarning("Error processing music video at {Path}: {Error}", file, error.Value); _logger.LogWarning("Error processing music video at {Path}: {Error}", file, error.Value);
@ -129,7 +117,7 @@ namespace ErsatzTV.Core.Metadata
{ {
_logger.LogInformation("Removing missing music video at {Path}", path); _logger.LogInformation("Removing missing music video at {Path}", path);
List<int> ids = await _musicVideoRepository.DeleteByPath(libraryPath, path); List<int> ids = await _musicVideoRepository.DeleteByPath(libraryPath, path);
await _searchIndex.RemoveItems(ids); await removeFromSearchIndex(ids);
} }
} }

20
ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs

@ -8,7 +8,6 @@ using ErsatzTV.Core.Errors;
using ErsatzTV.Core.Interfaces.Images; using ErsatzTV.Core.Interfaces.Images;
using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Search;
using LanguageExt; using LanguageExt;
using MediatR; using MediatR;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -23,7 +22,6 @@ namespace ErsatzTV.Core.Metadata
private readonly ILocalMetadataProvider _localMetadataProvider; private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly ILogger<TelevisionFolderScanner> _logger; private readonly ILogger<TelevisionFolderScanner> _logger;
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly ISearchIndex _searchIndex;
private readonly ITelevisionRepository _televisionRepository; private readonly ITelevisionRepository _televisionRepository;
public TelevisionFolderScanner( public TelevisionFolderScanner(
@ -33,7 +31,6 @@ namespace ErsatzTV.Core.Metadata
ILocalMetadataProvider localMetadataProvider, ILocalMetadataProvider localMetadataProvider,
IMetadataRepository metadataRepository, IMetadataRepository metadataRepository,
IImageCache imageCache, IImageCache imageCache,
ISearchIndex searchIndex,
IMediator mediator, IMediator mediator,
ILogger<TelevisionFolderScanner> logger) : base( ILogger<TelevisionFolderScanner> logger) : base(
localFileSystem, localFileSystem,
@ -45,7 +42,6 @@ namespace ErsatzTV.Core.Metadata
_localFileSystem = localFileSystem; _localFileSystem = localFileSystem;
_televisionRepository = televisionRepository; _televisionRepository = televisionRepository;
_localMetadataProvider = localMetadataProvider; _localMetadataProvider = localMetadataProvider;
_searchIndex = searchIndex;
_mediator = mediator; _mediator = mediator;
_logger = logger; _logger = logger;
} }
@ -55,7 +51,9 @@ namespace ErsatzTV.Core.Metadata
string ffprobePath, string ffprobePath,
DateTimeOffset lastScan, DateTimeOffset lastScan,
decimal progressMin, decimal progressMin,
decimal progressMax) decimal progressMax,
Func<List<MediaItem>, ValueTask> addToSearchIndex,
Func<List<int>, ValueTask> removeFromSearchIndex)
{ {
decimal progressSpread = progressMax - progressMin; decimal progressSpread = progressMax - progressMin;
@ -84,15 +82,7 @@ namespace ErsatzTV.Core.Metadata
await maybeShow.Match( await maybeShow.Match(
async result => async result =>
{ {
if (result.IsAdded) await addToSearchIndex(new List<MediaItem> { result.Item });
{
await _searchIndex.AddItems(new List<MediaItem> { result.Item });
}
else if (result.IsUpdated)
{
await _searchIndex.UpdateItems(new List<MediaItem> { result.Item });
}
await ScanSeasons( await ScanSeasons(
libraryPath, libraryPath,
ffprobePath, ffprobePath,
@ -122,7 +112,7 @@ namespace ErsatzTV.Core.Metadata
await _televisionRepository.DeleteEmptySeasons(libraryPath); await _televisionRepository.DeleteEmptySeasons(libraryPath);
List<int> ids = await _televisionRepository.DeleteEmptyShows(libraryPath); List<int> ids = await _televisionRepository.DeleteEmptyShows(libraryPath);
await _searchIndex.RemoveItems(ids); await removeFromSearchIndex(ids);
return Unit.Default; return Unit.Default;
} }

25
ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs

@ -1,10 +1,10 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Plex; using ErsatzTV.Core.Interfaces.Plex;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Search;
using ErsatzTV.Core.Metadata; using ErsatzTV.Core.Metadata;
using LanguageExt; using LanguageExt;
using MediatR; using MediatR;
@ -20,13 +20,11 @@ namespace ErsatzTV.Core.Plex
private readonly IMetadataRepository _metadataRepository; private readonly IMetadataRepository _metadataRepository;
private readonly IMovieRepository _movieRepository; private readonly IMovieRepository _movieRepository;
private readonly IPlexServerApiClient _plexServerApiClient; private readonly IPlexServerApiClient _plexServerApiClient;
private readonly ISearchIndex _searchIndex;
public PlexMovieLibraryScanner( public PlexMovieLibraryScanner(
IPlexServerApiClient plexServerApiClient, IPlexServerApiClient plexServerApiClient,
IMovieRepository movieRepository, IMovieRepository movieRepository,
IMetadataRepository metadataRepository, IMetadataRepository metadataRepository,
ISearchIndex searchIndex,
IMediator mediator, IMediator mediator,
ILogger<PlexMovieLibraryScanner> logger) ILogger<PlexMovieLibraryScanner> logger)
: base(metadataRepository, logger) : base(metadataRepository, logger)
@ -34,7 +32,6 @@ namespace ErsatzTV.Core.Plex
_plexServerApiClient = plexServerApiClient; _plexServerApiClient = plexServerApiClient;
_movieRepository = movieRepository; _movieRepository = movieRepository;
_metadataRepository = metadataRepository; _metadataRepository = metadataRepository;
_searchIndex = searchIndex;
_mediator = mediator; _mediator = mediator;
_logger = logger; _logger = logger;
} }
@ -42,7 +39,9 @@ namespace ErsatzTV.Core.Plex
public async Task<Either<BaseError, Unit>> ScanLibrary( public async Task<Either<BaseError, Unit>> ScanLibrary(
PlexConnection connection, PlexConnection connection,
PlexServerAuthToken token, PlexServerAuthToken token,
PlexLibrary plexMediaSourceLibrary) PlexLibrary plexMediaSourceLibrary,
Func<List<MediaItem>, ValueTask> addToSearchIndex,
Func<List<int>, ValueTask> removeFromSearchIndex)
{ {
Either<BaseError, List<PlexMovie>> entries = await _plexServerApiClient.GetMovieLibraryContents( Either<BaseError, List<PlexMovie>> entries = await _plexServerApiClient.GetMovieLibraryContents(
plexMediaSourceLibrary, plexMediaSourceLibrary,
@ -65,17 +64,7 @@ namespace ErsatzTV.Core.Plex
.BindT(existing => UpdateArtwork(existing, incoming)); .BindT(existing => UpdateArtwork(existing, incoming));
await maybeMovie.Match( await maybeMovie.Match(
async result => async result => await addToSearchIndex(new List<MediaItem> { result.Item }),
{
if (result.IsAdded)
{
await _searchIndex.AddItems(new List<MediaItem> { result.Item });
}
else if (result.IsUpdated)
{
await _searchIndex.UpdateItems(new List<MediaItem> { result.Item });
}
},
error => error =>
{ {
_logger.LogWarning( _logger.LogWarning(
@ -88,7 +77,7 @@ namespace ErsatzTV.Core.Plex
var movieKeys = movieEntries.Map(s => s.Key).ToList(); var movieKeys = movieEntries.Map(s => s.Key).ToList();
List<int> ids = await _movieRepository.RemoveMissingPlexMovies(plexMediaSourceLibrary, movieKeys); List<int> ids = await _movieRepository.RemoveMissingPlexMovies(plexMediaSourceLibrary, movieKeys);
await _searchIndex.RemoveItems(ids); await removeFromSearchIndex(ids);
await _mediator.Publish(new LibraryScanProgress(plexMediaSourceLibrary.Id, 0)); await _mediator.Publish(new LibraryScanProgress(plexMediaSourceLibrary.Id, 0));
}, },

23
ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs

@ -1,10 +1,10 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Plex; using ErsatzTV.Core.Interfaces.Plex;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Search;
using ErsatzTV.Core.Metadata; using ErsatzTV.Core.Metadata;
using LanguageExt; using LanguageExt;
using MediatR; using MediatR;
@ -20,14 +20,12 @@ namespace ErsatzTV.Core.Plex
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly IMetadataRepository _metadataRepository; private readonly IMetadataRepository _metadataRepository;
private readonly IPlexServerApiClient _plexServerApiClient; private readonly IPlexServerApiClient _plexServerApiClient;
private readonly ISearchIndex _searchIndex;
private readonly ITelevisionRepository _televisionRepository; private readonly ITelevisionRepository _televisionRepository;
public PlexTelevisionLibraryScanner( public PlexTelevisionLibraryScanner(
IPlexServerApiClient plexServerApiClient, IPlexServerApiClient plexServerApiClient,
ITelevisionRepository televisionRepository, ITelevisionRepository televisionRepository,
IMetadataRepository metadataRepository, IMetadataRepository metadataRepository,
ISearchIndex searchIndex,
IMediator mediator, IMediator mediator,
ILogger<PlexTelevisionLibraryScanner> logger) ILogger<PlexTelevisionLibraryScanner> logger)
: base(metadataRepository, logger) : base(metadataRepository, logger)
@ -35,7 +33,6 @@ namespace ErsatzTV.Core.Plex
_plexServerApiClient = plexServerApiClient; _plexServerApiClient = plexServerApiClient;
_televisionRepository = televisionRepository; _televisionRepository = televisionRepository;
_metadataRepository = metadataRepository; _metadataRepository = metadataRepository;
_searchIndex = searchIndex;
_mediator = mediator; _mediator = mediator;
_logger = logger; _logger = logger;
} }
@ -43,7 +40,9 @@ namespace ErsatzTV.Core.Plex
public async Task<Either<BaseError, Unit>> ScanLibrary( public async Task<Either<BaseError, Unit>> ScanLibrary(
PlexConnection connection, PlexConnection connection,
PlexServerAuthToken token, PlexServerAuthToken token,
PlexLibrary plexMediaSourceLibrary) PlexLibrary plexMediaSourceLibrary,
Func<List<MediaItem>, ValueTask> addToSearchIndex,
Func<List<int>, ValueTask> removeFromSearchIndex)
{ {
Either<BaseError, List<PlexShow>> entries = await _plexServerApiClient.GetShowLibraryContents( Either<BaseError, List<PlexShow>> entries = await _plexServerApiClient.GetShowLibraryContents(
plexMediaSourceLibrary, plexMediaSourceLibrary,
@ -67,15 +66,7 @@ namespace ErsatzTV.Core.Plex
await maybeShow.Match( await maybeShow.Match(
async result => async result =>
{ {
if (result.IsAdded) await addToSearchIndex(new List<MediaItem> { result.Item });
{
await _searchIndex.AddItems(new List<MediaItem> { result.Item });
}
else if (result.IsUpdated)
{
await _searchIndex.UpdateItems(new List<MediaItem> { result.Item });
}
await ScanSeasons(plexMediaSourceLibrary, result.Item, connection, token); await ScanSeasons(plexMediaSourceLibrary, result.Item, connection, token);
}, },
error => error =>
@ -91,7 +82,7 @@ namespace ErsatzTV.Core.Plex
var showKeys = showEntries.Map(s => s.Key).ToList(); var showKeys = showEntries.Map(s => s.Key).ToList();
List<int> ids = List<int> ids =
await _televisionRepository.RemoveMissingPlexShows(plexMediaSourceLibrary, showKeys); await _televisionRepository.RemoveMissingPlexShows(plexMediaSourceLibrary, showKeys);
await _searchIndex.RemoveItems(ids); await removeFromSearchIndex(ids);
await _mediator.Publish(new LibraryScanProgress(plexMediaSourceLibrary.Id, 0)); await _mediator.Publish(new LibraryScanProgress(plexMediaSourceLibrary.Id, 0));

2
ErsatzTV/Pages/Movie.razor

@ -19,7 +19,7 @@
@if (!string.IsNullOrWhiteSpace(_movie.Poster)) @if (!string.IsNullOrWhiteSpace(_movie.Poster))
{ {
<img class="mud-elevation-2 mr-6" <img class="mud-elevation-2 mr-6"
style="border-radius: 4px; max-height: 440px; flex-shrink: 0" style="border-radius: 4px; flex-shrink: 0; max-height: 440px;"
src="@($"/artwork/posters/{_movie.Poster}")" alt="movie poster"/> src="@($"/artwork/posters/{_movie.Poster}")" alt="movie poster"/>
} }
<div style="display: flex; flex-direction: column; height: 100%"> <div style="display: flex; flex-direction: column; height: 100%">

5
ErsatzTV/Pages/MovieList.razor

@ -130,7 +130,10 @@
private void SelectClicked(MediaCardViewModel card, MouseEventArgs e) private void SelectClicked(MediaCardViewModel card, MouseEventArgs e)
{ {
List<MediaCardViewModel> GetSortedItems() => _data.Cards.OrderBy(m => m.SortTitle).ToList<MediaCardViewModel>(); List<MediaCardViewModel> GetSortedItems()
{
return _data.Cards.OrderBy(m => m.SortTitle).ToList<MediaCardViewModel>();
}
SelectClicked(GetSortedItems, card, e); SelectClicked(GetSortedItems, card, e);
} }

5
ErsatzTV/Pages/MusicVideoList.razor

@ -131,7 +131,10 @@
private void SelectClicked(MediaCardViewModel card, MouseEventArgs e) private void SelectClicked(MediaCardViewModel card, MouseEventArgs e)
{ {
List<MediaCardViewModel> GetSortedItems() => _data.Cards.OrderBy(m => m.SortTitle).ToList<MediaCardViewModel>(); List<MediaCardViewModel> GetSortedItems()
{
return _data.Cards.OrderBy(m => m.SortTitle).ToList<MediaCardViewModel>();
}
SelectClicked(GetSortedItems, card, e); SelectClicked(GetSortedItems, card, e);
} }

2
ErsatzTV/Pages/TelevisionEpisodeList.razor

@ -29,7 +29,7 @@
@if (!string.IsNullOrWhiteSpace(_season.Poster)) @if (!string.IsNullOrWhiteSpace(_season.Poster))
{ {
<img class="mud-elevation-2 mr-6" <img class="mud-elevation-2 mr-6"
style="border-radius: 4px; max-height: 440px; flex-shrink: 0" style="border-radius: 4px; flex-shrink: 0; max-height: 440px;"
src="@($"/artwork/posters/{_season.Poster}")" alt="show poster"/> src="@($"/artwork/posters/{_season.Poster}")" alt="show poster"/>
} }
<div style="display: flex; flex-direction: column; height: 100%"> <div style="display: flex; flex-direction: column; height: 100%">

2
ErsatzTV/Pages/TelevisionSeasonList.razor

@ -27,7 +27,7 @@
@if (!string.IsNullOrWhiteSpace(_show.Poster)) @if (!string.IsNullOrWhiteSpace(_show.Poster))
{ {
<img class="mud-elevation-2 mr-6" <img class="mud-elevation-2 mr-6"
style="border-radius: 4px; max-height: 440px; flex-shrink: 0" style="border-radius: 4px; flex-shrink: 0; max-height: 440px;"
src="@($"/artwork/posters/{_show.Poster}")" alt="show poster"/> src="@($"/artwork/posters/{_show.Poster}")" alt="show poster"/>
} }
<div style="display: flex; flex-direction: column; height: 100%"> <div style="display: flex; flex-direction: column; height: 100%">

5
ErsatzTV/Pages/TelevisionShowList.razor

@ -130,7 +130,10 @@
private void SelectClicked(MediaCardViewModel card, MouseEventArgs e) private void SelectClicked(MediaCardViewModel card, MouseEventArgs e)
{ {
List<MediaCardViewModel> GetSortedItems() => _data.Cards.OrderBy(m => m.SortTitle).ToList<MediaCardViewModel>(); List<MediaCardViewModel> GetSortedItems()
{
return _data.Cards.OrderBy(m => m.SortTitle).ToList<MediaCardViewModel>();
}
SelectClicked(GetSortedItems, card, e); SelectClicked(GetSortedItems, card, e);
} }

5
ErsatzTV/Services/SchedulerService.cs

@ -24,6 +24,7 @@ namespace ErsatzTV.Services
private readonly IEntityLocker _entityLocker; private readonly IEntityLocker _entityLocker;
private readonly ILogger<SchedulerService> _logger; private readonly ILogger<SchedulerService> _logger;
private readonly ChannelWriter<IPlexBackgroundServiceRequest> _plexWorkerChannel; private readonly ChannelWriter<IPlexBackgroundServiceRequest> _plexWorkerChannel;
private readonly ChannelWriter<ISearchBackgroundServiceRequest> _searchWorkerChannel;
private readonly IServiceScopeFactory _serviceScopeFactory; private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly ChannelWriter<IBackgroundServiceRequest> _workerChannel; private readonly ChannelWriter<IBackgroundServiceRequest> _workerChannel;
@ -31,12 +32,14 @@ namespace ErsatzTV.Services
IServiceScopeFactory serviceScopeFactory, IServiceScopeFactory serviceScopeFactory,
ChannelWriter<IBackgroundServiceRequest> workerChannel, ChannelWriter<IBackgroundServiceRequest> workerChannel,
ChannelWriter<IPlexBackgroundServiceRequest> plexWorkerChannel, ChannelWriter<IPlexBackgroundServiceRequest> plexWorkerChannel,
ChannelWriter<ISearchBackgroundServiceRequest> searchWorkerChannel,
IEntityLocker entityLocker, IEntityLocker entityLocker,
ILogger<SchedulerService> logger) ILogger<SchedulerService> logger)
{ {
_serviceScopeFactory = serviceScopeFactory; _serviceScopeFactory = serviceScopeFactory;
_workerChannel = workerChannel; _workerChannel = workerChannel;
_plexWorkerChannel = plexWorkerChannel; _plexWorkerChannel = plexWorkerChannel;
_searchWorkerChannel = searchWorkerChannel;
_entityLocker = entityLocker; _entityLocker = entityLocker;
_logger = logger; _logger = logger;
} }
@ -123,6 +126,6 @@ namespace ErsatzTV.Services
} }
private ValueTask RebuildSearchIndex(CancellationToken cancellationToken) => private ValueTask RebuildSearchIndex(CancellationToken cancellationToken) =>
_workerChannel.WriteAsync(new RebuildSearchIndex(), cancellationToken); _searchWorkerChannel.WriteAsync(new RebuildSearchIndex(), cancellationToken);
} }
} }

61
ErsatzTV/Services/SearchIndexService.cs

@ -0,0 +1,61 @@
using System;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using ErsatzTV.Application;
using ErsatzTV.Application.Search.Commands;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.Services
{
public class SearchIndexService : BackgroundService
{
private readonly ChannelReader<ISearchBackgroundServiceRequest> _channel;
private readonly ILogger<SearchIndexService> _logger;
private readonly IServiceScopeFactory _serviceScopeFactory;
public SearchIndexService(
ChannelReader<ISearchBackgroundServiceRequest> channel,
IServiceScopeFactory serviceScopeFactory,
ILogger<SearchIndexService> logger)
{
_channel = channel;
_serviceScopeFactory = serviceScopeFactory;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Search index service started");
await foreach (ISearchBackgroundServiceRequest request in _channel.ReadAllAsync(cancellationToken))
{
try
{
using IServiceScope scope = _serviceScopeFactory.CreateScope();
IMediator mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
switch (request)
{
case RebuildSearchIndex rebuildSearchIndex:
await mediator.Send(rebuildSearchIndex, cancellationToken);
break;
case AddItemsToSearchIndex addItemsToSearchIndex:
await mediator.Send(addItemsToSearchIndex, cancellationToken);
break;
case RemoveItemsFromSearchIndex removeItemsFromSearchIndex:
await mediator.Send(removeItemsFromSearchIndex, cancellationToken);
break;
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to process search index service request");
}
}
}
}
}

5
ErsatzTV/Shared/FragmentLetterAnchor.razor

@ -1,5 +1,4 @@
@using ErsatzTV.Application.MediaCards @using LanguageExt.UnsafeValueAccess
@using LanguageExt.UnsafeValueAccess
@typeparam TCard @typeparam TCard
@{ var letters = new System.Collections.Generic.HashSet<char>(); } @{ var letters = new System.Collections.Generic.HashSet<char>(); }
@ -25,4 +24,4 @@
{ {
@ChildContent @ChildContent
} }
} }

1
ErsatzTV/Startup.cs

@ -187,6 +187,7 @@ namespace ErsatzTV
services.AddSingleton<IEntityLocker, EntityLocker>(); services.AddSingleton<IEntityLocker, EntityLocker>();
AddChannel<IBackgroundServiceRequest>(services); AddChannel<IBackgroundServiceRequest>(services);
AddChannel<IPlexBackgroundServiceRequest>(services); AddChannel<IPlexBackgroundServiceRequest>(services);
AddChannel<ISearchBackgroundServiceRequest>(services);
services.AddScoped<IChannelRepository, ChannelRepository>(); services.AddScoped<IChannelRepository, ChannelRepository>();
services.AddScoped<IFFmpegProfileRepository, FFmpegProfileRepository>(); services.AddScoped<IFFmpegProfileRepository, FFmpegProfileRepository>();

Loading…
Cancel
Save