Skip to content

Commit

Permalink
feat: SOF-752 purge internal elements
Browse files Browse the repository at this point in the history
  • Loading branch information
ianshade committed Mar 10, 2022
1 parent 02bd142 commit 2317f79
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 25 deletions.
10 changes: 5 additions & 5 deletions src/mse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { flattenEntry, AtomEntry, FlatEntry } from './xml'
import { Rundown } from './rundown'
import * as uuid from 'uuid'

const uuidRe = /[a-fA-f0-9]{8}-[a-fA-f0-9]{4}-[a-fA-f0-9]{4}-[a-fA-f0-9]{4}-[a-fA-f0-9]{12}/
export const creatorName = 'Sofie'
const UUID_RE = /[a-fA-f0-9]{8}-[a-fA-f0-9]{4}-[a-fA-f0-9]{4}-[a-fA-f0-9]{4}-[a-fA-f0-9]{12}/
export const CREATOR_NAME = 'Sofie'

export class MSERep extends EventEmitter implements MSE {
readonly hostname: string
Expand Down Expand Up @@ -134,7 +134,7 @@ export class MSERep extends EventEmitter implements MSE {
if (!showId.endsWith('}')) {
showId = showId + '}'
}
if (!showId.match(uuidRe)) {
if (!showId.match(UUID_RE)) {
return Promise.reject(new Error(`Show id must be a UUID and '${showId}' is not.`))
}
await this.checkConnection()
Expand Down Expand Up @@ -163,7 +163,7 @@ export class MSERep extends EventEmitter implements MSE {
if (!playlistName.endsWith('}')) {
playlistName = playlistName + '}'
}
if (!playlistName.match(uuidRe)) {
if (!playlistName.match(UUID_RE)) {
return Promise.reject(new Error(`Playlist name must be a UUID and '${playlistName}' is not.`))
}
await this.checkConnection()
Expand Down Expand Up @@ -204,7 +204,7 @@ export class MSERep extends EventEmitter implements MSE {
}
}
if (!playlistExists) {
playlistID = playlistID && playlistID.match(uuidRe) ? playlistID.toUpperCase() : uuid.v4().toUpperCase()
playlistID = playlistID && playlistID.match(UUID_RE) ? playlistID.toUpperCase() : uuid.v4().toUpperCase()
const modifiedDate = `${date.getUTCDate().toString().padStart(2, '0')}.${(date.getUTCMonth() + 1)
.toString()
.padStart(2, '0')}.${date.getFullYear()} ${date
Expand Down
63 changes: 45 additions & 18 deletions src/rundown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import {
isExternalElement,
InternalElementId,
isInternalElement,
InternalElementIdWithCreator,
} from './v-connection'
import { CommandResult, createHTTPContext, HttpMSEClient, HTTPRequestError } from './msehttp'
import { InexistentError, LocationType, PepResponse } from './peptalk'
import { MSERep, creatorName } from './mse'
import { CREATOR_NAME, MSERep } from './mse'
import { flattenEntry, AtomEntry, FlatEntry } from './xml'
import * as uuid from 'uuid'

Expand Down Expand Up @@ -56,8 +57,12 @@ export class Rundown implements VRundown {
)
}

private static makeKey(elementId: ExternalElementId) {
return `${elementId.vcpid}_${elementId.channel ?? ''}`
private static makeKey(elementId: ElementId) {
if (isExternalElement(elementId)) {
return `${elementId.vcpid}_${elementId.channel ?? ''}`
} else {
return `${elementId.showId}_${elementId.instanceName}`
}
}

private async buildChannelMap(elementId?: ExternalElementId): Promise<boolean> {
Expand Down Expand Up @@ -181,7 +186,7 @@ export class Rundown implements VRundown {
`/storage/shows/{${elementId.showId}}/elements/${elementId.instanceName}`,
`<element name="${
elementId.instanceName
}" guid="${uuid.v4()}" updated="${new Date().toISOString()}" creator="${creatorName}" ${vizProgram}>
}" guid="${uuid.v4()}" updated="${new Date().toISOString()}" creator="${CREATOR_NAME}" ${vizProgram}>
<ref name="master_template">/storage/shows/{${elementId.showId}}/mastertemplates/${templateName}</ref>
<entry name="default_alternatives"/>
<entry name="data">
Expand Down Expand Up @@ -227,15 +232,16 @@ ${entries}
}
}

async listInternalElements(showId: string): Promise<InternalElementId[]> {
async listInternalElements(showId: string): Promise<InternalElementIdWithCreator[]> {
await this.mse.checkConnection()
const showElementsList = await this.pep.getJS(`/storage/shows/{${showId}}/elements`, 1)
const flatShowElements = await flattenEntry(showElementsList.js as AtomEntry)
const elementNames: Array<InternalElementId> = Object.keys(flatShowElements)
const elementNames: Array<InternalElementIdWithCreator> = Object.keys(flatShowElements)
.filter((x) => x !== 'name')
.map((element) => ({
instanceName: element,
showId,
creator: (flatShowElements[element] as FlatEntry).creator as string | undefined,
}))
return elementNames
}
Expand Down Expand Up @@ -394,25 +400,46 @@ ${entries}
}
}

