add option to skip missing items in playouts (#795)
This commit is contained in:
@@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- Add `metadata_kind` field to search index to allow searching for items with a particular metdata source
|
- Add `metadata_kind` field to search index to allow searching for items with a particular metdata source
|
||||||
- Valid metadata kinds are `fallback`, `sidecar` (NFO), `external` (from a media server) and `embedded` (songs)
|
- Valid metadata kinds are `fallback`, `sidecar` (NFO), `external` (from a media server) and `embedded` (songs)
|
||||||
- Add autocomplete functionality to search bar to quickly navigate to channels, ffmpeg profiles, collections and schedules by name
|
- Add autocomplete functionality to search bar to quickly navigate to channels, ffmpeg profiles, collections and schedules by name
|
||||||
|
- Add global setting to skip missing (file-not-found or unavailable) items when building playouts
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Replace invalid (control) characters in NFO metadata with replacement character `�` before parsing
|
- Replace invalid (control) characters in NFO metadata with replacement character `�` before parsing
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
using ErsatzTV.Core;
|
|
||||||
|
|
||||||
namespace ErsatzTV.Application.Configuration;
|
|
||||||
|
|
||||||
public record UpdatePlayoutDaysToBuild(int DaysToBuild) : IRequest<Either<BaseError, Unit>>;
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Configuration;
|
||||||
|
|
||||||
|
public record UpdatePlayoutSettings(PlayoutSettingsViewModel PlayoutSettings) : IRequest<Either<BaseError, Unit>>;
|
||||||
+12
-9
@@ -9,13 +9,13 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
|
|
||||||
namespace ErsatzTV.Application.Configuration;
|
namespace ErsatzTV.Application.Configuration;
|
||||||
|
|
||||||
public class UpdatePlayoutDaysToBuildHandler : IRequestHandler<UpdatePlayoutDaysToBuild, Either<BaseError, Unit>>
|
public class UpdatePlayoutSettingsHandler : IRequestHandler<UpdatePlayoutSettings, Either<BaseError, Unit>>
|
||||||
{
|
{
|
||||||
private readonly IConfigElementRepository _configElementRepository;
|
private readonly IConfigElementRepository _configElementRepository;
|
||||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||||
private readonly ChannelWriter<IBackgroundServiceRequest> _workerChannel;
|
private readonly ChannelWriter<IBackgroundServiceRequest> _workerChannel;
|
||||||
|
|
||||||
public UpdatePlayoutDaysToBuildHandler(
|
public UpdatePlayoutSettingsHandler(
|
||||||
IConfigElementRepository configElementRepository,
|
IConfigElementRepository configElementRepository,
|
||||||
IDbContextFactory<TvContext> dbContextFactory,
|
IDbContextFactory<TvContext> dbContextFactory,
|
||||||
ChannelWriter<IBackgroundServiceRequest> workerChannel)
|
ChannelWriter<IBackgroundServiceRequest> workerChannel)
|
||||||
@@ -26,17 +26,20 @@ public class UpdatePlayoutDaysToBuildHandler : IRequestHandler<UpdatePlayoutDays
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Either<BaseError, Unit>> Handle(
|
public async Task<Either<BaseError, Unit>> Handle(
|
||||||
UpdatePlayoutDaysToBuild request,
|
UpdatePlayoutSettings request,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
Validation<BaseError, Unit> validation = await Validate(request);
|
Validation<BaseError, Unit> validation = await Validate(request);
|
||||||
return await validation.Apply<Unit, Unit>(_ => ApplyUpdate(dbContext, request.DaysToBuild));
|
return await validation.Apply<Unit, Unit>(_ => ApplyUpdate(dbContext, request.PlayoutSettings));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Unit> ApplyUpdate(TvContext dbContext, int daysToBuild)
|
private async Task<Unit> ApplyUpdate(TvContext dbContext, PlayoutSettingsViewModel playoutSettings)
|
||||||
{
|
{
|
||||||
await _configElementRepository.Upsert(ConfigElementKey.PlayoutDaysToBuild, daysToBuild);
|
await _configElementRepository.Upsert(ConfigElementKey.PlayoutDaysToBuild, playoutSettings.DaysToBuild);
|
||||||
|
await _configElementRepository.Upsert(
|
||||||
|
ConfigElementKey.PlayoutSkipMissingItems,
|
||||||
|
playoutSettings.SkipMissingItems);
|
||||||
|
|
||||||
// continue all playouts to proper number of days
|
// continue all playouts to proper number of days
|
||||||
List<Playout> playouts = await dbContext.Playouts
|
List<Playout> playouts = await dbContext.Playouts
|
||||||
@@ -50,8 +53,8 @@ public class UpdatePlayoutDaysToBuildHandler : IRequestHandler<UpdatePlayoutDays
|
|||||||
return Unit.Default;
|
return Unit.Default;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Task<Validation<BaseError, Unit>> Validate(UpdatePlayoutDaysToBuild request) =>
|
private static Task<Validation<BaseError, Unit>> Validate(UpdatePlayoutSettings request) =>
|
||||||
Optional(request.DaysToBuild)
|
Optional(request.PlayoutSettings.DaysToBuild)
|
||||||
.Where(days => days > 0)
|
.Where(days => days > 0)
|
||||||
.Map(_ => Unit.Default)
|
.Map(_ => Unit.Default)
|
||||||
.ToValidation<BaseError>("Days to build must be greater than zero")
|
.ToValidation<BaseError>("Days to build must be greater than zero")
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace ErsatzTV.Application.Configuration;
|
||||||
|
|
||||||
|
public class PlayoutSettingsViewModel
|
||||||
|
{
|
||||||
|
public int DaysToBuild { get; set; }
|
||||||
|
public bool SkipMissingItems { get; set; }
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
namespace ErsatzTV.Application.Configuration;
|
|
||||||
|
|
||||||
public record GetPlayoutDaysToBuild : IRequest<int>;
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using ErsatzTV.Core.Domain;
|
|
||||||
using ErsatzTV.Core.Interfaces.Repositories;
|
|
||||||
|
|
||||||
namespace ErsatzTV.Application.Configuration;
|
|
||||||
|
|
||||||
public class GetPlayoutDaysToBuildHandler : IRequestHandler<GetPlayoutDaysToBuild, int>
|
|
||||||
{
|
|
||||||
private readonly IConfigElementRepository _configElementRepository;
|
|
||||||
|
|
||||||
public GetPlayoutDaysToBuildHandler(IConfigElementRepository configElementRepository) =>
|
|
||||||
_configElementRepository = configElementRepository;
|
|
||||||
|
|
||||||
public Task<int> Handle(GetPlayoutDaysToBuild request, CancellationToken cancellationToken) =>
|
|
||||||
_configElementRepository.GetValue<int>(ConfigElementKey.PlayoutDaysToBuild)
|
|
||||||
.Map(result => result.IfNone(2));
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace ErsatzTV.Application.Configuration;
|
||||||
|
|
||||||
|
public record GetPlayoutSettings : IRequest<PlayoutSettingsViewModel>;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
using ErsatzTV.Core.Interfaces.Repositories;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Configuration;
|
||||||
|
|
||||||
|
public class GetPlayoutSettingsHandler : IRequestHandler<GetPlayoutSettings, PlayoutSettingsViewModel>
|
||||||
|
{
|
||||||
|
private readonly IConfigElementRepository _configElementRepository;
|
||||||
|
|
||||||
|
public GetPlayoutSettingsHandler(IConfigElementRepository configElementRepository) =>
|
||||||
|
_configElementRepository = configElementRepository;
|
||||||
|
|
||||||
|
public async Task<PlayoutSettingsViewModel> Handle(GetPlayoutSettings request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Option<int> daysToBuild = await _configElementRepository.GetValue<int>(ConfigElementKey.PlayoutDaysToBuild);
|
||||||
|
|
||||||
|
Option<bool> skipMissingItems =
|
||||||
|
await _configElementRepository.GetValue<bool>(ConfigElementKey.PlayoutSkipMissingItems);
|
||||||
|
|
||||||
|
return new PlayoutSettingsViewModel
|
||||||
|
{
|
||||||
|
DaysToBuild = await daysToBuild.IfNoneAsync(2),
|
||||||
|
SkipMissingItems = await skipMissingItems.IfNoneAsync(false)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -70,6 +70,180 @@ public class PlayoutBuilderTests
|
|||||||
result.Items.Head().FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6));
|
result.Items.Head().FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Timeout(2000)]
|
||||||
|
public async Task OnlyFileNotFoundItem_Should_Abort()
|
||||||
|
{
|
||||||
|
var mediaItems = new List<MediaItem>
|
||||||
|
{
|
||||||
|
TestMovie(1, TimeSpan.FromHours(1), DateTime.Today)
|
||||||
|
};
|
||||||
|
|
||||||
|
mediaItems[0].State = MediaItemState.FileNotFound;
|
||||||
|
|
||||||
|
var configRepo = new Mock<IConfigElementRepository>();
|
||||||
|
configRepo.Setup(
|
||||||
|
repo => repo.GetValue<bool>(
|
||||||
|
It.Is<ConfigElementKey>(k => k.Key == ConfigElementKey.PlayoutSkipMissingItems.Key)))
|
||||||
|
.ReturnsAsync(Some(true));
|
||||||
|
|
||||||
|
(PlayoutBuilder builder, Playout playout) =
|
||||||
|
TestDataFloodForItems(mediaItems, PlaybackOrder.Random, configRepo);
|
||||||
|
|
||||||
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset);
|
||||||
|
|
||||||
|
configRepo.Verify();
|
||||||
|
|
||||||
|
result.Items.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task FileNotFoundItem_Should_BeSkipped()
|
||||||
|
{
|
||||||
|
var mediaItems = new List<MediaItem>
|
||||||
|
{
|
||||||
|
TestMovie(1, TimeSpan.FromHours(1), DateTime.Today),
|
||||||
|
TestMovie(2, TimeSpan.FromHours(6), DateTime.Today)
|
||||||
|
};
|
||||||
|
|
||||||
|
mediaItems[0].State = MediaItemState.FileNotFound;
|
||||||
|
|
||||||
|
var configRepo = new Mock<IConfigElementRepository>();
|
||||||
|
configRepo.Setup(
|
||||||
|
repo => repo.GetValue<bool>(
|
||||||
|
It.Is<ConfigElementKey>(k => k.Key == ConfigElementKey.PlayoutSkipMissingItems.Key)))
|
||||||
|
.ReturnsAsync(Some(true));
|
||||||
|
|
||||||
|
(PlayoutBuilder builder, Playout playout) =
|
||||||
|
TestDataFloodForItems(mediaItems, PlaybackOrder.Random, configRepo);
|
||||||
|
DateTimeOffset start = HoursAfterMidnight(0);
|
||||||
|
DateTimeOffset finish = start + TimeSpan.FromHours(6);
|
||||||
|
|
||||||
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish);
|
||||||
|
|
||||||
|
configRepo.Verify();
|
||||||
|
|
||||||
|
result.Items.Count.Should().Be(1);
|
||||||
|
result.Items.Head().MediaItemId.Should().Be(2);
|
||||||
|
result.Items.Head().StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero);
|
||||||
|
result.Items.Head().FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Timeout(2000)]
|
||||||
|
public async Task OnlyUnavailableItem_Should_Abort()
|
||||||
|
{
|
||||||
|
var mediaItems = new List<MediaItem>
|
||||||
|
{
|
||||||
|
TestMovie(1, TimeSpan.FromHours(1), DateTime.Today)
|
||||||
|
};
|
||||||
|
|
||||||
|
mediaItems[0].State = MediaItemState.Unavailable;
|
||||||
|
|
||||||
|
var configRepo = new Mock<IConfigElementRepository>();
|
||||||
|
configRepo.Setup(
|
||||||
|
repo => repo.GetValue<bool>(
|
||||||
|
It.Is<ConfigElementKey>(k => k.Key == ConfigElementKey.PlayoutSkipMissingItems.Key)))
|
||||||
|
.ReturnsAsync(Some(true));
|
||||||
|
|
||||||
|
(PlayoutBuilder builder, Playout playout) =
|
||||||
|
TestDataFloodForItems(mediaItems, PlaybackOrder.Random, configRepo);
|
||||||
|
|
||||||
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset);
|
||||||
|
|
||||||
|
configRepo.Verify();
|
||||||
|
|
||||||
|
result.Items.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task UnavailableItem_Should_BeSkipped()
|
||||||
|
{
|
||||||
|
var mediaItems = new List<MediaItem>
|
||||||
|
{
|
||||||
|
TestMovie(1, TimeSpan.FromHours(1), DateTime.Today),
|
||||||
|
TestMovie(2, TimeSpan.FromHours(6), DateTime.Today)
|
||||||
|
};
|
||||||
|
|
||||||
|
mediaItems[0].State = MediaItemState.Unavailable;
|
||||||
|
|
||||||
|
var configRepo = new Mock<IConfigElementRepository>();
|
||||||
|
configRepo.Setup(
|
||||||
|
repo => repo.GetValue<bool>(
|
||||||
|
It.Is<ConfigElementKey>(k => k.Key == ConfigElementKey.PlayoutSkipMissingItems.Key)))
|
||||||
|
.ReturnsAsync(Some(true));
|
||||||
|
|
||||||
|
(PlayoutBuilder builder, Playout playout) =
|
||||||
|
TestDataFloodForItems(mediaItems, PlaybackOrder.Random, configRepo);
|
||||||
|
DateTimeOffset start = HoursAfterMidnight(0);
|
||||||
|
DateTimeOffset finish = start + TimeSpan.FromHours(6);
|
||||||
|
|
||||||
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish);
|
||||||
|
|
||||||
|
configRepo.Verify();
|
||||||
|
|
||||||
|
result.Items.Count.Should().Be(1);
|
||||||
|
result.Items.Head().MediaItemId.Should().Be(2);
|
||||||
|
result.Items.Head().StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero);
|
||||||
|
result.Items.Head().FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task FileNotFound_Should_NotBeSkippedIfConfigured()
|
||||||
|
{
|
||||||
|
var mediaItems = new List<MediaItem>
|
||||||
|
{
|
||||||
|
TestMovie(1, TimeSpan.FromHours(6), DateTime.Today)
|
||||||
|
};
|
||||||
|
|
||||||
|
mediaItems[0].State = MediaItemState.FileNotFound;
|
||||||
|
|
||||||
|
var configRepo = new Mock<IConfigElementRepository>();
|
||||||
|
configRepo.Setup(
|
||||||
|
repo => repo.GetValue<bool>(
|
||||||
|
It.Is<ConfigElementKey>(k => k.Key == ConfigElementKey.PlayoutSkipMissingItems.Key)))
|
||||||
|
.ReturnsAsync(Some(false));
|
||||||
|
|
||||||
|
(PlayoutBuilder builder, Playout playout) =
|
||||||
|
TestDataFloodForItems(mediaItems, PlaybackOrder.Random, configRepo);
|
||||||
|
DateTimeOffset start = HoursAfterMidnight(0);
|
||||||
|
DateTimeOffset finish = start + TimeSpan.FromHours(6);
|
||||||
|
|
||||||
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish);
|
||||||
|
|
||||||
|
result.Items.Count.Should().Be(1);
|
||||||
|
result.Items.Head().StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero);
|
||||||
|
result.Items.Head().FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Unavailable_Should_NotBeSkippedIfConfigured()
|
||||||
|
{
|
||||||
|
var mediaItems = new List<MediaItem>
|
||||||
|
{
|
||||||
|
TestMovie(1, TimeSpan.FromHours(6), DateTime.Today)
|
||||||
|
};
|
||||||
|
|
||||||
|
mediaItems[0].State = MediaItemState.Unavailable;
|
||||||
|
|
||||||
|
var configRepo = new Mock<IConfigElementRepository>();
|
||||||
|
configRepo.Setup(
|
||||||
|
repo => repo.GetValue<bool>(
|
||||||
|
It.Is<ConfigElementKey>(k => k.Key == ConfigElementKey.PlayoutSkipMissingItems.Key)))
|
||||||
|
.ReturnsAsync(Some(false));
|
||||||
|
|
||||||
|
(PlayoutBuilder builder, Playout playout) =
|
||||||
|
TestDataFloodForItems(mediaItems, PlaybackOrder.Random, configRepo);
|
||||||
|
DateTimeOffset start = HoursAfterMidnight(0);
|
||||||
|
DateTimeOffset finish = start + TimeSpan.FromHours(6);
|
||||||
|
|
||||||
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish);
|
||||||
|
|
||||||
|
result.Items.Count.Should().Be(1);
|
||||||
|
result.Items.Head().StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero);
|
||||||
|
result.Items.Head().FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task InitialFlood_Should_StartAtMidnight()
|
public async Task InitialFlood_Should_StartAtMidnight()
|
||||||
{
|
{
|
||||||
@@ -2142,11 +2316,20 @@ public class PlayoutBuilderTests
|
|||||||
MovieMetadata = new List<MovieMetadata> { new() { ReleaseDate = aired } },
|
MovieMetadata = new List<MovieMetadata> { new() { ReleaseDate = aired } },
|
||||||
MediaVersions = new List<MediaVersion>
|
MediaVersions = new List<MediaVersion>
|
||||||
{
|
{
|
||||||
new() { Duration = duration }
|
new()
|
||||||
|
{
|
||||||
|
Duration = duration, MediaFiles = new List<MediaFile>
|
||||||
|
{
|
||||||
|
new() { Path = $"/fake/path/{id}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private TestData TestDataFloodForItems(List<MediaItem> mediaItems, PlaybackOrder playbackOrder)
|
private TestData TestDataFloodForItems(
|
||||||
|
List<MediaItem> mediaItems,
|
||||||
|
PlaybackOrder playbackOrder,
|
||||||
|
Mock<IConfigElementRepository> configMock = null)
|
||||||
{
|
{
|
||||||
var mediaCollection = new Collection
|
var mediaCollection = new Collection
|
||||||
{
|
{
|
||||||
@@ -2154,7 +2337,8 @@ public class PlayoutBuilderTests
|
|||||||
MediaItems = mediaItems
|
MediaItems = mediaItems
|
||||||
};
|
};
|
||||||
|
|
||||||
var configRepo = new Mock<IConfigElementRepository>();
|
Mock<IConfigElementRepository> configRepo = configMock ?? new Mock<IConfigElementRepository>();
|
||||||
|
|
||||||
var collectionRepo = new FakeMediaCollectionRepository(Map((mediaCollection.Id, mediaItems)));
|
var collectionRepo = new FakeMediaCollectionRepository(Map((mediaCollection.Id, mediaItems)));
|
||||||
var televisionRepo = new FakeTelevisionRepository();
|
var televisionRepo = new FakeTelevisionRepository();
|
||||||
var artistRepo = new Mock<IArtistRepository>();
|
var artistRepo = new Mock<IArtistRepository>();
|
||||||
|
|||||||
@@ -33,4 +33,5 @@ public class ConfigElementKey
|
|||||||
public static ConfigElementKey FillerPresetsPageSize => new("pages.filler_presets.page_size");
|
public static ConfigElementKey FillerPresetsPageSize => new("pages.filler_presets.page_size");
|
||||||
public static ConfigElementKey LibraryRefreshInterval => new("scanner.library_refresh_interval");
|
public static ConfigElementKey LibraryRefreshInterval => new("scanner.library_refresh_interval");
|
||||||
public static ConfigElementKey PlayoutDaysToBuild => new("playout.days_to_build");
|
public static ConfigElementKey PlayoutDaysToBuild => new("playout.days_to_build");
|
||||||
|
public static ConfigElementKey PlayoutSkipMissingItems => new("playout.skip_missing_items");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
|
using ErsatzTV.Core.Extensions;
|
||||||
using ErsatzTV.Core.Interfaces.Repositories;
|
using ErsatzTV.Core.Interfaces.Repositories;
|
||||||
using ErsatzTV.Core.Interfaces.Scheduling;
|
using ErsatzTV.Core.Interfaces.Scheduling;
|
||||||
using LanguageExt.UnsafeValueAccess;
|
using LanguageExt.UnsafeValueAccess;
|
||||||
@@ -234,7 +235,13 @@ public class PlayoutBuilder : IPlayoutBuilder
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Option<CollectionKey> maybeEmptyCollection = await CheckForEmptyCollections(collectionMediaItems);
|
Option<bool> skipMissingItems =
|
||||||
|
await _configElementRepository.GetValue<bool>(ConfigElementKey.PlayoutSkipMissingItems);
|
||||||
|
|
||||||
|
Option<CollectionKey> maybeEmptyCollection = await CheckForEmptyCollections(
|
||||||
|
collectionMediaItems,
|
||||||
|
await skipMissingItems.IfNoneAsync(false));
|
||||||
|
|
||||||
foreach (CollectionKey emptyCollection in maybeEmptyCollection)
|
foreach (CollectionKey emptyCollection in maybeEmptyCollection)
|
||||||
{
|
{
|
||||||
Option<string> maybeName = await _mediaCollectionRepository.GetNameFromKey(emptyCollection);
|
Option<string> maybeName = await _mediaCollectionRepository.GetNameFromKey(emptyCollection);
|
||||||
@@ -496,12 +503,13 @@ public class PlayoutBuilder : IPlayoutBuilder
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Option<CollectionKey>> CheckForEmptyCollections(
|
private async Task<Option<CollectionKey>> CheckForEmptyCollections(
|
||||||
Map<CollectionKey, List<MediaItem>> collectionMediaItems)
|
Map<CollectionKey, List<MediaItem>> collectionMediaItems,
|
||||||
|
bool skipMissingItems)
|
||||||
{
|
{
|
||||||
foreach ((CollectionKey _, List<MediaItem> items) in collectionMediaItems)
|
foreach ((CollectionKey _, List<MediaItem> items) in collectionMediaItems)
|
||||||
{
|
{
|
||||||
var zeroItems = new List<MediaItem>();
|
var zeroItems = new List<MediaItem>();
|
||||||
// var missingItems = new List<MediaItem>();
|
var missingItems = new List<MediaItem>();
|
||||||
|
|
||||||
foreach (MediaItem item in items)
|
foreach (MediaItem item in items)
|
||||||
{
|
{
|
||||||
@@ -520,18 +528,17 @@ public class PlayoutBuilder : IPlayoutBuilder
|
|||||||
_ => true
|
_ => true
|
||||||
};
|
};
|
||||||
|
|
||||||
// if (item.State == MediaItemState.FileNotFound)
|
if (skipMissingItems && item.State is MediaItemState.FileNotFound or MediaItemState.Unavailable)
|
||||||
// {
|
{
|
||||||
// _logger.LogWarning(
|
_logger.LogWarning(
|
||||||
// "Skipping media item that does not exist on disk {MediaItem} - {MediaItemTitle} - {Path}",
|
"Skipping media item that does not exist on disk {MediaItem} - {MediaItemTitle} - {Path}",
|
||||||
// item.Id,
|
item.Id,
|
||||||
// DisplayTitle(item),
|
DisplayTitle(item),
|
||||||
// item.GetHeadVersion().MediaFiles.Head().Path);
|
item.GetHeadVersion().MediaFiles.Head().Path);
|
||||||
//
|
|
||||||
// missingItems.Add(item);
|
missingItems.Add(item);
|
||||||
// }
|
}
|
||||||
// else
|
else if (isZero)
|
||||||
if (isZero)
|
|
||||||
{
|
{
|
||||||
_logger.LogWarning(
|
_logger.LogWarning(
|
||||||
"Skipping media item with zero duration {MediaItem} - {MediaItemTitle}",
|
"Skipping media item with zero duration {MediaItem} - {MediaItemTitle}",
|
||||||
@@ -542,7 +549,7 @@ public class PlayoutBuilder : IPlayoutBuilder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// items.RemoveAll(missingItems.Contains);
|
items.RemoveAll(missingItems.Contains);
|
||||||
items.RemoveAll(zeroItems.Contains);
|
items.RemoveAll(zeroItems.Contains);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
@using ErsatzTV.Application.MediaItems
|
@using ErsatzTV.Application.MediaItems
|
||||||
@using ErsatzTV.Application.Watermarks
|
@using ErsatzTV.Application.Watermarks
|
||||||
@using System.Globalization
|
@using System.Globalization
|
||||||
@using ErsatzTV.Core.Domain.Filler
|
|
||||||
@using ErsatzTV.Application.Configuration
|
@using ErsatzTV.Application.Configuration
|
||||||
|
@using ErsatzTV.Core.Domain.Filler
|
||||||
@implements IDisposable
|
@implements IDisposable
|
||||||
@inject IMediator _mediator
|
@inject IMediator _mediator
|
||||||
@inject ISnackbar _snackbar
|
@inject ISnackbar _snackbar
|
||||||
@@ -147,12 +147,19 @@
|
|||||||
<MudForm @bind-IsValid="@_playoutSuccess">
|
<MudForm @bind-IsValid="@_playoutSuccess">
|
||||||
<MudTextField T="int"
|
<MudTextField T="int"
|
||||||
Label="Days To Build"
|
Label="Days To Build"
|
||||||
@bind-Value="_playoutDaysToBuild"
|
@bind-Value="_playoutSettings.DaysToBuild"
|
||||||
Validation="@(new Func<int, string>(ValidatePlayoutDaysToBuild))"
|
Validation="@(new Func<int, string>(ValidatePlayoutDaysToBuild))"
|
||||||
Required="true"
|
Required="true"
|
||||||
RequiredError="Days to build is required!"
|
RequiredError="Days to build is required!"
|
||||||
Adornment="Adornment.End"
|
Adornment="Adornment.End"
|
||||||
AdornmentText="Days"/>
|
AdornmentText="Days"/>
|
||||||
|
<MudElement HtmlTag="div" Class="mt-3">
|
||||||
|
<MudTooltip Text="Controls whether file-not-found or unavailable items should be included in playouts">
|
||||||
|
<MudCheckBox Label="Skip Missing Items"
|
||||||
|
@bind-Checked="_playoutSettings.SkipMissingItems"
|
||||||
|
For="@(() => _playoutSettings.SkipMissingItems)"/>
|
||||||
|
</MudTooltip>
|
||||||
|
</MudElement>
|
||||||
</MudForm>
|
</MudForm>
|
||||||
</MudCardContent>
|
</MudCardContent>
|
||||||
<MudCardActions>
|
<MudCardActions>
|
||||||
@@ -176,7 +183,7 @@
|
|||||||
private List<FillerPresetViewModel> _fillerPresets;
|
private List<FillerPresetViewModel> _fillerPresets;
|
||||||
private int _tunerCount;
|
private int _tunerCount;
|
||||||
private int _libraryRefreshInterval;
|
private int _libraryRefreshInterval;
|
||||||
private int _playoutDaysToBuild;
|
private PlayoutSettingsViewModel _playoutSettings;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
@@ -198,8 +205,8 @@
|
|||||||
_hdhrSuccess = string.IsNullOrWhiteSpace(ValidateTunerCount(_tunerCount));
|
_hdhrSuccess = string.IsNullOrWhiteSpace(ValidateTunerCount(_tunerCount));
|
||||||
_libraryRefreshInterval = await _mediator.Send(new GetLibraryRefreshInterval(), _cts.Token);
|
_libraryRefreshInterval = await _mediator.Send(new GetLibraryRefreshInterval(), _cts.Token);
|
||||||
_scannerSuccess = _libraryRefreshInterval > 0;
|
_scannerSuccess = _libraryRefreshInterval > 0;
|
||||||
_playoutDaysToBuild = await _mediator.Send(new GetPlayoutDaysToBuild(), _cts.Token);
|
_playoutSettings = await _mediator.Send(new GetPlayoutSettings(), _cts.Token);
|
||||||
_playoutSuccess = _playoutDaysToBuild > 0;
|
_playoutSuccess = _playoutSettings.DaysToBuild > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ValidatePathExists(string path) => !File.Exists(path) ? "Path does not exist" : null;
|
private static string ValidatePathExists(string path) => !File.Exists(path) ? "Path does not exist" : null;
|
||||||
@@ -257,7 +264,7 @@
|
|||||||
|
|
||||||
private async Task SavePlayoutSettings()
|
private async Task SavePlayoutSettings()
|
||||||
{
|
{
|
||||||
Either<BaseError, Unit> result = await _mediator.Send(new UpdatePlayoutDaysToBuild(_playoutDaysToBuild), _cts.Token);
|
Either<BaseError, Unit> result = await _mediator.Send(new UpdatePlayoutSettings(_playoutSettings), _cts.Token);
|
||||||
result.Match(
|
result.Match(
|
||||||
Left: error =>
|
Left: error =>
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user