Skip to content

Commit 27957cd

Browse files
committed
wip: first draft
1 parent f57c8a5 commit 27957cd

File tree

4 files changed

+175
-9
lines changed

4 files changed

+175
-9
lines changed

packages/corelib/src/dataModel/PieceInstance.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
RundownId,
88
PartInstanceId,
99
PieceId,
10+
ExpectedPackageId,
1011
} from './Ids'
1112
import { Piece } from './Piece'
1213
import { omit } from '../lib'
@@ -79,6 +80,8 @@ export interface PieceInstance {
7980
reportedStoppedPlayback?: Time
8081
plannedStartedPlayback?: Time
8182
plannedStoppedPlayback?: Time
83+
84+
neededExpectedPackageIds?: ExpectedPackageId[]
8285
}
8386

8487
export interface ResolvedPieceInstance {
Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,131 @@
11
import type { CleanupOrphanedExpectedPackageReferencesProps } from '@sofie-automation/corelib/dist/worker/studio'
22
import type { JobContext } from '../jobs'
3+
import { runWithPlaylistLock } from './lock'
4+
import {
5+
ExpectedPackageDB,
6+
isPackageReferencedByPlayout,
7+
} from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages'
8+
import { PieceInstance } from '@sofie-automation/corelib/dist/dataModel/PieceInstance'
9+
import { AnyBulkWriteOperation } from 'mongodb'
10+
import { ExpectedPackageId, PieceInstanceId } from '@sofie-automation/corelib/dist/dataModel/Ids'
311

412
export async function handleCleanupOrphanedExpectedPackageReferences(
513
context: JobContext,
614
data: CleanupOrphanedExpectedPackageReferencesProps
715
): Promise<void> {
8-
// TODO
16+
// Something has changed in the PieceInstances, we need to check that the ExpectedPackages have only valid PieceInstances as owners, and remove any which no longer have owners
17+
18+
await runWithPlaylistLock(context, data.playlistId, async () => {
19+
const [existingPackages, validPieceInstances] = await Promise.all([
20+
context.directCollections.ExpectedPackages.findFetch(
21+
{
22+
studioId: context.studioId,
23+
rundownId: data.rundownId,
24+
bucketId: null,
25+
},
26+
{
27+
projection: {
28+
_id: 1,
29+
playoutSources: 1,
30+
// We only need to know if there are any entries, so project them to be as minimal as possible
31+
'ingestSources.fromPieceType': 1,
32+
},
33+
}
34+
) as Promise<
35+
Array<
36+
Pick<ExpectedPackageDB, '_id' | 'playoutSources' | 'ingestSources'> & {
37+
ingestSources: unknown[]
38+
}
39+
>
40+
>,
41+
context.directCollections.PieceInstances.findFetch(
42+
{
43+
rundownId: data.rundownId,
44+
reset: { $ne: true },
45+
},
46+
{
47+
projection: {
48+
_id: 1,
49+
neededExpectedPackageIds: 1,
50+
},
51+
}
52+
) as Promise<Array<Pick<PieceInstance, '_id' | 'neededExpectedPackageIds'>>>,
53+
])
54+
55+
const pieceInstancePackageMap = new Map<PieceInstanceId, Set<ExpectedPackageId>>()
56+
for (const pieceInstance of validPieceInstances) {
57+
if (pieceInstance.neededExpectedPackageIds && pieceInstance.neededExpectedPackageIds.length > 0)
58+
pieceInstancePackageMap.set(pieceInstance._id, new Set(pieceInstance.neededExpectedPackageIds))
59+
}
60+
61+
const writeOps: AnyBulkWriteOperation<ExpectedPackageDB>[] = []
62+
63+
for (const expectedPackage of existingPackages) {
64+
// Find the pieceInstanceIds that are stale
65+
const pieceInstanceIdsToRemove: PieceInstanceId[] = []
66+
for (const pieceInstanceId of expectedPackage.playoutSources.pieceInstanceIds) {
67+
const pieceInstancePackageIds = pieceInstancePackageMap.get(pieceInstanceId)
68+
if (!pieceInstancePackageIds || !pieceInstancePackageIds.has(expectedPackage._id)) {
69+
// This pieceInstanceId is no longer valid, queue it to be removed
70+
pieceInstanceIdsToRemove.push(pieceInstanceId)
71+
}
72+
}
73+
74+
// Queue the write
75+
if (pieceInstanceIdsToRemove.length === expectedPackage.playoutSources.pieceInstanceIds.length) {
76+
// It looks like all the pieceInstanceIds are being removed
77+
78+
if (
79+
expectedPackage.ingestSources.length === 0 &&
80+
!isPackageReferencedByPlayout({
81+
// Test with a fake package
82+
...expectedPackage,
83+
playoutSources: {
84+
...expectedPackage.playoutSources,
85+
pieceInstanceIds: [],
86+
},
87+
})
88+
) {
89+
// This package is not referenced by anything, so we can delete it
90+
writeOps.push({
91+
deleteOne: {
92+
filter: {
93+
_id: expectedPackage._id,
94+
},
95+
},
96+
})
97+
} else {
98+
// This package is still referenced by something, so we need to keep it
99+
writeOps.push({
100+
updateOne: {
101+
filter: {
102+
_id: expectedPackage._id,
103+
},
104+
update: {
105+
$set: {
106+
'playoutSources.pieceInstanceIds': [],
107+
},
108+
},
109+
},
110+
})
111+
}
112+
} else if (pieceInstanceIdsToRemove.length > 0) {
113+
// Some of the pieceInstanceIds are being removed
114+
writeOps.push({
115+
updateOne: {
116+
filter: {
117+
_id: expectedPackage._id,
118+
},
119+
update: {
120+
$pull: {
121+
'playoutSources.pieceInstanceIds': { $in: pieceInstanceIdsToRemove },
122+
},
123+
},
124+
},
125+
})
126+
}
127+
}
128+
129+
await context.directCollections.ExpectedPackages.bulkWrite(writeOps)
130+
})
9131
}

packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { PieceInstanceInfiniteId } from '@sofie-automation/corelib/dist/dataModel/Ids'
1+
import { ExpectedPackageId, PieceInstanceInfiniteId, RundownId } from '@sofie-automation/corelib/dist/dataModel/Ids'
22
import { ReadonlyDeep } from 'type-fest'
33
import { PieceInstance, PieceInstancePiece } from '@sofie-automation/corelib/dist/dataModel/PieceInstance'
44
import { clone, getRandomId } from '@sofie-automation/corelib/dist/lib'
5-
import { Time } from '@sofie-automation/blueprints-integration'
5+
import { ExpectedPackage, Time } from '@sofie-automation/blueprints-integration'
66
import { PlayoutPieceInstanceModel } from '../PlayoutPieceInstanceModel'
77
import _ = require('underscore')
8+
import { getExpectedPackageIdNew } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages'
89

910
export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel {
1011
/**
@@ -13,6 +14,8 @@ export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel
1314
*/
1415
PieceInstanceImpl: PieceInstance
1516

17+
updatedExpectedPackages: Map<ExpectedPackageId, ReadonlyDeep<ExpectedPackage.Base>> | null
18+
1619
/**
1720
* Set/delete a value for this PieceInstance, and track that there are changes
1821
* @param key Property key
@@ -26,6 +29,16 @@ export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel
2629
}
2730

2831
this.#hasChanges = true
32+
33+
// Updating the 'piece' has side effects on the expectedPackages
34+
if (key === 'piece') {
35+
const newPiece = newValue as PieceInstance['piece'] | undefined
36+
this.updatedExpectedPackages = createExpectedPackagesMap(
37+
this.PieceInstanceImpl.rundownId,
38+
newPiece?.expectedPackages
39+
)
40+
this.PieceInstanceImpl.neededExpectedPackageIds = Array.from(this.updatedExpectedPackages.keys())
41+
}
2942
}
3043

3144
/**
@@ -57,7 +70,7 @@ export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel
5770
* Whether this PieceInstance has unsaved changes
5871
*/
5972
get HasChanges(): boolean {
60-
return this.#hasChanges
73+
return this.#hasChanges || !!this.updatedExpectedPackages
6174
}
6275

6376
/**
@@ -71,9 +84,18 @@ export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel
7184
return this.PieceInstanceImpl
7285
}
7386

74-
constructor(pieceInstances: PieceInstance, hasChanges: boolean) {
75-
this.PieceInstanceImpl = pieceInstances
87+
constructor(pieceInstance: PieceInstance, hasChanges: boolean) {
88+
this.PieceInstanceImpl = pieceInstance
7689
this.#hasChanges = hasChanges
90+
91+
if (hasChanges) {
92+
this.updatedExpectedPackages = createExpectedPackagesMap(
93+
pieceInstance.rundownId,
94+
pieceInstance.piece.expectedPackages
95+
)
96+
} else {
97+
this.updatedExpectedPackages = null
98+
}
7799
}
78100

79101
/**
@@ -137,3 +159,16 @@ export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel
137159
)
138160
}
139161
}
162+
163+
function createExpectedPackagesMap(
164+
rundownId: RundownId,
165+
packages: ExpectedPackage.Base[] | undefined
166+
): Map<ExpectedPackageId, ReadonlyDeep<ExpectedPackage.Base>> {
167+
const map = new Map<ExpectedPackageId, ReadonlyDeep<ExpectedPackage.Base>>()
168+
if (!packages) return map
169+
170+
for (const pkg of packages) {
171+
map.set(getExpectedPackageIdNew(rundownId, pkg), pkg)
172+
}
173+
return map
174+
}

packages/job-worker/src/playout/model/implementation/SavePlayoutModel.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { PlayoutPartInstanceModelImpl } from './PlayoutPartInstanceModelImpl'
1515
import { PlayoutRundownModelImpl } from './PlayoutRundownModelImpl'
1616
import { ReadonlyDeep } from 'type-fest'
1717
import { ExpectedPackage } from '@sofie-automation/blueprints-integration'
18-
import { ExpectedPackageDB, getExpectedPackageIdNew } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages'
18+
import { ExpectedPackageDB } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages'
1919
import { normalizeArrayToMap } from '@sofie-automation/corelib/dist/lib'
2020
import { StudioJobs } from '@sofie-automation/corelib/dist/worker/studio'
2121

@@ -183,12 +183,17 @@ export async function writeExpectedPackagesForPlayoutSources(
183183

184184
for (const partInstance of partInstancesForRundown) {
185185
if (!partInstance) continue
186+
186187
for (const pieceInstance of partInstance.pieceInstancesImpl.values()) {
187188
if (!pieceInstance) continue // Future: We could handle these deleted pieces here?
188189

189-
for (const expectedPackage of pieceInstance.pieceInstance.piece.expectedPackages || []) {
190-
const packageId = getExpectedPackageIdNew(rundownId, expectedPackage)
190+
// The expectedPackages of the PieceInstance has not been modified, so there is nothing to do
191+
if (!pieceInstance.updatedExpectedPackages) continue
192+
193+
// nocommit - can this cleanup any references from the pieceInstance to packages which are no longer referenced?
194+
// That would simplify the debounced job a lot, as it allows it to only consider the ids of each pieceInstance, not the packages it references
191195

196+
for (const [packageId, expectedPackage] of pieceInstance.updatedExpectedPackages) {
192197
const existingPackage = existingPackagesMap.get(packageId)
193198
if (existingPackage?.playoutSources.pieceInstanceIds.includes(pieceInstance.pieceInstance._id)) {
194199
// Reference already exists, nothing to do
@@ -256,6 +261,7 @@ export async function writeExpectedPackagesForPlayoutSources(
256261
}
257262

258263
// We can't easily track any references which have been deleted, so we should schedule a cleanup job to deal with that for us
264+
// Always perform this, in case any pieceInstances have been purged directly from the db
259265
await context.queueStudioJob(
260266
StudioJobs.CleanupOrphanedExpectedPackageReferences,
261267
{

0 commit comments

Comments
 (0)