ClientgRPC ServerMusicAgregatorServiceMetadataServicemetadata-agregatorPostgreSQLIndexerServiceMagnetResolverTorrentClientRiver QueuePollDownloadWorkerClientClientgRPC ServergRPC ServerMusicAgregatorServiceMusicAgregatorServiceMetadataServiceMetadataServicemetadata-agregator(gRPC)metadata-agregator(gRPC)PostgreSQLPostgreSQLIndexerService(Jackett)IndexerService(Jackett)MagnetResolverMagnetResolverTorrentClient(qBittorrent)TorrentClient(qBittorrent)River QueueRiver QueuePollDownloadWorkerPollDownloadWorker1. Fetch Album MetadataMonitorAlbum(album_id, quality, tracker)MonitorAlbum(ctx, req)GetAlbum(album_id)GetAlbum(id)Album (title, artists, genres, ...)albums.GetByExternalID(external_id)Check if album already persistednot foundartists.Create(artist, state=monitored)Upsert artistnever downgradesmonitored/excludedalbums.Create(album, state=monitored)Upsert albumnever downgradesmonitored/excludedAlbum2. Set Monitor Statealbums.GetByExternalID(external_id)dbAlbumalbums.SetMonitorState(id, monitored)Explicitly markalbum as monitored3. Check If Already Owneddownloads.HasAlbumInQuality(album_id, format, quality)false (not owned)4. Search IndexersSearch(artist + album title, tracker)Jackett API/api/v2.0/indexers/all/resultsSearchResponse (N items)5. Parse & Resolve Releasesloop[for each search result (with download link & seeders > 0)]alt[magnet link]Extract: format, bitDepth, sampleRate,source, trackCount, coverArt, cueSheet, ripLogResolve(magnet_uri)DHT lookup, 30s timeout15s early exit if peersbut none activetorrent metadata (files, hash, size)ParseTorrent(torrentData, album)[HTTP torrent link]downloadTorrentData(url)ParseTorrent(torrentData, album)6. Filter & Select BestfilterByQuality(parsed, quality)Match LOSSLESS/LOSSY/UNSPECIFIEDagainst release formatselectBestRelease(filtered)Highest seeder count wins7. Add to Torrent ClientFind(hash)not foundalt[magnet link]AddMagnet(magnet_uri)[torrent file]AddTorrent(file)OK8. Persist Torrent & Downloadtorrents.Create(torrent)Upsert on info_hashupdates seeders/peerstorrents.GetByInfoHash(hash)savedTorrent (with DB id)downloads.GetActiveByTorrentID(torrent_id)not found (no active download)downloads.Create(download)state = "downloading"format, quality, qbit_hashdownload (with DB id)9. Schedule Download PollInsert(PollDownloadArgs)download_id, torrent_hashcheck_interval = 30sscheduled_at = now + 30sjob scheduled10. Build & Return Responsealbums.GetByExternalID(external_id)dbAlbum (refreshed)downloads.GetByAlbumID(album_id)downloads (with state)artists.GetByExternalID(artist_external_id)dbArtistMonitorAlbumResponsealbum: id, title, monitor_state=monitored,download: state, format, qualityartist: id, name, monitor_staterelease: hash, format, seeders, tracksMonitorAlbumResponse11. Async: Download Polling (River Worker)Work(PollDownloadArgs)Find(hash)TorrentInfo (progress, state, path)alt[progress < 100%]Insert(PollDownloadArgs)Reschedule after check_interval[progress == 100%]downloads.SetCompleted(id, save_path)scanAndHashFiles(content_path)Walk directory, identify audio files(.flac, .mp3, .aac, ...)SHA-256 hash each filedownload_files.CreateBatch(files)file_path, file_size, file_type,sha256_hash, verified_at