fix delete old segments (#1536)

* code cleanup

* ignore errors deleting old hls segments
This commit is contained in:
Jason Dove
2024-01-04 10:42:04 -06:00
committed by GitHub
parent 18ed20e203
commit c18be5559b
71 changed files with 207 additions and 187 deletions
+1
View File
@@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix error loading path replacements when using MySql
- Fix tray icon shortcut to open logs folder on Windows
- Unlock playout when playout build fails
- Ignore errors deleting old HLS segments; this should improve stream reliability
### Changed
- Upgrade from .NET 7 to .NET 8
@@ -2,5 +2,10 @@ using ErsatzTV.Core.Iptv;
namespace ErsatzTV.Application.Channels;
public record GetChannelPlaylist
(string Scheme, string Host, string BaseUrl, string Mode, string UserAgent, string AccessToken) : IRequest<ChannelPlaylist>;
public record GetChannelPlaylist(
string Scheme,
string Host,
string BaseUrl,
string Mode,
string UserAgent,
string AccessToken) : IRequest<ChannelPlaylist>;
@@ -40,7 +40,9 @@ public class CallJellyfinCollectionScannerHandler : CallLibraryScannerHandler<Sy
});
}
protected override async Task<DateTimeOffset> GetLastScan(TvContext dbContext, SynchronizeJellyfinCollections request)
protected override async Task<DateTimeOffset> GetLastScan(
TvContext dbContext,
SynchronizeJellyfinCollections request)
{
DateTime minDateTime = await dbContext.JellyfinMediaSources
.SelectOneAsync(l => l.Id, l => l.Id == request.JellyfinMediaSourceId)
@@ -2,5 +2,6 @@ using ErsatzTV.Core;
namespace ErsatzTV.Application.Jellyfin;
public record SynchronizeJellyfinCollections(int JellyfinMediaSourceId, bool ForceScan) : IRequest<Either<BaseError, Unit>>,
public record SynchronizeJellyfinCollections(int JellyfinMediaSourceId, bool ForceScan) :
IRequest<Either<BaseError, Unit>>,
IScannerBackgroundServiceRequest;
@@ -18,15 +18,15 @@ namespace ErsatzTV.Application.Streaming;
public class StartFFmpegSessionHandler : IRequestHandler<StartFFmpegSession, Either<BaseError, Unit>>
{
private readonly IClient _client;
private readonly IConfigElementRepository _configElementRepository;
private readonly IHostApplicationLifetime _hostApplicationLifetime;
private readonly IFFmpegSegmenterService _ffmpegSegmenterService;
private readonly IHlsPlaylistFilter _hlsPlaylistFilter;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly IMediator _mediator;
private readonly IClient _client;
private readonly IHostApplicationLifetime _hostApplicationLifetime;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILogger<StartFFmpegSessionHandler> _logger;
private readonly IMediator _mediator;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly ILogger<HlsSessionWorker> _sessionWorkerLogger;
private readonly ChannelWriter<IBackgroundServiceRequest> _workerChannel;
@@ -22,23 +22,23 @@ public class HlsSessionWorker : IHlsSessionWorker
{
private static readonly SemaphoreSlim Slim = new(1, 1);
private static int _workAheadCount;
private readonly IMediator _mediator;
private readonly IClient _client;
private readonly IHlsPlaylistFilter _hlsPlaylistFilter;
private readonly IConfigElementRepository _configElementRepository;
private readonly IHlsPlaylistFilter _hlsPlaylistFilter;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILogger<HlsSessionWorker> _logger;
private readonly Option<int> _targetFramerate;
private readonly IMediator _mediator;
private readonly object _sync = new();
private readonly Option<int> _targetFramerate;
private string _channelNumber;
private bool _disposedValue;
private bool _hasWrittenSegments;
private DateTimeOffset _lastAccess;
private DateTimeOffset _lastDelete = DateTimeOffset.MinValue;
private IServiceScope _serviceScope;
private HlsSessionState _state;
private Timer _timer;
private DateTimeOffset _transcodedUntil;
private IServiceScope _serviceScope;
public HlsSessionWorker(
IServiceScopeFactory serviceScopeFactory,
@@ -512,7 +512,16 @@ public class HlsSessionWorker : IHlsSessionWorker
foreach (Segment segment in toDelete)
{
File.Delete(segment.File);
try
{
File.Delete(segment.File);
}
catch (IOException)
{
// work around lots of:
// The process cannot access the file '...' because it is being used by another process
_logger.LogDebug("Failed to delete old segment {File}", segment.File);
}
}
}
@@ -551,11 +560,9 @@ public class HlsSessionWorker : IHlsSessionWorker
}
}
private async Task<int> GetWorkAheadLimit()
{
return await _configElementRepository.GetValue<int>(ConfigElementKey.FFmpegWorkAheadSegmenters)
private async Task<int> GetWorkAheadLimit() =>
await _configElementRepository.GetValue<int>(ConfigElementKey.FFmpegWorkAheadSegmenters)
.Map(maybeCount => maybeCount.Match(identity, () => 1));
}
private async Task<Option<string[]>> ReadPlaylistLines(CancellationToken cancellationToken)
{
@@ -742,7 +742,7 @@ public class PlayoutModeSchedulerDurationTests : SchedulerTestBase
{ 10, TimeSpan.Parse("00:00:31.5791160") },
{ 11, TimeSpan.Parse("00:00:31.2540360") },
{ 12, TimeSpan.Parse("00:00:36.2231070") },
{ 13, TimeSpan.Parse("00:02:00.0471430") },
{ 13, TimeSpan.Parse("00:02:00.0471430") }
});
Collection collectionThree = TwoItemCollection(14, 15, TimeSpan.Parse("00:00:55.6349890"));
@@ -13,8 +13,10 @@ public abstract class ProgramScheduleItem
public GuideMode GuideMode { get; set; }
public string CustomTitle { get; set; }
public int ProgramScheduleId { get; set; }
[JsonIgnore]
public ProgramSchedule ProgramSchedule { get; set; }
public int? CollectionId { get; set; }
public Collection Collection { get; set; }
public int? MediaItemId { get; set; }
+1 -1
View File
@@ -9,9 +9,9 @@ public class ChannelPlaylist
private readonly string _accessToken;
private readonly string _baseUrl;
private readonly List<Channel> _channels;
private readonly string _userAgent;
private readonly string _host;
private readonly string _scheme;
private readonly string _userAgent;
public ChannelPlaylist(
string scheme,
+4 -2
View File
@@ -428,7 +428,8 @@ public class PlayoutBuilder : IPlayoutBuilder
var collectionItemCount = collectionMediaItems.Map((k, v) => (k, v.Count)).Values.ToDictionary();
var scheduleItemsFillGroupEnumerators = new Dictionary<int, IScheduleItemsEnumerator>();
foreach (ProgramScheduleItem scheduleItem in sortedScheduleItems.Where(si => si.FillWithGroupMode is not FillWithGroupMode.None))
foreach (ProgramScheduleItem scheduleItem in sortedScheduleItems.Where(
si => si.FillWithGroupMode is not FillWithGroupMode.None))
{
var collectionKey = CollectionKey.ForScheduleItem(scheduleItem);
List<MediaItem> mediaItems = await MediaItemsForCollection.Collect(
@@ -439,7 +440,8 @@ public class PlayoutBuilder : IPlayoutBuilder
var fakeCollections = _mediaCollectionRepository.GroupIntoFakeCollections(mediaItems)
.Filter(c => c.ShowId > 0 || c.ArtistId > 0)
.ToList();
List<ProgramScheduleItem> fakeScheduleItems = [];
List<ProgramScheduleItem> fakeScheduleItems = []
;
// this will be used to clone a schedule item
MethodInfo generic = typeof(JsonConvert).GetMethods()
@@ -11,10 +11,8 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche
private readonly Map<CollectionKey, int> _collectionItemCount;
public PlayoutModeSchedulerMultiple(Map<CollectionKey, int> collectionItemCount, ILogger logger)
: base(logger)
{
: base(logger) =>
_collectionItemCount = collectionItemCount;
}
public override Tuple<PlayoutBuilderState, List<PlayoutItem>> Schedule(
PlayoutBuilderState playoutBuilderState,
@@ -15,7 +15,8 @@ public class RandomizedMediaCollectionEnumerator : IMediaCollectionEnumerator
{
_mediaItems = mediaItems;
_lazyMinimumDuration =
new Lazy<Option<TimeSpan>>(() => _mediaItems.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone());
new Lazy<Option<TimeSpan>>(
() => _mediaItems.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone());
_random = new Random(state.Seed);
State = new CollectionEnumeratorState { Seed = state.Seed };
@@ -34,7 +34,8 @@ public class ShuffleInOrderCollectionEnumerator : IMediaCollectionEnumerator
_random = new Random(state.Seed);
_shuffled = Shuffle(_collections, _random);
_lazyMinimumDuration =
new Lazy<Option<TimeSpan>>(() => _shuffled.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone());
new Lazy<Option<TimeSpan>>(
() => _shuffled.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone());
State = new CollectionEnumeratorState { Seed = state.Seed };
while (State.Index < state.Index)
@@ -31,7 +31,8 @@ public class ShuffledMediaCollectionEnumerator : IMediaCollectionEnumerator
_random = new CloneableRandom(state.Seed);
_shuffled = Shuffle(_mediaItems, _random);
_lazyMinimumDuration =
new Lazy<Option<TimeSpan>>(() => _shuffled.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone());
new Lazy<Option<TimeSpan>>(
() => _shuffled.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone());
State = new CollectionEnumeratorState { Seed = state.Seed };
while (State.Index < state.Index)
@@ -5,10 +5,10 @@ namespace ErsatzTV.FFmpeg.Capabilities;
public class FFmpegCapabilities : IFFmpegCapabilities
{
private readonly IReadOnlySet<string> _ffmpegHardwareAccelerations;
private readonly IReadOnlySet<string> _ffmpegDecoders;
private readonly IReadOnlySet<string> _ffmpegEncoders;
private readonly IReadOnlySet<string> _ffmpegFilters;
private readonly IReadOnlySet<string> _ffmpegHardwareAccelerations;
private readonly IReadOnlySet<string> _ffmpegOptions;
public FFmpegCapabilities(
@@ -2,13 +2,6 @@ namespace ErsatzTV.FFmpeg.Capabilities;
public record FFmpegKnownDecoder
{
public string Name { get; }
private FFmpegKnownDecoder(string Name)
{
this.Name = Name;
}
public static readonly FFmpegKnownDecoder Av1Cuvid = new("av1_cuvid");
public static readonly FFmpegKnownDecoder H264Cuvid = new("h264_cuvid");
public static readonly FFmpegKnownDecoder HevcCuvid = new("hevc_cuvid");
@@ -17,6 +10,10 @@ public record FFmpegKnownDecoder
public static readonly FFmpegKnownDecoder Vc1Cuvid = new("vc1_cuvid");
public static readonly FFmpegKnownDecoder Vp9Cuvid = new("vp9_cuvid");
private FFmpegKnownDecoder(string Name) => this.Name = Name;
public string Name { get; }
public static IList<string> AllDecoders =>
new[]
{
@@ -2,12 +2,9 @@ namespace ErsatzTV.FFmpeg.Capabilities;
public record FFmpegKnownEncoder
{
public string Name { get; }
private FFmpegKnownEncoder(string Name) => this.Name = Name;
private FFmpegKnownEncoder(string Name)
{
this.Name = Name;
}
public string Name { get; }
// only list the encoders that we actually check for
public static IList<string> AllEncoders =>
@@ -2,15 +2,12 @@ namespace ErsatzTV.FFmpeg.Capabilities;
public record FFmpegKnownFilter
{
public string Name { get; }
private FFmpegKnownFilter(string Name)
{
this.Name = Name;
}
public static readonly FFmpegKnownFilter ScaleNpp = new("scale_npp");
private FFmpegKnownFilter(string Name) => this.Name = Name;
public string Name { get; }
public static IList<string> AllFilters =>
new[]
{
@@ -2,19 +2,16 @@ namespace ErsatzTV.FFmpeg.Capabilities;
public record FFmpegKnownHardwareAcceleration
{
public string Name { get; }
private FFmpegKnownHardwareAcceleration(string Name)
{
this.Name = Name;
}
public static readonly FFmpegKnownHardwareAcceleration Amf = new("amf");
public static readonly FFmpegKnownHardwareAcceleration Cuda = new("cuda");
public static readonly FFmpegKnownHardwareAcceleration Qsv = new("qsv");
public static readonly FFmpegKnownHardwareAcceleration Vaapi = new("vaapi");
public static readonly FFmpegKnownHardwareAcceleration VideoToolbox = new("videotoolbox");
private FFmpegKnownHardwareAcceleration(string Name) => this.Name = Name;
public string Name { get; }
public static IList<string> AllAccels =>
new[]
{
@@ -6,15 +6,12 @@ namespace ErsatzTV.FFmpeg.Capabilities;
[SuppressMessage("ReSharper", "StringLiteralTypo")]
public record FFmpegKnownOption
{
public string Name { get; }
private FFmpegKnownOption(string Name)
{
this.Name = Name;
}
public static readonly FFmpegKnownOption ReadrateInitialBurst = new("readrate_initial_burst");
private FFmpegKnownOption(string Name) => this.Name = Name;
public string Name { get; }
public static IList<string> AllOptions =>
new[]
{
@@ -18,7 +18,10 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
{
private const string ArchitectureCacheKey = "ffmpeg.hardware.nvidia.architecture";
private const string ModelCacheKey = "ffmpeg.hardware.nvidia.model";
private static readonly CompositeFormat VaapiCacheKeyFormat = CompositeFormat.Parse("ffmpeg.hardware.vaapi.{0}.{1}");
private static readonly CompositeFormat
VaapiCacheKeyFormat = CompositeFormat.Parse("ffmpeg.hardware.vaapi.{0}.{1}");
private static readonly CompositeFormat QsvCacheKeyFormat = CompositeFormat.Parse("ffmpeg.hardware.qsv.{0}");
private static readonly CompositeFormat FFmpegCapabilitiesCacheKeyFormat = CompositeFormat.Parse("ffmpeg.{0}");
@@ -4,6 +4,7 @@ public class DecoderImplicitCuda : DecoderBase
{
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
public override string Name => string.Empty;
public override string[] InputOptions(InputFile inputFile) =>
new[]
{
+1 -1
View File
@@ -4,8 +4,8 @@ namespace ErsatzTV.FFmpeg.Filter;
public class CropFilter : BaseFilter
{
private readonly FrameState _currentState;
private readonly FrameSize _croppedSize;
private readonly FrameState _currentState;
public CropFilter(FrameState currentState, FrameSize croppedSize)
{
@@ -4,10 +4,10 @@ namespace ErsatzTV.FFmpeg.Filter.Cuda;
public class ScaleCudaFilter : BaseFilter
{
private readonly Option<FrameSize> _croppedSize;
private readonly FrameState _currentState;
private readonly bool _isAnamorphicEdgeCase;
private readonly FrameSize _paddedSize;
private readonly Option<FrameSize> _croppedSize;
private readonly FrameSize _scaledSize;
public ScaleCudaFilter(
@@ -4,10 +4,7 @@ public class NormalizeLoudnessFilter : BaseFilter
{
private readonly AudioFilter _loudnessFilter;
public NormalizeLoudnessFilter(AudioFilter loudnessFilter)
{
_loudnessFilter = loudnessFilter;
}
public NormalizeLoudnessFilter(AudioFilter loudnessFilter) => _loudnessFilter = loudnessFilter;
public override string Filter => _loudnessFilter switch
{
+1 -1
View File
@@ -4,10 +4,10 @@ namespace ErsatzTV.FFmpeg.Filter;
public class ScaleFilter : BaseFilter
{
private readonly Option<FrameSize> _croppedSize;
private readonly FrameState _currentState;
private readonly bool _isAnamorphicEdgeCase;
private readonly FrameSize _paddedSize;
private readonly Option<FrameSize> _croppedSize;
private readonly FrameSize _scaledSize;
public ScaleFilter(
@@ -4,10 +4,10 @@ namespace ErsatzTV.FFmpeg.Filter.Vaapi;
public class ScaleVaapiFilter : BaseFilter
{
private readonly Option<FrameSize> _croppedSize;
private readonly FrameState _currentState;
private readonly bool _isAnamorphicEdgeCase;
private readonly FrameSize _paddedSize;
private readonly Option<FrameSize> _croppedSize;
private readonly FrameSize _scaledSize;
public ScaleVaapiFilter(
@@ -2,7 +2,6 @@
public class VaapiSubtitlePixelFormatFilter : BaseFilter
{
public override FrameState NextState(FrameState currentState) => currentState;
public override string Filter => "format=vaapi|yuva420p|yuva444p|yuva422p|rgba|abgr|bgra|gbrap|ya8";
public override FrameState NextState(FrameState currentState) => currentState;
}
+3 -4
View File
@@ -16,9 +16,8 @@ public class VideoFilter : IPipelineStep
public FrameState NextState(FrameState currentState) => currentState;
private string[] Arguments() =>
new []
{
"-vf",
[
"-vf",
string.Join(",", _filterSteps.Map(fs => fs.Filter))
};
];
}
@@ -6,8 +6,8 @@ public class OutputFormatHls : IPipelineStep
{
private readonly FrameState _desiredState;
private readonly Option<string> _mediaFrameRate;
private readonly string _playlistPath;
private readonly bool _oneSecondGop;
private readonly string _playlistPath;
private readonly string _segmentTemplate;
public OutputFormatHls(
@@ -475,7 +475,8 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder
subtitle.FilterSteps.Add(subtitleHardwareUpload);
// only scale if scaling or padding was used for main video stream
if (videoInputFile.FilterSteps.Any(s => s is ScaleFilter or ScaleCudaFilter { IsFormatOnly: false } or PadFilter))
if (videoInputFile.FilterSteps.Any(
s => s is ScaleFilter or ScaleCudaFilter { IsFormatOnly: false } or PadFilter))
{
var scaleFilter = new SubtitleScaleNppFilter(desiredState.PaddedSize);
subtitle.FilterSteps.Add(scaleFilter);
@@ -484,7 +485,8 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder
else
{
// only scale if scaling or padding was used for main video stream
if (videoInputFile.FilterSteps.Any(s => s is ScaleFilter or ScaleCudaFilter { IsFormatOnly: false } or PadFilter))
if (videoInputFile.FilterSteps.Any(
s => s is ScaleFilter or ScaleCudaFilter { IsFormatOnly: false } or PadFilter))
{
var scaleFilter = new ScaleImageFilter(desiredState.PaddedSize);
subtitle.FilterSteps.Add(scaleFilter);
@@ -72,17 +72,18 @@ public class PipelineBuilderFactory : IPipelineBuilderFactory
reportsFolder,
fontsFolder,
_logger),
HardwareAccelerationMode.VideoToolbox when capabilities is not NoHardwareCapabilities => new VideoToolboxPipelineBuilder(
ffmpegCapabilities,
capabilities,
hardwareAccelerationMode,
videoInputFile,
audioInputFile,
watermarkInputFile,
subtitleInputFile,
reportsFolder,
fontsFolder,
_logger),
HardwareAccelerationMode.VideoToolbox when capabilities is not NoHardwareCapabilities => new
VideoToolboxPipelineBuilder(
ffmpegCapabilities,
capabilities,
hardwareAccelerationMode,
videoInputFile,
audioInputFile,
watermarkInputFile,
subtitleInputFile,
reportsFolder,
fontsFolder,
_logger),
HardwareAccelerationMode.Amf when capabilities is not NoHardwareCapabilities => new AmfPipelineBuilder(
ffmpegCapabilities,
capabilities,
@@ -4,7 +4,8 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations;
public class PlayoutScheduleItemFillGroupIndexConfiguration : IEntityTypeConfiguration<PlayoutScheduleItemFillGroupIndex>
public class
PlayoutScheduleItemFillGroupIndexConfiguration : IEntityTypeConfiguration<PlayoutScheduleItemFillGroupIndex>
{
public void Configure(EntityTypeBuilder<PlayoutScheduleItemFillGroupIndex> builder)
{
@@ -14,8 +14,8 @@ namespace ErsatzTV.Infrastructure.Jellyfin;
public class JellyfinApiClient : IJellyfinApiClient
{
private readonly IJellyfinPathReplacementService _jellyfinPathReplacementService;
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
private readonly IJellyfinPathReplacementService _jellyfinPathReplacementService;
private readonly ILogger<JellyfinApiClient> _logger;
private readonly IMemoryCache _memoryCache;
@@ -10,8 +10,8 @@ public class EntityLocker : IEntityLocker
private readonly ConcurrentDictionary<Type, byte> _lockedRemoteMediaSourceTypes = new();
private bool _embyCollections;
private bool _jellyfinCollections;
private bool _plexCollections;
private bool _plex;
private bool _plexCollections;
private bool _trakt;
public event EventHandler OnLibraryChanged;
@@ -53,7 +53,9 @@ public class PlexServerApiClient : IPlexServerApiClient
return directory
// .Filter(l => l.Hidden == 0)
.Filter(l => l.Type.ToLowerInvariant() is "movie" or "show")
.Filter(l => l.Type.ToLowerInvariant() is not "movie" || (l.Agent ?? string.Empty).ToLowerInvariant() is not "com.plexapp.agents.none")
.Filter(
l => l.Type.ToLowerInvariant() is not "movie" ||
(l.Agent ?? string.Empty).ToLowerInvariant() is not "com.plexapp.agents.none")
.Map(Project)
.Somes()
.ToList();
@@ -407,7 +409,9 @@ public class PlexServerApiClient : IPlexServerApiClient
_ => None
};
private Option<PlexCollection> ProjectToCollection(PlexMediaSource plexMediaSource, PlexCollectionMetadataResponse item)
private Option<PlexCollection> ProjectToCollection(
PlexMediaSource plexMediaSource,
PlexCollectionMetadataResponse item)
{
try
{
@@ -78,7 +78,8 @@ public class MultiEpisodeShuffleCollectionEnumerator : IMediaCollectionEnumerato
_random = new CloneableRandom(state.Seed);
_shuffled = Shuffle(_random);
_lazyMinimumDuration =
new Lazy<Option<TimeSpan>>(() => _shuffled.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone());
new Lazy<Option<TimeSpan>>(
() => _shuffled.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone());
State = new CollectionEnumeratorState { Seed = state.Seed };
while (State.Index < state.Index)
@@ -809,7 +809,10 @@ public sealed class LuceneSearchIndex : ISearchIndex
new StringField(IdField, musicVideo.Id.ToString(CultureInfo.InvariantCulture), Field.Store.YES),
new StringField(TypeField, MusicVideoType, Field.Store.YES),
new TextField(TitleField, metadata.Title ?? string.Empty, Field.Store.NO),
new StringField(SortTitleField, (metadata.SortTitle ?? string.Empty).ToLowerInvariant(), Field.Store.NO),
new StringField(
SortTitleField,
(metadata.SortTitle ?? string.Empty).ToLowerInvariant(),
Field.Store.NO),
new TextField(LibraryNameField, musicVideo.LibraryPath.Library.Name, Field.Store.NO),
new StringField(
LibraryIdField,
@@ -2,4 +2,5 @@
namespace ErsatzTV.Scanner.Application.Jellyfin;
public record SynchronizeJellyfinCollections(int JellyfinMediaSourceId, bool ForceScan) : IRequest<Either<BaseError, Unit>>;
public record SynchronizeJellyfinCollections
(int JellyfinMediaSourceId, bool ForceScan) : IRequest<Either<BaseError, Unit>>;
@@ -9,10 +9,10 @@ namespace ErsatzTV.Scanner.Application.Jellyfin;
public class
SynchronizeJellyfinCollectionsHandler : IRequestHandler<SynchronizeJellyfinCollections, Either<BaseError, Unit>>
{
private readonly IConfigElementRepository _configElementRepository;
private readonly IJellyfinSecretStore _jellyfinSecretStore;
private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IJellyfinCollectionScanner _scanner;
private readonly IConfigElementRepository _configElementRepository;
public SynchronizeJellyfinCollectionsHandler(
IMediaSourceRepository mediaSourceRepository,
@@ -9,8 +9,8 @@ namespace ErsatzTV.Scanner.Application.Plex;
public class SynchronizePlexCollectionsHandler : IRequestHandler<SynchronizePlexCollections, Either<BaseError, Unit>>
{
private readonly IConfigElementRepository _configElementRepository;
private readonly IPlexSecretStore _plexSecretStore;
private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IPlexSecretStore _plexSecretStore;
private readonly IPlexCollectionScanner _scanner;
public SynchronizePlexCollectionsHandler(
@@ -41,7 +41,7 @@ public class ShowNfoReader : NfoReader<ShowNfo>, IShowNfoReader
var settings = new XmlReaderSettings { Async = true, ConformanceLevel = ConformanceLevel.Fragment };
using var reader = XmlReader.Create(input, settings);
var done = false;
int showDepth = 0;
var showDepth = 0;
while (!done && await reader.ReadAsync())
{
@@ -131,7 +131,8 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
.HeadOrNone();
// skip folder if etag matches
if (allFiles.Count == 0 || await knownFolder.Map(f => f.Etag ?? string.Empty).IfNoneAsync(string.Empty) ==
if (allFiles.Count == 0 ||
await knownFolder.Map(f => f.Etag ?? string.Empty).IfNoneAsync(string.Empty) ==
etag)
{
continue;
@@ -126,7 +126,8 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
.HeadOrNone();
// skip folder if etag matches
if (allFiles.Count == 0 || await knownFolder.Map(f => f.Etag ?? string.Empty).IfNoneAsync(string.Empty) ==
if (allFiles.Count == 0 ||
await knownFolder.Map(f => f.Etag ?? string.Empty).IfNoneAsync(string.Empty) ==
etag)
{
continue;
@@ -10,10 +10,10 @@ namespace ErsatzTV.Scanner.Core.Plex;
public class PlexCollectionScanner : IPlexCollectionScanner
{
private readonly IPlexServerApiClient _plexServerApiClient;
private readonly IPlexCollectionRepository _plexCollectionRepository;
private readonly ILogger<PlexCollectionScanner> _logger;
private readonly IMediator _mediator;
private readonly IPlexCollectionRepository _plexCollectionRepository;
private readonly IPlexServerApiClient _plexServerApiClient;
public PlexCollectionScanner(
IMediator mediator,