Update flow diagrams for event bus architecture, cancel cleanup, and SubscribeEvents

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/claude-agent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Alexander
2026-05-11 15:54:32 +02:00
parent 93821ab214
commit 69752bd6a2
5 changed files with 231 additions and 103 deletions
@@ -1,113 +1,99 @@
@startuml MonitorAlbumStream - Automatic Mode Happy Path
skinparam sequenceMessageAlign center
skinparam responseMessageBelowArrow true
title MonitorAlbumStream: Automatic Mode (Status Updates Only)
title MonitorAlbumStream: Automatic Mode (Fire-and-Forget)
actor Client
participant "gRPC Server" as Server
participant "monitorWorkflow" as Workflow
participant "MusicAgregatorService" as Service
participant "MetadataService" as Metadata
database "metadata-agregator\n(gRPC)" as MetaGRPC
participant "WorkflowRegistry" as Registry
participant "EventBus" as Bus
participant "monitorWorkflow\n(background goroutine)" as Workflow
database "PostgreSQL" as DB
participant "IndexerService\n(Jackett)" as Indexer
participant "MusicAgregatorService" as Service
participant "IndexerService" as Indexer
participant "MagnetResolver" as Magnet
participant "TorrentClient\n(qBittorrent)" as QBit
participant "River Queue" as River
== 1. Initialize Bidirectional Stream ==
== 1. Initialize Stream ==
Client -> Server: MonitorAlbumStream()
note right: Opens bidirectional\ngRPC stream
Server -> Client: stream established
Client -> Server: StartMonitorRequest
note right: album_id, quality, tracker\nmode = AUTOMATIC
note right: album_id, quality\nmode = AUTOMATIC
Server -> Workflow: newMonitorWorkflow(stream, req, service)
note right: Creates workflow with\ndecisions channel and\nreceiver goroutine
== 2. Start or Subscribe to Workflow ==
== 2. Fetch Album Metadata ==
Server -> Registry: GetOrCreate(albumID, quality)
Workflow ->> Client: StatusUpdate(FETCHING_METADATA)
note right #lightblue: "Fetching metadata..."
alt New workflow
Registry --> Server: (entry, created=true)
Workflow -> Service: getAlbumWithPersist(ctx, album_id)
Service -> Metadata: GetAlbum(album_id)
Metadata -> MetaGRPC: GetAlbum(id)
MetaGRPC --> Metadata: Album
Metadata -> DB: artists.Create / albums.Create
Metadata --> Service: Album
Server -> Bus: Subscribe(topic)
note right #lightblue: Subscribe BEFORE\nstarting goroutine\n(no missed events)
Workflow ->> Client: StatusUpdate(FETCHING_METADATA)
note right #lightblue: "Got: Artist - Title"\nData: StreamAlbumInfo
Server -> Workflow: go workflow.run(entry.Ctx)
note right #lightyellow: Background goroutine.\nDecoupled from stream context.\nClient can disconnect freely.
else Existing workflow
Registry --> Server: (entry, created=false)
== 3. Check If Already Owned ==
Server -> Bus: Subscribe(topic)
Workflow ->> Client: StatusUpdate(CHECKING_OWNED)
note right #lightblue: "Checking ownership..."
Workflow -> DB: downloads.HasAlbumInQuality()
DB --> Workflow: false
== 4. Search Indexers ==
Workflow ->> Client: StatusUpdate(SEARCHING_INDEXER)
note right #lightblue: "Searching indexers..."
Workflow -> Indexer: Search(query, tracker)
Indexer --> Workflow: SearchResponse (N items)
== 5. Parse Releases ==
loop for each search result
Workflow -> Magnet: Resolve(magnet_uri)
Magnet --> Workflow: torrent metadata
Workflow -> Workflow: ParseTorrent()
Server -> DB: Replay album_events\n(since last seq)
DB --> Server: historical events
Server ->> Client: replayed events
end
Workflow ->> Client: StatusUpdate(PARSING_RESULTS)
note right #lightblue: "Parsed M from N torrents"\nData: TorrentList
== 3. Event Bridge (concurrent) ==
== 6. Filter by Quality ==
note over Client, Workflow #lightblue
**Left side**: Event bus → gRPC stream bridge
**Right side**: Workflow executing in background
Both run concurrently. Client disconnect only stops the bridge.
end note
Workflow ->> Client: StatusUpdate(FILTERING_QUALITY)
note right #lightblue: "Filtering by quality..."
|||
Workflow -> Workflow: filterByQuality(parsed, quality)
Workflow -> DB: album_events.Create(FETCHING_METADATA)
Workflow -> Bus: Publish(FETCHING_METADATA)
Bus ->> Server: event notification
Server ->> Client: StatusUpdate(FETCHING_METADATA)
== 7. Select Best Release ==
Workflow -> Service: getAlbumWithPersist()
Workflow -> DB: workflow_runs.Create(albumID, quality)
Workflow -> Workflow: selectBestRelease(filtered)
note right: Highest seeder count wins\n(automatic selection)
Workflow -> DB: album_events.Create(CHECKING_OWNED)
Workflow -> Bus: Publish(CHECKING_OWNED)
Bus ->> Server: event notification
Server ->> Client: StatusUpdate(CHECKING_OWNED)
Workflow ->> Client: StatusUpdate(SELECTING_RELEASE)
note right #lightblue: "Selected: Title (N seeders)"\nData: ReleaseInfo
Workflow -> Indexer: Search()
Workflow -> DB: album_events.Create(PARSING_RESULTS)
Workflow -> Bus: Publish(PARSING_RESULTS)
Bus ->> Server: event notification
Server ->> Client: StatusUpdate(PARSING_RESULTS)
== 8. Add to Torrent Client ==
Workflow -> Workflow: filterByQuality + selectBest
Workflow ->> Client: StatusUpdate(ADDING_TORRENT)
note right #lightblue: "Adding torrent..."
Workflow -> DB: saveTorrentAndDownload()
note right: DB save BEFORE qBit add\n(prevents orphan torrents)
Workflow -> QBit: AddMagnet()
Workflow -> QBit: AddMagnet(magnet_uri)
QBit --> Workflow: OK
Workflow -> DB: album_events.Create(COMPLETE)
Workflow -> Bus: Publish(COMPLETE)
Bus ->> Server: event notification
Server ->> Client: MonitorAlbumResponse
note right #lightgreen: Final result
== 9. Persist & Schedule ==
Workflow -> DB: workflow_runs.SetCompleted()
Workflow -> Registry: Remove(albumID, quality)
Workflow ->> Client: StatusUpdate(SAVING)
note right #lightblue: "Saving to database..."
== 4. Client Disconnect (Fire-and-Forget) ==
Workflow -> DB: torrents.Create / downloads.Create
Workflow -> River: Insert(PollDownloadArgs)
== 10. Return Result ==
Workflow ->> Client: StatusUpdate(COMPLETE)
note right #lightblue: "Download started"
Workflow ->> Client: MonitorAlbumResponse
note right #lightgreen: Final result with:\nalbum, artist, release, download
Server -> Server: Close stream
note over Client, Workflow #lightyellow
Client can disconnect at ANY point during the workflow.
The workflow goroutine continues independently.
Another client can subscribe to the same workflow later.
end note
@enduml