- Route every endpoint through a new send_with_retry: it waits on the rate limiter, GETs, logs the x-ratelimit budget, and on a 503/429 backs off the server's Retry-After (capped 8s) and retries, up to 3 attempts. get_artist_tags and get_recording_isrcs, which skipped status handling entirely, now go through it too. - Also retry transient TRANSPORT failures (a hung connection to the request timeout, a drop or refusal). MusicBrainz under load hangs instead of returning 503, which surfaced as "error sending request for url". Capped at 2 attempts with a 6s request timeout so a total failure is ~13s, not ~33s, while a hang that clears still recovers on the second try. - Share ONE process-wide direct rate limiter across every client (core, the tag editor, remote metadata) so several clients on one public IP pace as one 1.1s stream instead of each pacing itself. - Log MusicBrainz's x-ratelimit-limit/remaining/reset headers (debug on 2xx, warn on 429/503) as the only signal of whether the bucket was ours or MB's. Tests: 503-then-200, a dropped connection, and shared-limiter serialization, each red-to-green. Dev-only deps (tokio test features + rustls/aws-lc-rs to install the crypto provider); no runtime dependency added. cc
45 lines
1.2 KiB
TOML
45 lines
1.2 KiB
TOML
[package]
|
|
name = "qbz-integrations"
|
|
version = "0.1.0"
|
|
edition = "2021"
|
|
description = "Third-party service integrations for QBZ (Last.fm, ListenBrainz, MusicBrainz)"
|
|
license = "MIT"
|
|
|
|
[dependencies]
|
|
# Async runtime
|
|
tokio = { version = "1", features = ["sync", "time"] }
|
|
|
|
# HTTP client
|
|
reqwest = { workspace = true, default-features = false, features = ["json", "query", "rustls-no-provider"] }
|
|
|
|
# Serialization
|
|
serde = { version = "1", features = ["derive"] }
|
|
serde_json = "1"
|
|
|
|
# Logging
|
|
log = "0.4"
|
|
|
|
# Error handling
|
|
thiserror = "1"
|
|
|
|
# URL encoding for API queries
|
|
urlencoding = "2"
|
|
|
|
# Discord Rich Presence (now-listening activity). Same crate + version the
|
|
# Tauri build used; frontend-agnostic (IPC-only, no Tauri dependency).
|
|
discord-rich-presence = "1.1.0"
|
|
|
|
# SQLite for caching (optional, enabled by default)
|
|
rusqlite = { version = "0.40", features = ["bundled"], optional = true }
|
|
|
|
# Time handling for cache TTL
|
|
chrono = { version = "0.4", optional = true }
|
|
|
|
[dev-dependencies]
|
|
tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "io-util"] }
|
|
rustls = { version = "0.23", features = ["aws_lc_rs"] }
|
|
|
|
[features]
|
|
default = ["cache"]
|
|
cache = ["dep:rusqlite", "dep:chrono"]
|