Browse Source

chore: address four TODOs asking for logging and validation (#2964)

* fix: improve validation and error logging in playout failure paths

- log and throw a clear error when an external json channel has no start
  time, in both channel data refresh and playout item lookup
- log a warning for each unmet precondition when remote plex streaming
  cannot proceed (unknown server, no active connection, no auth token)
- return a validation error when playout alternate schedule items are empty
- log a warning when an unsupported playback order falls back to random

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* tweaks

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Jason Dove <1695733+jasongdove@users.noreply.github.com>
pull/3012/head
Ministorm3 6 days ago committed by GitHub
parent
commit
4c3683ae88
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 15
      ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs
  2. 5
      ErsatzTV.Application/Playouts/Commands/ReplacePlayoutAlternateScheduleItemsHandler.cs
  3. 2
      ErsatzTV.Core/Scheduling/PlayoutBuilder.cs
  4. 41
      ErsatzTV.Infrastructure/Streaming/ExternalJsonPlayoutItemProvider.cs

15
ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs

@ -1194,12 +1194,17 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -1194,12 +1194,17 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
// must deserialize channel from json
foreach (ExternalJsonChannel channel in maybeChannel)
{
// TODO: null start time should log and throw
if (!DateTimeOffset.TryParse(
channel.StartTime,
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal,
out DateTimeOffset parsed))
{
throw new InvalidOperationException(
$"External json channel in file {path} has an invalid start time '{channel.StartTime}'");
}
DateTimeOffset startTime = DateTimeOffset.Parse(
channel.StartTime ?? string.Empty,
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal).ToLocalTime();
DateTimeOffset startTime = parsed.ToLocalTime();
for (var i = 0; i < channel.Programs.Length; i++)
{

5
ErsatzTV.Application/Playouts/Commands/ReplacePlayoutAlternateScheduleItemsHandler.cs

@ -20,7 +20,10 @@ public class ReplacePlayoutAlternateScheduleItemsHandler( @@ -20,7 +20,10 @@ public class ReplacePlayoutAlternateScheduleItemsHandler(
ReplacePlayoutAlternateScheduleItems request,
CancellationToken cancellationToken)
{
// TODO: validate that items is not empty
if (request.Items.Count == 0)
{
return BaseError.New("Playout alternate schedule items must not be empty");
}
try
{

2
ErsatzTV.Core/Scheduling/PlayoutBuilder.cs

@ -1448,10 +1448,10 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -1448,10 +1448,10 @@ public class PlayoutBuilder : IPlayoutBuilder
}
// fall through to default case if we can't make the proper enumerator
_logger.LogWarning("Unable to build marathon enumerator; falling back to random");
goto default;
default:
// TODO: handle this error case differently?
return new RandomizedMediaCollectionEnumerator(mediaItems, state);
}
}

41
ErsatzTV.Infrastructure/Streaming/ExternalJsonPlayoutItemProvider.cs

@ -93,12 +93,21 @@ public class ExternalJsonPlayoutItemProvider : IExternalJsonPlayoutItemProvider @@ -93,12 +93,21 @@ public class ExternalJsonPlayoutItemProvider : IExternalJsonPlayoutItemProvider
// must deserialize channel from json
foreach (ExternalJsonChannel channel in maybeChannel)
{
// TODO: null start time should log and throw
if (!DateTimeOffset.TryParse(
channel.StartTime,
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal,
out DateTimeOffset parsed))
{
_logger.LogError(
"External json channel in file {ScheduleFile} has an invalid start time {StartTime}",
playout.ScheduleFile,
channel.StartTime);
return new UnableToLocatePlayoutItem();
}
DateTimeOffset startTime = DateTimeOffset.Parse(
channel.StartTime ?? string.Empty,
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal).ToLocalTime();
DateTimeOffset startTime = parsed.ToLocalTime();
//_logger.LogDebug("external json start time: {StartTime}", startTime);
@ -208,14 +217,35 @@ public class ExternalJsonPlayoutItemProvider : IExternalJsonPlayoutItemProvider @@ -208,14 +217,35 @@ public class ExternalJsonPlayoutItemProvider : IExternalJsonPlayoutItemProvider
.Include(pms => pms.Connections)
.SelectOneAsync(pms => pms.ServerName, pms => pms.ServerName == program.ServerKey, cancellationToken);
if (maybeServer.IsNone)
{
_logger.LogWarning(
"Unable to stream remotely; no Plex server found with server name {ServerName}",
program.ServerKey);
}
foreach (PlexMediaSource server in maybeServer)
{
Option<PlexConnection> maybeConnection = server.Connections.SingleOrDefault(c => c.IsActive);
if (maybeConnection.IsNone)
{
_logger.LogWarning(
"Unable to stream remotely; Plex server {ServerName} has no active connection",
server.ServerName);
}
foreach (PlexConnection connection in maybeConnection)
{
Option<PlexServerAuthToken> maybeToken =
await _plexSecretStore.GetServerAuthToken(server.ClientIdentifier);
if (maybeToken.IsNone)
{
_logger.LogWarning(
"Unable to stream remotely; Plex server {ServerName} has no auth token",
server.ServerName);
}
foreach (PlexServerAuthToken token in maybeToken)
{
MediaItem mediaItem = program.Type switch
@ -241,7 +271,6 @@ public class ExternalJsonPlayoutItemProvider : IExternalJsonPlayoutItemProvider @@ -241,7 +271,6 @@ public class ExternalJsonPlayoutItemProvider : IExternalJsonPlayoutItemProvider
}
}
// TODO: log errors?
return new UnableToLocatePlayoutItem();
}

Loading…
Cancel
Save