@@ -42,6 +42,7 @@ import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part'
4242import { DatabasePersistedModel } from '../modelBase'
4343import { updateSegmentIdsForAdlibbedPartInstances } from './commit/updateSegmentIdsForAdlibbedPartInstances'
4444import { stringifyError } from '@sofie-automation/shared-lib/dist/lib/stringifyError'
45+ import { AnyBulkWriteOperation } from 'mongodb'
4546
4647export type BeforePartMapItem = { id : PartId ; rank : number }
4748export type BeforeIngestOperationPartMap = ReadonlyMap < SegmentId , Array < BeforePartMapItem > >
@@ -176,6 +177,9 @@ export async function CommitIngestOperation(
176177 // Ensure any adlibbed parts are updated to follow the segmentId of the previous part
177178 await updateSegmentIdsForAdlibbedPartInstances ( context , ingestModel , beforePartMap )
178179
180+ if ( data . renamedSegments && data . renamedSegments . size > 0 ) {
181+ logger . debug ( `Renamed segments: ${ JSON . stringify ( Array . from ( data . renamedSegments . entries ( ) ) ) } ` )
182+ }
179183 // ensure instances have matching segmentIds with the parts
180184 await updatePartInstancesSegmentIds ( context , ingestModel , data . renamedSegments , beforePartMap )
181185
@@ -261,11 +265,17 @@ export async function CommitIngestOperation(
261265}
262266
263267function canRemoveSegment (
268+ prevPartInstance : ReadonlyDeep < DBPartInstance > | undefined ,
264269 currentPartInstance : ReadonlyDeep < DBPartInstance > | undefined ,
265270 nextPartInstance : ReadonlyDeep < DBPartInstance > | undefined ,
266271 segmentId : SegmentId
267272) : boolean {
268- if ( currentPartInstance ?. segmentId === segmentId || nextPartInstance ?. segmentId === segmentId ) {
273+ if ( prevPartInstance ?. segmentId === segmentId ) {
274+ // Don't allow removing an active rundown
275+ logger . warn ( `Not allowing removal of previous playing segment "${ segmentId } ", making segment unsynced instead` )
276+ return false
277+ }
278+ if ( currentPartInstance ?. segmentId === segmentId ) {
269279 // Don't allow removing an active rundown
270280 logger . warn ( `Not allowing removal of current playing segment "${ segmentId } ", making segment unsynced instead` )
271281 return false
@@ -294,26 +304,32 @@ async function updatePartInstancesSegmentIds(
294304 renamedSegments : ReadonlyMap < SegmentId , SegmentId > | null ,
295305 beforePartMap : BeforeIngestOperationPartMap
296306) {
297- // A set of rules which can be translated to mongo queries for PartInstances to update
307+ /**
308+ * Maps new SegmentId ->
309+ * A set of rules which can be translated to mongo queries for PartInstances to update
310+ */
298311 const renameRules = new Map <
299312 SegmentId ,
300313 {
314+ /** Parts that have been moved to the new SegmentId */
301315 partIds : PartId [ ]
302- fromSegmentId : SegmentId | null
316+ /** Segments that have been renamed to the new SegmentId */
317+ fromSegmentIds : SegmentId [ ]
303318 }
304319 > ( )
305320
306321 // Add whole segment renames to the set of rules
307322 if ( renamedSegments ) {
308323 for ( const [ fromSegmentId , toSegmentId ] of renamedSegments ) {
309- const rule = renameRules . get ( toSegmentId ) ?? { partIds : [ ] , fromSegmentId : null }
324+ const rule = renameRules . get ( toSegmentId ) ?? { partIds : [ ] , fromSegmentIds : [ ] }
310325 renameRules . set ( toSegmentId , rule )
311326
312- rule . fromSegmentId = fromSegmentId
327+ rule . fromSegmentIds . push ( fromSegmentId )
313328 }
314329 }
315330
316- // Reverse the structure
331+ // Reverse the Map structure
332+ /** Maps Part -> SegmentId-of-the-part-before-ingest-changes */
317333 const beforePartSegmentIdMap = new Map < PartId , SegmentId > ( )
318334 for ( const [ segmentId , partItems ] of beforePartMap . entries ( ) ) {
319335 for ( const partItem of partItems ) {
@@ -324,8 +340,11 @@ async function updatePartInstancesSegmentIds(
324340 // Some parts may have gotten a different segmentId to the base rule, so track those seperately in the rules
325341 for ( const partModel of ingestModel . getAllOrderedParts ( ) ) {
326342 const oldSegmentId = beforePartSegmentIdMap . get ( partModel . part . _id )
343+
327344 if ( oldSegmentId && oldSegmentId !== partModel . part . segmentId ) {
328- const rule = renameRules . get ( partModel . part . segmentId ) ?? { partIds : [ ] , fromSegmentId : null }
345+ // The part has moved to another segment, add a rule to update its corresponding PartInstances:
346+
347+ const rule = renameRules . get ( partModel . part . segmentId ) ?? { partIds : [ ] , fromSegmentIds : [ ] }
329348 renameRules . set ( partModel . part . segmentId , rule )
330349
331350 rule . partIds . push ( partModel . part . _id )
@@ -334,30 +353,80 @@ async function updatePartInstancesSegmentIds(
334353
335354 // Perform a mongo update to modify the PartInstances
336355 if ( renameRules . size > 0 ) {
337- await context . directCollections . PartInstances . bulkWrite (
338- Array . from ( renameRules . entries ( ) ) . map ( ( [ newSegmentId , rule ] ) => ( {
339- updateMany : {
340- filter : {
341- $or : _ . compact ( [
342- rule . fromSegmentId
343- ? {
344- segmentId : rule . fromSegmentId ,
345- }
346- : undefined ,
347- {
348- 'part._id' : { $in : rule . partIds } ,
356+ const rulesInOrder = Array . from ( renameRules . entries ( ) ) . sort ( ( a , b ) => {
357+ // Ensure that the ones with partIds are processed last,
358+ // as that should take precedence.
359+
360+ if ( a [ 1 ] . partIds . length && ! b [ 1 ] . partIds . length ) return 1
361+ if ( ! a [ 1 ] . partIds . length && b [ 1 ] . partIds . length ) return - 1
362+ return 0
363+ } )
364+
365+ const writeOps : AnyBulkWriteOperation < DBPartInstance > [ ] = [ ]
366+
367+ for ( const [ newSegmentId , rule ] of rulesInOrder ) {
368+ if ( rule . fromSegmentIds . length ) {
369+ writeOps . push ( {
370+ updateMany : {
371+ filter : {
372+ rundownId : ingestModel . rundownId ,
373+ segmentId : { $in : rule . fromSegmentIds } ,
374+ } ,
375+ update : {
376+ $set : {
377+ segmentId : newSegmentId ,
378+ 'part.segmentId' : newSegmentId ,
349379 } ,
350- ] ) ,
380+ } ,
351381 } ,
352- update : {
353- $set : {
354- segmentId : newSegmentId ,
355- 'part.segmentId' : newSegmentId ,
382+ } )
383+ }
384+ if ( rule . partIds . length ) {
385+ writeOps . push ( {
386+ updateMany : {
387+ filter : {
388+ rundownId : ingestModel . rundownId ,
389+ 'part._id' : { $in : rule . partIds } ,
390+ } ,
391+ update : {
392+ $set : {
393+ segmentId : newSegmentId ,
394+ 'part.segmentId' : newSegmentId ,
395+ } ,
356396 } ,
357397 } ,
358- } ,
359- } ) )
360- )
398+ } )
399+ }
400+ }
401+ if ( writeOps . length ) await context . directCollections . PartInstances . bulkWrite ( writeOps )
402+
403+ // Double check that there are no parts using the old segment ids:
404+ const oldSegmentIds = Array . from ( renameRules . keys ( ) )
405+ const [ badPartInstances , badParts ] = await Promise . all ( [
406+ await context . directCollections . PartInstances . findFetch ( {
407+ rundownId : ingestModel . rundownId ,
408+ segmentId : { $in : oldSegmentIds } ,
409+ } ) ,
410+ await context . directCollections . Parts . findFetch ( {
411+ rundownId : ingestModel . rundownId ,
412+ segmentId : { $in : oldSegmentIds } ,
413+ } ) ,
414+ ] )
415+ if ( badPartInstances . length > 0 ) {
416+ logger . error (
417+ `updatePartInstancesSegmentIds: Failed to update all PartInstances using old SegmentIds "${ JSON . stringify (
418+ oldSegmentIds
419+ ) } ": ${ JSON . stringify ( badPartInstances ) } , writeOps: ${ JSON . stringify ( writeOps ) } `
420+ )
421+ }
422+
423+ if ( badParts . length > 0 ) {
424+ logger . error (
425+ `updatePartInstancesSegmentIds: Failed to update all Parts using old SegmentIds "${ JSON . stringify (
426+ oldSegmentIds
427+ ) } ": ${ JSON . stringify ( badParts ) } , writeOps: ${ JSON . stringify ( writeOps ) } `
428+ )
429+ }
361430 }
362431}
363432
@@ -661,7 +730,7 @@ async function removeSegments(
661730 _changedSegmentIds : ReadonlyDeep < SegmentId [ ] > ,
662731 removedSegmentIds : ReadonlyDeep < SegmentId [ ] >
663732) {
664- const { currentPartInstance, nextPartInstance } = await getSelectedPartInstances (
733+ const { previousPartInstance , currentPartInstance, nextPartInstance } = await getSelectedPartInstances (
665734 context ,
666735 newPlaylist ,
667736 rundownsInPlaylist . map ( ( r ) => r . _id )
@@ -671,7 +740,7 @@ async function removeSegments(
671740 const orphanDeletedSegmentIds = new Set < SegmentId > ( )
672741 const orphanHiddenSegmentIds = new Set < SegmentId > ( )
673742 for ( const segmentId of removedSegmentIds ) {
674- if ( canRemoveSegment ( currentPartInstance , nextPartInstance , segmentId ) ) {
743+ if ( canRemoveSegment ( previousPartInstance , currentPartInstance , nextPartInstance , segmentId ) ) {
675744 purgeSegmentIds . add ( segmentId )
676745 } else {
677746 logger . warn (
@@ -684,8 +753,10 @@ async function removeSegments(
684753 for ( const segment of ingestModel . getAllSegments ( ) ) {
685754 const segmentId = segment . segment . _id
686755 if ( segment . segment . isHidden ) {
687- if ( ! canRemoveSegment ( currentPartInstance , nextPartInstance , segmentId ) ) {
688- // Protect live segment from being hidden
756+ // Blueprints want to hide the Segment
757+
758+ if ( ! canRemoveSegment ( previousPartInstance , currentPartInstance , nextPartInstance , segmentId ) ) {
759+ // The Segment is live, so we need to protect it from being hidden
689760 logger . warn ( `Cannot hide live segment ${ segmentId } , it will be orphaned` )
690761 switch ( segment . segment . orphaned ) {
691762 case SegmentOrphanedReason . DELETED :
@@ -705,7 +776,7 @@ async function removeSegments(
705776 } else if ( ! orphanDeletedSegmentIds . has ( segmentId ) && segment . parts . length === 0 ) {
706777 // No parts in segment
707778
708- if ( ! canRemoveSegment ( currentPartInstance , nextPartInstance , segmentId ) ) {
779+ if ( ! canRemoveSegment ( previousPartInstance , currentPartInstance , nextPartInstance , segmentId ) ) {
709780 // Protect live segment from being hidden
710781 logger . warn ( `Cannot hide live segment ${ segmentId } , it will be orphaned` )
711782 orphanHiddenSegmentIds . add ( segmentId )
0 commit comments