fix missing trashed episodes (#881)
* fix episodes missing from trash * cleanup
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
using Dapper;
|
using Dapper;
|
||||||
using ErsatzTV.Core;
|
using ErsatzTV.Core;
|
||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
|
using ErsatzTV.Core.Interfaces.Metadata;
|
||||||
using ErsatzTV.Core.Interfaces.Repositories;
|
using ErsatzTV.Core.Interfaces.Repositories;
|
||||||
using ErsatzTV.Core.Interfaces.Search;
|
using ErsatzTV.Core.Interfaces.Search;
|
||||||
using ErsatzTV.Infrastructure.Data;
|
using ErsatzTV.Infrastructure.Data;
|
||||||
@@ -13,6 +14,7 @@ namespace ErsatzTV.Application.Libraries;
|
|||||||
public class MoveLocalLibraryPathHandler : IRequestHandler<MoveLocalLibraryPath, Either<BaseError, Unit>>
|
public class MoveLocalLibraryPathHandler : IRequestHandler<MoveLocalLibraryPath, Either<BaseError, Unit>>
|
||||||
{
|
{
|
||||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||||
|
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
|
||||||
private readonly ILogger<MoveLocalLibraryPathHandler> _logger;
|
private readonly ILogger<MoveLocalLibraryPathHandler> _logger;
|
||||||
private readonly ISearchIndex _searchIndex;
|
private readonly ISearchIndex _searchIndex;
|
||||||
private readonly ISearchRepository _searchRepository;
|
private readonly ISearchRepository _searchRepository;
|
||||||
@@ -20,11 +22,13 @@ public class MoveLocalLibraryPathHandler : IRequestHandler<MoveLocalLibraryPath,
|
|||||||
public MoveLocalLibraryPathHandler(
|
public MoveLocalLibraryPathHandler(
|
||||||
ISearchIndex searchIndex,
|
ISearchIndex searchIndex,
|
||||||
ISearchRepository searchRepository,
|
ISearchRepository searchRepository,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
IDbContextFactory<TvContext> dbContextFactory,
|
IDbContextFactory<TvContext> dbContextFactory,
|
||||||
ILogger<MoveLocalLibraryPathHandler> logger)
|
ILogger<MoveLocalLibraryPathHandler> logger)
|
||||||
{
|
{
|
||||||
_searchIndex = searchIndex;
|
_searchIndex = searchIndex;
|
||||||
_searchRepository = searchRepository;
|
_searchRepository = searchRepository;
|
||||||
|
_fallbackMetadataProvider = fallbackMetadataProvider;
|
||||||
_dbContextFactory = dbContextFactory;
|
_dbContextFactory = dbContextFactory;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
@@ -35,7 +39,7 @@ public class MoveLocalLibraryPathHandler : IRequestHandler<MoveLocalLibraryPath,
|
|||||||
{
|
{
|
||||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
Validation<BaseError, Parameters> validation = await Validate(dbContext, request);
|
Validation<BaseError, Parameters> validation = await Validate(dbContext, request);
|
||||||
return await LanguageExtensions.Apply(validation, parameters => MovePath(dbContext, parameters));
|
return await validation.Apply(parameters => MovePath(dbContext, parameters));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Unit> MovePath(TvContext dbContext, Parameters parameters)
|
private async Task<Unit> MovePath(TvContext dbContext, Parameters parameters)
|
||||||
@@ -57,7 +61,10 @@ public class MoveLocalLibraryPathHandler : IRequestHandler<MoveLocalLibraryPath,
|
|||||||
foreach (MediaItem mediaItem in maybeMediaItem)
|
foreach (MediaItem mediaItem in maybeMediaItem)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Moving item at {Path}", await GetPath(dbContext, mediaItem));
|
_logger.LogInformation("Moving item at {Path}", await GetPath(dbContext, mediaItem));
|
||||||
await _searchIndex.UpdateItems(_searchRepository, new List<MediaItem> { mediaItem });
|
await _searchIndex.UpdateItems(
|
||||||
|
_searchRepository,
|
||||||
|
_fallbackMetadataProvider,
|
||||||
|
new List<MediaItem> { mediaItem });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using ErsatzTV.Core;
|
using ErsatzTV.Core;
|
||||||
using ErsatzTV.Core.Interfaces.Locking;
|
using ErsatzTV.Core.Interfaces.Locking;
|
||||||
|
using ErsatzTV.Core.Interfaces.Metadata;
|
||||||
using ErsatzTV.Core.Interfaces.Repositories;
|
using ErsatzTV.Core.Interfaces.Repositories;
|
||||||
using ErsatzTV.Core.Interfaces.Search;
|
using ErsatzTV.Core.Interfaces.Search;
|
||||||
using ErsatzTV.Core.Interfaces.Trakt;
|
using ErsatzTV.Core.Interfaces.Trakt;
|
||||||
@@ -19,10 +20,11 @@ public class AddTraktListHandler : TraktCommandBase, IRequestHandler<AddTraktLis
|
|||||||
ITraktApiClient traktApiClient,
|
ITraktApiClient traktApiClient,
|
||||||
ISearchRepository searchRepository,
|
ISearchRepository searchRepository,
|
||||||
ISearchIndex searchIndex,
|
ISearchIndex searchIndex,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
IDbContextFactory<TvContext> dbContextFactory,
|
IDbContextFactory<TvContext> dbContextFactory,
|
||||||
ILogger<AddTraktListHandler> logger,
|
ILogger<AddTraktListHandler> logger,
|
||||||
IEntityLocker entityLocker)
|
IEntityLocker entityLocker)
|
||||||
: base(traktApiClient, searchRepository, searchIndex, logger)
|
: base(traktApiClient, searchRepository, searchIndex, fallbackMetadataProvider, logger)
|
||||||
{
|
{
|
||||||
_dbContextFactory = dbContextFactory;
|
_dbContextFactory = dbContextFactory;
|
||||||
_entityLocker = entityLocker;
|
_entityLocker = entityLocker;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using ErsatzTV.Core;
|
using ErsatzTV.Core;
|
||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
using ErsatzTV.Core.Interfaces.Locking;
|
using ErsatzTV.Core.Interfaces.Locking;
|
||||||
|
using ErsatzTV.Core.Interfaces.Metadata;
|
||||||
using ErsatzTV.Core.Interfaces.Repositories;
|
using ErsatzTV.Core.Interfaces.Repositories;
|
||||||
using ErsatzTV.Core.Interfaces.Search;
|
using ErsatzTV.Core.Interfaces.Search;
|
||||||
using ErsatzTV.Core.Interfaces.Trakt;
|
using ErsatzTV.Core.Interfaces.Trakt;
|
||||||
@@ -14,6 +15,7 @@ public class DeleteTraktListHandler : TraktCommandBase, IRequestHandler<DeleteTr
|
|||||||
{
|
{
|
||||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||||
private readonly IEntityLocker _entityLocker;
|
private readonly IEntityLocker _entityLocker;
|
||||||
|
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
|
||||||
private readonly ISearchIndex _searchIndex;
|
private readonly ISearchIndex _searchIndex;
|
||||||
private readonly ISearchRepository _searchRepository;
|
private readonly ISearchRepository _searchRepository;
|
||||||
|
|
||||||
@@ -21,13 +23,15 @@ public class DeleteTraktListHandler : TraktCommandBase, IRequestHandler<DeleteTr
|
|||||||
ITraktApiClient traktApiClient,
|
ITraktApiClient traktApiClient,
|
||||||
ISearchRepository searchRepository,
|
ISearchRepository searchRepository,
|
||||||
ISearchIndex searchIndex,
|
ISearchIndex searchIndex,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
IDbContextFactory<TvContext> dbContextFactory,
|
IDbContextFactory<TvContext> dbContextFactory,
|
||||||
ILogger<DeleteTraktListHandler> logger,
|
ILogger<DeleteTraktListHandler> logger,
|
||||||
IEntityLocker entityLocker)
|
IEntityLocker entityLocker)
|
||||||
: base(traktApiClient, searchRepository, searchIndex, logger)
|
: base(traktApiClient, searchRepository, searchIndex, fallbackMetadataProvider, logger)
|
||||||
{
|
{
|
||||||
_searchRepository = searchRepository;
|
_searchRepository = searchRepository;
|
||||||
_searchIndex = searchIndex;
|
_searchIndex = searchIndex;
|
||||||
|
_fallbackMetadataProvider = fallbackMetadataProvider;
|
||||||
_dbContextFactory = dbContextFactory;
|
_dbContextFactory = dbContextFactory;
|
||||||
_entityLocker = entityLocker;
|
_entityLocker = entityLocker;
|
||||||
}
|
}
|
||||||
@@ -38,8 +42,7 @@ public class DeleteTraktListHandler : TraktCommandBase, IRequestHandler<DeleteTr
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
Validation<BaseError, TraktList> validation = await TraktListMustExist(dbContext, request.TraktListId);
|
Validation<BaseError, TraktList> validation = await TraktListMustExist(dbContext, request.TraktListId);
|
||||||
return await LanguageExtensions.Apply(validation, c => DoDeletion(dbContext, c));
|
return await LanguageExtensions.Apply(validation, c => DoDeletion(dbContext, c));
|
||||||
}
|
}
|
||||||
@@ -56,7 +59,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)
|
||||||
{
|
{
|
||||||
await _searchIndex.RebuildItems(_searchRepository, mediaItemIds);
|
await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, mediaItemIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
_searchIndex.Commit();
|
_searchIndex.Commit();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using ErsatzTV.Core;
|
using ErsatzTV.Core;
|
||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
using ErsatzTV.Core.Interfaces.Locking;
|
using ErsatzTV.Core.Interfaces.Locking;
|
||||||
|
using ErsatzTV.Core.Interfaces.Metadata;
|
||||||
using ErsatzTV.Core.Interfaces.Repositories;
|
using ErsatzTV.Core.Interfaces.Repositories;
|
||||||
using ErsatzTV.Core.Interfaces.Search;
|
using ErsatzTV.Core.Interfaces.Search;
|
||||||
using ErsatzTV.Core.Interfaces.Trakt;
|
using ErsatzTV.Core.Interfaces.Trakt;
|
||||||
@@ -20,9 +21,15 @@ public class MatchTraktListItemsHandler : TraktCommandBase,
|
|||||||
ITraktApiClient traktApiClient,
|
ITraktApiClient traktApiClient,
|
||||||
ISearchRepository searchRepository,
|
ISearchRepository searchRepository,
|
||||||
ISearchIndex searchIndex,
|
ISearchIndex searchIndex,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
IDbContextFactory<TvContext> dbContextFactory,
|
IDbContextFactory<TvContext> dbContextFactory,
|
||||||
ILogger<MatchTraktListItemsHandler> logger,
|
ILogger<MatchTraktListItemsHandler> logger,
|
||||||
IEntityLocker entityLocker) : base(traktApiClient, searchRepository, searchIndex, logger)
|
IEntityLocker entityLocker) : base(
|
||||||
|
traktApiClient,
|
||||||
|
searchRepository,
|
||||||
|
searchIndex,
|
||||||
|
fallbackMetadataProvider,
|
||||||
|
logger)
|
||||||
{
|
{
|
||||||
_dbContextFactory = dbContextFactory;
|
_dbContextFactory = dbContextFactory;
|
||||||
_entityLocker = entityLocker;
|
_entityLocker = entityLocker;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using ErsatzTV.Core;
|
using ErsatzTV.Core;
|
||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
|
using ErsatzTV.Core.Interfaces.Metadata;
|
||||||
using ErsatzTV.Core.Interfaces.Repositories;
|
using ErsatzTV.Core.Interfaces.Repositories;
|
||||||
using ErsatzTV.Core.Interfaces.Search;
|
using ErsatzTV.Core.Interfaces.Search;
|
||||||
using ErsatzTV.Core.Interfaces.Trakt;
|
using ErsatzTV.Core.Interfaces.Trakt;
|
||||||
@@ -13,6 +14,7 @@ namespace ErsatzTV.Application.MediaCollections;
|
|||||||
|
|
||||||
public abstract class TraktCommandBase
|
public abstract class TraktCommandBase
|
||||||
{
|
{
|
||||||
|
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly ISearchIndex _searchIndex;
|
private readonly ISearchIndex _searchIndex;
|
||||||
private readonly ISearchRepository _searchRepository;
|
private readonly ISearchRepository _searchRepository;
|
||||||
@@ -21,10 +23,12 @@ public abstract class TraktCommandBase
|
|||||||
ITraktApiClient traktApiClient,
|
ITraktApiClient traktApiClient,
|
||||||
ISearchRepository searchRepository,
|
ISearchRepository searchRepository,
|
||||||
ISearchIndex searchIndex,
|
ISearchIndex searchIndex,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
ILogger logger)
|
ILogger logger)
|
||||||
{
|
{
|
||||||
_searchRepository = searchRepository;
|
_searchRepository = searchRepository;
|
||||||
_searchIndex = searchIndex;
|
_searchIndex = searchIndex;
|
||||||
|
_fallbackMetadataProvider = fallbackMetadataProvider;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
TraktApiClient = traktApiClient;
|
TraktApiClient = traktApiClient;
|
||||||
}
|
}
|
||||||
@@ -158,7 +162,7 @@ public abstract class TraktCommandBase
|
|||||||
|
|
||||||
if (await dbContext.SaveChangesAsync() > 0)
|
if (await dbContext.SaveChangesAsync() > 0)
|
||||||
{
|
{
|
||||||
await _searchIndex.RebuildItems(_searchRepository, ids.ToList());
|
await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, ids.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
_searchIndex.Commit();
|
_searchIndex.Commit();
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ namespace ErsatzTV.Application.Search;
|
|||||||
public class RebuildSearchIndexHandler : IRequestHandler<RebuildSearchIndex, Unit>
|
public class RebuildSearchIndexHandler : IRequestHandler<RebuildSearchIndex, Unit>
|
||||||
{
|
{
|
||||||
private readonly IConfigElementRepository _configElementRepository;
|
private readonly IConfigElementRepository _configElementRepository;
|
||||||
|
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
|
||||||
private readonly ILocalFileSystem _localFileSystem;
|
private readonly ILocalFileSystem _localFileSystem;
|
||||||
private readonly ILogger<RebuildSearchIndexHandler> _logger;
|
private readonly ILogger<RebuildSearchIndexHandler> _logger;
|
||||||
private readonly ISearchIndex _searchIndex;
|
private readonly ISearchIndex _searchIndex;
|
||||||
@@ -22,6 +23,7 @@ public class RebuildSearchIndexHandler : IRequestHandler<RebuildSearchIndex, Uni
|
|||||||
ISearchRepository searchRepository,
|
ISearchRepository searchRepository,
|
||||||
IConfigElementRepository configElementRepository,
|
IConfigElementRepository configElementRepository,
|
||||||
ILocalFileSystem localFileSystem,
|
ILocalFileSystem localFileSystem,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
ILogger<RebuildSearchIndexHandler> logger)
|
ILogger<RebuildSearchIndexHandler> logger)
|
||||||
{
|
{
|
||||||
_searchIndex = searchIndex;
|
_searchIndex = searchIndex;
|
||||||
@@ -29,6 +31,7 @@ public class RebuildSearchIndexHandler : IRequestHandler<RebuildSearchIndex, Uni
|
|||||||
_searchRepository = searchRepository;
|
_searchRepository = searchRepository;
|
||||||
_configElementRepository = configElementRepository;
|
_configElementRepository = configElementRepository;
|
||||||
_localFileSystem = localFileSystem;
|
_localFileSystem = localFileSystem;
|
||||||
|
_fallbackMetadataProvider = fallbackMetadataProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Unit> Handle(RebuildSearchIndex request, CancellationToken cancellationToken)
|
public async Task<Unit> Handle(RebuildSearchIndex request, CancellationToken cancellationToken)
|
||||||
@@ -48,7 +51,7 @@ public class RebuildSearchIndexHandler : IRequestHandler<RebuildSearchIndex, Uni
|
|||||||
_logger.LogInformation("Migrating search index to version {Version}", _searchIndex.Version);
|
_logger.LogInformation("Migrating search index to version {Version}", _searchIndex.Version);
|
||||||
|
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
await _searchIndex.Rebuild(_searchRepository);
|
await _searchIndex.Rebuild(_searchRepository, _fallbackMetadataProvider);
|
||||||
|
|
||||||
await _configElementRepository.Upsert(ConfigElementKey.SearchIndexVersion, _searchIndex.Version);
|
await _configElementRepository.Upsert(ConfigElementKey.SearchIndexVersion, _searchIndex.Version);
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
|
|||||||
@@ -3,10 +3,14 @@ using ErsatzTV.Core.Domain;
|
|||||||
using ErsatzTV.Core.Extensions;
|
using ErsatzTV.Core.Extensions;
|
||||||
using ErsatzTV.Core.Interfaces.Emby;
|
using ErsatzTV.Core.Interfaces.Emby;
|
||||||
using ErsatzTV.Core.Interfaces.Jellyfin;
|
using ErsatzTV.Core.Interfaces.Jellyfin;
|
||||||
|
using ErsatzTV.Core.Interfaces.Metadata;
|
||||||
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.Interfaces.Search;
|
||||||
using ErsatzTV.Core.Search;
|
using ErsatzTV.Core.Search;
|
||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using ErsatzTV.Infrastructure.Extensions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using static ErsatzTV.Application.MediaCards.Mapper;
|
using static ErsatzTV.Application.MediaCards.Mapper;
|
||||||
|
|
||||||
namespace ErsatzTV.Application.Search;
|
namespace ErsatzTV.Application.Search;
|
||||||
@@ -14,7 +18,9 @@ namespace ErsatzTV.Application.Search;
|
|||||||
public class
|
public class
|
||||||
QuerySearchIndexEpisodesHandler : IRequestHandler<QuerySearchIndexEpisodes, TelevisionEpisodeCardResultsViewModel>
|
QuerySearchIndexEpisodesHandler : IRequestHandler<QuerySearchIndexEpisodes, TelevisionEpisodeCardResultsViewModel>
|
||||||
{
|
{
|
||||||
|
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||||
private readonly IEmbyPathReplacementService _embyPathReplacementService;
|
private readonly IEmbyPathReplacementService _embyPathReplacementService;
|
||||||
|
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
|
||||||
private readonly IJellyfinPathReplacementService _jellyfinPathReplacementService;
|
private readonly IJellyfinPathReplacementService _jellyfinPathReplacementService;
|
||||||
private readonly IMediaSourceRepository _mediaSourceRepository;
|
private readonly IMediaSourceRepository _mediaSourceRepository;
|
||||||
private readonly IPlexPathReplacementService _plexPathReplacementService;
|
private readonly IPlexPathReplacementService _plexPathReplacementService;
|
||||||
@@ -27,7 +33,9 @@ public class
|
|||||||
IMediaSourceRepository mediaSourceRepository,
|
IMediaSourceRepository mediaSourceRepository,
|
||||||
IPlexPathReplacementService plexPathReplacementService,
|
IPlexPathReplacementService plexPathReplacementService,
|
||||||
IJellyfinPathReplacementService jellyfinPathReplacementService,
|
IJellyfinPathReplacementService jellyfinPathReplacementService,
|
||||||
IEmbyPathReplacementService embyPathReplacementService)
|
IEmbyPathReplacementService embyPathReplacementService,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
|
IDbContextFactory<TvContext> dbContextFactory)
|
||||||
{
|
{
|
||||||
_searchIndex = searchIndex;
|
_searchIndex = searchIndex;
|
||||||
_televisionRepository = televisionRepository;
|
_televisionRepository = televisionRepository;
|
||||||
@@ -35,6 +43,8 @@ public class
|
|||||||
_plexPathReplacementService = plexPathReplacementService;
|
_plexPathReplacementService = plexPathReplacementService;
|
||||||
_jellyfinPathReplacementService = jellyfinPathReplacementService;
|
_jellyfinPathReplacementService = jellyfinPathReplacementService;
|
||||||
_embyPathReplacementService = embyPathReplacementService;
|
_embyPathReplacementService = embyPathReplacementService;
|
||||||
|
_fallbackMetadataProvider = fallbackMetadataProvider;
|
||||||
|
_dbContextFactory = dbContextFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<TelevisionEpisodeCardResultsViewModel> Handle(
|
public async Task<TelevisionEpisodeCardResultsViewModel> Handle(
|
||||||
@@ -52,8 +62,40 @@ public class
|
|||||||
Option<EmbyMediaSource> maybeEmby = await _mediaSourceRepository.GetAllEmby()
|
Option<EmbyMediaSource> maybeEmby = await _mediaSourceRepository.GetAllEmby()
|
||||||
.Map(list => list.HeadOrNone());
|
.Map(list => list.HeadOrNone());
|
||||||
|
|
||||||
List<EpisodeMetadata> episodes = await _televisionRepository
|
var episodeIds = searchResult.Items.Map(i => i.Id).ToList();
|
||||||
.GetEpisodesForCards(searchResult.Items.Map(i => i.Id).ToList());
|
|
||||||
|
List<EpisodeMetadata> episodes = await _televisionRepository.GetEpisodesForCards(episodeIds);
|
||||||
|
|
||||||
|
// try to load fallback metadata for episodes that have none
|
||||||
|
// this handles an edge case of trashed items with no saved metadata
|
||||||
|
var missingEpisodes = episodeIds.Except(episodes.Map(e => e.EpisodeId)).ToList();
|
||||||
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
foreach (int missingEpisodeId in missingEpisodes)
|
||||||
|
{
|
||||||
|
Option<Episode> maybeEpisode = await dbContext.Episodes
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(e => e.MediaVersions)
|
||||||
|
.ThenInclude(e => e.MediaFiles)
|
||||||
|
.Include(e => e.Season)
|
||||||
|
.ThenInclude(s => s.SeasonMetadata)
|
||||||
|
.ThenInclude(sm => sm.Artwork)
|
||||||
|
.Include(e => e.Season)
|
||||||
|
.ThenInclude(s => s.Show)
|
||||||
|
.ThenInclude(s => s.ShowMetadata)
|
||||||
|
.ThenInclude(sm => sm.Artwork)
|
||||||
|
.SelectOneAsync(e => e.Id, e => e.Id == missingEpisodeId);
|
||||||
|
|
||||||
|
foreach (Episode episode in maybeEpisode)
|
||||||
|
{
|
||||||
|
foreach (EpisodeMetadata headMetadata in _fallbackMetadataProvider.GetFallbackMetadata(episode)
|
||||||
|
.HeadOrNone())
|
||||||
|
{
|
||||||
|
headMetadata.Episode = episode;
|
||||||
|
episode.EpisodeMetadata = new List<EpisodeMetadata> { headMetadata };
|
||||||
|
episodes.Add(headMetadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var items = new List<TelevisionEpisodeCardViewModel>();
|
var items = new List<TelevisionEpisodeCardViewModel>();
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,13 @@ public class FallbackMetadataProviderTests
|
|||||||
"Awesome.Show.S01E02.Description.more.Description.QUAlity.codec.CODEC-GROUP.mkv",
|
"Awesome.Show.S01E02.Description.more.Description.QUAlity.codec.CODEC-GROUP.mkv",
|
||||||
1,
|
1,
|
||||||
2)]
|
2)]
|
||||||
|
[TestCase("Awesome Show - s01.e02.mkv", 1, 2)]
|
||||||
|
[TestCase("Awesome Show - S01.E02.mkv", 1, 2)]
|
||||||
|
[TestCase("Awesome Show - s01_e02.mkv", 1, 2)]
|
||||||
|
[TestCase("Awesome Show - S01_E02.mkv", 1, 2)]
|
||||||
|
[TestCase("Awesome Show - s01xe02.mkv", 1, 2)]
|
||||||
|
[TestCase("Awesome Show - S01XE02.mkv", 1, 2)]
|
||||||
|
[TestCase("Awesome Show - 1x02.mkv", 1, 2)]
|
||||||
public void GetFallbackMetadata_ShouldHandleVariousFormats(string path, int season, int episode)
|
public void GetFallbackMetadata_ShouldHandleVariousFormats(string path, int season, int episode)
|
||||||
{
|
{
|
||||||
List<EpisodeMetadata> metadata = _fallbackMetadataProvider.GetFallbackMetadata(
|
List<EpisodeMetadata> metadata = _fallbackMetadataProvider.GetFallbackMetadata(
|
||||||
|
|||||||
@@ -623,6 +623,7 @@ public class MovieFolderScannerTests
|
|||||||
_imageCache.Object,
|
_imageCache.Object,
|
||||||
new Mock<ISearchIndex>().Object,
|
new Mock<ISearchIndex>().Object,
|
||||||
new Mock<ISearchRepository>().Object,
|
new Mock<ISearchRepository>().Object,
|
||||||
|
new Mock<IFallbackMetadataProvider>().Object,
|
||||||
new Mock<ILibraryRepository>().Object,
|
new Mock<ILibraryRepository>().Object,
|
||||||
_mediaItemRepository.Object,
|
_mediaItemRepository.Object,
|
||||||
new Mock<IMediator>().Object,
|
new Mock<IMediator>().Object,
|
||||||
@@ -643,6 +644,7 @@ public class MovieFolderScannerTests
|
|||||||
_imageCache.Object,
|
_imageCache.Object,
|
||||||
new Mock<ISearchIndex>().Object,
|
new Mock<ISearchIndex>().Object,
|
||||||
new Mock<ISearchRepository>().Object,
|
new Mock<ISearchRepository>().Object,
|
||||||
|
new Mock<IFallbackMetadataProvider>().Object,
|
||||||
new Mock<ILibraryRepository>().Object,
|
new Mock<ILibraryRepository>().Object,
|
||||||
_mediaItemRepository.Object,
|
_mediaItemRepository.Object,
|
||||||
new Mock<IMediator>().Object,
|
new Mock<IMediator>().Object,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
using ErsatzTV.Core.Interfaces.Emby;
|
using ErsatzTV.Core.Interfaces.Emby;
|
||||||
|
using ErsatzTV.Core.Interfaces.Metadata;
|
||||||
using ErsatzTV.Core.Interfaces.Repositories;
|
using ErsatzTV.Core.Interfaces.Repositories;
|
||||||
using ErsatzTV.Core.Interfaces.Search;
|
using ErsatzTV.Core.Interfaces.Search;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -10,6 +11,7 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner
|
|||||||
{
|
{
|
||||||
private readonly IEmbyApiClient _embyApiClient;
|
private readonly IEmbyApiClient _embyApiClient;
|
||||||
private readonly IEmbyCollectionRepository _embyCollectionRepository;
|
private readonly IEmbyCollectionRepository _embyCollectionRepository;
|
||||||
|
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
|
||||||
private readonly ILogger<EmbyCollectionScanner> _logger;
|
private readonly ILogger<EmbyCollectionScanner> _logger;
|
||||||
private readonly ISearchIndex _searchIndex;
|
private readonly ISearchIndex _searchIndex;
|
||||||
private readonly ISearchRepository _searchRepository;
|
private readonly ISearchRepository _searchRepository;
|
||||||
@@ -19,12 +21,14 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner
|
|||||||
IEmbyApiClient embyApiClient,
|
IEmbyApiClient embyApiClient,
|
||||||
ISearchRepository searchRepository,
|
ISearchRepository searchRepository,
|
||||||
ISearchIndex searchIndex,
|
ISearchIndex searchIndex,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
ILogger<EmbyCollectionScanner> logger)
|
ILogger<EmbyCollectionScanner> logger)
|
||||||
{
|
{
|
||||||
_embyCollectionRepository = embyCollectionRepository;
|
_embyCollectionRepository = embyCollectionRepository;
|
||||||
_embyApiClient = embyApiClient;
|
_embyApiClient = embyApiClient;
|
||||||
_searchRepository = searchRepository;
|
_searchRepository = searchRepository;
|
||||||
_searchIndex = searchIndex;
|
_searchIndex = searchIndex;
|
||||||
|
_fallbackMetadataProvider = fallbackMetadataProvider;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +111,7 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner
|
|||||||
var changedIds = removedIds.Except(addedIds).ToList();
|
var changedIds = removedIds.Except(addedIds).ToList();
|
||||||
changedIds.AddRange(addedIds.Except(removedIds));
|
changedIds.AddRange(addedIds.Except(removedIds));
|
||||||
|
|
||||||
await _searchIndex.RebuildItems(_searchRepository, changedIds);
|
await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, changedIds);
|
||||||
_searchIndex.Commit();
|
_searchIndex.Commit();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public class EmbyMovieLibraryScanner :
|
|||||||
IMediaSourceRepository mediaSourceRepository,
|
IMediaSourceRepository mediaSourceRepository,
|
||||||
IEmbyMovieRepository embyMovieRepository,
|
IEmbyMovieRepository embyMovieRepository,
|
||||||
ISearchRepository searchRepository,
|
ISearchRepository searchRepository,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
IEmbyPathReplacementService pathReplacementService,
|
IEmbyPathReplacementService pathReplacementService,
|
||||||
ILocalFileSystem localFileSystem,
|
ILocalFileSystem localFileSystem,
|
||||||
ILocalStatisticsProvider localStatisticsProvider,
|
ILocalStatisticsProvider localStatisticsProvider,
|
||||||
@@ -38,6 +39,7 @@ public class EmbyMovieLibraryScanner :
|
|||||||
mediator,
|
mediator,
|
||||||
searchIndex,
|
searchIndex,
|
||||||
searchRepository,
|
searchRepository,
|
||||||
|
fallbackMetadataProvider,
|
||||||
logger)
|
logger)
|
||||||
{
|
{
|
||||||
_embyApiClient = embyApiClient;
|
_embyApiClient = embyApiClient;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ public class EmbyTelevisionLibraryScanner : MediaServerTelevisionLibraryScanner<
|
|||||||
IEmbyTelevisionRepository televisionRepository,
|
IEmbyTelevisionRepository televisionRepository,
|
||||||
ISearchIndex searchIndex,
|
ISearchIndex searchIndex,
|
||||||
ISearchRepository searchRepository,
|
ISearchRepository searchRepository,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
IEmbyPathReplacementService pathReplacementService,
|
IEmbyPathReplacementService pathReplacementService,
|
||||||
ILocalFileSystem localFileSystem,
|
ILocalFileSystem localFileSystem,
|
||||||
ILocalStatisticsProvider localStatisticsProvider,
|
ILocalStatisticsProvider localStatisticsProvider,
|
||||||
@@ -37,6 +38,7 @@ public class EmbyTelevisionLibraryScanner : MediaServerTelevisionLibraryScanner<
|
|||||||
localFileSystem,
|
localFileSystem,
|
||||||
searchRepository,
|
searchRepository,
|
||||||
searchIndex,
|
searchIndex,
|
||||||
|
fallbackMetadataProvider,
|
||||||
mediator,
|
mediator,
|
||||||
logger)
|
logger)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,9 +9,18 @@ public interface ISearchIndex : IDisposable
|
|||||||
{
|
{
|
||||||
public int Version { get; }
|
public int Version { get; }
|
||||||
Task<bool> Initialize(ILocalFileSystem localFileSystem, IConfigElementRepository configElementRepository);
|
Task<bool> Initialize(ILocalFileSystem localFileSystem, IConfigElementRepository configElementRepository);
|
||||||
Task<Unit> Rebuild(ISearchRepository searchRepository);
|
Task<Unit> Rebuild(ISearchRepository searchRepository, IFallbackMetadataProvider fallbackMetadataProvider);
|
||||||
Task<Unit> RebuildItems(ISearchRepository searchRepository, List<int> itemIds);
|
|
||||||
Task<Unit> UpdateItems(ISearchRepository searchRepository, List<MediaItem> items);
|
Task<Unit> RebuildItems(
|
||||||
|
ISearchRepository searchRepository,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
|
List<int> itemIds);
|
||||||
|
|
||||||
|
Task<Unit> UpdateItems(
|
||||||
|
ISearchRepository searchRepository,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
|
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 = "");
|
||||||
void Commit();
|
void Commit();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
using ErsatzTV.Core.Interfaces.Jellyfin;
|
using ErsatzTV.Core.Interfaces.Jellyfin;
|
||||||
|
using ErsatzTV.Core.Interfaces.Metadata;
|
||||||
using ErsatzTV.Core.Interfaces.Repositories;
|
using ErsatzTV.Core.Interfaces.Repositories;
|
||||||
using ErsatzTV.Core.Interfaces.Search;
|
using ErsatzTV.Core.Interfaces.Search;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -8,6 +9,7 @@ namespace ErsatzTV.Core.Jellyfin;
|
|||||||
|
|
||||||
public class JellyfinCollectionScanner : IJellyfinCollectionScanner
|
public class JellyfinCollectionScanner : IJellyfinCollectionScanner
|
||||||
{
|
{
|
||||||
|
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
|
||||||
private readonly IJellyfinApiClient _jellyfinApiClient;
|
private readonly IJellyfinApiClient _jellyfinApiClient;
|
||||||
private readonly IJellyfinCollectionRepository _jellyfinCollectionRepository;
|
private readonly IJellyfinCollectionRepository _jellyfinCollectionRepository;
|
||||||
private readonly ILogger<JellyfinCollectionScanner> _logger;
|
private readonly ILogger<JellyfinCollectionScanner> _logger;
|
||||||
@@ -19,12 +21,14 @@ public class JellyfinCollectionScanner : IJellyfinCollectionScanner
|
|||||||
IJellyfinApiClient jellyfinApiClient,
|
IJellyfinApiClient jellyfinApiClient,
|
||||||
ISearchRepository searchRepository,
|
ISearchRepository searchRepository,
|
||||||
ISearchIndex searchIndex,
|
ISearchIndex searchIndex,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
ILogger<JellyfinCollectionScanner> logger)
|
ILogger<JellyfinCollectionScanner> logger)
|
||||||
{
|
{
|
||||||
_jellyfinCollectionRepository = jellyfinCollectionRepository;
|
_jellyfinCollectionRepository = jellyfinCollectionRepository;
|
||||||
_jellyfinApiClient = jellyfinApiClient;
|
_jellyfinApiClient = jellyfinApiClient;
|
||||||
_searchRepository = searchRepository;
|
_searchRepository = searchRepository;
|
||||||
_searchIndex = searchIndex;
|
_searchIndex = searchIndex;
|
||||||
|
_fallbackMetadataProvider = fallbackMetadataProvider;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +120,7 @@ public class JellyfinCollectionScanner : IJellyfinCollectionScanner
|
|||||||
var changedIds = removedIds.Except(addedIds).ToList();
|
var changedIds = removedIds.Except(addedIds).ToList();
|
||||||
changedIds.AddRange(addedIds.Except(removedIds));
|
changedIds.AddRange(addedIds.Except(removedIds));
|
||||||
|
|
||||||
await _searchIndex.RebuildItems(_searchRepository, changedIds);
|
await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, changedIds);
|
||||||
_searchIndex.Commit();
|
_searchIndex.Commit();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ public class JellyfinMovieLibraryScanner :
|
|||||||
IMediator mediator,
|
IMediator mediator,
|
||||||
IJellyfinMovieRepository jellyfinMovieRepository,
|
IJellyfinMovieRepository jellyfinMovieRepository,
|
||||||
ISearchRepository searchRepository,
|
ISearchRepository searchRepository,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
IJellyfinPathReplacementService pathReplacementService,
|
IJellyfinPathReplacementService pathReplacementService,
|
||||||
IMediaSourceRepository mediaSourceRepository,
|
IMediaSourceRepository mediaSourceRepository,
|
||||||
ILocalFileSystem localFileSystem,
|
ILocalFileSystem localFileSystem,
|
||||||
@@ -38,6 +39,7 @@ public class JellyfinMovieLibraryScanner :
|
|||||||
mediator,
|
mediator,
|
||||||
searchIndex,
|
searchIndex,
|
||||||
searchRepository,
|
searchRepository,
|
||||||
|
fallbackMetadataProvider,
|
||||||
logger)
|
logger)
|
||||||
{
|
{
|
||||||
_jellyfinApiClient = jellyfinApiClient;
|
_jellyfinApiClient = jellyfinApiClient;
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public class JellyfinTelevisionLibraryScanner : MediaServerTelevisionLibraryScan
|
|||||||
IJellyfinTelevisionRepository televisionRepository,
|
IJellyfinTelevisionRepository televisionRepository,
|
||||||
ISearchIndex searchIndex,
|
ISearchIndex searchIndex,
|
||||||
ISearchRepository searchRepository,
|
ISearchRepository searchRepository,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
IJellyfinPathReplacementService pathReplacementService,
|
IJellyfinPathReplacementService pathReplacementService,
|
||||||
ILocalFileSystem localFileSystem,
|
ILocalFileSystem localFileSystem,
|
||||||
ILocalStatisticsProvider localStatisticsProvider,
|
ILocalStatisticsProvider localStatisticsProvider,
|
||||||
@@ -38,6 +39,7 @@ public class JellyfinTelevisionLibraryScanner : MediaServerTelevisionLibraryScan
|
|||||||
localFileSystem,
|
localFileSystem,
|
||||||
searchRepository,
|
searchRepository,
|
||||||
searchIndex,
|
searchIndex,
|
||||||
|
fallbackMetadataProvider,
|
||||||
mediator,
|
mediator,
|
||||||
logger)
|
logger)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -161,8 +161,14 @@ public class FallbackMetadataProvider : IFallbackMetadataProvider
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
const string PATTERN = @"[sS]\d+[eE]([e\-\d{1,2}]+)";
|
const string PATTERN = @"[sS]\d+[\._xX]?[eE]([e\-\d{1,2}]+)";
|
||||||
|
const string PATTERN_2 = @"\d+[\._xX]([e\-\d{1,2}]+)";
|
||||||
MatchCollection matches = Regex.Matches(fileName, PATTERN);
|
MatchCollection matches = Regex.Matches(fileName, PATTERN);
|
||||||
|
if (matches.Count == 0)
|
||||||
|
{
|
||||||
|
matches = Regex.Matches(fileName, PATTERN_2);
|
||||||
|
}
|
||||||
|
|
||||||
if (matches.Count > 0)
|
if (matches.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (Match match in matches)
|
foreach (Match match in matches)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
|
|||||||
where TMovie : Movie
|
where TMovie : Movie
|
||||||
where TEtag : MediaServerItemEtag
|
where TEtag : MediaServerItemEtag
|
||||||
{
|
{
|
||||||
|
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
|
||||||
private readonly ILocalFileSystem _localFileSystem;
|
private readonly ILocalFileSystem _localFileSystem;
|
||||||
private readonly ILocalStatisticsProvider _localStatisticsProvider;
|
private readonly ILocalStatisticsProvider _localStatisticsProvider;
|
||||||
private readonly ILocalSubtitlesProvider _localSubtitlesProvider;
|
private readonly ILocalSubtitlesProvider _localSubtitlesProvider;
|
||||||
@@ -30,6 +31,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
|
|||||||
IMediator mediator,
|
IMediator mediator,
|
||||||
ISearchIndex searchIndex,
|
ISearchIndex searchIndex,
|
||||||
ISearchRepository searchRepository,
|
ISearchRepository searchRepository,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
ILogger logger)
|
ILogger logger)
|
||||||
{
|
{
|
||||||
_localStatisticsProvider = localStatisticsProvider;
|
_localStatisticsProvider = localStatisticsProvider;
|
||||||
@@ -38,6 +40,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
|
|||||||
_mediator = mediator;
|
_mediator = mediator;
|
||||||
_searchIndex = searchIndex;
|
_searchIndex = searchIndex;
|
||||||
_searchRepository = searchRepository;
|
_searchRepository = searchRepository;
|
||||||
|
_fallbackMetadataProvider = fallbackMetadataProvider;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +171,10 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
|
|||||||
|
|
||||||
if (result.IsAdded || result.IsUpdated)
|
if (result.IsAdded || result.IsUpdated)
|
||||||
{
|
{
|
||||||
await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
|
await _searchIndex.RebuildItems(
|
||||||
|
_searchRepository,
|
||||||
|
_fallbackMetadataProvider,
|
||||||
|
new List<int> { result.Item.Id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,7 +182,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
|
|||||||
// trash movies that are no longer present on the media server
|
// trash movies that are no longer present on the media server
|
||||||
var fileNotFoundItemIds = existingMovies.Map(m => m.MediaServerItemId).Except(incomingItemIds).ToList();
|
var fileNotFoundItemIds = existingMovies.Map(m => m.MediaServerItemId).Except(incomingItemIds).ToList();
|
||||||
List<int> ids = await movieRepository.FlagFileNotFound(library, fileNotFoundItemIds);
|
List<int> ids = await movieRepository.FlagFileNotFound(library, fileNotFoundItemIds);
|
||||||
await _searchIndex.RebuildItems(_searchRepository, ids);
|
await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, ids);
|
||||||
|
|
||||||
await _mediator.Publish(new LibraryScanProgress(library.Id, 0), cancellationToken);
|
await _mediator.Publish(new LibraryScanProgress(library.Id, 0), cancellationToken);
|
||||||
|
|
||||||
@@ -241,7 +247,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
|
|||||||
{
|
{
|
||||||
foreach (int id in await movieRepository.FlagUnavailable(library, incoming))
|
foreach (int id in await movieRepository.FlagUnavailable(library, incoming))
|
||||||
{
|
{
|
||||||
await _searchIndex.RebuildItems(_searchRepository, new List<int> { id });
|
await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, new List<int> { id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
|
|||||||
where TEpisode : Episode
|
where TEpisode : Episode
|
||||||
where TEtag : MediaServerItemEtag
|
where TEtag : MediaServerItemEtag
|
||||||
{
|
{
|
||||||
|
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
|
||||||
private readonly ILocalFileSystem _localFileSystem;
|
private readonly ILocalFileSystem _localFileSystem;
|
||||||
private readonly ILocalStatisticsProvider _localStatisticsProvider;
|
private readonly ILocalStatisticsProvider _localStatisticsProvider;
|
||||||
private readonly ILocalSubtitlesProvider _localSubtitlesProvider;
|
private readonly ILocalSubtitlesProvider _localSubtitlesProvider;
|
||||||
@@ -32,6 +33,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
|
|||||||
ILocalFileSystem localFileSystem,
|
ILocalFileSystem localFileSystem,
|
||||||
ISearchRepository searchRepository,
|
ISearchRepository searchRepository,
|
||||||
ISearchIndex searchIndex,
|
ISearchIndex searchIndex,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
IMediator mediator,
|
IMediator mediator,
|
||||||
ILogger logger)
|
ILogger logger)
|
||||||
{
|
{
|
||||||
@@ -40,6 +42,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
|
|||||||
_localFileSystem = localFileSystem;
|
_localFileSystem = localFileSystem;
|
||||||
_searchRepository = searchRepository;
|
_searchRepository = searchRepository;
|
||||||
_searchIndex = searchIndex;
|
_searchIndex = searchIndex;
|
||||||
|
_fallbackMetadataProvider = fallbackMetadataProvider;
|
||||||
_mediator = mediator;
|
_mediator = mediator;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
@@ -196,7 +199,10 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
|
|||||||
|
|
||||||
if (result.IsAdded || result.IsUpdated)
|
if (result.IsAdded || result.IsUpdated)
|
||||||
{
|
{
|
||||||
await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
|
await _searchIndex.RebuildItems(
|
||||||
|
_searchRepository,
|
||||||
|
_fallbackMetadataProvider,
|
||||||
|
new List<int> { result.Item.Id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,7 +210,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
|
|||||||
// trash shows that are no longer present on the media server
|
// trash shows that are no longer present on the media server
|
||||||
var fileNotFoundItemIds = existingShows.Map(s => s.MediaServerItemId).Except(incomingItemIds).ToList();
|
var fileNotFoundItemIds = existingShows.Map(s => s.MediaServerItemId).Except(incomingItemIds).ToList();
|
||||||
List<int> ids = await televisionRepository.FlagFileNotFoundShows(library, fileNotFoundItemIds);
|
List<int> ids = await televisionRepository.FlagFileNotFoundShows(library, fileNotFoundItemIds);
|
||||||
await _searchIndex.RebuildItems(_searchRepository, ids);
|
await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, ids);
|
||||||
|
|
||||||
await _mediator.Publish(new LibraryScanProgress(library.Id, 0), cancellationToken);
|
await _mediator.Publish(new LibraryScanProgress(library.Id, 0), cancellationToken);
|
||||||
|
|
||||||
@@ -358,7 +364,10 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
|
|||||||
|
|
||||||
if (result.IsAdded || result.IsUpdated)
|
if (result.IsAdded || result.IsUpdated)
|
||||||
{
|
{
|
||||||
await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
|
await _searchIndex.RebuildItems(
|
||||||
|
_searchRepository,
|
||||||
|
_fallbackMetadataProvider,
|
||||||
|
new List<int> { result.Item.Id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -366,7 +375,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
|
|||||||
// trash seasons that are no longer present on the media server
|
// trash seasons that are no longer present on the media server
|
||||||
var fileNotFoundItemIds = existingSeasons.Map(s => s.MediaServerItemId).Except(incomingItemIds).ToList();
|
var fileNotFoundItemIds = existingSeasons.Map(s => s.MediaServerItemId).Except(incomingItemIds).ToList();
|
||||||
List<int> ids = await televisionRepository.FlagFileNotFoundSeasons(library, fileNotFoundItemIds);
|
List<int> ids = await televisionRepository.FlagFileNotFoundSeasons(library, fileNotFoundItemIds);
|
||||||
await _searchIndex.RebuildItems(_searchRepository, ids);
|
await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, ids);
|
||||||
|
|
||||||
return Unit.Default;
|
return Unit.Default;
|
||||||
}
|
}
|
||||||
@@ -461,7 +470,10 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
|
|||||||
|
|
||||||
if (result.IsAdded || result.IsUpdated)
|
if (result.IsAdded || result.IsUpdated)
|
||||||
{
|
{
|
||||||
await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
|
await _searchIndex.RebuildItems(
|
||||||
|
_searchRepository,
|
||||||
|
_fallbackMetadataProvider,
|
||||||
|
new List<int> { result.Item.Id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -469,7 +481,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
|
|||||||
// trash episodes that are no longer present on the media server
|
// trash episodes that are no longer present on the media server
|
||||||
var fileNotFoundItemIds = existingEpisodes.Map(m => m.MediaServerItemId).Except(incomingItemIds).ToList();
|
var fileNotFoundItemIds = existingEpisodes.Map(m => m.MediaServerItemId).Except(incomingItemIds).ToList();
|
||||||
List<int> ids = await televisionRepository.FlagFileNotFoundEpisodes(library, fileNotFoundItemIds);
|
List<int> ids = await televisionRepository.FlagFileNotFoundEpisodes(library, fileNotFoundItemIds);
|
||||||
await _searchIndex.RebuildItems(_searchRepository, ids);
|
await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, ids);
|
||||||
|
|
||||||
return Unit.Default;
|
return Unit.Default;
|
||||||
}
|
}
|
||||||
@@ -511,7 +523,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
|
|||||||
{
|
{
|
||||||
foreach (int id in await televisionRepository.FlagUnavailable(library, incoming))
|
foreach (int id in await televisionRepository.FlagUnavailable(library, incoming))
|
||||||
{
|
{
|
||||||
await _searchIndex.RebuildItems(_searchRepository, new List<int> { id });
|
await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, new List<int> { id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ namespace ErsatzTV.Core.Metadata;
|
|||||||
public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner
|
public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner
|
||||||
{
|
{
|
||||||
private readonly IClient _client;
|
private readonly IClient _client;
|
||||||
|
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
|
||||||
private readonly ILibraryRepository _libraryRepository;
|
private readonly ILibraryRepository _libraryRepository;
|
||||||
private readonly ILocalFileSystem _localFileSystem;
|
private readonly ILocalFileSystem _localFileSystem;
|
||||||
private readonly ILocalMetadataProvider _localMetadataProvider;
|
private readonly ILocalMetadataProvider _localMetadataProvider;
|
||||||
@@ -35,6 +36,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner
|
|||||||
IImageCache imageCache,
|
IImageCache imageCache,
|
||||||
ISearchIndex searchIndex,
|
ISearchIndex searchIndex,
|
||||||
ISearchRepository searchRepository,
|
ISearchRepository searchRepository,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
ILibraryRepository libraryRepository,
|
ILibraryRepository libraryRepository,
|
||||||
IMediaItemRepository mediaItemRepository,
|
IMediaItemRepository mediaItemRepository,
|
||||||
IMediator mediator,
|
IMediator mediator,
|
||||||
@@ -59,6 +61,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner
|
|||||||
_localMetadataProvider = localMetadataProvider;
|
_localMetadataProvider = localMetadataProvider;
|
||||||
_searchIndex = searchIndex;
|
_searchIndex = searchIndex;
|
||||||
_searchRepository = searchRepository;
|
_searchRepository = searchRepository;
|
||||||
|
_fallbackMetadataProvider = fallbackMetadataProvider;
|
||||||
_libraryRepository = libraryRepository;
|
_libraryRepository = libraryRepository;
|
||||||
_mediator = mediator;
|
_mediator = mediator;
|
||||||
_client = client;
|
_client = client;
|
||||||
@@ -160,7 +163,10 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner
|
|||||||
{
|
{
|
||||||
if (result.IsAdded || result.IsUpdated)
|
if (result.IsAdded || result.IsUpdated)
|
||||||
{
|
{
|
||||||
await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
|
await _searchIndex.RebuildItems(
|
||||||
|
_searchRepository,
|
||||||
|
_fallbackMetadataProvider,
|
||||||
|
new List<int> { result.Item.Id });
|
||||||
}
|
}
|
||||||
|
|
||||||
await _libraryRepository.SetEtag(libraryPath, knownFolder, movieFolder, etag);
|
await _libraryRepository.SetEtag(libraryPath, knownFolder, movieFolder, etag);
|
||||||
@@ -174,7 +180,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner
|
|||||||
{
|
{
|
||||||
_logger.LogInformation("Flagging missing movie at {Path}", path);
|
_logger.LogInformation("Flagging missing movie at {Path}", path);
|
||||||
List<int> ids = await FlagFileNotFound(libraryPath, path);
|
List<int> ids = await FlagFileNotFound(libraryPath, path);
|
||||||
await _searchIndex.RebuildItems(_searchRepository, ids);
|
await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, ids);
|
||||||
}
|
}
|
||||||
else if (Path.GetFileName(path).StartsWith("._"))
|
else if (Path.GetFileName(path).StartsWith("._"))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
|
|||||||
{
|
{
|
||||||
private readonly IArtistRepository _artistRepository;
|
private readonly IArtistRepository _artistRepository;
|
||||||
private readonly IClient _client;
|
private readonly IClient _client;
|
||||||
|
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
|
||||||
private readonly ILibraryRepository _libraryRepository;
|
private readonly ILibraryRepository _libraryRepository;
|
||||||
private readonly ILocalFileSystem _localFileSystem;
|
private readonly ILocalFileSystem _localFileSystem;
|
||||||
private readonly ILocalMetadataProvider _localMetadataProvider;
|
private readonly ILocalMetadataProvider _localMetadataProvider;
|
||||||
@@ -34,6 +35,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
|
|||||||
IImageCache imageCache,
|
IImageCache imageCache,
|
||||||
ISearchIndex searchIndex,
|
ISearchIndex searchIndex,
|
||||||
ISearchRepository searchRepository,
|
ISearchRepository searchRepository,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
IArtistRepository artistRepository,
|
IArtistRepository artistRepository,
|
||||||
IMusicVideoRepository musicVideoRepository,
|
IMusicVideoRepository musicVideoRepository,
|
||||||
ILibraryRepository libraryRepository,
|
ILibraryRepository libraryRepository,
|
||||||
@@ -58,6 +60,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
|
|||||||
_localSubtitlesProvider = localSubtitlesProvider;
|
_localSubtitlesProvider = localSubtitlesProvider;
|
||||||
_searchIndex = searchIndex;
|
_searchIndex = searchIndex;
|
||||||
_searchRepository = searchRepository;
|
_searchRepository = searchRepository;
|
||||||
|
_fallbackMetadataProvider = fallbackMetadataProvider;
|
||||||
_artistRepository = artistRepository;
|
_artistRepository = artistRepository;
|
||||||
_musicVideoRepository = musicVideoRepository;
|
_musicVideoRepository = musicVideoRepository;
|
||||||
_libraryRepository = libraryRepository;
|
_libraryRepository = libraryRepository;
|
||||||
@@ -137,7 +140,10 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
|
|||||||
|
|
||||||
if (result.IsAdded || result.IsUpdated)
|
if (result.IsAdded || result.IsUpdated)
|
||||||
{
|
{
|
||||||
await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
|
await _searchIndex.RebuildItems(
|
||||||
|
_searchRepository,
|
||||||
|
_fallbackMetadataProvider,
|
||||||
|
new List<int> { result.Item.Id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -155,7 +161,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
|
|||||||
{
|
{
|
||||||
_logger.LogInformation("Flagging missing music video at {Path}", path);
|
_logger.LogInformation("Flagging missing music video at {Path}", path);
|
||||||
List<int> musicVideoIds = await FlagFileNotFound(libraryPath, path);
|
List<int> musicVideoIds = await FlagFileNotFound(libraryPath, path);
|
||||||
await _searchIndex.RebuildItems(_searchRepository, musicVideoIds);
|
await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, musicVideoIds);
|
||||||
}
|
}
|
||||||
else if (Path.GetFileName(path).StartsWith("._"))
|
else if (Path.GetFileName(path).StartsWith("._"))
|
||||||
{
|
{
|
||||||
@@ -330,7 +336,10 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
|
|||||||
{
|
{
|
||||||
if (result.IsAdded || result.IsUpdated)
|
if (result.IsAdded || result.IsUpdated)
|
||||||
{
|
{
|
||||||
await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
|
await _searchIndex.RebuildItems(
|
||||||
|
_searchRepository,
|
||||||
|
_fallbackMetadataProvider,
|
||||||
|
new List<int> { result.Item.Id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace ErsatzTV.Core.Metadata;
|
|||||||
public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScanner
|
public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScanner
|
||||||
{
|
{
|
||||||
private readonly IClient _client;
|
private readonly IClient _client;
|
||||||
|
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
|
||||||
private readonly ILibraryRepository _libraryRepository;
|
private readonly ILibraryRepository _libraryRepository;
|
||||||
private readonly ILocalFileSystem _localFileSystem;
|
private readonly ILocalFileSystem _localFileSystem;
|
||||||
private readonly ILocalMetadataProvider _localMetadataProvider;
|
private readonly ILocalMetadataProvider _localMetadataProvider;
|
||||||
@@ -34,6 +35,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
|
|||||||
IMediator mediator,
|
IMediator mediator,
|
||||||
ISearchIndex searchIndex,
|
ISearchIndex searchIndex,
|
||||||
ISearchRepository searchRepository,
|
ISearchRepository searchRepository,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
IOtherVideoRepository otherVideoRepository,
|
IOtherVideoRepository otherVideoRepository,
|
||||||
ILibraryRepository libraryRepository,
|
ILibraryRepository libraryRepository,
|
||||||
IMediaItemRepository mediaItemRepository,
|
IMediaItemRepository mediaItemRepository,
|
||||||
@@ -57,6 +59,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
|
|||||||
_mediator = mediator;
|
_mediator = mediator;
|
||||||
_searchIndex = searchIndex;
|
_searchIndex = searchIndex;
|
||||||
_searchRepository = searchRepository;
|
_searchRepository = searchRepository;
|
||||||
|
_fallbackMetadataProvider = fallbackMetadataProvider;
|
||||||
_otherVideoRepository = otherVideoRepository;
|
_otherVideoRepository = otherVideoRepository;
|
||||||
_libraryRepository = libraryRepository;
|
_libraryRepository = libraryRepository;
|
||||||
_client = client;
|
_client = client;
|
||||||
@@ -160,7 +163,10 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
|
|||||||
{
|
{
|
||||||
if (result.IsAdded || result.IsUpdated)
|
if (result.IsAdded || result.IsUpdated)
|
||||||
{
|
{
|
||||||
await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
|
await _searchIndex.RebuildItems(
|
||||||
|
_searchRepository,
|
||||||
|
_fallbackMetadataProvider,
|
||||||
|
new List<int> { result.Item.Id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,7 +184,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
|
|||||||
{
|
{
|
||||||
_logger.LogInformation("Flagging missing other video at {Path}", path);
|
_logger.LogInformation("Flagging missing other video at {Path}", path);
|
||||||
List<int> otherVideoIds = await FlagFileNotFound(libraryPath, path);
|
List<int> otherVideoIds = await FlagFileNotFound(libraryPath, path);
|
||||||
await _searchIndex.RebuildItems(_searchRepository, otherVideoIds);
|
await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, otherVideoIds);
|
||||||
}
|
}
|
||||||
else if (Path.GetFileName(path).StartsWith("._"))
|
else if (Path.GetFileName(path).StartsWith("._"))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ namespace ErsatzTV.Core.Metadata;
|
|||||||
public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
|
public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
|
||||||
{
|
{
|
||||||
private readonly IClient _client;
|
private readonly IClient _client;
|
||||||
|
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
|
||||||
private readonly ILibraryRepository _libraryRepository;
|
private readonly ILibraryRepository _libraryRepository;
|
||||||
private readonly ILocalFileSystem _localFileSystem;
|
private readonly ILocalFileSystem _localFileSystem;
|
||||||
private readonly ILocalMetadataProvider _localMetadataProvider;
|
private readonly ILocalMetadataProvider _localMetadataProvider;
|
||||||
@@ -33,6 +34,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
|
|||||||
IMediator mediator,
|
IMediator mediator,
|
||||||
ISearchIndex searchIndex,
|
ISearchIndex searchIndex,
|
||||||
ISearchRepository searchRepository,
|
ISearchRepository searchRepository,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
ISongRepository songRepository,
|
ISongRepository songRepository,
|
||||||
ILibraryRepository libraryRepository,
|
ILibraryRepository libraryRepository,
|
||||||
IMediaItemRepository mediaItemRepository,
|
IMediaItemRepository mediaItemRepository,
|
||||||
@@ -55,6 +57,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
|
|||||||
_mediator = mediator;
|
_mediator = mediator;
|
||||||
_searchIndex = searchIndex;
|
_searchIndex = searchIndex;
|
||||||
_searchRepository = searchRepository;
|
_searchRepository = searchRepository;
|
||||||
|
_fallbackMetadataProvider = fallbackMetadataProvider;
|
||||||
_songRepository = songRepository;
|
_songRepository = songRepository;
|
||||||
_libraryRepository = libraryRepository;
|
_libraryRepository = libraryRepository;
|
||||||
_client = client;
|
_client = client;
|
||||||
@@ -155,7 +158,10 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
|
|||||||
{
|
{
|
||||||
if (result.IsAdded || result.IsUpdated)
|
if (result.IsAdded || result.IsUpdated)
|
||||||
{
|
{
|
||||||
await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
|
await _searchIndex.RebuildItems(
|
||||||
|
_searchRepository,
|
||||||
|
_fallbackMetadataProvider,
|
||||||
|
new List<int> { result.Item.Id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,7 +179,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
|
|||||||
{
|
{
|
||||||
_logger.LogInformation("Flagging missing song at {Path}", path);
|
_logger.LogInformation("Flagging missing song at {Path}", path);
|
||||||
List<int> songIds = await FlagFileNotFound(libraryPath, path);
|
List<int> songIds = await FlagFileNotFound(libraryPath, path);
|
||||||
await _searchIndex.RebuildItems(_searchRepository, songIds);
|
await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, songIds);
|
||||||
}
|
}
|
||||||
else if (Path.GetFileName(path).StartsWith("._"))
|
else if (Path.GetFileName(path).StartsWith("._"))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace ErsatzTV.Core.Metadata;
|
|||||||
public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScanner
|
public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScanner
|
||||||
{
|
{
|
||||||
private readonly IClient _client;
|
private readonly IClient _client;
|
||||||
|
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
|
||||||
private readonly ILibraryRepository _libraryRepository;
|
private readonly ILibraryRepository _libraryRepository;
|
||||||
private readonly ILocalFileSystem _localFileSystem;
|
private readonly ILocalFileSystem _localFileSystem;
|
||||||
private readonly ILocalMetadataProvider _localMetadataProvider;
|
private readonly ILocalMetadataProvider _localMetadataProvider;
|
||||||
@@ -30,6 +31,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
|
|||||||
ITelevisionRepository televisionRepository,
|
ITelevisionRepository televisionRepository,
|
||||||
ILocalStatisticsProvider localStatisticsProvider,
|
ILocalStatisticsProvider localStatisticsProvider,
|
||||||
ILocalMetadataProvider localMetadataProvider,
|
ILocalMetadataProvider localMetadataProvider,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
ILocalSubtitlesProvider localSubtitlesProvider,
|
ILocalSubtitlesProvider localSubtitlesProvider,
|
||||||
IMetadataRepository metadataRepository,
|
IMetadataRepository metadataRepository,
|
||||||
IImageCache imageCache,
|
IImageCache imageCache,
|
||||||
@@ -55,6 +57,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
|
|||||||
_localFileSystem = localFileSystem;
|
_localFileSystem = localFileSystem;
|
||||||
_televisionRepository = televisionRepository;
|
_televisionRepository = televisionRepository;
|
||||||
_localMetadataProvider = localMetadataProvider;
|
_localMetadataProvider = localMetadataProvider;
|
||||||
|
_fallbackMetadataProvider = fallbackMetadataProvider;
|
||||||
_localSubtitlesProvider = localSubtitlesProvider;
|
_localSubtitlesProvider = localSubtitlesProvider;
|
||||||
_metadataRepository = metadataRepository;
|
_metadataRepository = metadataRepository;
|
||||||
_searchIndex = searchIndex;
|
_searchIndex = searchIndex;
|
||||||
@@ -127,7 +130,10 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
|
|||||||
|
|
||||||
if (result.IsAdded || result.IsUpdated)
|
if (result.IsAdded || result.IsUpdated)
|
||||||
{
|
{
|
||||||
await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
|
await _searchIndex.RebuildItems(
|
||||||
|
_searchRepository,
|
||||||
|
_fallbackMetadataProvider,
|
||||||
|
new List<int> { result.Item.Id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,8 +143,9 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
|
|||||||
if (!_localFileSystem.FileExists(path))
|
if (!_localFileSystem.FileExists(path))
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Flagging missing episode at {Path}", path);
|
_logger.LogInformation("Flagging missing episode at {Path}", path);
|
||||||
|
|
||||||
List<int> episodeIds = await FlagFileNotFound(libraryPath, path);
|
List<int> episodeIds = await FlagFileNotFound(libraryPath, path);
|
||||||
await _searchIndex.RebuildItems(_searchRepository, episodeIds);
|
await _searchIndex.RebuildItems(_searchRepository, _fallbackMetadataProvider, episodeIds);
|
||||||
}
|
}
|
||||||
else if (Path.GetFileName(path).StartsWith("._"))
|
else if (Path.GetFileName(path).StartsWith("._"))
|
||||||
{
|
{
|
||||||
@@ -241,7 +248,10 @@ 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.RebuildItems(_searchRepository, new List<int> { season.Id });
|
await _searchIndex.RebuildItems(
|
||||||
|
_searchRepository,
|
||||||
|
_fallbackMetadataProvider,
|
||||||
|
new List<int> { season.Id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,7 +297,10 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
|
|||||||
|
|
||||||
foreach (Episode episode in maybeEpisode.RightToSeq())
|
foreach (Episode episode in maybeEpisode.RightToSeq())
|
||||||
{
|
{
|
||||||
await _searchIndex.RebuildItems(_searchRepository, new List<int> { episode.Id });
|
await _searchIndex.RebuildItems(
|
||||||
|
_searchRepository,
|
||||||
|
_fallbackMetadataProvider,
|
||||||
|
new List<int> { episode.Id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ public class PlexMovieLibraryScanner :
|
|||||||
IMetadataRepository metadataRepository,
|
IMetadataRepository metadataRepository,
|
||||||
ISearchIndex searchIndex,
|
ISearchIndex searchIndex,
|
||||||
ISearchRepository searchRepository,
|
ISearchRepository searchRepository,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
IMediator mediator,
|
IMediator mediator,
|
||||||
IMediaSourceRepository mediaSourceRepository,
|
IMediaSourceRepository mediaSourceRepository,
|
||||||
IPlexMovieRepository plexMovieRepository,
|
IPlexMovieRepository plexMovieRepository,
|
||||||
@@ -43,6 +44,7 @@ public class PlexMovieLibraryScanner :
|
|||||||
mediator,
|
mediator,
|
||||||
searchIndex,
|
searchIndex,
|
||||||
searchRepository,
|
searchRepository,
|
||||||
|
fallbackMetadataProvider,
|
||||||
logger)
|
logger)
|
||||||
{
|
{
|
||||||
_plexServerApiClient = plexServerApiClient;
|
_plexServerApiClient = plexServerApiClient;
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ public class PlexTelevisionLibraryScanner :
|
|||||||
IMetadataRepository metadataRepository,
|
IMetadataRepository metadataRepository,
|
||||||
ISearchIndex searchIndex,
|
ISearchIndex searchIndex,
|
||||||
ISearchRepository searchRepository,
|
ISearchRepository searchRepository,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
IMediator mediator,
|
IMediator mediator,
|
||||||
IMediaSourceRepository mediaSourceRepository,
|
IMediaSourceRepository mediaSourceRepository,
|
||||||
IPlexPathReplacementService plexPathReplacementService,
|
IPlexPathReplacementService plexPathReplacementService,
|
||||||
@@ -42,6 +43,7 @@ public class PlexTelevisionLibraryScanner :
|
|||||||
localFileSystem,
|
localFileSystem,
|
||||||
searchRepository,
|
searchRepository,
|
||||||
searchIndex,
|
searchIndex,
|
||||||
|
fallbackMetadataProvider,
|
||||||
mediator,
|
mediator,
|
||||||
logger)
|
logger)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Numerics;
|
|
||||||
using ErsatzTV.FFmpeg.Format;
|
using ErsatzTV.FFmpeg.Format;
|
||||||
|
|
||||||
namespace ErsatzTV.FFmpeg.Capabilities;
|
namespace ErsatzTV.FFmpeg.Capabilities;
|
||||||
@@ -42,7 +41,7 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities
|
|||||||
|
|
||||||
// nvidia cannot encode 10-bit h264
|
// nvidia cannot encode 10-bit h264
|
||||||
VideoFormat.H264 when bitDepth == 10 => false,
|
VideoFormat.H264 when bitDepth == 10 => false,
|
||||||
|
|
||||||
_ => true
|
_ => true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories;
|
|||||||
public class SearchRepository : ISearchRepository
|
public class SearchRepository : ISearchRepository
|
||||||
{
|
{
|
||||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||||
|
private readonly SemaphoreSlim _slim = new(1, 1);
|
||||||
|
private List<string> _allLanguageCodes;
|
||||||
|
|
||||||
public SearchRepository(IDbContextFactory<TvContext> dbContextFactory) => _dbContextFactory = dbContextFactory;
|
public SearchRepository(IDbContextFactory<TvContext> dbContextFactory) => _dbContextFactory = dbContextFactory;
|
||||||
|
|
||||||
@@ -49,6 +51,8 @@ public class SearchRepository : ISearchRepository
|
|||||||
.ThenInclude(em => em.Guids)
|
.ThenInclude(em => em.Guids)
|
||||||
.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).MediaVersions)
|
||||||
|
.ThenInclude(em => em.MediaFiles)
|
||||||
.Include(mi => (mi as Episode).Season)
|
.Include(mi => (mi as Episode).Season)
|
||||||
.ThenInclude(s => s.Show)
|
.ThenInclude(s => s.Show)
|
||||||
.ThenInclude(s => s.ShowMetadata)
|
.ThenInclude(s => s.ShowMetadata)
|
||||||
@@ -162,8 +166,24 @@ public class SearchRepository : ISearchRepository
|
|||||||
|
|
||||||
public async Task<List<string>> GetAllLanguageCodes(List<string> mediaCodes)
|
public async Task<List<string>> GetAllLanguageCodes(List<string> mediaCodes)
|
||||||
{
|
{
|
||||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
|
if (_allLanguageCodes == null)
|
||||||
return await dbContext.LanguageCodes.GetAllLanguageCodes(mediaCodes);
|
{
|
||||||
|
await _slim.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_allLanguageCodes == null)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
_allLanguageCodes = await dbContext.LanguageCodes.GetAllLanguageCodes(mediaCodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_slim.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _allLanguageCodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAsyncEnumerable<MediaItem> GetAllMediaItems()
|
public IAsyncEnumerable<MediaItem> GetAllMediaItems()
|
||||||
@@ -203,6 +223,8 @@ public class SearchRepository : ISearchRepository
|
|||||||
.ThenInclude(em => em.Guids)
|
.ThenInclude(em => em.Guids)
|
||||||
.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).MediaVersions)
|
||||||
|
.ThenInclude(em => em.MediaFiles)
|
||||||
.Include(mi => (mi as Episode).Season)
|
.Include(mi => (mi as Episode).Season)
|
||||||
.ThenInclude(s => s.Show)
|
.ThenInclude(s => s.Show)
|
||||||
.ThenInclude(s => s.ShowMetadata)
|
.ThenInclude(s => s.ShowMetadata)
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ public sealed class SearchIndex : ISearchIndex
|
|||||||
_initialized = false;
|
_initialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Version => 27;
|
public int Version => 28;
|
||||||
|
|
||||||
public async Task<bool> Initialize(
|
public async Task<bool> Initialize(
|
||||||
ILocalFileSystem localFileSystem,
|
ILocalFileSystem localFileSystem,
|
||||||
@@ -117,7 +117,10 @@ public sealed class SearchIndex : ISearchIndex
|
|||||||
return _initialized;
|
return _initialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Unit> UpdateItems(ISearchRepository searchRepository, List<MediaItem> items)
|
public async Task<Unit> UpdateItems(
|
||||||
|
ISearchRepository searchRepository,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
|
List<MediaItem> items)
|
||||||
{
|
{
|
||||||
foreach (MediaItem item in items)
|
foreach (MediaItem item in items)
|
||||||
{
|
{
|
||||||
@@ -139,7 +142,7 @@ public sealed class SearchIndex : ISearchIndex
|
|||||||
await UpdateMusicVideo(searchRepository, musicVideo);
|
await UpdateMusicVideo(searchRepository, musicVideo);
|
||||||
break;
|
break;
|
||||||
case Episode episode:
|
case Episode episode:
|
||||||
await UpdateEpisode(searchRepository, episode);
|
await UpdateEpisode(searchRepository, fallbackMetadataProvider, episode);
|
||||||
break;
|
break;
|
||||||
case OtherVideo otherVideo:
|
case OtherVideo otherVideo:
|
||||||
await UpdateOtherVideo(searchRepository, otherVideo);
|
await UpdateOtherVideo(searchRepository, otherVideo);
|
||||||
@@ -215,27 +218,32 @@ public sealed class SearchIndex : ISearchIndex
|
|||||||
_directory?.Dispose();
|
_directory?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Unit> Rebuild(ISearchRepository searchRepository)
|
public async Task<Unit> Rebuild(
|
||||||
|
ISearchRepository searchRepository,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider)
|
||||||
{
|
{
|
||||||
_writer.DeleteAll();
|
_writer.DeleteAll();
|
||||||
_writer.Commit();
|
_writer.Commit();
|
||||||
|
|
||||||
await foreach (MediaItem mediaItem in searchRepository.GetAllMediaItems())
|
await foreach (MediaItem mediaItem in searchRepository.GetAllMediaItems())
|
||||||
{
|
{
|
||||||
await RebuildItem(searchRepository, mediaItem);
|
await RebuildItem(searchRepository, fallbackMetadataProvider, mediaItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
_writer.Commit();
|
_writer.Commit();
|
||||||
return Unit.Default;
|
return Unit.Default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Unit> RebuildItems(ISearchRepository searchRepository, List<int> itemIds)
|
public async Task<Unit> RebuildItems(
|
||||||
|
ISearchRepository searchRepository,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
|
List<int> itemIds)
|
||||||
{
|
{
|
||||||
foreach (int id in itemIds)
|
foreach (int id in itemIds)
|
||||||
{
|
{
|
||||||
foreach (MediaItem mediaItem in await searchRepository.GetItemToIndex(id))
|
foreach (MediaItem mediaItem in await searchRepository.GetItemToIndex(id))
|
||||||
{
|
{
|
||||||
await RebuildItem(searchRepository, mediaItem);
|
await RebuildItem(searchRepository, fallbackMetadataProvider, mediaItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,7 +276,10 @@ public sealed class SearchIndex : ISearchIndex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RebuildItem(ISearchRepository searchRepository, MediaItem mediaItem)
|
private async Task RebuildItem(
|
||||||
|
ISearchRepository searchRepository,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
|
MediaItem mediaItem)
|
||||||
{
|
{
|
||||||
switch (mediaItem)
|
switch (mediaItem)
|
||||||
{
|
{
|
||||||
@@ -288,7 +299,7 @@ public sealed class SearchIndex : ISearchIndex
|
|||||||
await UpdateMusicVideo(searchRepository, musicVideo);
|
await UpdateMusicVideo(searchRepository, musicVideo);
|
||||||
break;
|
break;
|
||||||
case Episode episode:
|
case Episode episode:
|
||||||
await UpdateEpisode(searchRepository, episode);
|
await UpdateEpisode(searchRepository, fallbackMetadataProvider, episode);
|
||||||
break;
|
break;
|
||||||
case OtherVideo otherVideo:
|
case OtherVideo otherVideo:
|
||||||
await UpdateOtherVideo(searchRepository, otherVideo);
|
await UpdateOtherVideo(searchRepository, otherVideo);
|
||||||
@@ -807,8 +818,22 @@ public sealed class SearchIndex : ISearchIndex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateEpisode(ISearchRepository searchRepository, Episode episode)
|
private async Task UpdateEpisode(
|
||||||
|
ISearchRepository searchRepository,
|
||||||
|
IFallbackMetadataProvider fallbackMetadataProvider,
|
||||||
|
Episode episode)
|
||||||
{
|
{
|
||||||
|
// try to load metadata here, since episodes without metadata won't index
|
||||||
|
if (episode.EpisodeMetadata.Count == 0)
|
||||||
|
{
|
||||||
|
episode.EpisodeMetadata ??= new List<EpisodeMetadata>();
|
||||||
|
episode.EpisodeMetadata = fallbackMetadataProvider.GetFallbackMetadata(episode);
|
||||||
|
foreach (EpisodeMetadata metadata in episode.EpisodeMetadata)
|
||||||
|
{
|
||||||
|
metadata.Episode = episode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (EpisodeMetadata metadata in episode.EpisodeMetadata)
|
foreach (EpisodeMetadata metadata in episode.EpisodeMetadata)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
Reference in New Issue
Block a user