@@ -43,6 +43,7 @@ import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part'
43
43
import { DatabasePersistedModel } from '../modelBase'
44
44
import { updateSegmentIdsForAdlibbedPartInstances } from './commit/updateSegmentIdsForAdlibbedPartInstances'
45
45
import { stringifyError } from '@sofie-automation/shared-lib/dist/lib/stringifyError'
46
+ import { AnyBulkWriteOperation } from 'mongodb'
46
47
47
48
export type BeforePartMapItem = { id : PartId ; rank : number }
48
49
export type BeforeIngestOperationPartMap = ReadonlyMap < SegmentId , Array < BeforePartMapItem > >
@@ -177,6 +178,9 @@ export async function CommitIngestOperation(
177
178
// Ensure any adlibbed parts are updated to follow the segmentId of the previous part
178
179
await updateSegmentIdsForAdlibbedPartInstances ( context , ingestModel , beforePartMap )
179
180
181
+ if ( data . renamedSegments && data . renamedSegments . size > 0 ) {
182
+ logger . debug ( `Renamed segments: ${ JSON . stringify ( Array . from ( data . renamedSegments . entries ( ) ) ) } ` )
183
+ }
180
184
// ensure instances have matching segmentIds with the parts
181
185
await updatePartInstancesSegmentIds ( context , ingestModel , data . renamedSegments , beforePartMap )
182
186
@@ -259,10 +263,16 @@ export async function CommitIngestOperation(
259
263
}
260
264
261
265
function canRemoveSegment (
266
+ prevPartInstance : ReadonlyDeep < DBPartInstance > | undefined ,
262
267
currentPartInstance : ReadonlyDeep < DBPartInstance > | undefined ,
263
268
nextPartInstance : ReadonlyDeep < DBPartInstance > | undefined ,
264
269
segmentId : SegmentId
265
270
) : boolean {
271
+ if ( prevPartInstance ?. segmentId === segmentId ) {
272
+ // Don't allow removing an active rundown
273
+ logger . warn ( `Not allowing removal of previous playing segment "${ segmentId } ", making segment unsynced instead` )
274
+ return false
275
+ }
266
276
if (
267
277
currentPartInstance ?. segmentId === segmentId ||
268
278
( nextPartInstance ?. segmentId === segmentId && isTooCloseToAutonext ( currentPartInstance , false ) )
@@ -295,26 +305,32 @@ async function updatePartInstancesSegmentIds(
295
305
renamedSegments : ReadonlyMap < SegmentId , SegmentId > | null ,
296
306
beforePartMap : BeforeIngestOperationPartMap
297
307
) {
298
- // A set of rules which can be translated to mongo queries for PartInstances to update
308
+ /**
309
+ * Maps new SegmentId ->
310
+ * A set of rules which can be translated to mongo queries for PartInstances to update
311
+ */
299
312
const renameRules = new Map <
300
313
SegmentId ,
301
314
{
315
+ /** Parts that have been moved to the new SegmentId */
302
316
partIds : PartId [ ]
303
- fromSegmentId : SegmentId | null
317
+ /** Segments that have been renamed to the new SegmentId */
318
+ fromSegmentIds : SegmentId [ ]
304
319
}
305
320
> ( )
306
321
307
322
// Add whole segment renames to the set of rules
308
323
if ( renamedSegments ) {
309
324
for ( const [ fromSegmentId , toSegmentId ] of renamedSegments ) {
310
- const rule = renameRules . get ( toSegmentId ) ?? { partIds : [ ] , fromSegmentId : null }
325
+ const rule = renameRules . get ( toSegmentId ) ?? { partIds : [ ] , fromSegmentIds : [ ] }
311
326
renameRules . set ( toSegmentId , rule )
312
327
313
- rule . fromSegmentId = fromSegmentId
328
+ rule . fromSegmentIds . push ( fromSegmentId )
314
329
}
315
330
}
316
331
317
- // Reverse the structure
332
+ // Reverse the Map structure
333
+ /** Maps Part -> SegmentId-of-the-part-before-ingest-changes */
318
334
const beforePartSegmentIdMap = new Map < PartId , SegmentId > ( )
319
335
for ( const [ segmentId , partItems ] of beforePartMap . entries ( ) ) {
320
336
for ( const partItem of partItems ) {
@@ -325,8 +341,11 @@ async function updatePartInstancesSegmentIds(
325
341
// Some parts may have gotten a different segmentId to the base rule, so track those seperately in the rules
326
342
for ( const partModel of ingestModel . getAllOrderedParts ( ) ) {
327
343
const oldSegmentId = beforePartSegmentIdMap . get ( partModel . part . _id )
344
+
328
345
if ( oldSegmentId && oldSegmentId !== partModel . part . segmentId ) {
329
- const rule = renameRules . get ( partModel . part . segmentId ) ?? { partIds : [ ] , fromSegmentId : null }
346
+ // The part has moved to another segment, add a rule to update its corresponding PartInstances:
347
+
348
+ const rule = renameRules . get ( partModel . part . segmentId ) ?? { partIds : [ ] , fromSegmentIds : [ ] }
330
349
renameRules . set ( partModel . part . segmentId , rule )
331
350
332
351
rule . partIds . push ( partModel . part . _id )
@@ -335,30 +354,80 @@ async function updatePartInstancesSegmentIds(
335
354
336
355
// Perform a mongo update to modify the PartInstances
337
356
if ( renameRules . size > 0 ) {
338
- await context . directCollections . PartInstances . bulkWrite (
339
- Array . from ( renameRules . entries ( ) ) . map ( ( [ newSegmentId , rule ] ) => ( {
340
- updateMany : {
341
- filter : {
342
- $or : _ . compact ( [
343
- rule . fromSegmentId
344
- ? {
345
- segmentId : rule . fromSegmentId ,
346
- }
347
- : undefined ,
348
- {
349
- 'part._id' : { $in : rule . partIds } ,
357
+ const rulesInOrder = Array . from ( renameRules . entries ( ) ) . sort ( ( a , b ) => {
358
+ // Ensure that the ones with partIds are processed last,
359
+ // as that should take precedence.
360
+
361
+ if ( a [ 1 ] . partIds . length && ! b [ 1 ] . partIds . length ) return 1
362
+ if ( ! a [ 1 ] . partIds . length && b [ 1 ] . partIds . length ) return - 1
363
+ return 0
364
+ } )
365
+
366
+ const writeOps : AnyBulkWriteOperation < DBPartInstance > [ ] = [ ]
367
+
368
+ for ( const [ newSegmentId , rule ] of rulesInOrder ) {
369
+ if ( rule . fromSegmentIds . length ) {
370
+ writeOps . push ( {
371
+ updateMany : {
372
+ filter : {
373
+ rundownId : ingestModel . rundownId ,
374
+ segmentId : { $in : rule . fromSegmentIds } ,
375
+ } ,
376
+ update : {
377
+ $set : {
378
+ segmentId : newSegmentId ,
379
+ 'part.segmentId' : newSegmentId ,
350
380
} ,
351
- ] ) ,
381
+ } ,
352
382
} ,
353
- update : {
354
- $set : {
355
- segmentId : newSegmentId ,
356
- 'part.segmentId' : newSegmentId ,
383
+ } )
384
+ }
385
+ if ( rule . partIds . length ) {
386
+ writeOps . push ( {
387
+ updateMany : {
388
+ filter : {
389
+ rundownId : ingestModel . rundownId ,
390
+ 'part._id' : { $in : rule . partIds } ,
391
+ } ,
392
+ update : {
393
+ $set : {
394
+ segmentId : newSegmentId ,
395
+ 'part.segmentId' : newSegmentId ,
396
+ } ,
357
397
} ,
358
398
} ,
359
- } ,
360
- } ) )
361
- )
399
+ } )
400
+ }
401
+ }
402
+ if ( writeOps . length ) await context . directCollections . PartInstances . bulkWrite ( writeOps )
403
+
404
+ // Double check that there are no parts using the old segment ids:
405
+ const oldSegmentIds = Array . from ( renameRules . keys ( ) )
406
+ const [ badPartInstances , badParts ] = await Promise . all ( [
407
+ await context . directCollections . PartInstances . findFetch ( {
408
+ rundownId : ingestModel . rundownId ,
409
+ segmentId : { $in : oldSegmentIds } ,
410
+ } ) ,
411
+ await context . directCollections . Parts . findFetch ( {
412
+ rundownId : ingestModel . rundownId ,
413
+ segmentId : { $in : oldSegmentIds } ,
414
+ } ) ,
415
+ ] )
416
+ if ( badPartInstances . length > 0 ) {
417
+ logger . error (
418
+ `updatePartInstancesSegmentIds: Failed to update all PartInstances using old SegmentIds "${ JSON . stringify (
419
+ oldSegmentIds
420
+ ) } ": ${ JSON . stringify ( badPartInstances ) } , writeOps: ${ JSON . stringify ( writeOps ) } `
421
+ )
422
+ }
423
+
424
+ if ( badParts . length > 0 ) {
425
+ logger . error (
426
+ `updatePartInstancesSegmentIds: Failed to update all Parts using old SegmentIds "${ JSON . stringify (
427
+ oldSegmentIds
428
+ ) } ": ${ JSON . stringify ( badParts ) } , writeOps: ${ JSON . stringify ( writeOps ) } `
429
+ )
430
+ }
362
431
}
363
432
}
364
433
@@ -662,7 +731,7 @@ async function removeSegments(
662
731
_changedSegmentIds : ReadonlyDeep < SegmentId [ ] > ,
663
732
removedSegmentIds : ReadonlyDeep < SegmentId [ ] >
664
733
) {
665
- const { currentPartInstance, nextPartInstance } = await getSelectedPartInstances (
734
+ const { previousPartInstance , currentPartInstance, nextPartInstance } = await getSelectedPartInstances (
666
735
context ,
667
736
newPlaylist ,
668
737
rundownsInPlaylist . map ( ( r ) => r . _id )
@@ -672,7 +741,7 @@ async function removeSegments(
672
741
const orphanDeletedSegmentIds = new Set < SegmentId > ( )
673
742
const orphanHiddenSegmentIds = new Set < SegmentId > ( )
674
743
for ( const segmentId of removedSegmentIds ) {
675
- if ( canRemoveSegment ( currentPartInstance , nextPartInstance , segmentId ) ) {
744
+ if ( canRemoveSegment ( previousPartInstance , currentPartInstance , nextPartInstance , segmentId ) ) {
676
745
purgeSegmentIds . add ( segmentId )
677
746
} else {
678
747
logger . warn (
@@ -685,8 +754,10 @@ async function removeSegments(
685
754
for ( const segment of ingestModel . getAllSegments ( ) ) {
686
755
const segmentId = segment . segment . _id
687
756
if ( segment . segment . isHidden ) {
688
- if ( ! canRemoveSegment ( currentPartInstance , nextPartInstance , segmentId ) ) {
689
- // Protect live segment from being hidden
757
+ // Blueprints want to hide the Segment
758
+
759
+ if ( ! canRemoveSegment ( previousPartInstance , currentPartInstance , nextPartInstance , segmentId ) ) {
760
+ // The Segment is live, so we need to protect it from being hidden
690
761
logger . warn ( `Cannot hide live segment ${ segmentId } , it will be orphaned` )
691
762
switch ( segment . segment . orphaned ) {
692
763
case SegmentOrphanedReason . DELETED :
@@ -706,7 +777,7 @@ async function removeSegments(
706
777
} else if ( ! orphanDeletedSegmentIds . has ( segmentId ) && segment . parts . length === 0 ) {
707
778
// No parts in segment
708
779
709
- if ( ! canRemoveSegment ( currentPartInstance , nextPartInstance , segmentId ) ) {
780
+ if ( ! canRemoveSegment ( previousPartInstance , currentPartInstance , nextPartInstance , segmentId ) ) {
710
781
// Protect live segment from being hidden
711
782
logger . warn ( `Cannot hide live segment ${ segmentId } , it will be orphaned` )
712
783
orphanHiddenSegmentIds . add ( segmentId )
0 commit comments