From a7e59950d86a78a0ac797d837d700c3cfa54dd92 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 17 Dec 2024 15:11:27 +0000 Subject: [PATCH] chore: refactor --- packages/mos-gateway/src/mosHandler.ts | 2 +- packages/mos-gateway/src/mosStatus/diff.ts | 161 +++++++++++++++++ .../handler.ts} | 171 +----------------- 3 files changed, 169 insertions(+), 165 deletions(-) create mode 100644 packages/mos-gateway/src/mosStatus/diff.ts rename packages/mos-gateway/src/{mosStatusHandler.ts => mosStatus/handler.ts} (54%) diff --git a/packages/mos-gateway/src/mosHandler.ts b/packages/mos-gateway/src/mosHandler.ts index 989a479049..dacfe192e7 100644 --- a/packages/mos-gateway/src/mosHandler.ts +++ b/packages/mos-gateway/src/mosHandler.ts @@ -38,7 +38,7 @@ import { MosGatewayConfig } from './generated/options' import { MosDeviceConfig } from './generated/devices' import { PeripheralDeviceForDevice } from '@sofie-automation/server-core-integration' import _ = require('underscore') -import { MosStatusHandler } from './mosStatusHandler' +import { MosStatusHandler } from './mosStatus/handler' import { isPromise } from 'util/types' export interface MosConfig { diff --git a/packages/mos-gateway/src/mosStatus/diff.ts b/packages/mos-gateway/src/mosStatus/diff.ts new file mode 100644 index 0000000000..3952b1fbcb --- /dev/null +++ b/packages/mos-gateway/src/mosStatus/diff.ts @@ -0,0 +1,161 @@ +import { IMOSObjectStatus } from '@mos-connection/connector' +import type { MosDeviceStatusesConfig } from '../generated/devices' +import { + IngestPartPlaybackStatus, + type IngestPartStatus, + type IngestRundownStatus, +} from '@sofie-automation/shared-lib/dist/ingest/rundownStatus' + +export const MOS_STATUS_UNKNOWN = '' as IMOSObjectStatus // Force the status to be empty, which isn't a valid state in the enum + +export type SomeStatusEntry = StoryStatusEntry | ItemStatusEntry + +export interface ItemStatusEntry { + type: 'item' + rundownExternalId: string + storyId: string + itemId: string + mosStatus: IMOSObjectStatus +} + +export interface StoryStatusEntry { + type: 'story' + rundownExternalId: string + storyId: string + mosStatus: IMOSObjectStatus +} + +export function diffStatuses( + config: MosDeviceStatusesConfig, + previousStatuses: IngestRundownStatus | undefined, + newStatuses: IngestRundownStatus | undefined +): SomeStatusEntry[] { + const rundownExternalId = previousStatuses?.externalId ?? newStatuses?.externalId + + if ((!previousStatuses && !newStatuses) || !rundownExternalId) return [] + + const statuses: SomeStatusEntry[] = [] + + const previousStories = buildStoriesMap(previousStatuses) + const newStories = buildStoriesMap(newStatuses) + + // Process any removed stories first + for (const [storyId, story] of previousStories) { + if (!newStories.has(storyId)) { + // The story has been removed + statuses.push({ + type: 'story', + rundownExternalId, + storyId, + mosStatus: MOS_STATUS_UNKNOWN, + }) + + // Clear any items too + for (const [itemId, isReady] of Object.entries(story.itemsReady)) { + if (isReady === undefined) continue + + statuses.push({ + type: 'item', + rundownExternalId, + storyId, + itemId: itemId, + mosStatus: MOS_STATUS_UNKNOWN, + }) + } + } + } + + // Then any remaining stories in order + for (const [storyId, status] of newStories) { + const previousStatus = previousStories.get(storyId) + + const newMosStatus = buildMosStatus(config, status.playbackStatus, status.isReady, newStatuses?.active) + if ( + newMosStatus !== null && + (!previousStatus || + buildMosStatus( + config, + previousStatus.playbackStatus, + previousStatus.isReady, + previousStatuses?.active + ) !== newMosStatus) + ) { + statuses.push({ + type: 'story', + rundownExternalId, + storyId, + mosStatus: newMosStatus, + }) + } + + // Diff each item in the story + const previousItemStatuses = previousStatus?.itemsReady ?? {} + const allItemIds = new Set([...Object.keys(status.itemsReady), ...Object.keys(previousItemStatuses)]) + + for (const itemId of allItemIds) { + const newReady = status.itemsReady[itemId] + const previousReady = previousItemStatuses[itemId] + + const newMosStatus = + newReady !== undefined + ? buildMosStatus(config, status.playbackStatus, newReady, newStatuses?.active) + : null + const previousMosStatus = + previousReady !== undefined && previousStatus + ? buildMosStatus(config, previousStatus.playbackStatus, previousReady, previousStatuses?.active) + : null + + if (newMosStatus !== null && previousMosStatus !== newMosStatus) { + statuses.push({ + type: 'item', + rundownExternalId, + storyId, + itemId, + mosStatus: newMosStatus, + }) + } + } + } + + return statuses +} + +function buildStoriesMap(state: IngestRundownStatus | undefined): Map { + const stories = new Map() + + if (state) { + for (const segment of state.segments) { + for (const part of segment.parts) { + stories.set(part.externalId, part) + } + } + } + + return stories +} + +function buildMosStatus( + config: MosDeviceStatusesConfig, + playbackStatus: IngestPartPlaybackStatus, + isReady: boolean | null | undefined, + active: IngestRundownStatus['active'] | undefined +): IMOSObjectStatus | null { + if (active === 'inactive') return MOS_STATUS_UNKNOWN + if (active === 'rehearsal' && !config.sendInRehearsal) return null + + switch (playbackStatus) { + case IngestPartPlaybackStatus.PLAY: + return IMOSObjectStatus.PLAY + case IngestPartPlaybackStatus.STOP: + return IMOSObjectStatus.STOP + default: + switch (isReady) { + case true: + return IMOSObjectStatus.READY + case false: + return IMOSObjectStatus.NOT_READY + default: + return MOS_STATUS_UNKNOWN + } + } +} diff --git a/packages/mos-gateway/src/mosStatusHandler.ts b/packages/mos-gateway/src/mosStatus/handler.ts similarity index 54% rename from packages/mos-gateway/src/mosStatusHandler.ts rename to packages/mos-gateway/src/mosStatus/handler.ts index 4efddc9ec1..689c74c231 100644 --- a/packages/mos-gateway/src/mosStatusHandler.ts +++ b/packages/mos-gateway/src/mosStatus/handler.ts @@ -1,13 +1,13 @@ import { getMosTypes, - IMOSItemStatus, + type IMOSItemStatus, IMOSObjectStatus, - IMOSStoryStatus, - MosTypes, + type IMOSStoryStatus, + type MosTypes, type IMOSDevice, } from '@mos-connection/connector' -import type { MosDeviceStatusesConfig } from './generated/devices' -import type { CoreMosDeviceHandler } from './CoreMosDeviceHandler' +import type { MosDeviceStatusesConfig } from '../generated/devices' +import type { CoreMosDeviceHandler } from '../CoreMosDeviceHandler' import { assertNever, type Observer, @@ -16,16 +16,11 @@ import { stringifyError, SubscriptionId, } from '@sofie-automation/server-core-integration' -import { - IngestPartPlaybackStatus, - type IngestPartStatus, - type IngestRundownStatus, -} from '@sofie-automation/shared-lib/dist/ingest/rundownStatus' +import type { IngestRundownStatus } from '@sofie-automation/shared-lib/dist/ingest/rundownStatus' import type { RundownId } from '@sofie-automation/shared-lib/dist/core/model/Ids' import type winston = require('winston') import { Queue } from '@sofie-automation/server-core-integration/dist/lib/queue' - -const MOS_STATUS_UNKNOWN = '' as IMOSObjectStatus // Force the status to be empty, which isn't a valid state in the enum +import { diffStatuses } from './diff' export class MosStatusHandler { readonly #logger: winston.Logger @@ -166,155 +161,3 @@ export class MosStatusHandler { if (this.#subId) this.#coreMosHandler.core.unsubscribe(this.#subId) } } - -type SomeStatusEntry = StoryStatusEntry | ItemStatusEntry - -interface ItemStatusEntry { - type: 'item' - rundownExternalId: string - storyId: string - itemId: string - mosStatus: IMOSObjectStatus -} - -interface StoryStatusEntry { - type: 'story' - rundownExternalId: string - storyId: string - mosStatus: IMOSObjectStatus -} - -function diffStatuses( - config: MosDeviceStatusesConfig, - previousStatuses: IngestRundownStatus | undefined, - newStatuses: IngestRundownStatus | undefined -): SomeStatusEntry[] { - const rundownExternalId = previousStatuses?.externalId ?? newStatuses?.externalId - - if ((!previousStatuses && !newStatuses) || !rundownExternalId) return [] - - const statuses: SomeStatusEntry[] = [] - - const previousStories = buildStoriesMap(previousStatuses) - const newStories = buildStoriesMap(newStatuses) - - // Process any removed stories first - for (const [storyId, story] of previousStories) { - if (!newStories.has(storyId)) { - // The story has been removed - statuses.push({ - type: 'story', - rundownExternalId, - storyId, - mosStatus: MOS_STATUS_UNKNOWN, - }) - - // Clear any items too - for (const [itemId, isReady] of Object.entries(story.itemsReady)) { - if (isReady === undefined) continue - - statuses.push({ - type: 'item', - rundownExternalId, - storyId, - itemId: itemId, - mosStatus: MOS_STATUS_UNKNOWN, - }) - } - } - } - - // Then any remaining stories in order - for (const [storyId, status] of newStories) { - const previousStatus = previousStories.get(storyId) - - const newMosStatus = buildMosStatus(config, status.playbackStatus, status.isReady, newStatuses?.active) - if ( - newMosStatus !== null && - (!previousStatus || - buildMosStatus( - config, - previousStatus.playbackStatus, - previousStatus.isReady, - previousStatuses?.active - ) !== newMosStatus) - ) { - statuses.push({ - type: 'story', - rundownExternalId, - storyId, - mosStatus: newMosStatus, - }) - } - - // Diff each item in the story - const previousItemStatuses = previousStatus?.itemsReady ?? {} - const allItemIds = new Set([...Object.keys(status.itemsReady), ...Object.keys(previousItemStatuses)]) - - for (const itemId of allItemIds) { - const newReady = status.itemsReady[itemId] - const previousReady = previousItemStatuses[itemId] - - const newMosStatus = - newReady !== undefined - ? buildMosStatus(config, status.playbackStatus, newReady, newStatuses?.active) - : null - const previousMosStatus = - previousReady !== undefined && previousStatus - ? buildMosStatus(config, previousStatus.playbackStatus, previousReady, previousStatuses?.active) - : null - - if (newMosStatus !== null && previousMosStatus !== newMosStatus) { - statuses.push({ - type: 'item', - rundownExternalId, - storyId, - itemId, - mosStatus: newMosStatus, - }) - } - } - } - - return statuses -} - -function buildStoriesMap(state: IngestRundownStatus | undefined): Map { - const stories = new Map() - - if (state) { - for (const segment of state.segments) { - for (const part of segment.parts) { - stories.set(part.externalId, part) - } - } - } - - return stories -} - -function buildMosStatus( - config: MosDeviceStatusesConfig, - playbackStatus: IngestPartPlaybackStatus, - isReady: boolean | null | undefined, - active: IngestRundownStatus['active'] | undefined -): IMOSObjectStatus | null { - if (active === 'inactive') return MOS_STATUS_UNKNOWN - if (active === 'rehearsal' && !config.sendInRehearsal) return null - - switch (playbackStatus) { - case IngestPartPlaybackStatus.PLAY: - return IMOSObjectStatus.PLAY - case IngestPartPlaybackStatus.STOP: - return IMOSObjectStatus.STOP - default: - switch (isReady) { - case true: - return IMOSObjectStatus.READY - case false: - return IMOSObjectStatus.NOT_READY - default: - return MOS_STATUS_UNKNOWN - } - } -}