Collect fix 2
This commit is contained in:
parent
1116f80066
commit
b8562f48e7
@ -1,30 +1,33 @@
|
||||
package no.iktdev.mediaprocessing.coordinator.listeners.events
|
||||
|
||||
import mu.KotlinLogging
|
||||
import no.iktdev.eventi.events.EventListener
|
||||
import no.iktdev.eventi.models.Event
|
||||
import no.iktdev.mediaprocessing.shared.common.event_task_contract.events.CollectedEvent
|
||||
import no.iktdev.mediaprocessing.shared.common.listeners.SummaryEventListener
|
||||
import no.iktdev.mediaprocessing.shared.common.projection.CollectProjection
|
||||
import no.iktdev.mediaprocessing.shared.database.stores.EventStore
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class CollectEventsListener : EventListener() {
|
||||
class CollectEventsListener(eventStore: no.iktdev.eventi.stores.EventStore = EventStore) : SummaryEventListener(eventStore) {
|
||||
|
||||
private val log = KotlinLogging.logger {}
|
||||
override fun shouldSummarize(fullHistory: List<Event>): Boolean {
|
||||
val projection = CollectProjection(fullHistory)
|
||||
if (projection.startedWith == null) return false
|
||||
if (!projection.isWorkflowComplete()) return false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onEvent(event: Event, history: List<Event>): Event? {
|
||||
// Avoid double-collection
|
||||
if (event is CollectedEvent || history.any { it is CollectedEvent }) return null
|
||||
|
||||
val projection = CollectProjection(history)
|
||||
|
||||
// Must have a StartProcessingEvent
|
||||
if (projection.startedWith == null) return null
|
||||
|
||||
|
||||
override fun produceSummary(fullHistory: List<Event>): Event {
|
||||
// Must have all relevant tasks completed
|
||||
if (!projection.isWorkflowComplete()) return null
|
||||
val eventIds = fullHistory.map { it.eventId }.toSet()
|
||||
|
||||
return CollectedEvent(history.map { it.eventId }.toSet()).derivedOf(event)
|
||||
return CollectedEvent(eventIds).derivedOf(fullHistory.last())
|
||||
}
|
||||
|
||||
override fun summaryAlreadyExists(fullHistory: List<Event>): Boolean {
|
||||
return fullHistory.any { it is CollectedEvent }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
package no.iktdev.mediaprocessing
|
||||
|
||||
import io.mockk.*
|
||||
import no.iktdev.eventi.events.EventTypeRegistry
|
||||
import no.iktdev.eventi.models.Event
|
||||
import no.iktdev.eventi.models.Task
|
||||
import no.iktdev.mediaprocessing.coordinator.*
|
||||
import no.iktdev.mediaprocessing.ffmpeg.dsl.AudioCodec
|
||||
import no.iktdev.mediaprocessing.ffmpeg.dsl.VideoCodec
|
||||
import no.iktdev.mediaprocessing.shared.common.event_task_contract.EventRegistry
|
||||
import no.iktdev.mediaprocessing.shared.common.event_task_contract.events.OperationType
|
||||
import no.iktdev.mediaprocessing.shared.common.event_task_contract.events.StartData
|
||||
import no.iktdev.mediaprocessing.shared.common.event_task_contract.events.StartProcessingEvent
|
||||
import no.iktdev.mediaprocessing.shared.database.InMemoryEventStore
|
||||
import no.iktdev.mediaprocessing.shared.database.stores.TaskStore
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
@ -16,6 +19,9 @@ import java.io.File
|
||||
import java.util.*
|
||||
|
||||
open class TestBase {
|
||||
val eventStore = InMemoryEventStore()
|
||||
|
||||
|
||||
class DummyEvent: Event()
|
||||
class DummyTask: Task()
|
||||
|
||||
@ -35,14 +41,14 @@ open class TestBase {
|
||||
every { coordinatorEnv.incomingContent } returns File("./tmp/input")
|
||||
every { coordinatorEnv.cachedContent } returns File("./tmp/cached")
|
||||
every { coordinatorEnv.streamitAddress } returns "http://streamit.lan"
|
||||
|
||||
EventRegistry.getEvents().let {
|
||||
EventTypeRegistry.register(it)
|
||||
}
|
||||
eventStore.clear()
|
||||
}
|
||||
|
||||
|
||||
fun mockkIO() {
|
||||
mockkConstructor(File::class)
|
||||
every { anyConstructed<File>().exists() } returns true
|
||||
}
|
||||
|
||||
fun defaultStartEvent(): StartProcessingEvent {
|
||||
val start = StartProcessingEvent(
|
||||
data = StartData(
|
||||
|
||||
@ -17,13 +17,13 @@ import org.junit.jupiter.api.Test
|
||||
|
||||
|
||||
class CollectEventsListenerTest : TestBase() {
|
||||
private val listener = CollectEventsListener(eventStore)
|
||||
|
||||
private val listener = CollectEventsListener()
|
||||
|
||||
@Test
|
||||
@DisplayName(
|
||||
"""
|
||||
Hvis historikken har alle påkrevde hendelser og alle oppgaver er i en gyldig tisltand
|
||||
Hvis historikken har alle påkrevde hendelser og alle oppgaver er i en gyldig tilstand
|
||||
Når onEvent kalles og projeksjonen tilsier gyldig status
|
||||
Så:
|
||||
Opprettes CollectEvent basert på historikken
|
||||
@ -54,23 +54,21 @@ class CollectEventsListenerTest : TestBase() {
|
||||
*convert.toTypedArray(),
|
||||
*cover.toTypedArray(),
|
||||
)
|
||||
|
||||
eventStore.setHistory(history)
|
||||
val result = listener.onEvent(history.last(), history)
|
||||
|
||||
assertThat(result).isNotNull()
|
||||
assertThat {
|
||||
result is CollectedEvent
|
||||
}
|
||||
assertThat(result).isInstanceOf(CollectedEvent::class.java)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
@DisplayName(
|
||||
"""
|
||||
Hvis vi har kun encoded hendelse, men vi har sagt at vi også skal ha extract, men ikke har opprettet extract
|
||||
Når encode result kommer inn
|
||||
Så:
|
||||
Opprettes CollectEvent basert på historikken
|
||||
Opprettes ikke CollectEvent
|
||||
"""
|
||||
)
|
||||
fun success2() {
|
||||
@ -101,6 +99,7 @@ class CollectEventsListenerTest : TestBase() {
|
||||
metadata,
|
||||
*encode.toTypedArray(),
|
||||
)
|
||||
eventStore.setHistory(history)
|
||||
|
||||
val result = listener.onEvent(history.last(), history)
|
||||
|
||||
@ -108,7 +107,6 @@ class CollectEventsListenerTest : TestBase() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
@DisplayName(
|
||||
"""
|
||||
@ -150,6 +148,7 @@ class CollectEventsListenerTest : TestBase() {
|
||||
*metadata.toTypedArray(),
|
||||
*convert.toTypedArray(),
|
||||
)
|
||||
eventStore.setHistory(history)
|
||||
|
||||
val result = listener.onEvent(history.last(), history)
|
||||
|
||||
@ -165,7 +164,7 @@ class CollectEventsListenerTest : TestBase() {
|
||||
Når extract result kommer inn
|
||||
Så:
|
||||
Skal vi si pending på convert
|
||||
Listener skal returnerere null
|
||||
Listener skal returnere null
|
||||
"""
|
||||
)
|
||||
fun failure1() {
|
||||
@ -186,6 +185,8 @@ class CollectEventsListenerTest : TestBase() {
|
||||
*encode.toTypedArray(),
|
||||
*extract.toTypedArray(),
|
||||
)
|
||||
eventStore.setHistory(history)
|
||||
|
||||
val result = listener.onEvent(history.last(), history)
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
@ -196,7 +197,7 @@ class CollectEventsListenerTest : TestBase() {
|
||||
Hvis historikken har alle påkrevde media hendelser, men venter på metadata
|
||||
Når onEvent kalles og projeksjonen tilsier ugyldig tilstand
|
||||
Så:
|
||||
Returerer vi failure
|
||||
Returnerer vi null
|
||||
"""
|
||||
)
|
||||
fun failure2() {
|
||||
@ -221,20 +222,20 @@ class CollectEventsListenerTest : TestBase() {
|
||||
*extract.toTypedArray(),
|
||||
*convert.toTypedArray(),
|
||||
)
|
||||
eventStore.setHistory(history)
|
||||
|
||||
val result = listener.onEvent(history.last(), history)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@DisplayName(
|
||||
"""
|
||||
Hvis historikken har alle påkrevde hendelser og encode feilet
|
||||
Når onEvent kalles og projeksjonen tilsier ugyldig tilstand
|
||||
Så:
|
||||
Collect feiler
|
||||
Collect returnerer null
|
||||
"""
|
||||
)
|
||||
fun failure3() {
|
||||
@ -262,11 +263,14 @@ class CollectEventsListenerTest : TestBase() {
|
||||
*convert.toTypedArray(),
|
||||
*cover.toTypedArray(),
|
||||
)
|
||||
eventStore.setHistory(history)
|
||||
|
||||
val result = listener.onEvent(history.last(), history)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@DisplayName(
|
||||
"""
|
||||
@ -287,17 +291,76 @@ class CollectEventsListenerTest : TestBase() {
|
||||
mediaType = MediaType.Movie
|
||||
).derivedOf(started)
|
||||
|
||||
|
||||
|
||||
val history = listOf(
|
||||
started,
|
||||
parsed,
|
||||
)
|
||||
eventStore.setHistory(history)
|
||||
|
||||
val result = listener.onEvent(history.last(), history)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName(
|
||||
"""
|
||||
Summarizer skal være idempotent:
|
||||
- Første kjøring skal produsere CollectedEvent
|
||||
- Andre kjøring skal returnere null
|
||||
- Re-feed skal ikke produsere flere CollectedEvent
|
||||
"""
|
||||
)
|
||||
fun summarizerDoesNotGoHaywire() {
|
||||
val started = defaultStartEvent()
|
||||
|
||||
val parsed = mediaParsedEvent(
|
||||
collection = "MyCollection",
|
||||
fileName = "MyCollection 1",
|
||||
mediaType = MediaType.Movie
|
||||
).derivedOf(started)
|
||||
|
||||
val metadata = metadataEvent(parsed)
|
||||
val encode = encodeEvent("/tmp/video.mp4", parsed)
|
||||
val extract = extractEvent("en", "/tmp/sub1.srt", encode.last())
|
||||
val convert = convertEvent(
|
||||
language = "en",
|
||||
baseName = "sub1",
|
||||
outputFiles = listOf("/tmp/sub1.vtt"),
|
||||
derivedFrom = extract.last()
|
||||
)
|
||||
val cover = coverEvent("/tmp/cover.jpg", metadata.last())
|
||||
|
||||
val history = listOf(
|
||||
started,
|
||||
parsed,
|
||||
*metadata.toTypedArray(),
|
||||
*encode.toTypedArray(),
|
||||
*extract.toTypedArray(),
|
||||
*convert.toTypedArray(),
|
||||
*cover.toTypedArray(),
|
||||
)
|
||||
|
||||
// Gi summarizeren full historikk
|
||||
eventStore.setHistory(history)
|
||||
|
||||
// Første kjøring: skal produsere CollectedEvent
|
||||
val first = listener.onEvent(history.last(), history)
|
||||
assertThat(first).isInstanceOf(CollectedEvent::class.java)
|
||||
|
||||
// Simuler at summarizer-eventet ble lagret i historikken
|
||||
val collected = first as CollectedEvent
|
||||
val newHistory = history + collected
|
||||
eventStore.setHistory(newHistory)
|
||||
|
||||
// Andre kjøring: summarizer skal se at CollectedEvent finnes → returnere null
|
||||
val second = listener.onEvent(collected, newHistory)
|
||||
assertThat(second).isNull()
|
||||
|
||||
// Tredje kjøring: re-feed av siste event → fortsatt null
|
||||
val third = listener.onEvent(collected, newHistory)
|
||||
assertThat(third).isNull()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package no.iktdev.mediaprocessing.shared.common.listeners
|
||||
|
||||
import no.iktdev.eventi.ZDS.toEvent
|
||||
import no.iktdev.eventi.events.EventListener
|
||||
import no.iktdev.eventi.models.Event
|
||||
import no.iktdev.eventi.stores.EventStore
|
||||
|
||||
abstract class SummaryEventListener(
|
||||
private val eventStore: EventStore
|
||||
) : EventListener() {
|
||||
|
||||
final override fun onEvent(event: Event, history: List<Event>): Event? {
|
||||
val fullHistory = eventStore.getPersistedEventsFor(event.referenceId)
|
||||
val events = fullHistory.map { it.toEvent() }.filterNotNull()
|
||||
|
||||
if (!shouldSummarize(events)) return null
|
||||
if (summaryAlreadyExists(events)) return null
|
||||
|
||||
return produceSummary(events).derivedOf(event)
|
||||
}
|
||||
|
||||
abstract fun shouldSummarize(fullHistory: List<Event>): Boolean
|
||||
abstract fun produceSummary(fullHistory: List<Event>): Event
|
||||
abstract fun summaryAlreadyExists(fullHistory: List<Event>): Boolean
|
||||
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package no.iktdev.mediaprocessing.shared.database
|
||||
|
||||
import no.iktdev.eventi.MyTime
|
||||
import no.iktdev.eventi.ZDS.toPersisted
|
||||
import no.iktdev.eventi.models.Event
|
||||
import no.iktdev.eventi.models.store.PersistedEvent
|
||||
import no.iktdev.eventi.stores.EventStore
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
class InMemoryEventStore : EventStore {
|
||||
private val persisted = mutableListOf<PersistedEvent>()
|
||||
private var nextId = 1L
|
||||
|
||||
override fun getPersistedEventsAfter(timestamp: Instant): List<PersistedEvent> =
|
||||
persisted.filter { it.persistedAt > timestamp }
|
||||
|
||||
override fun getPersistedEventsFor(referenceId: UUID): List<PersistedEvent> =
|
||||
persisted.filter { it.referenceId == referenceId }
|
||||
|
||||
override fun persist(event: Event) {
|
||||
val persistedEvent = event.toPersisted(nextId++, MyTime.utcNow())
|
||||
persisted += persistedEvent!!
|
||||
}
|
||||
|
||||
fun persistAt(event: Event, persistedAt: Instant) {
|
||||
val persistedEvent = event.toPersisted(nextId++, persistedAt)
|
||||
persisted += persistedEvent!!
|
||||
}
|
||||
|
||||
fun setHistory(events: List<Event>) {
|
||||
events.forEach { persist(it) }
|
||||
}
|
||||
|
||||
fun all(): List<PersistedEvent> = persisted
|
||||
fun clear() { persisted.clear(); nextId = 1L }
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user