async purge(showIds: string[], elementsToKeep?: ElementId[]): Promise<PepResponse> {
async purgeInternalElements(
showIds: string[],
onlyCreatedByUs?: boolean,
elementsToKeep?: InternalElementId[]
): Promise<PepResponse> {
const elementsToKeepSet = new Set(
elementsToKeep?.map((e) => {
return Rundown.makeKey(e)
})
)
for (const showId of showIds) {
if (!onlyCreatedByUs && !elementsToKeep?.length) {
await this.pep.replace(`/storage/shows/{${showId}}/elements`, '<elements/>')
} else {
const elements = await this.listInternalElements(showId)
await Promise.all(
elements.map(async (element) => {
if (
(!onlyCreatedByUs || element.creator === CREATOR_NAME) &&
!elementsToKeepSet.has(Rundown.makeKey(element))
) {
return this.deleteElement(element)
}
return Promise.resolve()
})
)
}
}
return { id: '*', status: 'ok' } as PepResponse
}

async purgeExternalElements(elementsToKeep?: ExternalElementId[]): Promise<PepResponse> {
// let playlist = await this.mse.getPlaylist(this.playlist)
// if (playlist.active_profile.value) {
// throw new Error(`Cannot purge an active profile.`)
// }
for (const showId of showIds) {
// @todo: don't purge elementsToKeep or elements not created by us
await this.pep.replace(`/storage/shows/{${showId}}/elements`, '<elements/>')
}
if (elementsToKeep && elementsToKeep.length) {
const externalElementsToKeep: ExternalElementId[] = []
for (const element of elementsToKeep) {
if (isExternalElement(element)) {
externalElementsToKeep.push(element)
}
}
await this.buildChannelMap()
const elementsSet = new Set(
externalElementsToKeep.map((e) => {
elementsToKeep.map((e) => {
return Rundown.makeKey(e)
})
)
Expand Down
23 changes: 21 additions & 2 deletions src/v-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ export interface InternalElementId {
showId: string
}

export interface InternalElementIdWithCreator extends InternalElementId {
/** Who created the element */
creator?: string
}

/** Object uniquely identifying an external element loaded into an Engine */
export interface ExternalElementId {
/** Unique identifier for the template in the external system. */
Expand Down Expand Up @@ -295,11 +300,25 @@ export interface VRundown {
*/
cleanupShow(showId: string): Promise<CommandResult>
/**
* Clear up all graphical elements and state associated with a rundown,
* Clear up all Internal Elements and state associated with given shows,
* including those required for post-rundown analysis.
* @param showIds Names (UUIDs) of the shows to purge.
* @param onlyCreatedByUs Restricted to removing only elements that have a matching creator attribute
* @param elementsToKeep Elements to omit from deleting.
* @result Resolves on successful rundown purge.
*/
purgeInternalElements(
showIds: string[],
onlyCreatedByUs?: boolean,
elementsToKeep?: InternalElementId[]
): Promise<PepResponse>
/**
* Clear up all External Elements and state associated with a rundown,
* including those required for post-rundown analysis.
* @param elementsToKeep Elements to omit from deleting.
* @result Resolves on successful rundown purge.
*/
purge(showIds: string[], elementsToKeep?: ElementId[]): Promise<PepResponse>
purgeExternalElements(elementsToKeep?: ExternalElementId[]): Promise<PepResponse>
/**
* Is the associated MSE playlist currently active?
* @returns Resolves with the activation status of the associated MSE playlist.
Expand Down

0 comments on commit 2317f79

Please sign in to comment.