252 lines
8.5 KiB
C#
252 lines
8.5 KiB
C#
using System.Text.Json;
|
|
using MBN_STOCK_WEBVIEW.Core.Playout;
|
|
using MBN_STOCK_WEBVIEW.Core.Playout.Scenes;
|
|
using MBN_STOCK_WEBVIEW.Infrastructure;
|
|
|
|
namespace MBN_STOCK_WEBVIEW;
|
|
|
|
public sealed partial class MainWindow
|
|
{
|
|
private async Task HandlePlaylistPagePlansRequestAsync(JsonElement payload)
|
|
{
|
|
var parsed = PlayoutBridgeProtocol.ParsePagePlanRequest(payload);
|
|
var request = parsed.Request;
|
|
if (!parsed.IsValid)
|
|
{
|
|
PostPlaylistPagePlansError(
|
|
request.RequestId,
|
|
"INVALID_REQUEST",
|
|
parsed.Error,
|
|
retryable: false);
|
|
return;
|
|
}
|
|
|
|
var workflow = _playoutWorkflow;
|
|
var engine = _playoutEngine;
|
|
if (workflow is null || engine is null)
|
|
{
|
|
PostPlaylistPagePlansError(
|
|
request.RequestId,
|
|
"PLAYOUT_UNAVAILABLE",
|
|
_playoutInitializationError ??
|
|
"The playout workflow is not available for page preflight.",
|
|
retryable: false);
|
|
return;
|
|
}
|
|
|
|
var requestCancellation = CancellationTokenSource.CreateLinkedTokenSource(
|
|
_lifetimeCancellation.Token);
|
|
var requestToken = requestCancellation.Token;
|
|
var browserGeneration = Volatile.Read(ref _playlistPagePlanBrowserGeneration);
|
|
var previousCancellation = Interlocked.Exchange(
|
|
ref _playlistPagePlanCancellation,
|
|
requestCancellation);
|
|
CancelRequest(previousCancellation);
|
|
|
|
var databaseActivityEntered = false;
|
|
try
|
|
{
|
|
if (Volatile.Read(ref _playoutCommandInFlight) != 0)
|
|
{
|
|
PostPlaylistPagePlansErrorIfCurrent(
|
|
request.RequestId,
|
|
"PLAYOUT_PRIORITY",
|
|
"A playout command has priority over page preflight.",
|
|
retryable: true,
|
|
requestCancellation,
|
|
browserGeneration);
|
|
return;
|
|
}
|
|
|
|
if (!IsPlaylistPagePlanIdle(workflow, engine))
|
|
{
|
|
PostPlaylistPagePlansErrorIfCurrent(
|
|
request.RequestId,
|
|
"PLAYOUT_NOT_IDLE",
|
|
"Page preflight is available only while no scene is prepared or on air.",
|
|
retryable: true,
|
|
requestCancellation,
|
|
browserGeneration);
|
|
return;
|
|
}
|
|
|
|
await _databaseActivityGate.WaitAsync(requestToken);
|
|
databaseActivityEntered = true;
|
|
requestToken.ThrowIfCancellationRequested();
|
|
|
|
if (Volatile.Read(ref _playoutCommandInFlight) != 0)
|
|
{
|
|
PostPlaylistPagePlansErrorIfCurrent(
|
|
request.RequestId,
|
|
"PLAYOUT_PRIORITY",
|
|
"A playout command cancelled page preflight before the database read.",
|
|
retryable: true,
|
|
requestCancellation,
|
|
browserGeneration);
|
|
return;
|
|
}
|
|
|
|
if (!IsPlaylistPagePlanIdle(workflow, engine))
|
|
{
|
|
PostPlaylistPagePlansErrorIfCurrent(
|
|
request.RequestId,
|
|
"PLAYOUT_NOT_IDLE",
|
|
"Playout became active before page preflight could start.",
|
|
retryable: true,
|
|
requestCancellation,
|
|
browserGeneration);
|
|
return;
|
|
}
|
|
|
|
var plans = await workflow.PreflightPagePlansAsync(
|
|
request.Entries!,
|
|
requestToken);
|
|
requestToken.ThrowIfCancellationRequested();
|
|
if (!IsCurrentPlaylistPagePlanRequest(requestCancellation, browserGeneration))
|
|
{
|
|
return;
|
|
}
|
|
|
|
PostMessage("playlist-page-plans-results", new
|
|
{
|
|
requestId = request.RequestId,
|
|
calculatedAt = DateTimeOffset.UtcNow,
|
|
plans = plans.Select(plan => new
|
|
{
|
|
entryId = plan.EntryId,
|
|
itemCount = plan.ItemCount,
|
|
pageSize = plan.PageSize,
|
|
pageCount = plan.PageCount,
|
|
accessibleItemCount = plan.AccessibleItemCount,
|
|
isTruncated = plan.IsTruncated,
|
|
builderKey = plan.BuilderKey
|
|
})
|
|
});
|
|
}
|
|
catch (OperationCanceledException) when (requestToken.IsCancellationRequested)
|
|
{
|
|
if (Volatile.Read(ref _playoutCommandInFlight) != 0)
|
|
{
|
|
PostPlaylistPagePlansErrorIfCurrent(
|
|
request.RequestId,
|
|
"PLAYOUT_PRIORITY",
|
|
"A playout command cancelled the read-only page preflight.",
|
|
retryable: true,
|
|
requestCancellation,
|
|
browserGeneration);
|
|
}
|
|
}
|
|
catch (DatabaseOperationException exception)
|
|
{
|
|
PostPlaylistPagePlansErrorIfCurrent(
|
|
request.RequestId,
|
|
"DATABASE_UNAVAILABLE",
|
|
exception.Message,
|
|
retryable: true,
|
|
requestCancellation,
|
|
browserGeneration);
|
|
}
|
|
catch (LegacySceneDataException exception)
|
|
{
|
|
PostPlaylistPagePlansErrorIfCurrent(
|
|
request.RequestId,
|
|
"SCENE_DATA_INVALID",
|
|
exception.Message,
|
|
retryable: false,
|
|
requestCancellation,
|
|
browserGeneration);
|
|
}
|
|
catch (InvalidOperationException exception)
|
|
{
|
|
PostPlaylistPagePlansErrorIfCurrent(
|
|
request.RequestId,
|
|
"PLAYOUT_NOT_IDLE",
|
|
exception.Message,
|
|
retryable: true,
|
|
requestCancellation,
|
|
browserGeneration);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
PostPlaylistPagePlansErrorIfCurrent(
|
|
request.RequestId,
|
|
"PAGE_PREFLIGHT_FAILED",
|
|
exception.Message,
|
|
retryable: false,
|
|
requestCancellation,
|
|
browserGeneration);
|
|
}
|
|
finally
|
|
{
|
|
if (databaseActivityEntered)
|
|
{
|
|
_databaseActivityGate.Release();
|
|
}
|
|
|
|
Interlocked.CompareExchange(
|
|
ref _playlistPagePlanCancellation,
|
|
null,
|
|
requestCancellation);
|
|
requestCancellation.Dispose();
|
|
}
|
|
}
|
|
|
|
private static bool IsPlaylistPagePlanIdle(
|
|
LegacyPlayoutWorkflow workflow,
|
|
IPlayoutEngine engine)
|
|
{
|
|
var workflowState = workflow.State;
|
|
var engineStatus = engine.Status;
|
|
return string.IsNullOrWhiteSpace(workflowState.PreparedCutCode) &&
|
|
string.IsNullOrWhiteSpace(workflowState.OnAirCutCode) &&
|
|
string.IsNullOrWhiteSpace(engineStatus.PreparedSceneName) &&
|
|
string.IsNullOrWhiteSpace(engineStatus.OnAirSceneName);
|
|
}
|
|
|
|
private bool IsCurrentPlaylistPagePlanRequest(
|
|
CancellationTokenSource requestCancellation,
|
|
int browserGeneration) =>
|
|
ReferenceEquals(
|
|
Volatile.Read(ref _playlistPagePlanCancellation),
|
|
requestCancellation) &&
|
|
browserGeneration == Volatile.Read(ref _playlistPagePlanBrowserGeneration) &&
|
|
!_lifetimeCancellation.IsCancellationRequested;
|
|
|
|
private void PostPlaylistPagePlansErrorIfCurrent(
|
|
string requestId,
|
|
string code,
|
|
string message,
|
|
bool retryable,
|
|
CancellationTokenSource requestCancellation,
|
|
int browserGeneration)
|
|
{
|
|
if (!IsCurrentPlaylistPagePlanRequest(requestCancellation, browserGeneration))
|
|
{
|
|
return;
|
|
}
|
|
|
|
PostPlaylistPagePlansError(requestId, code, message, retryable);
|
|
}
|
|
|
|
private void PostPlaylistPagePlansError(
|
|
string requestId,
|
|
string code,
|
|
string message,
|
|
bool retryable)
|
|
{
|
|
PostMessage("playlist-page-plans-error", new
|
|
{
|
|
requestId,
|
|
code,
|
|
message,
|
|
retryable
|
|
});
|
|
}
|
|
|
|
private void InvalidatePlaylistPagePlanBrowserRequest()
|
|
{
|
|
Interlocked.Increment(ref _playlistPagePlanBrowserGeneration);
|
|
CancelRequest(Interlocked.Exchange(ref _playlistPagePlanCancellation, null));
|
|
}
|
|
}
|