more bug reporting (#679)

This commit is contained in:
Jason Dove
2022-03-05 18:21:53 -06:00
committed by GitHub
parent dcc8f19a6b
commit 44e90b0ecc
6 changed files with 171 additions and 142 deletions
@@ -1,4 +1,5 @@
using ErsatzTV.Core; using Bugsnag;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -7,10 +8,12 @@ namespace ErsatzTV.Application.Filler;
public class CreateFillerPresetHandler : IRequestHandler<CreateFillerPreset, Either<BaseError, Unit>> public class CreateFillerPresetHandler : IRequestHandler<CreateFillerPreset, Either<BaseError, Unit>>
{ {
private readonly IClient _client;
private readonly IDbContextFactory<TvContext> _dbContextFactory; private readonly IDbContextFactory<TvContext> _dbContextFactory;
public CreateFillerPresetHandler(IDbContextFactory<TvContext> dbContextFactory) public CreateFillerPresetHandler(IClient client, IDbContextFactory<TvContext> dbContextFactory)
{ {
_client = client;
_dbContextFactory = dbContextFactory; _dbContextFactory = dbContextFactory;
} }
@@ -18,7 +21,7 @@ public class CreateFillerPresetHandler : IRequestHandler<CreateFillerPreset, Eit
{ {
try try
{ {
await using TvContext dbContext = _dbContextFactory.CreateDbContext(); await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
var fillerPreset = new FillerPreset var fillerPreset = new FillerPreset
{ {
@@ -42,6 +45,7 @@ public class CreateFillerPresetHandler : IRequestHandler<CreateFillerPreset, Eit
} }
catch (Exception ex) catch (Exception ex)
{ {
_client.Notify(ex);
return BaseError.New(ex.Message); return BaseError.New(ex.Message);
} }
} }
@@ -1,4 +1,5 @@
using ErsatzTV.Core; using Bugsnag;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Interfaces.Scheduling;
using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Data;
@@ -9,26 +10,36 @@ namespace ErsatzTV.Application.Playouts;
public class BuildPlayoutHandler : MediatR.IRequestHandler<BuildPlayout, Either<BaseError, Unit>> public class BuildPlayoutHandler : MediatR.IRequestHandler<BuildPlayout, Either<BaseError, Unit>>
{ {
private readonly IClient _client;
private readonly IDbContextFactory<TvContext> _dbContextFactory; private readonly IDbContextFactory<TvContext> _dbContextFactory;
private readonly IPlayoutBuilder _playoutBuilder; private readonly IPlayoutBuilder _playoutBuilder;
public BuildPlayoutHandler(IDbContextFactory<TvContext> dbContextFactory, IPlayoutBuilder playoutBuilder) public BuildPlayoutHandler(IClient client, IDbContextFactory<TvContext> dbContextFactory, IPlayoutBuilder playoutBuilder)
{ {
_client = client;
_dbContextFactory = dbContextFactory; _dbContextFactory = dbContextFactory;
_playoutBuilder = playoutBuilder; _playoutBuilder = playoutBuilder;
} }
public async Task<Either<BaseError, Unit>> Handle(BuildPlayout request, CancellationToken cancellationToken) public async Task<Either<BaseError, Unit>> Handle(BuildPlayout request, CancellationToken cancellationToken)
{ {
await using TvContext dbContext = _dbContextFactory.CreateDbContext(); await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, Playout> validation = await Validate(dbContext, request); Validation<BaseError, Playout> validation = await Validate(dbContext, request);
return await LanguageExtensions.Apply(validation, playout => ApplyUpdateRequest(dbContext, request, playout)); return await validation.Apply(playout => ApplyUpdateRequest(dbContext, request, playout));
} }
private async Task<Unit> ApplyUpdateRequest(TvContext dbContext, BuildPlayout request, Playout playout) private async Task<Unit> ApplyUpdateRequest(TvContext dbContext, BuildPlayout request, Playout playout)
{ {
await _playoutBuilder.BuildPlayoutItems(playout, request.Rebuild); try
await dbContext.SaveChangesAsync(); {
await _playoutBuilder.BuildPlayoutItems(playout, request.Rebuild);
await dbContext.SaveChangesAsync();
}
catch (Exception ex)
{
_client.Notify(ex);
}
return Unit.Default; return Unit.Default;
} }
@@ -18,8 +18,7 @@ using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Application.Streaming; namespace ErsatzTV.Application.Streaming;
public class GetPlayoutItemProcessByChannelNumberHandler : public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<GetPlayoutItemProcessByChannelNumber>
FFmpegProcessHandler<GetPlayoutItemProcessByChannelNumber>
{ {
private readonly IEmbyPathReplacementService _embyPathReplacementService; private readonly IEmbyPathReplacementService _embyPathReplacementService;
private readonly IMediaCollectionRepository _mediaCollectionRepository; private readonly IMediaCollectionRepository _mediaCollectionRepository;
@@ -62,7 +61,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler :
string ffmpegPath) string ffmpegPath)
{ {
DateTimeOffset now = request.Now; DateTimeOffset now = request.Now;
Either<BaseError, PlayoutItemWithPath> maybePlayoutItem = await dbContext.PlayoutItems Either<BaseError, PlayoutItemWithPath> maybePlayoutItem = await dbContext.PlayoutItems
.Include(i => i.MediaItem) .Include(i => i.MediaItem)
.ThenInclude(mi => (mi as Episode).MediaVersions) .ThenInclude(mi => (mi as Episode).MediaVersions)
@@ -108,144 +107,146 @@ public class GetPlayoutItemProcessByChannelNumberHandler :
IFFmpegProcessService ffmpegProcessService = await _ffmpegProcessServiceFactory.GetService(); IFFmpegProcessService ffmpegProcessService = await _ffmpegProcessServiceFactory.GetService();
return await maybePlayoutItem.Match( foreach (PlayoutItemWithPath playoutItemWithPath in maybePlayoutItem.RightToSeq())
async playoutItemWithPath => {
MediaVersion version = playoutItemWithPath.PlayoutItem.MediaItem.GetHeadVersion();
string videoPath = playoutItemWithPath.Path;
MediaVersion videoVersion = version;
string audioPath = playoutItemWithPath.Path;
MediaVersion audioVersion = version;
Option<ChannelWatermark> maybeGlobalWatermark = await dbContext.ConfigElements
.GetValue<int>(ConfigElementKey.FFmpegGlobalWatermarkId)
.BindT(
watermarkId => dbContext.ChannelWatermarks
.SelectOneAsync(w => w.Id, w => w.Id == watermarkId));
if (playoutItemWithPath.PlayoutItem.MediaItem is Song song)
{ {
MediaVersion version = playoutItemWithPath.PlayoutItem.MediaItem.GetHeadVersion(); (videoPath, videoVersion) = await _songVideoGenerator.GenerateSongVideo(
song,
string videoPath = playoutItemWithPath.Path;
MediaVersion videoVersion = version;
string audioPath = playoutItemWithPath.Path;
MediaVersion audioVersion = version;
Option<ChannelWatermark> maybeGlobalWatermark = await dbContext.ConfigElements
.GetValue<int>(ConfigElementKey.FFmpegGlobalWatermarkId)
.BindT(
watermarkId => dbContext.ChannelWatermarks
.SelectOneAsync(w => w.Id, w => w.Id == watermarkId));
if (playoutItemWithPath.PlayoutItem.MediaItem is Song song)
{
(videoPath, videoVersion) = await _songVideoGenerator.GenerateSongVideo(
song,
channel,
maybeGlobalWatermark,
ffmpegPath);
}
bool saveReports = await dbContext.ConfigElements
.GetValue<bool>(ConfigElementKey.FFmpegSaveReports)
.Map(result => result.IfNone(false));
Process process = await ffmpegProcessService.ForPlayoutItem(
ffmpegPath,
saveReports,
channel, channel,
videoVersion,
audioVersion,
videoPath,
audioPath,
playoutItemWithPath.PlayoutItem.StartOffset,
playoutItemWithPath.PlayoutItem.FinishOffset,
request.StartAtZero ? playoutItemWithPath.PlayoutItem.StartOffset : now,
maybeGlobalWatermark, maybeGlobalWatermark,
channel.FFmpegProfile.VaapiDriver, ffmpegPath);
channel.FFmpegProfile.VaapiDevice, }
request.HlsRealtime,
playoutItemWithPath.PlayoutItem.FillerKind,
playoutItemWithPath.PlayoutItem.InPoint,
playoutItemWithPath.PlayoutItem.OutPoint,
request.PtsOffset,
request.TargetFramerate);
var result = new PlayoutItemProcessModel(process, playoutItemWithPath.PlayoutItem.FinishOffset); bool saveReports = await dbContext.ConfigElements
.GetValue<bool>(ConfigElementKey.FFmpegSaveReports)
.Map(result => result.IfNone(false));
return Right<BaseError, PlayoutItemProcessModel>(result); Process process = await ffmpegProcessService.ForPlayoutItem(
}, ffmpegPath,
async error => saveReports,
channel,
videoVersion,
audioVersion,
videoPath,
audioPath,
playoutItemWithPath.PlayoutItem.StartOffset,
playoutItemWithPath.PlayoutItem.FinishOffset,
request.StartAtZero ? playoutItemWithPath.PlayoutItem.StartOffset : now,
maybeGlobalWatermark,
channel.FFmpegProfile.VaapiDriver,
channel.FFmpegProfile.VaapiDevice,
request.HlsRealtime,
playoutItemWithPath.PlayoutItem.FillerKind,
playoutItemWithPath.PlayoutItem.InPoint,
playoutItemWithPath.PlayoutItem.OutPoint,
request.PtsOffset,
request.TargetFramerate);
var result = new PlayoutItemProcessModel(process, playoutItemWithPath.PlayoutItem.FinishOffset);
return Right<BaseError, PlayoutItemProcessModel>(result);
}
foreach (BaseError error in maybePlayoutItem.LeftToSeq())
{
var offlineTranscodeMessage =
$"offline image is unavailable because transcoding is disabled in ffmpeg profile '{channel.FFmpegProfile.Name}'";
Option<TimeSpan> maybeDuration = await Optional(channel.FFmpegProfile.Transcode)
.Where(transcode => transcode)
.Match(
_ => dbContext.PlayoutItems
.Filter(pi => pi.Playout.ChannelId == channel.Id)
.Filter(pi => pi.Start > now.UtcDateTime)
.OrderBy(pi => pi.Start)
.FirstOrDefaultAsync()
.Map(Optional)
.MapT(pi => pi.StartOffset - now),
() => Option<TimeSpan>.None.AsTask());
DateTimeOffset finish = maybeDuration.Match(d => now.Add(d), () => now);
switch (error)
{ {
var offlineTranscodeMessage = case UnableToLocatePlayoutItem:
$"offline image is unavailable because transcoding is disabled in ffmpeg profile '{channel.FFmpegProfile.Name}'"; if (channel.FFmpegProfile.Transcode)
{
Process errorProcess = await ffmpegProcessService.ForError(
ffmpegPath,
channel,
maybeDuration,
"Channel is Offline",
request.HlsRealtime,
request.PtsOffset);
Option<TimeSpan> maybeDuration = await Optional(channel.FFmpegProfile.Transcode) return new PlayoutItemProcessModel(errorProcess, finish);
.Where(transcode => transcode) }
.Match( else
_ => dbContext.PlayoutItems {
.Filter(pi => pi.Playout.ChannelId == channel.Id) var message =
.Filter(pi => pi.Start > now.UtcDateTime) $"Unable to locate playout item for channel {channel.Number}; {offlineTranscodeMessage}";
.OrderBy(pi => pi.Start)
.FirstOrDefaultAsync()
.Map(Optional)
.MapT(pi => pi.StartOffset - now),
() => Option<TimeSpan>.None.AsTask());
DateTimeOffset finish = maybeDuration.Match(d => now.Add(d), () => now); return BaseError.New(message);
}
case PlayoutItemDoesNotExistOnDisk:
if (channel.FFmpegProfile.Transcode)
{
Process errorProcess = await ffmpegProcessService.ForError(
ffmpegPath,
channel,
maybeDuration,
error.Value,
request.HlsRealtime,
request.PtsOffset);
switch (error) return new PlayoutItemProcessModel(errorProcess, finish);
{ }
case UnableToLocatePlayoutItem: else
if (channel.FFmpegProfile.Transcode) {
{ var message =
Process errorProcess = await ffmpegProcessService.ForError( $"Playout item does not exist on disk for channel {channel.Number}; {offlineTranscodeMessage}";
ffmpegPath,
channel,
maybeDuration,
"Channel is Offline",
request.HlsRealtime,
request.PtsOffset);
return new PlayoutItemProcessModel(errorProcess, finish);
}
else
{
var message =
$"Unable to locate playout item for channel {channel.Number}; {offlineTranscodeMessage}";
return BaseError.New(message); return BaseError.New(message);
} }
case PlayoutItemDoesNotExistOnDisk: default:
if (channel.FFmpegProfile.Transcode) if (channel.FFmpegProfile.Transcode)
{ {
Process errorProcess = await ffmpegProcessService.ForError( Process errorProcess = await ffmpegProcessService.ForError(
ffmpegPath, ffmpegPath,
channel, channel,
maybeDuration, maybeDuration,
error.Value, "Channel is Offline",
request.HlsRealtime, request.HlsRealtime,
request.PtsOffset); request.PtsOffset);
return new PlayoutItemProcessModel(errorProcess, finish); return new PlayoutItemProcessModel(errorProcess, finish);
} }
else else
{ {
var message = var message =
$"Playout item does not exist on disk for channel {channel.Number}; {offlineTranscodeMessage}"; $"Unexpected error locating playout item for channel {channel.Number}; {offlineTranscodeMessage}";
return BaseError.New(message); return BaseError.New(message);
} }
default: }
if (channel.FFmpegProfile.Transcode) }
{
Process errorProcess = await ffmpegProcessService.ForError(
ffmpegPath,
channel,
maybeDuration,
"Channel is Offline",
request.HlsRealtime,
request.PtsOffset);
return new PlayoutItemProcessModel(errorProcess, finish); return BaseError.New($"Unexpected error locating playout item for channel {channel.Number}");
}
else
{
var message =
$"Unexpected error locating playout item for channel {channel.Number}; {offlineTranscodeMessage}";
return BaseError.New(message);
}
}
});
} }
private async Task<Either<BaseError, PlayoutItemWithPath>> CheckForFallbackFiller( private async Task<Either<BaseError, PlayoutItemWithPath>> CheckForFallbackFiller(
@@ -277,7 +277,7 @@ public class TranscodingTests
var localStatisticsProvider = new LocalStatisticsProvider( var localStatisticsProvider = new LocalStatisticsProvider(
metadataRepository.Object, metadataRepository.Object,
new LocalFileSystem(LoggerFactory.CreateLogger<LocalFileSystem>()), new LocalFileSystem(new Mock<IClient>().Object, LoggerFactory.CreateLogger<LocalFileSystem>()),
new Mock<IClient>().Object, new Mock<IClient>().Object,
LoggerFactory.CreateLogger<LocalStatisticsProvider>()); LoggerFactory.CreateLogger<LocalStatisticsProvider>());
+10 -4
View File
@@ -1,4 +1,5 @@
using ErsatzTV.Core.Domain; using Bugsnag;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Metadata;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -6,10 +7,12 @@ namespace ErsatzTV.Core.Metadata;
public class LocalFileSystem : ILocalFileSystem public class LocalFileSystem : ILocalFileSystem
{ {
private readonly IClient _client;
private readonly ILogger<LocalFileSystem> _logger; private readonly ILogger<LocalFileSystem> _logger;
public LocalFileSystem(ILogger<LocalFileSystem> logger) public LocalFileSystem(IClient client, ILogger<LocalFileSystem> logger)
{ {
_client = client;
_logger = logger; _logger = logger;
} }
@@ -44,9 +47,10 @@ public class LocalFileSystem : ILocalFileSystem
{ {
return Directory.EnumerateDirectories(folder); return Directory.EnumerateDirectories(folder);
} }
catch catch (Exception ex)
{ {
// do nothing // do nothing
_client.Notify(ex);
} }
} }
@@ -61,9 +65,10 @@ public class LocalFileSystem : ILocalFileSystem
{ {
return Directory.EnumerateFiles(folder, "*", SearchOption.TopDirectoryOnly); return Directory.EnumerateFiles(folder, "*", SearchOption.TopDirectoryOnly);
} }
catch catch (Exception ex)
{ {
// do nothing // do nothing
_client.Notify(ex);
} }
} }
@@ -92,6 +97,7 @@ public class LocalFileSystem : ILocalFileSystem
} }
catch (Exception ex) catch (Exception ex)
{ {
_client.Notify(ex);
return BaseError.New(ex.ToString()); return BaseError.New(ex.ToString());
} }
} }
@@ -18,6 +18,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
private readonly ILocalMetadataProvider _localMetadataProvider; private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly ILogger<TelevisionFolderScanner> _logger; private readonly ILogger<TelevisionFolderScanner> _logger;
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly IClient _client;
private readonly IMetadataRepository _metadataRepository; private readonly IMetadataRepository _metadataRepository;
private readonly ISearchIndex _searchIndex; private readonly ISearchIndex _searchIndex;
private readonly ISearchRepository _searchRepository; private readonly ISearchRepository _searchRepository;
@@ -57,6 +58,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
_searchRepository = searchRepository; _searchRepository = searchRepository;
_libraryRepository = libraryRepository; _libraryRepository = libraryRepository;
_mediator = mediator; _mediator = mediator;
_client = client;
_logger = logger; _logger = logger;
} }
@@ -287,6 +289,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
} }
catch (Exception ex) catch (Exception ex)
{ {
_client.Notify(ex);
return BaseError.New(ex.ToString()); return BaseError.New(ex.ToString());
} }
} }
@@ -348,6 +351,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
} }
catch (Exception ex) catch (Exception ex)
{ {
_client.Notify(ex);
return BaseError.New(ex.ToString()); return BaseError.New(ex.ToString());
} }
} }
@@ -371,6 +375,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
} }
catch (Exception ex) catch (Exception ex)
{ {
_client.Notify(ex);
return BaseError.New(ex.ToString()); return BaseError.New(ex.ToString());
} }
} }
@@ -390,6 +395,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
} }
catch (Exception ex) catch (Exception ex)
{ {
_client.Notify(ex);
return BaseError.New(ex.ToString()); return BaseError.New(ex.ToString());
} }
} }
@@ -411,6 +417,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
} }
catch (Exception ex) catch (Exception ex)
{ {
_client.Notify(ex);
return BaseError.New(ex.ToString()); return BaseError.New(ex.ToString());
} }
} }