1mod mecomp {
2 #![allow(
4 clippy::derive_partial_eq_without_eq,
5 clippy::missing_const_for_fn,
6 clippy::too_many_lines,
7 clippy::default_trait_access,
8 clippy::doc_markdown,
9 clippy::missing_errors_doc,
10 clippy::must_use_candidate
11 )]
12
13 #[cfg(not(tarpaulin_include))]
14 tonic::include_proto!("mecomp");
15}
16#[doc(hidden)]
17mod conversions;
18pub mod helpers;
19
20use std::time::Duration;
21
22use tonic::service::Interceptor;
23use tonic::service::interceptor::InterceptedService;
24use tonic::transport::Channel;
25
26pub use conversions::convert_std_duration;
27pub use mecomp::music_player_client as client;
28pub use mecomp::music_player_server as server;
29pub use mecomp::*;
30
31pub type LibraryBrief = mecomp::LibraryBriefResponse;
32pub type LibraryFull = mecomp::LibraryFullResponse;
33pub type LibraryHealth = mecomp::LibraryHealthResponse;
34
35pub type MusicPlayerClient =
36 client::MusicPlayerClient<InterceptedService<Channel, TraceInterceptor>>;
37
38#[derive(thiserror::Error, Debug)]
39pub enum ConnectionError {
40 #[error("{0}")]
41 Transport(#[from] tonic::transport::Error),
42 #[error("failed to connect to Music Player Daemon on port {port} after {retries} retries")]
43 MaxRetriesExceeded { port: u16, retries: u64 },
44}
45
46#[derive(Clone, Debug)]
47pub struct TraceInterceptor {}
48impl Interceptor for TraceInterceptor {
49 fn call(&mut self, req: tonic::Request<()>) -> Result<tonic::Request<()>, tonic::Status> {
50 tracing::trace!("Received request with extensions: {:?}", req.extensions());
51 Ok(req)
52 }
53}
54
55#[must_use]
65pub fn lazy_init_client(rpc_port: u16) -> MusicPlayerClient {
66 let endpoint = format!("http://localhost:{rpc_port}");
67
68 let endpoint = Channel::from_shared(endpoint)
69 .expect("Invalid endpoint URL")
70 .connect_lazy();
71
72 let interceptor = TraceInterceptor {};
73
74 music_player_client::MusicPlayerClient::with_interceptor(endpoint, interceptor)
75}
76
77pub async fn init_client(rpc_port: u16) -> Result<MusicPlayerClient, ConnectionError> {
87 let endpoint = format!("http://localhost:{rpc_port}");
88
89 let endpoint = Channel::from_shared(endpoint)
90 .expect("Invalid endpoint URL")
91 .connect()
92 .await?;
93
94 let interceptor = TraceInterceptor {};
95
96 let client = music_player_client::MusicPlayerClient::with_interceptor(endpoint, interceptor);
97
98 Ok(client)
99}
100
101#[allow(clippy::missing_inline_in_public_items)]
109pub async fn init_client_with_retry<const MAX_RETRIES: u64, const DELAY: u64>(
110 rpc_port: u16,
111) -> Result<MusicPlayerClient, ConnectionError> {
112 let mut retries = 0u64;
113
114 while retries < MAX_RETRIES {
115 match init_client(rpc_port).await {
116 Ok(client) => return Ok(client),
117 Err(e) => {
118 retries += 1;
119 log::warn!("Failed to connect to daemon: {e}");
120 tokio::time::sleep(Duration::from_secs(DELAY * retries)).await;
121 }
122 }
123 }
124
125 log::error!("{MAX_RETRIES} retries exceeded when attempting to connect to the daemon");
126
127 Err(ConnectionError::MaxRetriesExceeded {
128 port: rpc_port,
129 retries: MAX_RETRIES,
130 })
131}