@startuml MonitorAlbumStream - Manual Mode Happy Path skinparam sequenceMessageAlign center skinparam responseMessageBelowArrow true title MonitorAlbumStream: Manual Mode (Interactive Prompts) actor Client participant "gRPC Server" as Server participant "monitorWorkflow" as Workflow participant "MusicAgregatorService" as Service database "PostgreSQL" as DB participant "IndexerService" as Indexer participant "MagnetResolver" as Magnet participant "TorrentClient\n(qBittorrent)" as QBit == 1. Initialize Stream == Client -> Server: MonitorAlbumStream() Server -> Client: stream established Client -> Server: StartMonitorRequest note right: album_id, quality\nmode = MANUAL Server -> Workflow: newMonitorWorkflow() == 2. Fetch Metadata == Workflow ->> Client: StatusUpdate(FETCHING_METADATA) note right #lightblue: "Fetching album metadata..." Workflow -> Service: getAlbumWithPersist() Workflow ->> Client: StatusUpdate(FETCHING_METADATA) note right #lightblue: Data: StreamAlbumInfo\n{artist, title, release_date,\nalready_owned, owned_quality} == 3. Check Ownership (Interactive) == Workflow -> DB: downloads.HasAlbumInQuality() DB --> Workflow: true (already owned!) Workflow ->> Client: StatusUpdate(CHECKING_OWNED) note right #lightyellow: "Already owned in FLAC quality" Workflow ->> Client: PromptForDecision note right #orange: type: CONFIRM\nmessage: "Album already owned. Download anyway?"\ndefault: false\ntimeout: max 300s Client -> Server: UserDecision note right: confirm: true\n(user chooses to continue) Workflow -> Workflow: Continue with search == 4. Search & Parse == Workflow ->> Client: StatusUpdate(SEARCHING_INDEXER) Workflow -> Indexer: Search() Indexer --> Workflow: 3 results loop parse results Workflow -> Magnet: Resolve() end Workflow ->> Client: StatusUpdate(PARSING_RESULTS) note right #lightblue: Data: TorrentList\n[{id, title, seeders, format}, ...] == 5. Select Torrents (Interactive) == Workflow ->> Client: PromptForDecision note right #orange: type: SELECT_MANY\nmessage: "Select torrents to consider"\noptions: [{id, label, description}, ...]\ndefault: all selected\nmin: 1, max: N Client -> Server: UserDecision note right: selected_ids: ["torrent-0", "torrent-2"]\n(user deselects torrent-1) Workflow -> Workflow: Filter to selected torrents == 6. Filter by Quality == Workflow ->> Client: StatusUpdate(FILTERING_QUALITY) Workflow -> Workflow: filterByQuality() note right: 2 releases remain\nafter quality filter == 7. Select Release (Interactive) == Workflow ->> Client: PromptForDecision note right #orange: type: SELECT_ONE\nmessage: "Select release"\noptions: [{id, label, description}, ...]\ndefault: highest seeders Client -> Server: UserDecision note right: selected_id: "release-1"\n(user picks specific release) Workflow ->> Client: StatusUpdate(SELECTING_RELEASE) note right #lightblue: Data: ReleaseInfo\n{hash, format, seeders, tracker} == 8. Confirm Add (Interactive) == Workflow ->> Client: StatusUpdate(ADDING_TORRENT) note right #lightyellow: "Adding torrent: Title..." Workflow ->> Client: PromptForDecision note right #orange: type: CONFIRM\nmessage: "Add torrent 'Title' to client?"\nconfirm_label: "Add"\ncancel_label: "Skip"\ndefault: true Client -> Server: UserDecision note right: confirm: true == 9. Add & Save == Workflow -> QBit: AddMagnet() QBit --> Workflow: OK Workflow ->> Client: StatusUpdate(SAVING) Workflow -> DB: Create torrent & download == 10. Complete == Workflow ->> Client: StatusUpdate(COMPLETE) note right #lightblue: "Download started" Workflow ->> Client: MonitorAlbumResponse note right #lightgreen: Final result == Cancel Cleanup (Disconnect or Cancel Message) == note over Client, QBit #salmon **Manual Mode: Disconnect = Cancel** When client disconnects or sends CancelRequest: 1. Workflow context is cancelled (stops further processing) 2. If torrent was added to qBit: **DeleteTorrent(hash)** removes it + files 3. If download record exists: marked as **cancelled** in DB 4. workflow_run marked as **cancelled** in DB 5. All events persisted to album_events for audit trail Cleanup uses a fresh context (not the cancelled one). end note == Decision Points Summary == note over Client, QBit #lightyellow **Manual Mode Decision Points:** 1. **CHECKING_OWNED** (CONFIRM) - Triggered when: Album already owned in requested quality - Default: false (skip) - Timeout action: Use default 2. **PARSING_RESULTS** (SELECT_MANY) - Triggered when: Multiple torrents found (>1) - Default: All selected - Timeout action: Use defaults 3. **SELECTING_RELEASE** (SELECT_ONE) - Triggered when: Multiple releases after quality filter (>1) - Default: Highest seeders - Timeout action: Use default 4. **ADDING_TORRENT** (CONFIRM) - Triggered: Always in manual mode - Default: true (add) - Timeout action: Use default end note @enduml