Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #152 from tsirysndr/feat/upnp-support

Add UPnP/DLNA media server and sink

authored by

Tsiry Sandratraina and committed by
GitHub
6215a297 fcee4317

+12685 -46
+2
.github/workflows/macos-intel-build.yml
··· 2 2 3 3 on: 4 4 push: 5 + branches: 6 + - master 5 7 6 8 jobs: 7 9 build:
+28
Cargo.lock
··· 9161 9161 "rockbox-settings", 9162 9162 "rockbox-slim", 9163 9163 "rockbox-typesense", 9164 + "rockbox-upnp", 9164 9165 "tokio", 9165 9166 "tracing", 9166 9167 "tracing-subscriber", ··· 9403 9404 "rockbox-traits", 9404 9405 "rockbox-types", 9405 9406 "rockbox-typesense", 9407 + "rockbox-upnp", 9406 9408 "serde", 9407 9409 "serde_json", 9408 9410 "sqlx", ··· 9419 9421 dependencies = [ 9420 9422 "anyhow", 9421 9423 "rockbox-sys", 9424 + "rockbox-upnp", 9422 9425 "toml 0.8.19", 9423 9426 "tracing", 9424 9427 ] ··· 9478 9481 "rockbox-library", 9479 9482 "serde", 9480 9483 "serde_json", 9484 + "tracing", 9485 + "uuid", 9486 + ] 9487 + 9488 + [[package]] 9489 + name = "rockbox-upnp" 9490 + version = "0.1.0" 9491 + dependencies = [ 9492 + "anyhow", 9493 + "bytes", 9494 + "http 1.1.0", 9495 + "http-body-util", 9496 + "hyper 1.4.1", 9497 + "hyper-util", 9498 + "prost", 9499 + "reqwest", 9500 + "rockbox-sys", 9501 + "serde", 9502 + "socket2 0.5.7", 9503 + "sqlx", 9504 + "tokio", 9505 + "tonic", 9506 + "tonic-build", 9507 + "tonic-reflection", 9508 + "tonic-web", 9481 9509 "tracing", 9482 9510 "uuid", 9483 9511 ]
+76 -9
README.md
··· 45 45 - [x] [MPRIS](https://specifications.freedesktop.org/mpris-spec/) — desktop media key and taskbar integration 46 46 - [x] Fast search powered by [Typesense](https://typesense.org) 47 47 - [x] Navigate by folders or tag database 48 + - [x] UPnP/DLNA 48 49 49 50 ### Clients 50 51 - [x] Web client (React) ··· 57 58 - [ ] Mobile app (React Native) 58 59 - [ ] Stream from YouTube / Spotify / Tidal 59 60 - [ ] TuneIn Radio 60 - - [ ] UPnP/DLNA 61 61 - [ ] Kodi output 62 62 - [ ] TypeScript ([Deno](https://deno.com)) plugin API 63 63 - [ ] Wasm extensions ··· 174 174 175 175 ## 🔌 Ports 176 176 177 - | Service | Default port | Protocol | 178 - |---|---|---| 179 - | gRPC | 6061 | gRPC / gRPC-Web | 180 - | GraphQL + Web UI | 6062 | HTTP | 181 - | HTTP REST API | 6063 | HTTP | 182 - | MPD server | 6600 | MPD protocol | 183 - | Slim Protocol (squeezelite) | 3483 | TCP | 184 - | HTTP PCM stream (squeezelite) | 9999 | HTTP | 177 + | Service | Default port | Protocol | 178 + |---------------------------------------|--------------|-----------------| 179 + | gRPC | 6061 | gRPC / gRPC-Web | 180 + | GraphQL + Web UI | 6062 | HTTP | 181 + | HTTP REST API | 6063 | HTTP | 182 + | MPD server | 6600 | MPD protocol | 183 + | Slim Protocol (squeezelite) | 3483 | TCP | 184 + | HTTP PCM stream (squeezelite) | 9999 | HTTP | 185 + | UPnP Media Server (ContentDirectory) | 7878 | HTTP / SSDP | 186 + | UPnP WAV broadcast (PCM sink) | 7879 | HTTP | 187 + | UPnP MediaRenderer (AVTransport) | 7880 | HTTP / SSDP | 185 188 186 189 --- 187 190 ··· 287 290 squeezelite -s localhost -o "" # system default 288 291 squeezelite -s localhost -o "Built-in Output" 289 292 ``` 293 + 294 + ### UPnP / DLNA 295 + 296 + Rockbox has three independent UPnP/DLNA modes that can be combined freely. 297 + 298 + #### PCM sink — stream live audio to a UPnP renderer (Kodi, VLC, …) 299 + 300 + ```toml 301 + music_dir = "/path/to/Music" 302 + audio_output = "upnp" 303 + 304 + # AVTransport controlURL of the target renderer (required for metadata push) 305 + upnp_renderer_url = "http://192.168.1.x:7777/AVTransport/control" 306 + 307 + # Port for the WAV HTTP broadcast server (default: 7879) 308 + upnp_http_port = 7879 309 + ``` 310 + 311 + Rockbox encodes live PCM as a continuous WAV-over-HTTP stream and commands the 312 + renderer to play it via AVTransport SOAP. Track metadata (title, artist, album, 313 + album art, duration) is sent as DIDL-Lite XML in `SetAVTransportURI` and 314 + auto-refreshed on every track change so the renderer's "Now Playing" display 315 + stays accurate. 316 + 317 + > **Finding `upnp_renderer_url`**: start `rockboxd` with `RUST_LOG=info` — it 318 + > scans the LAN on startup and logs `upnp scan: found renderer "…" av=http://…` 319 + > for every discovered renderer. 320 + 321 + #### Media Server — expose library to control points (BubbleUPnP, Kodi, …) 322 + 323 + ```toml 324 + upnp_server_enabled = true 325 + upnp_server_port = 7878 # default 326 + upnp_friendly_name = "Rockbox" # name shown in apps 327 + ``` 328 + 329 + Starts a ContentDirectory service so control points can browse artists, albums, 330 + and tracks and pull audio directly from Rockbox. 331 + 332 + #### MediaRenderer — let control points push media to Rockbox 333 + 334 + ```toml 335 + upnp_renderer_enabled = true 336 + upnp_renderer_port = 7880 # default 337 + upnp_friendly_name = "Rockbox" 338 + ``` 339 + 340 + Rockbox registers as a `MediaRenderer:1`. Any DLNA control point (BubbleUPnP, 341 + Foobar2000, etc.) can push a URI to Rockbox and control playback remotely. 342 + Incoming DIDL-Lite metadata (title, artist, album, album art, duration) is 343 + parsed and displayed. 344 + 345 + #### All UPnP settings 346 + 347 + | Key | Default | Description | 348 + |----------------------------|--------------|------------------------------------------------| 349 + | `audio_output = "upnp"` | — | Enable the PCM → WAV streaming sink | 350 + | `upnp_renderer_url` | — | AVTransport controlURL of the target renderer | 351 + | `upnp_http_port` | `7879` | WAV broadcast HTTP port | 352 + | `upnp_server_enabled` | `false` | Start the ContentDirectory media server | 353 + | `upnp_server_port` | `7878` | Media server HTTP port | 354 + | `upnp_renderer_enabled` | `false` | Start the MediaRenderer endpoint | 355 + | `upnp_renderer_port` | `7880` | MediaRenderer HTTP port | 356 + | `upnp_friendly_name` | `"Rockbox"` | Display name shown to control points | 290 357 291 358 --- 292 359
+13 -3
apps/playback.c
··· 3989 3989 #endif 3990 3990 3991 3991 LOGFQUEUE("audio >| audio Q_AUDIO_PLAY: %lu %lX", elapsed, offset); 3992 - audio_queue_send(Q_AUDIO_PLAY, 3993 - (intptr_t)&(struct audio_resume_info){ elapsed, offset }); 3992 + if (elapsed == 0 && offset == 0) 3993 + { 3994 + /* Non-blocking post: safe to call from any OS thread (no resume 3995 + * position needed; audio_start_playback handles NULL gracefully). */ 3996 + audio_queue_post(Q_AUDIO_PLAY, 0); 3997 + } 3998 + else 3999 + { 4000 + audio_queue_send(Q_AUDIO_PLAY, 4001 + (intptr_t)&(struct audio_resume_info){ elapsed, offset }); 4002 + } 3994 4003 } 3995 4004 3996 4005 /* Stop playback if playing */ ··· 4024 4033 void audio_resume(void) 4025 4034 { 4026 4035 LOGFQUEUE("audio >| audio Q_AUDIO_PAUSE resume"); 4027 - audio_queue_send(Q_AUDIO_PAUSE, false); 4036 + /* Non-blocking post: safe to call from any OS thread. */ 4037 + audio_queue_post(Q_AUDIO_PAUSE, false); 4028 4038 } 4029 4039 4030 4040 /* Internal function used by REPEAT_ONE extern playlist.c */
+1
crates/cli/Cargo.toml
··· 10 10 anyhow = "1.0.90" 11 11 rockbox-airplay = {path = "../airplay"} 12 12 rockbox-slim = {path = "../slim"} 13 + rockbox-upnp = {path = "../upnp"} 13 14 clap = "4.5.16" 14 15 owo-colors = "4.1.0" 15 16 reqwest = { workspace = true, features = ["rustls-tls", "json"] }
+2
crates/cli/src/lib.rs
··· 12 12 use rockbox_slim::_link_slim as _; 13 13 use rockbox_typesense::client::*; 14 14 use rockbox_typesense::types::*; 15 + #[allow(unused_imports)] 16 + use rockbox_upnp::_link_upnp as _; 15 17 use std::io::{BufRead, BufReader}; 16 18 use std::process::Stdio; 17 19 use std::sync::atomic::{AtomicI32, Ordering};
+1
crates/graphql/src/schema/playback.rs
··· 234 234 shuffle: Option<bool>, 235 235 position: Option<i32>, 236 236 ) -> Result<i32, Error> { 237 + let path = path.trim().to_string(); 237 238 let client = ctx.data::<reqwest::Client>().unwrap(); 238 239 let mut tracks: Vec<String> = vec![]; 239 240
+7
crates/graphql/src/schema/playlist.rs
··· 12 12 rockbox_url, schema::objects::playlist::Playlist, simplebroker::SimpleBroker, types::StatusCode, 13 13 }; 14 14 15 + fn trim_path(s: &str) -> String { 16 + let s = s.trim(); 17 + s.split('#').next().unwrap_or(s).to_string() 18 + } 19 + 15 20 #[derive(Default)] 16 21 pub struct PlaylistQuery; 17 22 ··· 148 153 name: String, 149 154 tracks: Vec<String>, 150 155 ) -> Result<i32, Error> { 156 + let tracks: Vec<String> = tracks.into_iter().map(|t| trim_path(&t)).collect(); 151 157 let client = ctx.data::<reqwest::Client>().unwrap(); 152 158 let body = serde_json::json!({ 153 159 "name": name, ··· 167 173 position: i32, 168 174 tracks: Vec<String>, 169 175 ) -> Result<i32, Error> { 176 + let tracks: Vec<String> = tracks.into_iter().map(|t| trim_path(&t)).collect(); 170 177 let client = ctx.data::<reqwest::Client>().unwrap(); 171 178 let body = serde_json::json!({ 172 179 "position": position,
+16
crates/library/src/audio_scan.rs
··· 340 340 const MAX_PROBE_BYTES: usize = 8 * 1024 * 1024; 341 341 342 342 let client = reqwest::Client::new(); 343 + 344 + // Try a bounded range first. If the file is smaller than MAX_PROBE_BYTES 345 + // some servers return 416; in that case fall back to an open-ended range 346 + // which downloads only to the (small) end of file. 343 347 let mut response = client 344 348 .get(url) 345 349 .header( ··· 348 352 ) 349 353 .send() 350 354 .await?; 355 + 356 + if response.status() == reqwest::StatusCode::RANGE_NOT_SATISFIABLE { 357 + tracing::info!( 358 + "probe remote media {}: 416 Range Not Satisfiable, retrying with open-ended range", 359 + url 360 + ); 361 + response = client 362 + .get(url) 363 + .header(reqwest::header::RANGE, "bytes=0-") 364 + .send() 365 + .await?; 366 + } 351 367 352 368 if !response.status().is_success() && response.status() != reqwest::StatusCode::PARTIAL_CONTENT 353 369 {
+78 -12
crates/netstream/src/lib.rs
··· 93 93 /// Falls back to reopening from byte 0 and discarding bytes if the server 94 94 /// ignores Range and responds with the full body. 95 95 fn seek_to(&mut self, new_pos: u64) -> bool { 96 + // Use a bounded range when content_length is known so the request is 97 + // always within [0, content_length). This prevents spurious 416 98 + // responses and tells the server exactly how many bytes we want. 99 + let range_header = match self.content_length { 100 + Some(cl) if cl > 0 => format!("bytes={}-{}", new_pos, cl - 1), 101 + _ => format!("bytes={}-", new_pos), 102 + }; 96 103 self.response = None; 97 - let result = CLIENT 98 - .get(&self.url) 99 - .header("Range", format!("bytes={}-", new_pos)) 100 - .send(); 104 + let result = CLIENT.get(&self.url).header("Range", range_header).send(); 101 105 102 106 match result { 103 107 Ok(resp) if resp.status().as_u16() == 206 => { ··· 181 185 return INVALID_HANDLE; 182 186 } 183 187 let url_str = match CStr::from_ptr(url).to_str() { 184 - Ok(s) => s.to_owned(), 188 + Ok(s) => { 189 + let s = s.trim(); 190 + // Drop any URL fragment — the codec only needs the resource itself. 191 + let s = s.split('#').next().unwrap_or(s); 192 + s.to_owned() 193 + } 185 194 Err(_) => return INVALID_HANDLE, 186 195 }; 187 196 ··· 234 243 let pos_before = state.pos; 235 244 let resp = match &mut state.response { 236 245 Some(r) => r, 237 - None => return -1, 246 + None => { 247 + // No active response. If we know content_length and pos is at or 248 + // beyond it, signal EOF (0) rather than an error (-1) so callers 249 + // that iterate until EOF terminate cleanly. 250 + if state.content_length.map_or(false, |cl| state.pos >= cl) { 251 + return 0; 252 + } 253 + return -1; 254 + } 238 255 }; 239 256 let buf = std::slice::from_raw_parts_mut(dst as *mut u8, n); 240 257 match read_as_file(resp, buf) { ··· 314 331 } 315 332 _ => return -1, 316 333 }; 334 + 335 + // Guard: never issue a Range request past EOF — the server would return 416 336 + // and seek_to would leave response=None, permanently breaking the stream. 337 + // This can happen when the C MP4 parser has a uint32_t underflow in its 338 + // atom-size arithmetic and tries to seek gigabytes forward. 339 + if let Some(cl) = state.content_length { 340 + if new_pos >= cl { 341 + warn!( 342 + "[netstream] rb_net_lseek: h={} off={} whence={} new_pos={} >= content_length={}, clamping to EOF", 343 + h, off, whence, new_pos, cl 344 + ); 345 + state.pos = cl; 346 + state.response = None; // EOF: rb_net_read will return 0 347 + return cl as i64; 348 + } 349 + } 317 350 318 351 // Fast-path: already there (no need to restart the request). 319 352 if new_pos == state.pos { ··· 664 697 .with_body(full_body) 665 698 .create(); 666 699 667 - // Range request from byte 8. 700 + // Range request from byte 8 — bounded range since content-length is known. 668 701 let _range = server 669 702 .mock("GET", "/seekable.mp3") 670 - .match_header("range", "bytes=8-") 703 + .match_header("range", "bytes=8-15") 671 704 .with_status(206) 672 705 .with_header("content-range", "bytes 8-15/16") 673 706 .with_header("content-length", "8") ··· 734 767 735 768 let _range = server 736 769 .mock("GET", "/end.mp3") 737 - .match_header("range", "bytes=8-") 770 + .match_header("range", "bytes=8-9") 738 771 .with_status(206) 739 772 .with_header("content-range", "bytes 8-9/10") 740 773 .with_body(&full_body[8..]) ··· 840 873 841 874 let _ignored_range = server 842 875 .mock("GET", "/ignore-range.mp3") 843 - .match_header("range", "bytes=8-") 876 + .match_header("range", "bytes=8-15") 844 877 .with_status(200) 845 878 .with_header("content-length", "16") 846 879 .with_body(full_body) ··· 878 911 879 912 let _bad_range = server 880 913 .mock("GET", "/bad-range.mp3") 881 - .match_header("range", "bytes=8-") 914 + .match_header("range", "bytes=8-15") 882 915 .with_status(206) 883 916 .with_header("content-range", "bytes 0-7/16") 884 917 .with_body(&full_body[..8]) ··· 914 947 // Range request: 206 includes Content-Range which also reveals total size. 915 948 let _range = server 916 949 .mock("GET", "/range-len.mp3") 917 - .match_header("range", "bytes=5-") 950 + .match_header("range", "bytes=5-9") 918 951 .with_status(206) 919 952 .with_header("content-range", "bytes 5-9/10") 920 953 .with_body(&full_body[5..]) ··· 941 974 10, 942 975 "length should remain correct after seek" 943 976 ); 977 + 978 + rb_net_close(handle); 979 + } 980 + 981 + /// Seeking past content_length (e.g. from a uint32_t underflow in the MP4 982 + /// parser) is clamped to content_length. The stream is not permanently 983 + /// broken: reads return 0 (EOF) rather than -1 (error). 984 + #[test] 985 + fn test_seek_past_eof_is_clamped() { 986 + let full_body: &[u8] = b"0123456789"; // 10 bytes, content-length=10 987 + let mut server = mockito::Server::new(); 988 + 989 + let _initial = server 990 + .mock("GET", "/clamped.mp3") 991 + .match_header("range", Matcher::Missing) 992 + .with_status(200) 993 + .with_header("content-length", "10") 994 + .with_body(full_body) 995 + .create(); 996 + 997 + let url = c_url(&server, "/clamped.mp3"); 998 + let handle = unsafe { rb_net_open(url.as_ptr()) }; 999 + assert!(handle >= 0); 1000 + 1001 + // Seek 4 GB past current position (simulates uint32_t underflow in C). 1002 + let result = rb_net_lseek(handle, 4_294_967_295, libc::SEEK_CUR); 1003 + // Should clamp to content_length (10), not return -1. 1004 + assert_eq!(result, 10, "seek past EOF should clamp to content_length"); 1005 + 1006 + // Subsequent reads must return 0 (EOF), not -1 (broken stream). 1007 + let mut buf = vec![0u8; 16]; 1008 + let n = unsafe { rb_net_read(handle, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) }; 1009 + assert_eq!(n, 0, "read after clamped seek should return 0 (EOF)"); 944 1010 945 1011 rb_net_close(handle); 946 1012 }
+7
crates/rpc/src/lib.rs
··· 938 938 airplay_receivers: None, 939 939 squeezelite_http_port: None, 940 940 squeezelite_port: None, 941 + upnp_friendly_name: None, 942 + upnp_renderer_enabled: None, 943 + upnp_renderer_port: None, 944 + upnp_server_port: None, 945 + upnp_renderer_url: None, 946 + upnp_http_port: None, 947 + upnp_server_enabled: None, 941 948 } 942 949 } 943 950 }
+4 -2
crates/rpc/src/playback.rs
··· 397 397 request: tonic::Request<PlayDirectoryRequest>, 398 398 ) -> Result<tonic::Response<PlayDirectoryResponse>, tonic::Status> { 399 399 let request = request.into_inner(); 400 - let path = request.path; 400 + let path = request.path.trim().to_string(); 401 401 let recurse = request.recurse; 402 402 let shuffle = request.shuffle; 403 403 let position = request.position; ··· 583 583 request: tonic::Request<PlayTrackRequest>, 584 584 ) -> Result<tonic::Response<PlayTrackResponse>, tonic::Status> { 585 585 let request = request.into_inner(); 586 - let path = request.path.replace("file://", ""); 586 + let raw = request.path.replace("file://", ""); 587 + let raw = raw.trim(); 588 + let path = raw.split('#').next().unwrap_or(raw).to_string(); 587 589 let tracks = vec![path.clone()]; 588 590 589 591 let body = serde_json::json!({
+1
crates/server/Cargo.toml
··· 18 18 rand = "0.8.5" 19 19 reqwest = {version = "0.12.5", features = ["blocking", "rustls-tls"], default-features = false} 20 20 rockbox-chromecast = {path = "../chromecast"} 21 + rockbox-upnp = {path = "../upnp"} 21 22 rockbox-discovery = {path = "../discovery"} 22 23 rockbox-graphql = {path = "../graphql"} 23 24 rockbox-library = {path = "../library"}
+8 -1
crates/server/src/handlers/playlists.rs
··· 19 19 fn save_remote_track_metadata(url: *const std::ffi::c_char) -> i32; 20 20 } 21 21 22 + fn trim_path(s: String) -> String { 23 + let s = s.trim(); 24 + s.split('#').next().unwrap_or(s).to_string() 25 + } 26 + 22 27 pub async fn create_playlist( 23 28 _ctx: &Context, 24 29 req: &Request, ··· 29 34 return Ok(()); 30 35 } 31 36 let body = req.body.as_ref().unwrap(); 32 - let new_playlist: NewPlaylist = serde_json::from_str(body).unwrap(); 37 + let mut new_playlist: NewPlaylist = serde_json::from_str(body).unwrap(); 38 + new_playlist.tracks = new_playlist.tracks.into_iter().map(trim_path).collect(); 33 39 34 40 if new_playlist.tracks.is_empty() { 35 41 return Ok(()); ··· 195 201 pub async fn insert_tracks(ctx: &Context, req: &Request, res: &mut Response) -> Result<(), Error> { 196 202 let req_body = req.body.as_ref().unwrap(); 197 203 let mut tracklist: InsertTracks = serde_json::from_str(&req_body).unwrap(); 204 + tracklist.tracks = tracklist.tracks.into_iter().map(trim_path).collect(); 198 205 199 206 if let Some(dir) = &tracklist.directory { 200 207 tracklist.tracks = read_files(dir.clone()).await?;
+26 -18
crates/server/src/http.rs
··· 24 24 use crate::{ 25 25 kv::{build_tracks_kv, KV}, 26 26 player_events::listen_for_playback_changes, 27 - scan::scan_chromecast_devices, 27 + scan::{scan_chromecast_devices, scan_upnp_devices}, 28 28 }; 29 29 30 30 type Handler = fn(&Context, &Request, &mut Response) -> Result<(), Error>; ··· 91 91 let status_line = format!("HTTP/1.1 {} OK\r\n", self.status_code); 92 92 let mut response = status_line; 93 93 94 - // Add headers 95 94 for (key, value) in self.headers { 96 95 response.push_str(&format!("{}: {}\r\n", key, value)); 97 96 } 98 - 99 - // Add content length header 100 97 response.push_str(&format!("Content-Length: {}\r\n", self.body.len())); 101 - 102 - // End headers 103 98 response.push_str("\r\n"); 104 - 105 - // Add body 106 99 response.push_str(&self.body); 107 100 108 - // Write response to the stream 109 - stream.write_all(response.as_bytes()).unwrap(); 110 - stream.flush().unwrap(); 101 + if let Err(e) = stream.write_all(response.as_bytes()) { 102 + tracing::debug!("http: write error: {e}"); 103 + return; 104 + } 105 + if let Err(e) = stream.flush() { 106 + tracing::debug!("http: flush error: {e}"); 107 + } 111 108 } 112 109 } 113 110 ··· 262 259 263 260 // Start scanning for devices 264 261 scan_chromecast_devices(devices.clone()); 262 + scan_upnp_devices(devices.clone()); 265 263 listen_for_playback_changes(player.clone(), db_pool.clone()); 266 264 267 265 loop { 268 266 match listener.accept() { 269 267 Ok((stream, _)) => { 268 + // The listener is non-blocking so the accept loop can check 269 + // active connections; reset the accepted stream to blocking 270 + // so handler-thread reads/writes don't get WouldBlock. 271 + if let Err(e) = stream.set_nonblocking(false) { 272 + tracing::warn!("http: set_nonblocking(false) failed: {e}"); 273 + continue; 274 + } 270 275 let db_pool = db_pool.clone(); 271 276 let active_connections = Arc::clone(&active_connections); 272 277 { ··· 285 290 let mut buf_reader = BufReader::new(&stream); 286 291 let mut request = String::new(); 287 292 288 - buf_reader.read_line(&mut request).unwrap(); 293 + if buf_reader.read_line(&mut request).is_err() { 294 + let mut active_connections = active_connections.lock().unwrap(); 295 + *active_connections -= 1; 296 + return; 297 + } 289 298 290 299 let request_line_parts: Vec<&str> = request.split_whitespace().collect(); 291 300 if request_line_parts.len() >= 2 { ··· 294 303 295 304 let (path, query_string) = split_path_and_query(path_with_query); 296 305 297 - // Parse query parameters if present 298 306 let query_params: Value = match query_string { 299 307 Some(query_str) => queryst::parse(query_str).unwrap_or_default(), 300 308 None => Value::default(), ··· 310 318 || line.starts_with("content-length") 311 319 { 312 320 let parts: Vec<_> = line.split(":").collect(); 313 - content_length = parts[1].trim().parse().unwrap(); 321 + content_length = parts[1].trim().parse().unwrap_or(0); 314 322 } 315 323 316 324 if line.as_str() == "\r\n" || line == "\n" { ··· 325 333 let mut total_read: usize = 0; 326 334 327 335 while total_read < content_length { 328 - let read_size = buf_reader.read(&mut body[total_read..]).unwrap(); 329 - if read_size == 0 { 330 - break; 336 + match buf_reader.read(&mut body[total_read..]) { 337 + Ok(0) => break, 338 + Ok(n) => total_read += n, 339 + Err(_) => break, 331 340 } 332 - total_read += read_size; 333 341 } 334 342 335 343 let req_body = match content_length {
+37 -1
crates/server/src/scan.rs
··· 1 1 use futures_util::StreamExt; 2 2 use rockbox_discovery::{discover, CHROMECAST_SERVICE_NAME}; 3 3 use rockbox_graphql::simplebroker::SimpleBroker; 4 - use rockbox_types::device::Device; 4 + use rockbox_types::device::{Device, UPNP_DLNA_DEVICE}; 5 5 use std::{ 6 6 sync::{Arc, Mutex}, 7 7 thread, 8 8 }; 9 + 10 + /// Scan the local network for UPnP/DLNA MediaRenderer devices (Kodi, etc.). 11 + /// Runs once at startup; discovered renderers are added to the shared devices list. 12 + pub fn scan_upnp_devices(devices: Arc<Mutex<Vec<Device>>>) { 13 + thread::spawn(move || { 14 + tokio::runtime::Runtime::new().unwrap().block_on(async { 15 + let renderers = rockbox_upnp::scan::scan_renderers(3).await; 16 + let mut devices = devices.lock().unwrap(); 17 + for r in renderers { 18 + let id = if r.udn.is_empty() { 19 + format!("{:x}", md5::compute(r.location.as_bytes())) 20 + } else { 21 + r.udn.clone() 22 + }; 23 + if devices.iter().any(|d| d.id == id) { 24 + continue; 25 + } 26 + let device = Device { 27 + id: id.clone(), 28 + name: r.friendly_name.clone(), 29 + host: r.ip.clone(), 30 + ip: r.ip.clone(), 31 + port: r.port, 32 + service: "upnp".to_string(), 33 + app: UPNP_DLNA_DEVICE.to_string(), 34 + base_url: Some(r.av_transport_url.clone()), 35 + is_cast_device: true, 36 + is_source_device: false, 37 + ..Default::default() 38 + }; 39 + SimpleBroker::<Device>::publish(device.clone()); 40 + devices.push(device); 41 + } 42 + }); 43 + }); 44 + } 9 45 10 46 pub fn scan_chromecast_devices(devices: Arc<Mutex<Vec<Device>>>) { 11 47 thread::spawn(move || {
+1
crates/settings/Cargo.toml
··· 6 6 [dependencies] 7 7 anyhow = "1.0.91" 8 8 rockbox-sys = {path = "../sys"} 9 + rockbox-upnp = {path = "../upnp"} 9 10 toml = "0.8.19" 10 11 tracing = { workspace = true }
+26
crates/settings/src/lib.rs
··· 67 67 "audio output: squeezelite (Slim Protocol :{slim_port}, HTTP audio :{http_port})" 68 68 ); 69 69 } 70 + Some("upnp") => { 71 + let http_port = settings.upnp_http_port.unwrap_or(7879); 72 + pcm::upnp_set_http_port(http_port); 73 + if let Some(ref url) = settings.upnp_renderer_url { 74 + pcm::upnp_set_renderer_url(url); 75 + tracing::info!("audio output: upnp (WAV stream :{http_port}, renderer {url})"); 76 + } else { 77 + pcm::upnp_clear_renderer_url(); 78 + tracing::info!("audio output: upnp (WAV stream :{http_port})"); 79 + } 80 + pcm::switch_sink(pcm::PCM_SINK_UPNP); 81 + } 70 82 Some("builtin") | None => { 71 83 tracing::info!("audio output: builtin (SDL)"); 72 84 } ··· 76 88 other 77 89 ); 78 90 } 91 + } 92 + 93 + // Start UPnP/DLNA ContentDirectory media server if enabled. 94 + if settings.upnp_server_enabled.unwrap_or(false) { 95 + let port = settings.upnp_server_port.unwrap_or(7878); 96 + let name = settings.upnp_friendly_name.as_deref().unwrap_or("Rockbox"); 97 + rockbox_upnp::start_media_server(port, name); 98 + } 99 + 100 + // Start UPnP/DLNA MediaRenderer:1 if enabled. 101 + if settings.upnp_renderer_enabled.unwrap_or(false) { 102 + let port = settings.upnp_renderer_port.unwrap_or(7880); 103 + let name = settings.upnp_friendly_name.as_deref().unwrap_or("Rockbox"); 104 + rockbox_upnp::start_renderer(port, name); 79 105 } 80 106 81 107 rb::settings::apply_audio_settings();
+3
crates/sys/src/lib.rs
··· 1153 1153 fn pcm_airplay_clear_receivers(); 1154 1154 fn pcm_squeezelite_set_slim_port(port: c_ushort); 1155 1155 fn pcm_squeezelite_set_http_port(port: c_ushort); 1156 + fn pcm_upnp_set_http_port(port: c_ushort); 1157 + fn pcm_upnp_set_renderer_url(url: *const c_char); 1158 + fn pcm_upnp_set_sample_rate(rate: c_uint); 1156 1159 fn beep_play(frequency: c_uint, duration: c_uint, amplitude: c_uint); 1157 1160 fn dsp_set_crossfeed_type(r#type: c_int); 1158 1161 fn dsp_eq_enable(enable: c_uchar);
+15
crates/sys/src/sound/pcm.rs
··· 6 6 pub const PCM_SINK_FIFO: i32 = 1; 7 7 pub const PCM_SINK_AIRPLAY: i32 = 2; 8 8 pub const PCM_SINK_SQUEEZELITE: i32 = 3; 9 + pub const PCM_SINK_UPNP: i32 = 4; 9 10 10 11 pub fn apply_settings() { 11 12 unsafe { ··· 84 85 pub fn squeezelite_set_http_port(port: u16) { 85 86 unsafe { crate::pcm_squeezelite_set_http_port(port) } 86 87 } 88 + 89 + pub fn upnp_set_http_port(port: u16) { 90 + unsafe { crate::pcm_upnp_set_http_port(port) } 91 + } 92 + 93 + pub fn upnp_set_renderer_url(url: &str) { 94 + let curl = std::ffi::CString::new(url).expect("url must not contain null bytes"); 95 + unsafe { crate::pcm_upnp_set_renderer_url(curl.as_ptr()) } 96 + std::mem::forget(curl); 97 + } 98 + 99 + pub fn upnp_clear_renderer_url() { 100 + unsafe { crate::pcm_upnp_set_renderer_url(std::ptr::null()) } 101 + }
+22
crates/sys/src/types/user_settings.rs
··· 700 700 pub squeezelite_port: Option<u16>, 701 701 /// HTTP audio stream port for the squeezelite sink (default: 9999) 702 702 pub squeezelite_http_port: Option<u16>, 703 + /// Enable the UPnP/DLNA ContentDirectory media server (default: false) 704 + pub upnp_server_enabled: Option<bool>, 705 + /// HTTP port for the UPnP media server (default: 7878) 706 + pub upnp_server_port: Option<u16>, 707 + /// Friendly device name advertised via SSDP (default: "Rockbox") 708 + pub upnp_friendly_name: Option<String>, 709 + /// HTTP port for the UPnP PCM WAV stream sink (default: 7879) 710 + pub upnp_http_port: Option<u16>, 711 + /// AVTransport control URL of a UPnP renderer to auto-command when the 712 + /// upnp sink starts (e.g. "http://192.168.1.x:PORT/AVTransport/control") 713 + pub upnp_renderer_url: Option<String>, 714 + /// Enable the UPnP/DLNA MediaRenderer:1 (default: false) 715 + pub upnp_renderer_enabled: Option<bool>, 716 + /// HTTP port for the UPnP renderer (default: 7880) 717 + pub upnp_renderer_port: Option<u16>, 703 718 } 704 719 705 720 impl From<UserSettings> for NewGlobalSettings { ··· 740 755 airplay_receivers: None, 741 756 squeezelite_port: None, 742 757 squeezelite_http_port: None, 758 + upnp_server_enabled: None, 759 + upnp_server_port: None, 760 + upnp_friendly_name: None, 761 + upnp_http_port: None, 762 + upnp_renderer_url: None, 763 + upnp_renderer_enabled: None, 764 + upnp_renderer_port: None, 743 765 } 744 766 } 745 767 }
+33
crates/upnp/Cargo.toml
··· 1 + [package] 2 + name = "rockbox-upnp" 3 + version = "0.1.0" 4 + authors.workspace = true 5 + edition.workspace = true 6 + license.workspace = true 7 + repository.workspace = true 8 + 9 + [lib] 10 + crate-type = ["rlib"] 11 + 12 + [dependencies] 13 + anyhow = { workspace = true } 14 + tokio = { workspace = true } 15 + tracing = { workspace = true } 16 + uuid = { workspace = true } 17 + serde = { workspace = true } 18 + socket2 = { workspace = true } 19 + sqlx = { version = "0.8.2", features = ["runtime-tokio", "sqlite"] } 20 + bytes = { workspace = true } 21 + reqwest = { workspace = true } 22 + hyper = { workspace = true } 23 + hyper-util = { workspace = true } 24 + http-body-util = { workspace = true } 25 + http = { workspace = true } 26 + rockbox-sys = { path = "../sys" } 27 + prost = "0.13.2" 28 + tonic = "0.12.3" 29 + tonic-reflection = "0.12.3" 30 + tonic-web = "0.12.3" 31 + 32 + [build-dependencies] 33 + tonic-build = "0.12.3"
+299
crates/upnp/README.md
··· 1 + # rockbox-upnp 2 + 3 + UPnP/DLNA support for Rockbox Zig. This crate provides three independent but 4 + complementary features: 5 + 6 + | Feature | What it does | 7 + |--------------------------------------|-----------------------------------------------------------------------------------------------------| 8 + | **Media Server** (ContentDirectory) | Exposes the music library so UPnP control points (BubbleUPnP, Kodi, etc.) can browse and pull tracks | 9 + | **MediaRenderer** | Lets control points push media to Rockbox (Rockbox becomes the speaker) | 10 + | **PCM sink / WAV output** | Streams live PCM audio as WAV-over-HTTP to an external UPnP renderer (Kodi, etc.) | 11 + 12 + --- 13 + 14 + ## How UPnP/DLNA works 15 + 16 + ### Protocol stack 17 + 18 + ``` 19 + Application BubbleUPnP / Kodi / any DLNA app 20 + 21 + SOAP/XML over HTTP (AVTransport, ContentDirectory) 22 + 23 + UPnP Device Description (XML, fetched from LOCATION URL) 24 + 25 + SSDP (UDP multicast 239.255.255.250:1900) ← device discovery 26 + 27 + LAN 28 + ``` 29 + 30 + **SSDP** (Simple Service Discovery Protocol) is how devices announce themselves 31 + and how control points find them. Each device sends periodic `NOTIFY` multicasts 32 + and responds to `M-SEARCH` queries. 33 + 34 + **Device Description** is an XML document (fetched via HTTP from the `LOCATION` 35 + URL advertised in SSDP) that lists the device's friendly name, UDN (unique ID), 36 + and the services it supports, with their control/event URLs. 37 + 38 + **SOAP** is the RPC mechanism. Every action — browse library, set transport URI, 39 + start playback — is a `POST` with an XML envelope to the service's `controlURL`. 40 + 41 + ### UPnP service roles 42 + 43 + | UPnP term | Rockbox role | Description | 44 + |---------------------------------|----------------|----------------------------------------| 45 + | MediaServer / ContentDirectory | **server** | Hosts the music library for browsing | 46 + | MediaRenderer / AVTransport | **renderer** | Receives push-play commands | 47 + | Control Point | *external app* | BubbleUPnP, Kodi, Foobar2000, … | 48 + 49 + --- 50 + 51 + ## Feature 1 — Media Server (ContentDirectory) 52 + 53 + When `upnp_server_enabled = true`, Rockbox starts a UPnP ContentDirectory 54 + service that exposes the tag database (artists, albums, tracks). 55 + 56 + ### What a control point sees 57 + 58 + ``` 59 + Root 60 + ├── Artists 61 + │ └── Daft Punk 62 + │ └── Random Access Memories 63 + │ └── Get Lucky ← streamable via HTTP 64 + ├── Albums 65 + │ └── ... 66 + └── Tracks 67 + └── ... 68 + ``` 69 + 70 + Each track is served as a direct HTTP stream from the Rockbox GraphQL port 71 + (`http://<ip>:<graphql_port>/tracks/<id>/stream`). Control points can play 72 + individual tracks or enqueue them. 73 + 74 + ### Discovery flow 75 + 76 + ``` 77 + rockboxd starts 78 + └─ SSDP NOTIFY sent every 30 s to 239.255.255.250:1900 79 + └─ HTTP server on upnp_server_port (default 7878) answers: 80 + GET /device.xml → UPnP device description 81 + POST /ContentDirectory → Browse / Search actions (SOAP) 82 + GET /tracks/<id>/stream → audio file bytes 83 + ``` 84 + 85 + A control point (e.g. BubbleUPnP) that receives the NOTIFY fetches 86 + `/device.xml`, then issues `Browse` actions to walk the tree. 87 + 88 + --- 89 + 90 + ## Feature 2 — MediaRenderer (AVTransport) 91 + 92 + When `upnp_renderer_enabled = true`, Rockbox advertises itself as a 93 + `urn:schemas-upnp-org:device:MediaRenderer:1`. A control point can then push 94 + any URI to Rockbox and tell it to play. 95 + 96 + ### Push-play flow (control point → Rockbox) 97 + 98 + ``` 99 + Control Point Rockbox (renderer) 100 + │ │ 101 + │── SetAVTransportURI ─────────────>│ URL + DIDL-Lite metadata 102 + │ │ (title, artist, album, album art) 103 + │── Play ────────────────────────> │ start playback 104 + │ │ 105 + │── Pause / Stop / Seek ──────────> │ 106 + │ │ 107 + │── GetPositionInfo ─────────────> │ 108 + │<─ TrackDuration, RelTime ─────────│ 109 + ``` 110 + 111 + **DIDL-Lite** (Digital Item Declaration Language Lite) is the XML format used 112 + to carry track metadata in `SetAVTransportURI`. Rockbox parses the 113 + `CurrentURIMetaData` field, XML-unescapes it, and extracts: 114 + 115 + - `<dc:title>` — track title 116 + - `<upnp:artist>` — artist name 117 + - `<upnp:album>` — album name 118 + - `<upnp:albumArtURI>` — album art URL 119 + - `<res duration="H:MM:SS.mmm">` — track duration 120 + 121 + Rockbox stores this and returns it in `GetPositionInfo` / `GetMediaInfo` 122 + responses so control points can display a progress bar. 123 + 124 + ### Supported AVTransport actions 125 + 126 + | Action | Behaviour | 127 + |-----------------------|------------------------------------------------------------------------------------| 128 + | `SetAVTransportURI` | Store URI + parse DIDL-Lite metadata; open the stream in the Rockbox audio engine | 129 + | `Play` | Start or resume playback | 130 + | `Pause` | Pause/resume toggle | 131 + | `Stop` | Stop playback; clear stored metadata | 132 + | `Seek` | Seek to absolute time (REL_TIME target unit) | 133 + | `GetTransportInfo` | Return current transport state (PLAYING / PAUSED_PLAYBACK / STOPPED) | 134 + | `GetPositionInfo` | Return track URI, DIDL-Lite metadata, duration, elapsed time | 135 + | `GetMediaInfo` | Return current URI and DIDL-Lite metadata | 136 + 137 + --- 138 + 139 + ## Feature 3 — PCM / WAV output sink 140 + 141 + When `audio_output = "upnp"`, Rockbox encodes its live PCM output as a 142 + continuous WAV stream and broadcasts it over HTTP, then commands an external 143 + UPnP renderer to play that stream. 144 + 145 + ### Architecture 146 + 147 + ``` 148 + Rockbox audio engine (S16LE stereo PCM) 149 + 150 + pcm_upnp_write() ← called per DMA chunk 151 + 152 + BroadcastBuffer (4 MB ring) 153 + 154 + HTTP server (:upnp_http_port) 155 + GET /stream.wav ──────────────────────────────────→ UPnP renderer 156 + (Kodi, VLC, etc.) 157 + 158 + AVTransport SOAP (SetAVTransportURI + Play) 159 + ┌───────────────────────────────────────────────────> upnp_renderer_url 160 + │ CurrentURI = http://<local_ip>:<port>/stream.wav 161 + │ CurrentURIMetaData = DIDL-Lite with: 162 + │ title, artist, album, albumArtURI, duration 163 + │ (art URL: http://<local_ip>:<graphql_port>/covers/<filename>) 164 + │ (album art fetched from local SQLite library DB by track path) 165 + 166 + ``` 167 + 168 + ### Track-change metadata updates 169 + 170 + A background task polls `current_track()` every 2 seconds. When the track path 171 + changes it sends a new `SetAVTransportURI` with updated DIDL-Lite metadata 172 + (`send_play = false` so the renderer does not restart the stream — the WAV 173 + HTTP connection is continuous). This keeps the renderer's "Now Playing" display 174 + accurate across track boundaries without interrupting audio. 175 + 176 + ### WAV stream format 177 + 178 + ``` 179 + Content-Type: audio/wav 180 + Transfer-Encoding: chunked (infinite stream) 181 + Audio format: PCM S16LE, 2 channels, sample rate = upnp_http_port 182 + standard 44-byte WAV header followed by raw PCM forever 183 + ``` 184 + 185 + The `BroadcastBuffer` is a 4 MB lock-free ring. Multiple HTTP clients can 186 + connect simultaneously (each with an independent read cursor), lagging clients 187 + skip forward rather than blocking the writer. 188 + 189 + --- 190 + 191 + ## Settings reference (`~/.config/rockbox.org/settings.toml`) 192 + 193 + ### Audio output — PCM sink to an external renderer 194 + 195 + ```toml 196 + audio_output = "upnp" 197 + 198 + # URL of the renderer's AVTransport controlURL (required for metadata push) 199 + upnp_renderer_url = "http://192.168.1.x:7777/AVTransport/control" 200 + 201 + # Port for the WAV HTTP broadcast server (default: 7879) 202 + upnp_http_port = 7879 203 + ``` 204 + 205 + To find `upnp_renderer_url`: on the renderer device look in its UPnP device 206 + description XML (`LOCATION` from SSDP), find the `AVTransport` service block, 207 + and read the `controlURL` element. Or enable the auto-scan (see below) and copy 208 + the URL logged at startup. 209 + 210 + ### Media Server — expose library to control points 211 + 212 + ```toml 213 + upnp_server_enabled = true 214 + upnp_server_port = 7878 # HTTP port for ContentDirectory + device.xml 215 + upnp_friendly_name = "Rockbox" # name shown in control points 216 + ``` 217 + 218 + ### MediaRenderer — let control points push media to Rockbox 219 + 220 + ```toml 221 + upnp_renderer_enabled = true 222 + upnp_renderer_port = 7880 # HTTP port for AVTransport + device.xml 223 + upnp_friendly_name = "Rockbox" 224 + ``` 225 + 226 + ### All UPnP settings at a glance 227 + 228 + | Key | Type | Default | Description | 229 + |--------------------------|-----------|--------------|------------------------------------------------| 230 + | `audio_output` | string | `"builtin"` | Set to `"upnp"` to use the PCM sink | 231 + | `upnp_renderer_url` | string | — | AVTransport controlURL of the target renderer | 232 + | `upnp_http_port` | integer | `7879` | Port for the WAV broadcast HTTP server | 233 + | `upnp_server_enabled` | bool | `false` | Start the ContentDirectory media server | 234 + | `upnp_server_port` | integer | `7878` | HTTP port for the media server | 235 + | `upnp_renderer_enabled` | bool | `false` | Start the MediaRenderer | 236 + | `upnp_renderer_port` | integer | `7880` | HTTP port for the renderer | 237 + | `upnp_friendly_name` | string | `"Rockbox"` | Display name shown to control points | 238 + 239 + --- 240 + 241 + ## Using Kodi as a UPnP renderer 242 + 243 + 1. In Kodi: **Settings → Services → UPnP/DLNA** → enable "Allow remote control via UPnP". 244 + 245 + 2. Find Kodi's AVTransport URL. Either: 246 + - Run `rockboxd` with `RUST_LOG=info` and look for 247 + `upnp scan: found renderer "Kodi" av=http://...` 248 + (Rockbox scans the LAN for renderers at startup). 249 + - Or browse Kodi's device description at 250 + `http://<kodi-ip>:1234/upnpms/device.xml` and find the 251 + `AVTransport` `controlURL`. 252 + 253 + 3. Set `settings.toml`: 254 + ```toml 255 + audio_output = "upnp" 256 + upnp_renderer_url = "http://192.168.1.x:1234/upnpms/event" 257 + ``` 258 + 259 + 4. Start `rockboxd` and play a track. Kodi will show the track title, artist, 260 + album, and album art. 261 + 262 + --- 263 + 264 + ## LAN renderer auto-discovery 265 + 266 + At startup, Rockbox sends an SSDP `M-SEARCH` for 267 + `urn:schemas-upnp-org:device:MediaRenderer:1` and logs all discovered 268 + renderers. The scan result is also available through the HTTP/gRPC API (device 269 + list endpoint), so the UI can offer a renderer picker. 270 + 271 + Discovery sequence: 272 + 273 + ``` 274 + rockboxd LAN 275 + │── M-SEARCH (UDP multicast) ───>│ 276 + │<─ 200 OK LOCATION: http://... │ (one reply per renderer) 277 + │── GET <LOCATION> ─────────────>│ fetch device description XML 278 + │<─ device.xml ──────────────────│ 279 + │ parse: friendlyName, UDN, AVTransport controlURL 280 + │ → Device { app: UPNP_DLNA, base_url: controlURL } 281 + ``` 282 + 283 + --- 284 + 285 + ## Crate layout 286 + 287 + ``` 288 + src/ 289 + lib.rs — FFI exports (pcm_upnp_*), BroadcastBuffer, global state, 290 + AVTransport SOAP client, track-change monitor 291 + db.rs — SQLite helpers (open_pool, track_by_path, all_tracks, …) 292 + didl.rs — DIDL-Lite XML parser (for incoming SetAVTransportURI) 293 + format.rs — WAV header builder 294 + pcm_server.rs — HTTP broadcast server (one goroutine per client) 295 + renderer.rs — MediaRenderer HTTP handler (AVTransport SOAP endpoint) 296 + scan.rs — SSDP M-SEARCH + device description probe 297 + server.rs — ContentDirectory HTTP handler (Browse/Search SOAP) 298 + ssdp.rs — SSDP NOTIFY announcer 299 + ```
+22
crates/upnp/build.rs
··· 1 + fn main() -> Result<(), Box<dyn std::error::Error>> { 2 + // Re-link whenever the C/Zig static library changes. 3 + println!("cargo:rerun-if-changed=../build-lib/librockbox.a"); 4 + 5 + tonic_build::configure() 6 + .out_dir("src/api") 7 + .file_descriptor_set_path("src/api/rockbox_descriptor.bin") 8 + .compile_protos( 9 + &[ 10 + "proto/rockbox/v1alpha1/browse.proto", 11 + "proto/rockbox/v1alpha1/library.proto", 12 + "proto/rockbox/v1alpha1/metadata.proto", 13 + "proto/rockbox/v1alpha1/playback.proto", 14 + "proto/rockbox/v1alpha1/playlist.proto", 15 + "proto/rockbox/v1alpha1/settings.proto", 16 + "proto/rockbox/v1alpha1/sound.proto", 17 + "proto/rockbox/v1alpha1/system.proto", 18 + ], 19 + &["proto"], 20 + )?; 21 + Ok(()) 22 + }
+1
crates/upnp/proto
··· 1 + ../rpc/proto
+8079
crates/upnp/src/api/rockbox.v1alpha1.rs
··· 1 + // This file is @generated by prost-build. 2 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3 + pub struct RockboxBrowseRequest {} 4 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 5 + pub struct RockboxBrowseResponse {} 6 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 7 + pub struct TreeGetContextRequest {} 8 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 9 + pub struct TreeGetContextResponse {} 10 + #[derive(Clone, PartialEq, ::prost::Message)] 11 + pub struct TreeGetEntriesRequest { 12 + #[prost(string, optional, tag = "1")] 13 + pub path: ::core::option::Option<::prost::alloc::string::String>, 14 + } 15 + #[derive(Clone, PartialEq, ::prost::Message)] 16 + pub struct Entry { 17 + #[prost(string, tag = "1")] 18 + pub name: ::prost::alloc::string::String, 19 + #[prost(int32, tag = "2")] 20 + pub attr: i32, 21 + #[prost(uint32, tag = "3")] 22 + pub time_write: u32, 23 + #[prost(int32, tag = "4")] 24 + pub customaction: i32, 25 + } 26 + #[derive(Clone, PartialEq, ::prost::Message)] 27 + pub struct TreeGetEntriesResponse { 28 + #[prost(message, repeated, tag = "1")] 29 + pub entries: ::prost::alloc::vec::Vec<Entry>, 30 + } 31 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 32 + pub struct TreeGetEntryAtRequest {} 33 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 34 + pub struct TreeGetEntryAtResponse {} 35 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 36 + pub struct BrowseId3Request {} 37 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 38 + pub struct BrowseId3Response {} 39 + /// Generated client implementations. 40 + pub mod browse_service_client { 41 + #![allow( 42 + unused_variables, 43 + dead_code, 44 + missing_docs, 45 + clippy::wildcard_imports, 46 + clippy::let_unit_value 47 + )] 48 + use tonic::codegen::http::Uri; 49 + use tonic::codegen::*; 50 + #[derive(Debug, Clone)] 51 + pub struct BrowseServiceClient<T> { 52 + inner: tonic::client::Grpc<T>, 53 + } 54 + impl BrowseServiceClient<tonic::transport::Channel> { 55 + /// Attempt to create a new client by connecting to a given endpoint. 56 + pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error> 57 + where 58 + D: TryInto<tonic::transport::Endpoint>, 59 + D::Error: Into<StdError>, 60 + { 61 + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; 62 + Ok(Self::new(conn)) 63 + } 64 + } 65 + impl<T> BrowseServiceClient<T> 66 + where 67 + T: tonic::client::GrpcService<tonic::body::BoxBody>, 68 + T::Error: Into<StdError>, 69 + T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static, 70 + <T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send, 71 + { 72 + pub fn new(inner: T) -> Self { 73 + let inner = tonic::client::Grpc::new(inner); 74 + Self { inner } 75 + } 76 + pub fn with_origin(inner: T, origin: Uri) -> Self { 77 + let inner = tonic::client::Grpc::with_origin(inner, origin); 78 + Self { inner } 79 + } 80 + pub fn with_interceptor<F>( 81 + inner: T, 82 + interceptor: F, 83 + ) -> BrowseServiceClient<InterceptedService<T, F>> 84 + where 85 + F: tonic::service::Interceptor, 86 + T::ResponseBody: Default, 87 + T: tonic::codegen::Service< 88 + http::Request<tonic::body::BoxBody>, 89 + Response = http::Response< 90 + <T as tonic::client::GrpcService<tonic::body::BoxBody>>::ResponseBody, 91 + >, 92 + >, 93 + <T as tonic::codegen::Service<http::Request<tonic::body::BoxBody>>>::Error: 94 + Into<StdError> + std::marker::Send + std::marker::Sync, 95 + { 96 + BrowseServiceClient::new(InterceptedService::new(inner, interceptor)) 97 + } 98 + /// Compress requests with the given encoding. 99 + /// 100 + /// This requires the server to support it otherwise it might respond with an 101 + /// error. 102 + #[must_use] 103 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 104 + self.inner = self.inner.send_compressed(encoding); 105 + self 106 + } 107 + /// Enable decompressing responses. 108 + #[must_use] 109 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 110 + self.inner = self.inner.accept_compressed(encoding); 111 + self 112 + } 113 + /// Limits the maximum size of a decoded message. 114 + /// 115 + /// Default: `4MB` 116 + #[must_use] 117 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 118 + self.inner = self.inner.max_decoding_message_size(limit); 119 + self 120 + } 121 + /// Limits the maximum size of an encoded message. 122 + /// 123 + /// Default: `usize::MAX` 124 + #[must_use] 125 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 126 + self.inner = self.inner.max_encoding_message_size(limit); 127 + self 128 + } 129 + pub async fn tree_get_entries( 130 + &mut self, 131 + request: impl tonic::IntoRequest<super::TreeGetEntriesRequest>, 132 + ) -> std::result::Result<tonic::Response<super::TreeGetEntriesResponse>, tonic::Status> 133 + { 134 + self.inner.ready().await.map_err(|e| { 135 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 136 + })?; 137 + let codec = tonic::codec::ProstCodec::default(); 138 + let path = http::uri::PathAndQuery::from_static( 139 + "/rockbox.v1alpha1.BrowseService/TreeGetEntries", 140 + ); 141 + let mut req = request.into_request(); 142 + req.extensions_mut().insert(GrpcMethod::new( 143 + "rockbox.v1alpha1.BrowseService", 144 + "TreeGetEntries", 145 + )); 146 + self.inner.unary(req, path, codec).await 147 + } 148 + } 149 + } 150 + /// Generated server implementations. 151 + pub mod browse_service_server { 152 + #![allow( 153 + unused_variables, 154 + dead_code, 155 + missing_docs, 156 + clippy::wildcard_imports, 157 + clippy::let_unit_value 158 + )] 159 + use tonic::codegen::*; 160 + /// Generated trait containing gRPC methods that should be implemented for use with BrowseServiceServer. 161 + #[async_trait] 162 + pub trait BrowseService: std::marker::Send + std::marker::Sync + 'static { 163 + async fn tree_get_entries( 164 + &self, 165 + request: tonic::Request<super::TreeGetEntriesRequest>, 166 + ) -> std::result::Result<tonic::Response<super::TreeGetEntriesResponse>, tonic::Status>; 167 + } 168 + #[derive(Debug)] 169 + pub struct BrowseServiceServer<T> { 170 + inner: Arc<T>, 171 + accept_compression_encodings: EnabledCompressionEncodings, 172 + send_compression_encodings: EnabledCompressionEncodings, 173 + max_decoding_message_size: Option<usize>, 174 + max_encoding_message_size: Option<usize>, 175 + } 176 + impl<T> BrowseServiceServer<T> { 177 + pub fn new(inner: T) -> Self { 178 + Self::from_arc(Arc::new(inner)) 179 + } 180 + pub fn from_arc(inner: Arc<T>) -> Self { 181 + Self { 182 + inner, 183 + accept_compression_encodings: Default::default(), 184 + send_compression_encodings: Default::default(), 185 + max_decoding_message_size: None, 186 + max_encoding_message_size: None, 187 + } 188 + } 189 + pub fn with_interceptor<F>(inner: T, interceptor: F) -> InterceptedService<Self, F> 190 + where 191 + F: tonic::service::Interceptor, 192 + { 193 + InterceptedService::new(Self::new(inner), interceptor) 194 + } 195 + /// Enable decompressing requests with the given encoding. 196 + #[must_use] 197 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 198 + self.accept_compression_encodings.enable(encoding); 199 + self 200 + } 201 + /// Compress responses with the given encoding, if the client supports it. 202 + #[must_use] 203 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 204 + self.send_compression_encodings.enable(encoding); 205 + self 206 + } 207 + /// Limits the maximum size of a decoded message. 208 + /// 209 + /// Default: `4MB` 210 + #[must_use] 211 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 212 + self.max_decoding_message_size = Some(limit); 213 + self 214 + } 215 + /// Limits the maximum size of an encoded message. 216 + /// 217 + /// Default: `usize::MAX` 218 + #[must_use] 219 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 220 + self.max_encoding_message_size = Some(limit); 221 + self 222 + } 223 + } 224 + impl<T, B> tonic::codegen::Service<http::Request<B>> for BrowseServiceServer<T> 225 + where 226 + T: BrowseService, 227 + B: Body + std::marker::Send + 'static, 228 + B::Error: Into<StdError> + std::marker::Send + 'static, 229 + { 230 + type Response = http::Response<tonic::body::BoxBody>; 231 + type Error = std::convert::Infallible; 232 + type Future = BoxFuture<Self::Response, Self::Error>; 233 + fn poll_ready( 234 + &mut self, 235 + _cx: &mut Context<'_>, 236 + ) -> Poll<std::result::Result<(), Self::Error>> { 237 + Poll::Ready(Ok(())) 238 + } 239 + fn call(&mut self, req: http::Request<B>) -> Self::Future { 240 + match req.uri().path() { 241 + "/rockbox.v1alpha1.BrowseService/TreeGetEntries" => { 242 + #[allow(non_camel_case_types)] 243 + struct TreeGetEntriesSvc<T: BrowseService>(pub Arc<T>); 244 + impl<T: BrowseService> tonic::server::UnaryService<super::TreeGetEntriesRequest> 245 + for TreeGetEntriesSvc<T> 246 + { 247 + type Response = super::TreeGetEntriesResponse; 248 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 249 + fn call( 250 + &mut self, 251 + request: tonic::Request<super::TreeGetEntriesRequest>, 252 + ) -> Self::Future { 253 + let inner = Arc::clone(&self.0); 254 + let fut = async move { 255 + <T as BrowseService>::tree_get_entries(&inner, request).await 256 + }; 257 + Box::pin(fut) 258 + } 259 + } 260 + let accept_compression_encodings = self.accept_compression_encodings; 261 + let send_compression_encodings = self.send_compression_encodings; 262 + let max_decoding_message_size = self.max_decoding_message_size; 263 + let max_encoding_message_size = self.max_encoding_message_size; 264 + let inner = self.inner.clone(); 265 + let fut = async move { 266 + let method = TreeGetEntriesSvc(inner); 267 + let codec = tonic::codec::ProstCodec::default(); 268 + let mut grpc = tonic::server::Grpc::new(codec) 269 + .apply_compression_config( 270 + accept_compression_encodings, 271 + send_compression_encodings, 272 + ) 273 + .apply_max_message_size_config( 274 + max_decoding_message_size, 275 + max_encoding_message_size, 276 + ); 277 + let res = grpc.unary(method, req).await; 278 + Ok(res) 279 + }; 280 + Box::pin(fut) 281 + } 282 + _ => Box::pin(async move { 283 + let mut response = http::Response::new(empty_body()); 284 + let headers = response.headers_mut(); 285 + headers.insert( 286 + tonic::Status::GRPC_STATUS, 287 + (tonic::Code::Unimplemented as i32).into(), 288 + ); 289 + headers.insert( 290 + http::header::CONTENT_TYPE, 291 + tonic::metadata::GRPC_CONTENT_TYPE, 292 + ); 293 + Ok(response) 294 + }), 295 + } 296 + } 297 + } 298 + impl<T> Clone for BrowseServiceServer<T> { 299 + fn clone(&self) -> Self { 300 + let inner = self.inner.clone(); 301 + Self { 302 + inner, 303 + accept_compression_encodings: self.accept_compression_encodings, 304 + send_compression_encodings: self.send_compression_encodings, 305 + max_decoding_message_size: self.max_decoding_message_size, 306 + max_encoding_message_size: self.max_encoding_message_size, 307 + } 308 + } 309 + } 310 + /// Generated gRPC service name 311 + pub const SERVICE_NAME: &str = "rockbox.v1alpha1.BrowseService"; 312 + impl<T> tonic::server::NamedService for BrowseServiceServer<T> { 313 + const NAME: &'static str = SERVICE_NAME; 314 + } 315 + } 316 + #[derive(Clone, PartialEq, ::prost::Message)] 317 + pub struct Track { 318 + #[prost(string, tag = "1")] 319 + pub id: ::prost::alloc::string::String, 320 + #[prost(string, tag = "2")] 321 + pub path: ::prost::alloc::string::String, 322 + #[prost(string, tag = "3")] 323 + pub title: ::prost::alloc::string::String, 324 + #[prost(string, tag = "4")] 325 + pub artist: ::prost::alloc::string::String, 326 + #[prost(string, tag = "5")] 327 + pub album: ::prost::alloc::string::String, 328 + #[prost(string, tag = "6")] 329 + pub album_artist: ::prost::alloc::string::String, 330 + #[prost(uint32, tag = "7")] 331 + pub bitrate: u32, 332 + #[prost(string, tag = "8")] 333 + pub composer: ::prost::alloc::string::String, 334 + #[prost(uint32, tag = "9")] 335 + pub disc_number: u32, 336 + #[prost(uint32, tag = "10")] 337 + pub filesize: u32, 338 + #[prost(uint32, tag = "11")] 339 + pub frequency: u32, 340 + #[prost(uint32, tag = "12")] 341 + pub length: u32, 342 + #[prost(uint32, tag = "13")] 343 + pub track_number: u32, 344 + #[prost(uint32, tag = "14")] 345 + pub year: u32, 346 + #[prost(string, tag = "15")] 347 + pub year_string: ::prost::alloc::string::String, 348 + #[prost(string, tag = "16")] 349 + pub genre: ::prost::alloc::string::String, 350 + #[prost(string, tag = "17")] 351 + pub md5: ::prost::alloc::string::String, 352 + #[prost(string, optional, tag = "18")] 353 + pub album_art: ::core::option::Option<::prost::alloc::string::String>, 354 + #[prost(string, optional, tag = "19")] 355 + pub artist_id: ::core::option::Option<::prost::alloc::string::String>, 356 + #[prost(string, optional, tag = "20")] 357 + pub album_id: ::core::option::Option<::prost::alloc::string::String>, 358 + #[prost(string, optional, tag = "21")] 359 + pub genre_id: ::core::option::Option<::prost::alloc::string::String>, 360 + #[prost(string, tag = "22")] 361 + pub created_at: ::prost::alloc::string::String, 362 + #[prost(string, tag = "23")] 363 + pub updated_at: ::prost::alloc::string::String, 364 + } 365 + #[derive(Clone, PartialEq, ::prost::Message)] 366 + pub struct Artist { 367 + #[prost(string, tag = "1")] 368 + pub id: ::prost::alloc::string::String, 369 + #[prost(string, tag = "2")] 370 + pub name: ::prost::alloc::string::String, 371 + #[prost(string, optional, tag = "3")] 372 + pub bio: ::core::option::Option<::prost::alloc::string::String>, 373 + #[prost(string, optional, tag = "4")] 374 + pub image: ::core::option::Option<::prost::alloc::string::String>, 375 + #[prost(message, repeated, tag = "5")] 376 + pub albums: ::prost::alloc::vec::Vec<Album>, 377 + #[prost(message, repeated, tag = "6")] 378 + pub tracks: ::prost::alloc::vec::Vec<Track>, 379 + #[prost(string, optional, tag = "7")] 380 + pub genres: ::core::option::Option<::prost::alloc::string::String>, 381 + } 382 + #[derive(Clone, PartialEq, ::prost::Message)] 383 + pub struct Album { 384 + #[prost(string, tag = "1")] 385 + pub id: ::prost::alloc::string::String, 386 + #[prost(string, tag = "2")] 387 + pub title: ::prost::alloc::string::String, 388 + #[prost(string, tag = "3")] 389 + pub artist: ::prost::alloc::string::String, 390 + #[prost(uint32, tag = "4")] 391 + pub year: u32, 392 + #[prost(string, tag = "5")] 393 + pub year_string: ::prost::alloc::string::String, 394 + #[prost(string, optional, tag = "6")] 395 + pub album_art: ::core::option::Option<::prost::alloc::string::String>, 396 + #[prost(string, tag = "7")] 397 + pub md5: ::prost::alloc::string::String, 398 + #[prost(string, tag = "8")] 399 + pub artist_id: ::prost::alloc::string::String, 400 + #[prost(string, optional, tag = "9")] 401 + pub label: ::core::option::Option<::prost::alloc::string::String>, 402 + #[prost(string, optional, tag = "10")] 403 + pub copyright_message: ::core::option::Option<::prost::alloc::string::String>, 404 + #[prost(message, repeated, tag = "11")] 405 + pub tracks: ::prost::alloc::vec::Vec<Track>, 406 + } 407 + #[derive(Clone, PartialEq, ::prost::Message)] 408 + pub struct GetAlbumRequest { 409 + #[prost(string, tag = "1")] 410 + pub id: ::prost::alloc::string::String, 411 + } 412 + #[derive(Clone, PartialEq, ::prost::Message)] 413 + pub struct GetAlbumResponse { 414 + #[prost(message, optional, tag = "1")] 415 + pub album: ::core::option::Option<Album>, 416 + } 417 + #[derive(Clone, PartialEq, ::prost::Message)] 418 + pub struct GetArtistRequest { 419 + #[prost(string, tag = "1")] 420 + pub id: ::prost::alloc::string::String, 421 + } 422 + #[derive(Clone, PartialEq, ::prost::Message)] 423 + pub struct GetArtistResponse { 424 + #[prost(message, optional, tag = "1")] 425 + pub artist: ::core::option::Option<Artist>, 426 + } 427 + #[derive(Clone, PartialEq, ::prost::Message)] 428 + pub struct GetTrackRequest { 429 + #[prost(string, tag = "1")] 430 + pub id: ::prost::alloc::string::String, 431 + } 432 + #[derive(Clone, PartialEq, ::prost::Message)] 433 + pub struct GetTrackResponse { 434 + #[prost(message, optional, tag = "1")] 435 + pub track: ::core::option::Option<Track>, 436 + } 437 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 438 + pub struct GetAlbumsRequest {} 439 + #[derive(Clone, PartialEq, ::prost::Message)] 440 + pub struct GetAlbumsResponse { 441 + #[prost(message, repeated, tag = "1")] 442 + pub albums: ::prost::alloc::vec::Vec<Album>, 443 + } 444 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 445 + pub struct GetArtistsRequest {} 446 + #[derive(Clone, PartialEq, ::prost::Message)] 447 + pub struct GetArtistsResponse { 448 + #[prost(message, repeated, tag = "1")] 449 + pub artists: ::prost::alloc::vec::Vec<Artist>, 450 + } 451 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 452 + pub struct GetTracksRequest {} 453 + #[derive(Clone, PartialEq, ::prost::Message)] 454 + pub struct GetTracksResponse { 455 + #[prost(message, repeated, tag = "1")] 456 + pub tracks: ::prost::alloc::vec::Vec<Track>, 457 + } 458 + #[derive(Clone, PartialEq, ::prost::Message)] 459 + pub struct LikeTrackRequest { 460 + #[prost(string, tag = "1")] 461 + pub id: ::prost::alloc::string::String, 462 + } 463 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 464 + pub struct LikeTrackResponse {} 465 + #[derive(Clone, PartialEq, ::prost::Message)] 466 + pub struct LikeAlbumRequest { 467 + #[prost(string, tag = "1")] 468 + pub id: ::prost::alloc::string::String, 469 + } 470 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 471 + pub struct LikeAlbumResponse {} 472 + #[derive(Clone, PartialEq, ::prost::Message)] 473 + pub struct UnlikeTrackRequest { 474 + #[prost(string, tag = "1")] 475 + pub id: ::prost::alloc::string::String, 476 + } 477 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 478 + pub struct UnlikeTrackResponse {} 479 + #[derive(Clone, PartialEq, ::prost::Message)] 480 + pub struct UnlikeAlbumRequest { 481 + #[prost(string, tag = "1")] 482 + pub id: ::prost::alloc::string::String, 483 + } 484 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 485 + pub struct UnlikeAlbumResponse {} 486 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 487 + pub struct GetLikedTracksRequest {} 488 + #[derive(Clone, PartialEq, ::prost::Message)] 489 + pub struct GetLikedTracksResponse { 490 + #[prost(message, repeated, tag = "1")] 491 + pub tracks: ::prost::alloc::vec::Vec<Track>, 492 + } 493 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 494 + pub struct GetLikedAlbumsRequest {} 495 + #[derive(Clone, PartialEq, ::prost::Message)] 496 + pub struct GetLikedAlbumsResponse { 497 + #[prost(message, repeated, tag = "1")] 498 + pub albums: ::prost::alloc::vec::Vec<Album>, 499 + } 500 + #[derive(Clone, PartialEq, ::prost::Message)] 501 + pub struct ScanLibraryRequest { 502 + #[prost(string, optional, tag = "1")] 503 + pub path: ::core::option::Option<::prost::alloc::string::String>, 504 + #[prost(bool, optional, tag = "2")] 505 + pub rebuild_index: ::core::option::Option<bool>, 506 + } 507 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 508 + pub struct ScanLibraryResponse {} 509 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 510 + pub struct StreamLibraryRequest {} 511 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 512 + pub struct StreamLibraryResponse {} 513 + #[derive(Clone, PartialEq, ::prost::Message)] 514 + pub struct SearchRequest { 515 + #[prost(string, tag = "1")] 516 + pub term: ::prost::alloc::string::String, 517 + } 518 + #[derive(Clone, PartialEq, ::prost::Message)] 519 + pub struct SearchPlaylist { 520 + #[prost(string, tag = "1")] 521 + pub id: ::prost::alloc::string::String, 522 + #[prost(string, tag = "2")] 523 + pub name: ::prost::alloc::string::String, 524 + #[prost(string, optional, tag = "3")] 525 + pub description: ::core::option::Option<::prost::alloc::string::String>, 526 + #[prost(string, optional, tag = "4")] 527 + pub image: ::core::option::Option<::prost::alloc::string::String>, 528 + #[prost(bool, tag = "5")] 529 + pub is_smart: bool, 530 + #[prost(int64, tag = "6")] 531 + pub track_count: i64, 532 + } 533 + #[derive(Clone, PartialEq, ::prost::Message)] 534 + pub struct SearchResponse { 535 + #[prost(message, repeated, tag = "1")] 536 + pub tracks: ::prost::alloc::vec::Vec<Track>, 537 + #[prost(message, repeated, tag = "2")] 538 + pub albums: ::prost::alloc::vec::Vec<Album>, 539 + #[prost(message, repeated, tag = "3")] 540 + pub artists: ::prost::alloc::vec::Vec<Artist>, 541 + #[prost(message, repeated, tag = "4")] 542 + pub playlists: ::prost::alloc::vec::Vec<SearchPlaylist>, 543 + } 544 + /// Generated client implementations. 545 + pub mod library_service_client { 546 + #![allow( 547 + unused_variables, 548 + dead_code, 549 + missing_docs, 550 + clippy::wildcard_imports, 551 + clippy::let_unit_value 552 + )] 553 + use tonic::codegen::http::Uri; 554 + use tonic::codegen::*; 555 + #[derive(Debug, Clone)] 556 + pub struct LibraryServiceClient<T> { 557 + inner: tonic::client::Grpc<T>, 558 + } 559 + impl LibraryServiceClient<tonic::transport::Channel> { 560 + /// Attempt to create a new client by connecting to a given endpoint. 561 + pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error> 562 + where 563 + D: TryInto<tonic::transport::Endpoint>, 564 + D::Error: Into<StdError>, 565 + { 566 + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; 567 + Ok(Self::new(conn)) 568 + } 569 + } 570 + impl<T> LibraryServiceClient<T> 571 + where 572 + T: tonic::client::GrpcService<tonic::body::BoxBody>, 573 + T::Error: Into<StdError>, 574 + T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static, 575 + <T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send, 576 + { 577 + pub fn new(inner: T) -> Self { 578 + let inner = tonic::client::Grpc::new(inner); 579 + Self { inner } 580 + } 581 + pub fn with_origin(inner: T, origin: Uri) -> Self { 582 + let inner = tonic::client::Grpc::with_origin(inner, origin); 583 + Self { inner } 584 + } 585 + pub fn with_interceptor<F>( 586 + inner: T, 587 + interceptor: F, 588 + ) -> LibraryServiceClient<InterceptedService<T, F>> 589 + where 590 + F: tonic::service::Interceptor, 591 + T::ResponseBody: Default, 592 + T: tonic::codegen::Service< 593 + http::Request<tonic::body::BoxBody>, 594 + Response = http::Response< 595 + <T as tonic::client::GrpcService<tonic::body::BoxBody>>::ResponseBody, 596 + >, 597 + >, 598 + <T as tonic::codegen::Service<http::Request<tonic::body::BoxBody>>>::Error: 599 + Into<StdError> + std::marker::Send + std::marker::Sync, 600 + { 601 + LibraryServiceClient::new(InterceptedService::new(inner, interceptor)) 602 + } 603 + /// Compress requests with the given encoding. 604 + /// 605 + /// This requires the server to support it otherwise it might respond with an 606 + /// error. 607 + #[must_use] 608 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 609 + self.inner = self.inner.send_compressed(encoding); 610 + self 611 + } 612 + /// Enable decompressing responses. 613 + #[must_use] 614 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 615 + self.inner = self.inner.accept_compressed(encoding); 616 + self 617 + } 618 + /// Limits the maximum size of a decoded message. 619 + /// 620 + /// Default: `4MB` 621 + #[must_use] 622 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 623 + self.inner = self.inner.max_decoding_message_size(limit); 624 + self 625 + } 626 + /// Limits the maximum size of an encoded message. 627 + /// 628 + /// Default: `usize::MAX` 629 + #[must_use] 630 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 631 + self.inner = self.inner.max_encoding_message_size(limit); 632 + self 633 + } 634 + pub async fn get_albums( 635 + &mut self, 636 + request: impl tonic::IntoRequest<super::GetAlbumsRequest>, 637 + ) -> std::result::Result<tonic::Response<super::GetAlbumsResponse>, tonic::Status> { 638 + self.inner.ready().await.map_err(|e| { 639 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 640 + })?; 641 + let codec = tonic::codec::ProstCodec::default(); 642 + let path = 643 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.LibraryService/GetAlbums"); 644 + let mut req = request.into_request(); 645 + req.extensions_mut().insert(GrpcMethod::new( 646 + "rockbox.v1alpha1.LibraryService", 647 + "GetAlbums", 648 + )); 649 + self.inner.unary(req, path, codec).await 650 + } 651 + pub async fn get_artists( 652 + &mut self, 653 + request: impl tonic::IntoRequest<super::GetArtistsRequest>, 654 + ) -> std::result::Result<tonic::Response<super::GetArtistsResponse>, tonic::Status> 655 + { 656 + self.inner.ready().await.map_err(|e| { 657 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 658 + })?; 659 + let codec = tonic::codec::ProstCodec::default(); 660 + let path = 661 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.LibraryService/GetArtists"); 662 + let mut req = request.into_request(); 663 + req.extensions_mut().insert(GrpcMethod::new( 664 + "rockbox.v1alpha1.LibraryService", 665 + "GetArtists", 666 + )); 667 + self.inner.unary(req, path, codec).await 668 + } 669 + pub async fn get_tracks( 670 + &mut self, 671 + request: impl tonic::IntoRequest<super::GetTracksRequest>, 672 + ) -> std::result::Result<tonic::Response<super::GetTracksResponse>, tonic::Status> { 673 + self.inner.ready().await.map_err(|e| { 674 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 675 + })?; 676 + let codec = tonic::codec::ProstCodec::default(); 677 + let path = 678 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.LibraryService/GetTracks"); 679 + let mut req = request.into_request(); 680 + req.extensions_mut().insert(GrpcMethod::new( 681 + "rockbox.v1alpha1.LibraryService", 682 + "GetTracks", 683 + )); 684 + self.inner.unary(req, path, codec).await 685 + } 686 + pub async fn get_album( 687 + &mut self, 688 + request: impl tonic::IntoRequest<super::GetAlbumRequest>, 689 + ) -> std::result::Result<tonic::Response<super::GetAlbumResponse>, tonic::Status> { 690 + self.inner.ready().await.map_err(|e| { 691 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 692 + })?; 693 + let codec = tonic::codec::ProstCodec::default(); 694 + let path = 695 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.LibraryService/GetAlbum"); 696 + let mut req = request.into_request(); 697 + req.extensions_mut().insert(GrpcMethod::new( 698 + "rockbox.v1alpha1.LibraryService", 699 + "GetAlbum", 700 + )); 701 + self.inner.unary(req, path, codec).await 702 + } 703 + pub async fn get_artist( 704 + &mut self, 705 + request: impl tonic::IntoRequest<super::GetArtistRequest>, 706 + ) -> std::result::Result<tonic::Response<super::GetArtistResponse>, tonic::Status> { 707 + self.inner.ready().await.map_err(|e| { 708 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 709 + })?; 710 + let codec = tonic::codec::ProstCodec::default(); 711 + let path = 712 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.LibraryService/GetArtist"); 713 + let mut req = request.into_request(); 714 + req.extensions_mut().insert(GrpcMethod::new( 715 + "rockbox.v1alpha1.LibraryService", 716 + "GetArtist", 717 + )); 718 + self.inner.unary(req, path, codec).await 719 + } 720 + pub async fn get_track( 721 + &mut self, 722 + request: impl tonic::IntoRequest<super::GetTrackRequest>, 723 + ) -> std::result::Result<tonic::Response<super::GetTrackResponse>, tonic::Status> { 724 + self.inner.ready().await.map_err(|e| { 725 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 726 + })?; 727 + let codec = tonic::codec::ProstCodec::default(); 728 + let path = 729 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.LibraryService/GetTrack"); 730 + let mut req = request.into_request(); 731 + req.extensions_mut().insert(GrpcMethod::new( 732 + "rockbox.v1alpha1.LibraryService", 733 + "GetTrack", 734 + )); 735 + self.inner.unary(req, path, codec).await 736 + } 737 + pub async fn like_track( 738 + &mut self, 739 + request: impl tonic::IntoRequest<super::LikeTrackRequest>, 740 + ) -> std::result::Result<tonic::Response<super::LikeTrackResponse>, tonic::Status> { 741 + self.inner.ready().await.map_err(|e| { 742 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 743 + })?; 744 + let codec = tonic::codec::ProstCodec::default(); 745 + let path = 746 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.LibraryService/LikeTrack"); 747 + let mut req = request.into_request(); 748 + req.extensions_mut().insert(GrpcMethod::new( 749 + "rockbox.v1alpha1.LibraryService", 750 + "LikeTrack", 751 + )); 752 + self.inner.unary(req, path, codec).await 753 + } 754 + pub async fn unlike_track( 755 + &mut self, 756 + request: impl tonic::IntoRequest<super::UnlikeTrackRequest>, 757 + ) -> std::result::Result<tonic::Response<super::UnlikeTrackResponse>, tonic::Status> 758 + { 759 + self.inner.ready().await.map_err(|e| { 760 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 761 + })?; 762 + let codec = tonic::codec::ProstCodec::default(); 763 + let path = http::uri::PathAndQuery::from_static( 764 + "/rockbox.v1alpha1.LibraryService/UnlikeTrack", 765 + ); 766 + let mut req = request.into_request(); 767 + req.extensions_mut().insert(GrpcMethod::new( 768 + "rockbox.v1alpha1.LibraryService", 769 + "UnlikeTrack", 770 + )); 771 + self.inner.unary(req, path, codec).await 772 + } 773 + pub async fn like_album( 774 + &mut self, 775 + request: impl tonic::IntoRequest<super::LikeAlbumRequest>, 776 + ) -> std::result::Result<tonic::Response<super::LikeAlbumResponse>, tonic::Status> { 777 + self.inner.ready().await.map_err(|e| { 778 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 779 + })?; 780 + let codec = tonic::codec::ProstCodec::default(); 781 + let path = 782 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.LibraryService/LikeAlbum"); 783 + let mut req = request.into_request(); 784 + req.extensions_mut().insert(GrpcMethod::new( 785 + "rockbox.v1alpha1.LibraryService", 786 + "LikeAlbum", 787 + )); 788 + self.inner.unary(req, path, codec).await 789 + } 790 + pub async fn unlike_album( 791 + &mut self, 792 + request: impl tonic::IntoRequest<super::UnlikeAlbumRequest>, 793 + ) -> std::result::Result<tonic::Response<super::UnlikeAlbumResponse>, tonic::Status> 794 + { 795 + self.inner.ready().await.map_err(|e| { 796 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 797 + })?; 798 + let codec = tonic::codec::ProstCodec::default(); 799 + let path = http::uri::PathAndQuery::from_static( 800 + "/rockbox.v1alpha1.LibraryService/UnlikeAlbum", 801 + ); 802 + let mut req = request.into_request(); 803 + req.extensions_mut().insert(GrpcMethod::new( 804 + "rockbox.v1alpha1.LibraryService", 805 + "UnlikeAlbum", 806 + )); 807 + self.inner.unary(req, path, codec).await 808 + } 809 + pub async fn get_liked_tracks( 810 + &mut self, 811 + request: impl tonic::IntoRequest<super::GetLikedTracksRequest>, 812 + ) -> std::result::Result<tonic::Response<super::GetLikedTracksResponse>, tonic::Status> 813 + { 814 + self.inner.ready().await.map_err(|e| { 815 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 816 + })?; 817 + let codec = tonic::codec::ProstCodec::default(); 818 + let path = http::uri::PathAndQuery::from_static( 819 + "/rockbox.v1alpha1.LibraryService/GetLikedTracks", 820 + ); 821 + let mut req = request.into_request(); 822 + req.extensions_mut().insert(GrpcMethod::new( 823 + "rockbox.v1alpha1.LibraryService", 824 + "GetLikedTracks", 825 + )); 826 + self.inner.unary(req, path, codec).await 827 + } 828 + pub async fn get_liked_albums( 829 + &mut self, 830 + request: impl tonic::IntoRequest<super::GetLikedAlbumsRequest>, 831 + ) -> std::result::Result<tonic::Response<super::GetLikedAlbumsResponse>, tonic::Status> 832 + { 833 + self.inner.ready().await.map_err(|e| { 834 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 835 + })?; 836 + let codec = tonic::codec::ProstCodec::default(); 837 + let path = http::uri::PathAndQuery::from_static( 838 + "/rockbox.v1alpha1.LibraryService/GetLikedAlbums", 839 + ); 840 + let mut req = request.into_request(); 841 + req.extensions_mut().insert(GrpcMethod::new( 842 + "rockbox.v1alpha1.LibraryService", 843 + "GetLikedAlbums", 844 + )); 845 + self.inner.unary(req, path, codec).await 846 + } 847 + pub async fn scan_library( 848 + &mut self, 849 + request: impl tonic::IntoRequest<super::ScanLibraryRequest>, 850 + ) -> std::result::Result<tonic::Response<super::ScanLibraryResponse>, tonic::Status> 851 + { 852 + self.inner.ready().await.map_err(|e| { 853 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 854 + })?; 855 + let codec = tonic::codec::ProstCodec::default(); 856 + let path = http::uri::PathAndQuery::from_static( 857 + "/rockbox.v1alpha1.LibraryService/ScanLibrary", 858 + ); 859 + let mut req = request.into_request(); 860 + req.extensions_mut().insert(GrpcMethod::new( 861 + "rockbox.v1alpha1.LibraryService", 862 + "ScanLibrary", 863 + )); 864 + self.inner.unary(req, path, codec).await 865 + } 866 + pub async fn stream_library( 867 + &mut self, 868 + request: impl tonic::IntoRequest<super::StreamLibraryRequest>, 869 + ) -> std::result::Result< 870 + tonic::Response<tonic::codec::Streaming<super::StreamLibraryResponse>>, 871 + tonic::Status, 872 + > { 873 + self.inner.ready().await.map_err(|e| { 874 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 875 + })?; 876 + let codec = tonic::codec::ProstCodec::default(); 877 + let path = http::uri::PathAndQuery::from_static( 878 + "/rockbox.v1alpha1.LibraryService/StreamLibrary", 879 + ); 880 + let mut req = request.into_request(); 881 + req.extensions_mut().insert(GrpcMethod::new( 882 + "rockbox.v1alpha1.LibraryService", 883 + "StreamLibrary", 884 + )); 885 + self.inner.server_streaming(req, path, codec).await 886 + } 887 + pub async fn search( 888 + &mut self, 889 + request: impl tonic::IntoRequest<super::SearchRequest>, 890 + ) -> std::result::Result<tonic::Response<super::SearchResponse>, tonic::Status> { 891 + self.inner.ready().await.map_err(|e| { 892 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 893 + })?; 894 + let codec = tonic::codec::ProstCodec::default(); 895 + let path = 896 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.LibraryService/Search"); 897 + let mut req = request.into_request(); 898 + req.extensions_mut() 899 + .insert(GrpcMethod::new("rockbox.v1alpha1.LibraryService", "Search")); 900 + self.inner.unary(req, path, codec).await 901 + } 902 + } 903 + } 904 + /// Generated server implementations. 905 + pub mod library_service_server { 906 + #![allow( 907 + unused_variables, 908 + dead_code, 909 + missing_docs, 910 + clippy::wildcard_imports, 911 + clippy::let_unit_value 912 + )] 913 + use tonic::codegen::*; 914 + /// Generated trait containing gRPC methods that should be implemented for use with LibraryServiceServer. 915 + #[async_trait] 916 + pub trait LibraryService: std::marker::Send + std::marker::Sync + 'static { 917 + async fn get_albums( 918 + &self, 919 + request: tonic::Request<super::GetAlbumsRequest>, 920 + ) -> std::result::Result<tonic::Response<super::GetAlbumsResponse>, tonic::Status>; 921 + async fn get_artists( 922 + &self, 923 + request: tonic::Request<super::GetArtistsRequest>, 924 + ) -> std::result::Result<tonic::Response<super::GetArtistsResponse>, tonic::Status>; 925 + async fn get_tracks( 926 + &self, 927 + request: tonic::Request<super::GetTracksRequest>, 928 + ) -> std::result::Result<tonic::Response<super::GetTracksResponse>, tonic::Status>; 929 + async fn get_album( 930 + &self, 931 + request: tonic::Request<super::GetAlbumRequest>, 932 + ) -> std::result::Result<tonic::Response<super::GetAlbumResponse>, tonic::Status>; 933 + async fn get_artist( 934 + &self, 935 + request: tonic::Request<super::GetArtistRequest>, 936 + ) -> std::result::Result<tonic::Response<super::GetArtistResponse>, tonic::Status>; 937 + async fn get_track( 938 + &self, 939 + request: tonic::Request<super::GetTrackRequest>, 940 + ) -> std::result::Result<tonic::Response<super::GetTrackResponse>, tonic::Status>; 941 + async fn like_track( 942 + &self, 943 + request: tonic::Request<super::LikeTrackRequest>, 944 + ) -> std::result::Result<tonic::Response<super::LikeTrackResponse>, tonic::Status>; 945 + async fn unlike_track( 946 + &self, 947 + request: tonic::Request<super::UnlikeTrackRequest>, 948 + ) -> std::result::Result<tonic::Response<super::UnlikeTrackResponse>, tonic::Status>; 949 + async fn like_album( 950 + &self, 951 + request: tonic::Request<super::LikeAlbumRequest>, 952 + ) -> std::result::Result<tonic::Response<super::LikeAlbumResponse>, tonic::Status>; 953 + async fn unlike_album( 954 + &self, 955 + request: tonic::Request<super::UnlikeAlbumRequest>, 956 + ) -> std::result::Result<tonic::Response<super::UnlikeAlbumResponse>, tonic::Status>; 957 + async fn get_liked_tracks( 958 + &self, 959 + request: tonic::Request<super::GetLikedTracksRequest>, 960 + ) -> std::result::Result<tonic::Response<super::GetLikedTracksResponse>, tonic::Status>; 961 + async fn get_liked_albums( 962 + &self, 963 + request: tonic::Request<super::GetLikedAlbumsRequest>, 964 + ) -> std::result::Result<tonic::Response<super::GetLikedAlbumsResponse>, tonic::Status>; 965 + async fn scan_library( 966 + &self, 967 + request: tonic::Request<super::ScanLibraryRequest>, 968 + ) -> std::result::Result<tonic::Response<super::ScanLibraryResponse>, tonic::Status>; 969 + /// Server streaming response type for the StreamLibrary method. 970 + type StreamLibraryStream: tonic::codegen::tokio_stream::Stream< 971 + Item = std::result::Result<super::StreamLibraryResponse, tonic::Status>, 972 + > + std::marker::Send 973 + + 'static; 974 + async fn stream_library( 975 + &self, 976 + request: tonic::Request<super::StreamLibraryRequest>, 977 + ) -> std::result::Result<tonic::Response<Self::StreamLibraryStream>, tonic::Status>; 978 + async fn search( 979 + &self, 980 + request: tonic::Request<super::SearchRequest>, 981 + ) -> std::result::Result<tonic::Response<super::SearchResponse>, tonic::Status>; 982 + } 983 + #[derive(Debug)] 984 + pub struct LibraryServiceServer<T> { 985 + inner: Arc<T>, 986 + accept_compression_encodings: EnabledCompressionEncodings, 987 + send_compression_encodings: EnabledCompressionEncodings, 988 + max_decoding_message_size: Option<usize>, 989 + max_encoding_message_size: Option<usize>, 990 + } 991 + impl<T> LibraryServiceServer<T> { 992 + pub fn new(inner: T) -> Self { 993 + Self::from_arc(Arc::new(inner)) 994 + } 995 + pub fn from_arc(inner: Arc<T>) -> Self { 996 + Self { 997 + inner, 998 + accept_compression_encodings: Default::default(), 999 + send_compression_encodings: Default::default(), 1000 + max_decoding_message_size: None, 1001 + max_encoding_message_size: None, 1002 + } 1003 + } 1004 + pub fn with_interceptor<F>(inner: T, interceptor: F) -> InterceptedService<Self, F> 1005 + where 1006 + F: tonic::service::Interceptor, 1007 + { 1008 + InterceptedService::new(Self::new(inner), interceptor) 1009 + } 1010 + /// Enable decompressing requests with the given encoding. 1011 + #[must_use] 1012 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 1013 + self.accept_compression_encodings.enable(encoding); 1014 + self 1015 + } 1016 + /// Compress responses with the given encoding, if the client supports it. 1017 + #[must_use] 1018 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 1019 + self.send_compression_encodings.enable(encoding); 1020 + self 1021 + } 1022 + /// Limits the maximum size of a decoded message. 1023 + /// 1024 + /// Default: `4MB` 1025 + #[must_use] 1026 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 1027 + self.max_decoding_message_size = Some(limit); 1028 + self 1029 + } 1030 + /// Limits the maximum size of an encoded message. 1031 + /// 1032 + /// Default: `usize::MAX` 1033 + #[must_use] 1034 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 1035 + self.max_encoding_message_size = Some(limit); 1036 + self 1037 + } 1038 + } 1039 + impl<T, B> tonic::codegen::Service<http::Request<B>> for LibraryServiceServer<T> 1040 + where 1041 + T: LibraryService, 1042 + B: Body + std::marker::Send + 'static, 1043 + B::Error: Into<StdError> + std::marker::Send + 'static, 1044 + { 1045 + type Response = http::Response<tonic::body::BoxBody>; 1046 + type Error = std::convert::Infallible; 1047 + type Future = BoxFuture<Self::Response, Self::Error>; 1048 + fn poll_ready( 1049 + &mut self, 1050 + _cx: &mut Context<'_>, 1051 + ) -> Poll<std::result::Result<(), Self::Error>> { 1052 + Poll::Ready(Ok(())) 1053 + } 1054 + fn call(&mut self, req: http::Request<B>) -> Self::Future { 1055 + match req.uri().path() { 1056 + "/rockbox.v1alpha1.LibraryService/GetAlbums" => { 1057 + #[allow(non_camel_case_types)] 1058 + struct GetAlbumsSvc<T: LibraryService>(pub Arc<T>); 1059 + impl<T: LibraryService> tonic::server::UnaryService<super::GetAlbumsRequest> for GetAlbumsSvc<T> { 1060 + type Response = super::GetAlbumsResponse; 1061 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 1062 + fn call( 1063 + &mut self, 1064 + request: tonic::Request<super::GetAlbumsRequest>, 1065 + ) -> Self::Future { 1066 + let inner = Arc::clone(&self.0); 1067 + let fut = async move { 1068 + <T as LibraryService>::get_albums(&inner, request).await 1069 + }; 1070 + Box::pin(fut) 1071 + } 1072 + } 1073 + let accept_compression_encodings = self.accept_compression_encodings; 1074 + let send_compression_encodings = self.send_compression_encodings; 1075 + let max_decoding_message_size = self.max_decoding_message_size; 1076 + let max_encoding_message_size = self.max_encoding_message_size; 1077 + let inner = self.inner.clone(); 1078 + let fut = async move { 1079 + let method = GetAlbumsSvc(inner); 1080 + let codec = tonic::codec::ProstCodec::default(); 1081 + let mut grpc = tonic::server::Grpc::new(codec) 1082 + .apply_compression_config( 1083 + accept_compression_encodings, 1084 + send_compression_encodings, 1085 + ) 1086 + .apply_max_message_size_config( 1087 + max_decoding_message_size, 1088 + max_encoding_message_size, 1089 + ); 1090 + let res = grpc.unary(method, req).await; 1091 + Ok(res) 1092 + }; 1093 + Box::pin(fut) 1094 + } 1095 + "/rockbox.v1alpha1.LibraryService/GetArtists" => { 1096 + #[allow(non_camel_case_types)] 1097 + struct GetArtistsSvc<T: LibraryService>(pub Arc<T>); 1098 + impl<T: LibraryService> tonic::server::UnaryService<super::GetArtistsRequest> for GetArtistsSvc<T> { 1099 + type Response = super::GetArtistsResponse; 1100 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 1101 + fn call( 1102 + &mut self, 1103 + request: tonic::Request<super::GetArtistsRequest>, 1104 + ) -> Self::Future { 1105 + let inner = Arc::clone(&self.0); 1106 + let fut = async move { 1107 + <T as LibraryService>::get_artists(&inner, request).await 1108 + }; 1109 + Box::pin(fut) 1110 + } 1111 + } 1112 + let accept_compression_encodings = self.accept_compression_encodings; 1113 + let send_compression_encodings = self.send_compression_encodings; 1114 + let max_decoding_message_size = self.max_decoding_message_size; 1115 + let max_encoding_message_size = self.max_encoding_message_size; 1116 + let inner = self.inner.clone(); 1117 + let fut = async move { 1118 + let method = GetArtistsSvc(inner); 1119 + let codec = tonic::codec::ProstCodec::default(); 1120 + let mut grpc = tonic::server::Grpc::new(codec) 1121 + .apply_compression_config( 1122 + accept_compression_encodings, 1123 + send_compression_encodings, 1124 + ) 1125 + .apply_max_message_size_config( 1126 + max_decoding_message_size, 1127 + max_encoding_message_size, 1128 + ); 1129 + let res = grpc.unary(method, req).await; 1130 + Ok(res) 1131 + }; 1132 + Box::pin(fut) 1133 + } 1134 + "/rockbox.v1alpha1.LibraryService/GetTracks" => { 1135 + #[allow(non_camel_case_types)] 1136 + struct GetTracksSvc<T: LibraryService>(pub Arc<T>); 1137 + impl<T: LibraryService> tonic::server::UnaryService<super::GetTracksRequest> for GetTracksSvc<T> { 1138 + type Response = super::GetTracksResponse; 1139 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 1140 + fn call( 1141 + &mut self, 1142 + request: tonic::Request<super::GetTracksRequest>, 1143 + ) -> Self::Future { 1144 + let inner = Arc::clone(&self.0); 1145 + let fut = async move { 1146 + <T as LibraryService>::get_tracks(&inner, request).await 1147 + }; 1148 + Box::pin(fut) 1149 + } 1150 + } 1151 + let accept_compression_encodings = self.accept_compression_encodings; 1152 + let send_compression_encodings = self.send_compression_encodings; 1153 + let max_decoding_message_size = self.max_decoding_message_size; 1154 + let max_encoding_message_size = self.max_encoding_message_size; 1155 + let inner = self.inner.clone(); 1156 + let fut = async move { 1157 + let method = GetTracksSvc(inner); 1158 + let codec = tonic::codec::ProstCodec::default(); 1159 + let mut grpc = tonic::server::Grpc::new(codec) 1160 + .apply_compression_config( 1161 + accept_compression_encodings, 1162 + send_compression_encodings, 1163 + ) 1164 + .apply_max_message_size_config( 1165 + max_decoding_message_size, 1166 + max_encoding_message_size, 1167 + ); 1168 + let res = grpc.unary(method, req).await; 1169 + Ok(res) 1170 + }; 1171 + Box::pin(fut) 1172 + } 1173 + "/rockbox.v1alpha1.LibraryService/GetAlbum" => { 1174 + #[allow(non_camel_case_types)] 1175 + struct GetAlbumSvc<T: LibraryService>(pub Arc<T>); 1176 + impl<T: LibraryService> tonic::server::UnaryService<super::GetAlbumRequest> for GetAlbumSvc<T> { 1177 + type Response = super::GetAlbumResponse; 1178 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 1179 + fn call( 1180 + &mut self, 1181 + request: tonic::Request<super::GetAlbumRequest>, 1182 + ) -> Self::Future { 1183 + let inner = Arc::clone(&self.0); 1184 + let fut = async move { 1185 + <T as LibraryService>::get_album(&inner, request).await 1186 + }; 1187 + Box::pin(fut) 1188 + } 1189 + } 1190 + let accept_compression_encodings = self.accept_compression_encodings; 1191 + let send_compression_encodings = self.send_compression_encodings; 1192 + let max_decoding_message_size = self.max_decoding_message_size; 1193 + let max_encoding_message_size = self.max_encoding_message_size; 1194 + let inner = self.inner.clone(); 1195 + let fut = async move { 1196 + let method = GetAlbumSvc(inner); 1197 + let codec = tonic::codec::ProstCodec::default(); 1198 + let mut grpc = tonic::server::Grpc::new(codec) 1199 + .apply_compression_config( 1200 + accept_compression_encodings, 1201 + send_compression_encodings, 1202 + ) 1203 + .apply_max_message_size_config( 1204 + max_decoding_message_size, 1205 + max_encoding_message_size, 1206 + ); 1207 + let res = grpc.unary(method, req).await; 1208 + Ok(res) 1209 + }; 1210 + Box::pin(fut) 1211 + } 1212 + "/rockbox.v1alpha1.LibraryService/GetArtist" => { 1213 + #[allow(non_camel_case_types)] 1214 + struct GetArtistSvc<T: LibraryService>(pub Arc<T>); 1215 + impl<T: LibraryService> tonic::server::UnaryService<super::GetArtistRequest> for GetArtistSvc<T> { 1216 + type Response = super::GetArtistResponse; 1217 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 1218 + fn call( 1219 + &mut self, 1220 + request: tonic::Request<super::GetArtistRequest>, 1221 + ) -> Self::Future { 1222 + let inner = Arc::clone(&self.0); 1223 + let fut = async move { 1224 + <T as LibraryService>::get_artist(&inner, request).await 1225 + }; 1226 + Box::pin(fut) 1227 + } 1228 + } 1229 + let accept_compression_encodings = self.accept_compression_encodings; 1230 + let send_compression_encodings = self.send_compression_encodings; 1231 + let max_decoding_message_size = self.max_decoding_message_size; 1232 + let max_encoding_message_size = self.max_encoding_message_size; 1233 + let inner = self.inner.clone(); 1234 + let fut = async move { 1235 + let method = GetArtistSvc(inner); 1236 + let codec = tonic::codec::ProstCodec::default(); 1237 + let mut grpc = tonic::server::Grpc::new(codec) 1238 + .apply_compression_config( 1239 + accept_compression_encodings, 1240 + send_compression_encodings, 1241 + ) 1242 + .apply_max_message_size_config( 1243 + max_decoding_message_size, 1244 + max_encoding_message_size, 1245 + ); 1246 + let res = grpc.unary(method, req).await; 1247 + Ok(res) 1248 + }; 1249 + Box::pin(fut) 1250 + } 1251 + "/rockbox.v1alpha1.LibraryService/GetTrack" => { 1252 + #[allow(non_camel_case_types)] 1253 + struct GetTrackSvc<T: LibraryService>(pub Arc<T>); 1254 + impl<T: LibraryService> tonic::server::UnaryService<super::GetTrackRequest> for GetTrackSvc<T> { 1255 + type Response = super::GetTrackResponse; 1256 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 1257 + fn call( 1258 + &mut self, 1259 + request: tonic::Request<super::GetTrackRequest>, 1260 + ) -> Self::Future { 1261 + let inner = Arc::clone(&self.0); 1262 + let fut = async move { 1263 + <T as LibraryService>::get_track(&inner, request).await 1264 + }; 1265 + Box::pin(fut) 1266 + } 1267 + } 1268 + let accept_compression_encodings = self.accept_compression_encodings; 1269 + let send_compression_encodings = self.send_compression_encodings; 1270 + let max_decoding_message_size = self.max_decoding_message_size; 1271 + let max_encoding_message_size = self.max_encoding_message_size; 1272 + let inner = self.inner.clone(); 1273 + let fut = async move { 1274 + let method = GetTrackSvc(inner); 1275 + let codec = tonic::codec::ProstCodec::default(); 1276 + let mut grpc = tonic::server::Grpc::new(codec) 1277 + .apply_compression_config( 1278 + accept_compression_encodings, 1279 + send_compression_encodings, 1280 + ) 1281 + .apply_max_message_size_config( 1282 + max_decoding_message_size, 1283 + max_encoding_message_size, 1284 + ); 1285 + let res = grpc.unary(method, req).await; 1286 + Ok(res) 1287 + }; 1288 + Box::pin(fut) 1289 + } 1290 + "/rockbox.v1alpha1.LibraryService/LikeTrack" => { 1291 + #[allow(non_camel_case_types)] 1292 + struct LikeTrackSvc<T: LibraryService>(pub Arc<T>); 1293 + impl<T: LibraryService> tonic::server::UnaryService<super::LikeTrackRequest> for LikeTrackSvc<T> { 1294 + type Response = super::LikeTrackResponse; 1295 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 1296 + fn call( 1297 + &mut self, 1298 + request: tonic::Request<super::LikeTrackRequest>, 1299 + ) -> Self::Future { 1300 + let inner = Arc::clone(&self.0); 1301 + let fut = async move { 1302 + <T as LibraryService>::like_track(&inner, request).await 1303 + }; 1304 + Box::pin(fut) 1305 + } 1306 + } 1307 + let accept_compression_encodings = self.accept_compression_encodings; 1308 + let send_compression_encodings = self.send_compression_encodings; 1309 + let max_decoding_message_size = self.max_decoding_message_size; 1310 + let max_encoding_message_size = self.max_encoding_message_size; 1311 + let inner = self.inner.clone(); 1312 + let fut = async move { 1313 + let method = LikeTrackSvc(inner); 1314 + let codec = tonic::codec::ProstCodec::default(); 1315 + let mut grpc = tonic::server::Grpc::new(codec) 1316 + .apply_compression_config( 1317 + accept_compression_encodings, 1318 + send_compression_encodings, 1319 + ) 1320 + .apply_max_message_size_config( 1321 + max_decoding_message_size, 1322 + max_encoding_message_size, 1323 + ); 1324 + let res = grpc.unary(method, req).await; 1325 + Ok(res) 1326 + }; 1327 + Box::pin(fut) 1328 + } 1329 + "/rockbox.v1alpha1.LibraryService/UnlikeTrack" => { 1330 + #[allow(non_camel_case_types)] 1331 + struct UnlikeTrackSvc<T: LibraryService>(pub Arc<T>); 1332 + impl<T: LibraryService> tonic::server::UnaryService<super::UnlikeTrackRequest> 1333 + for UnlikeTrackSvc<T> 1334 + { 1335 + type Response = super::UnlikeTrackResponse; 1336 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 1337 + fn call( 1338 + &mut self, 1339 + request: tonic::Request<super::UnlikeTrackRequest>, 1340 + ) -> Self::Future { 1341 + let inner = Arc::clone(&self.0); 1342 + let fut = async move { 1343 + <T as LibraryService>::unlike_track(&inner, request).await 1344 + }; 1345 + Box::pin(fut) 1346 + } 1347 + } 1348 + let accept_compression_encodings = self.accept_compression_encodings; 1349 + let send_compression_encodings = self.send_compression_encodings; 1350 + let max_decoding_message_size = self.max_decoding_message_size; 1351 + let max_encoding_message_size = self.max_encoding_message_size; 1352 + let inner = self.inner.clone(); 1353 + let fut = async move { 1354 + let method = UnlikeTrackSvc(inner); 1355 + let codec = tonic::codec::ProstCodec::default(); 1356 + let mut grpc = tonic::server::Grpc::new(codec) 1357 + .apply_compression_config( 1358 + accept_compression_encodings, 1359 + send_compression_encodings, 1360 + ) 1361 + .apply_max_message_size_config( 1362 + max_decoding_message_size, 1363 + max_encoding_message_size, 1364 + ); 1365 + let res = grpc.unary(method, req).await; 1366 + Ok(res) 1367 + }; 1368 + Box::pin(fut) 1369 + } 1370 + "/rockbox.v1alpha1.LibraryService/LikeAlbum" => { 1371 + #[allow(non_camel_case_types)] 1372 + struct LikeAlbumSvc<T: LibraryService>(pub Arc<T>); 1373 + impl<T: LibraryService> tonic::server::UnaryService<super::LikeAlbumRequest> for LikeAlbumSvc<T> { 1374 + type Response = super::LikeAlbumResponse; 1375 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 1376 + fn call( 1377 + &mut self, 1378 + request: tonic::Request<super::LikeAlbumRequest>, 1379 + ) -> Self::Future { 1380 + let inner = Arc::clone(&self.0); 1381 + let fut = async move { 1382 + <T as LibraryService>::like_album(&inner, request).await 1383 + }; 1384 + Box::pin(fut) 1385 + } 1386 + } 1387 + let accept_compression_encodings = self.accept_compression_encodings; 1388 + let send_compression_encodings = self.send_compression_encodings; 1389 + let max_decoding_message_size = self.max_decoding_message_size; 1390 + let max_encoding_message_size = self.max_encoding_message_size; 1391 + let inner = self.inner.clone(); 1392 + let fut = async move { 1393 + let method = LikeAlbumSvc(inner); 1394 + let codec = tonic::codec::ProstCodec::default(); 1395 + let mut grpc = tonic::server::Grpc::new(codec) 1396 + .apply_compression_config( 1397 + accept_compression_encodings, 1398 + send_compression_encodings, 1399 + ) 1400 + .apply_max_message_size_config( 1401 + max_decoding_message_size, 1402 + max_encoding_message_size, 1403 + ); 1404 + let res = grpc.unary(method, req).await; 1405 + Ok(res) 1406 + }; 1407 + Box::pin(fut) 1408 + } 1409 + "/rockbox.v1alpha1.LibraryService/UnlikeAlbum" => { 1410 + #[allow(non_camel_case_types)] 1411 + struct UnlikeAlbumSvc<T: LibraryService>(pub Arc<T>); 1412 + impl<T: LibraryService> tonic::server::UnaryService<super::UnlikeAlbumRequest> 1413 + for UnlikeAlbumSvc<T> 1414 + { 1415 + type Response = super::UnlikeAlbumResponse; 1416 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 1417 + fn call( 1418 + &mut self, 1419 + request: tonic::Request<super::UnlikeAlbumRequest>, 1420 + ) -> Self::Future { 1421 + let inner = Arc::clone(&self.0); 1422 + let fut = async move { 1423 + <T as LibraryService>::unlike_album(&inner, request).await 1424 + }; 1425 + Box::pin(fut) 1426 + } 1427 + } 1428 + let accept_compression_encodings = self.accept_compression_encodings; 1429 + let send_compression_encodings = self.send_compression_encodings; 1430 + let max_decoding_message_size = self.max_decoding_message_size; 1431 + let max_encoding_message_size = self.max_encoding_message_size; 1432 + let inner = self.inner.clone(); 1433 + let fut = async move { 1434 + let method = UnlikeAlbumSvc(inner); 1435 + let codec = tonic::codec::ProstCodec::default(); 1436 + let mut grpc = tonic::server::Grpc::new(codec) 1437 + .apply_compression_config( 1438 + accept_compression_encodings, 1439 + send_compression_encodings, 1440 + ) 1441 + .apply_max_message_size_config( 1442 + max_decoding_message_size, 1443 + max_encoding_message_size, 1444 + ); 1445 + let res = grpc.unary(method, req).await; 1446 + Ok(res) 1447 + }; 1448 + Box::pin(fut) 1449 + } 1450 + "/rockbox.v1alpha1.LibraryService/GetLikedTracks" => { 1451 + #[allow(non_camel_case_types)] 1452 + struct GetLikedTracksSvc<T: LibraryService>(pub Arc<T>); 1453 + impl<T: LibraryService> 1454 + tonic::server::UnaryService<super::GetLikedTracksRequest> 1455 + for GetLikedTracksSvc<T> 1456 + { 1457 + type Response = super::GetLikedTracksResponse; 1458 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 1459 + fn call( 1460 + &mut self, 1461 + request: tonic::Request<super::GetLikedTracksRequest>, 1462 + ) -> Self::Future { 1463 + let inner = Arc::clone(&self.0); 1464 + let fut = async move { 1465 + <T as LibraryService>::get_liked_tracks(&inner, request).await 1466 + }; 1467 + Box::pin(fut) 1468 + } 1469 + } 1470 + let accept_compression_encodings = self.accept_compression_encodings; 1471 + let send_compression_encodings = self.send_compression_encodings; 1472 + let max_decoding_message_size = self.max_decoding_message_size; 1473 + let max_encoding_message_size = self.max_encoding_message_size; 1474 + let inner = self.inner.clone(); 1475 + let fut = async move { 1476 + let method = GetLikedTracksSvc(inner); 1477 + let codec = tonic::codec::ProstCodec::default(); 1478 + let mut grpc = tonic::server::Grpc::new(codec) 1479 + .apply_compression_config( 1480 + accept_compression_encodings, 1481 + send_compression_encodings, 1482 + ) 1483 + .apply_max_message_size_config( 1484 + max_decoding_message_size, 1485 + max_encoding_message_size, 1486 + ); 1487 + let res = grpc.unary(method, req).await; 1488 + Ok(res) 1489 + }; 1490 + Box::pin(fut) 1491 + } 1492 + "/rockbox.v1alpha1.LibraryService/GetLikedAlbums" => { 1493 + #[allow(non_camel_case_types)] 1494 + struct GetLikedAlbumsSvc<T: LibraryService>(pub Arc<T>); 1495 + impl<T: LibraryService> 1496 + tonic::server::UnaryService<super::GetLikedAlbumsRequest> 1497 + for GetLikedAlbumsSvc<T> 1498 + { 1499 + type Response = super::GetLikedAlbumsResponse; 1500 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 1501 + fn call( 1502 + &mut self, 1503 + request: tonic::Request<super::GetLikedAlbumsRequest>, 1504 + ) -> Self::Future { 1505 + let inner = Arc::clone(&self.0); 1506 + let fut = async move { 1507 + <T as LibraryService>::get_liked_albums(&inner, request).await 1508 + }; 1509 + Box::pin(fut) 1510 + } 1511 + } 1512 + let accept_compression_encodings = self.accept_compression_encodings; 1513 + let send_compression_encodings = self.send_compression_encodings; 1514 + let max_decoding_message_size = self.max_decoding_message_size; 1515 + let max_encoding_message_size = self.max_encoding_message_size; 1516 + let inner = self.inner.clone(); 1517 + let fut = async move { 1518 + let method = GetLikedAlbumsSvc(inner); 1519 + let codec = tonic::codec::ProstCodec::default(); 1520 + let mut grpc = tonic::server::Grpc::new(codec) 1521 + .apply_compression_config( 1522 + accept_compression_encodings, 1523 + send_compression_encodings, 1524 + ) 1525 + .apply_max_message_size_config( 1526 + max_decoding_message_size, 1527 + max_encoding_message_size, 1528 + ); 1529 + let res = grpc.unary(method, req).await; 1530 + Ok(res) 1531 + }; 1532 + Box::pin(fut) 1533 + } 1534 + "/rockbox.v1alpha1.LibraryService/ScanLibrary" => { 1535 + #[allow(non_camel_case_types)] 1536 + struct ScanLibrarySvc<T: LibraryService>(pub Arc<T>); 1537 + impl<T: LibraryService> tonic::server::UnaryService<super::ScanLibraryRequest> 1538 + for ScanLibrarySvc<T> 1539 + { 1540 + type Response = super::ScanLibraryResponse; 1541 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 1542 + fn call( 1543 + &mut self, 1544 + request: tonic::Request<super::ScanLibraryRequest>, 1545 + ) -> Self::Future { 1546 + let inner = Arc::clone(&self.0); 1547 + let fut = async move { 1548 + <T as LibraryService>::scan_library(&inner, request).await 1549 + }; 1550 + Box::pin(fut) 1551 + } 1552 + } 1553 + let accept_compression_encodings = self.accept_compression_encodings; 1554 + let send_compression_encodings = self.send_compression_encodings; 1555 + let max_decoding_message_size = self.max_decoding_message_size; 1556 + let max_encoding_message_size = self.max_encoding_message_size; 1557 + let inner = self.inner.clone(); 1558 + let fut = async move { 1559 + let method = ScanLibrarySvc(inner); 1560 + let codec = tonic::codec::ProstCodec::default(); 1561 + let mut grpc = tonic::server::Grpc::new(codec) 1562 + .apply_compression_config( 1563 + accept_compression_encodings, 1564 + send_compression_encodings, 1565 + ) 1566 + .apply_max_message_size_config( 1567 + max_decoding_message_size, 1568 + max_encoding_message_size, 1569 + ); 1570 + let res = grpc.unary(method, req).await; 1571 + Ok(res) 1572 + }; 1573 + Box::pin(fut) 1574 + } 1575 + "/rockbox.v1alpha1.LibraryService/StreamLibrary" => { 1576 + #[allow(non_camel_case_types)] 1577 + struct StreamLibrarySvc<T: LibraryService>(pub Arc<T>); 1578 + impl<T: LibraryService> 1579 + tonic::server::ServerStreamingService<super::StreamLibraryRequest> 1580 + for StreamLibrarySvc<T> 1581 + { 1582 + type Response = super::StreamLibraryResponse; 1583 + type ResponseStream = T::StreamLibraryStream; 1584 + type Future = 1585 + BoxFuture<tonic::Response<Self::ResponseStream>, tonic::Status>; 1586 + fn call( 1587 + &mut self, 1588 + request: tonic::Request<super::StreamLibraryRequest>, 1589 + ) -> Self::Future { 1590 + let inner = Arc::clone(&self.0); 1591 + let fut = async move { 1592 + <T as LibraryService>::stream_library(&inner, request).await 1593 + }; 1594 + Box::pin(fut) 1595 + } 1596 + } 1597 + let accept_compression_encodings = self.accept_compression_encodings; 1598 + let send_compression_encodings = self.send_compression_encodings; 1599 + let max_decoding_message_size = self.max_decoding_message_size; 1600 + let max_encoding_message_size = self.max_encoding_message_size; 1601 + let inner = self.inner.clone(); 1602 + let fut = async move { 1603 + let method = StreamLibrarySvc(inner); 1604 + let codec = tonic::codec::ProstCodec::default(); 1605 + let mut grpc = tonic::server::Grpc::new(codec) 1606 + .apply_compression_config( 1607 + accept_compression_encodings, 1608 + send_compression_encodings, 1609 + ) 1610 + .apply_max_message_size_config( 1611 + max_decoding_message_size, 1612 + max_encoding_message_size, 1613 + ); 1614 + let res = grpc.server_streaming(method, req).await; 1615 + Ok(res) 1616 + }; 1617 + Box::pin(fut) 1618 + } 1619 + "/rockbox.v1alpha1.LibraryService/Search" => { 1620 + #[allow(non_camel_case_types)] 1621 + struct SearchSvc<T: LibraryService>(pub Arc<T>); 1622 + impl<T: LibraryService> tonic::server::UnaryService<super::SearchRequest> for SearchSvc<T> { 1623 + type Response = super::SearchResponse; 1624 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 1625 + fn call( 1626 + &mut self, 1627 + request: tonic::Request<super::SearchRequest>, 1628 + ) -> Self::Future { 1629 + let inner = Arc::clone(&self.0); 1630 + let fut = 1631 + async move { <T as LibraryService>::search(&inner, request).await }; 1632 + Box::pin(fut) 1633 + } 1634 + } 1635 + let accept_compression_encodings = self.accept_compression_encodings; 1636 + let send_compression_encodings = self.send_compression_encodings; 1637 + let max_decoding_message_size = self.max_decoding_message_size; 1638 + let max_encoding_message_size = self.max_encoding_message_size; 1639 + let inner = self.inner.clone(); 1640 + let fut = async move { 1641 + let method = SearchSvc(inner); 1642 + let codec = tonic::codec::ProstCodec::default(); 1643 + let mut grpc = tonic::server::Grpc::new(codec) 1644 + .apply_compression_config( 1645 + accept_compression_encodings, 1646 + send_compression_encodings, 1647 + ) 1648 + .apply_max_message_size_config( 1649 + max_decoding_message_size, 1650 + max_encoding_message_size, 1651 + ); 1652 + let res = grpc.unary(method, req).await; 1653 + Ok(res) 1654 + }; 1655 + Box::pin(fut) 1656 + } 1657 + _ => Box::pin(async move { 1658 + let mut response = http::Response::new(empty_body()); 1659 + let headers = response.headers_mut(); 1660 + headers.insert( 1661 + tonic::Status::GRPC_STATUS, 1662 + (tonic::Code::Unimplemented as i32).into(), 1663 + ); 1664 + headers.insert( 1665 + http::header::CONTENT_TYPE, 1666 + tonic::metadata::GRPC_CONTENT_TYPE, 1667 + ); 1668 + Ok(response) 1669 + }), 1670 + } 1671 + } 1672 + } 1673 + impl<T> Clone for LibraryServiceServer<T> { 1674 + fn clone(&self) -> Self { 1675 + let inner = self.inner.clone(); 1676 + Self { 1677 + inner, 1678 + accept_compression_encodings: self.accept_compression_encodings, 1679 + send_compression_encodings: self.send_compression_encodings, 1680 + max_decoding_message_size: self.max_decoding_message_size, 1681 + max_encoding_message_size: self.max_encoding_message_size, 1682 + } 1683 + } 1684 + } 1685 + /// Generated gRPC service name 1686 + pub const SERVICE_NAME: &str = "rockbox.v1alpha1.LibraryService"; 1687 + impl<T> tonic::server::NamedService for LibraryServiceServer<T> { 1688 + const NAME: &'static str = SERVICE_NAME; 1689 + } 1690 + } 1691 + /// Generated client implementations. 1692 + pub mod metadata_service_client { 1693 + #![allow( 1694 + unused_variables, 1695 + dead_code, 1696 + missing_docs, 1697 + clippy::wildcard_imports, 1698 + clippy::let_unit_value 1699 + )] 1700 + use tonic::codegen::http::Uri; 1701 + use tonic::codegen::*; 1702 + #[derive(Debug, Clone)] 1703 + pub struct MetadataServiceClient<T> { 1704 + inner: tonic::client::Grpc<T>, 1705 + } 1706 + impl MetadataServiceClient<tonic::transport::Channel> { 1707 + /// Attempt to create a new client by connecting to a given endpoint. 1708 + pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error> 1709 + where 1710 + D: TryInto<tonic::transport::Endpoint>, 1711 + D::Error: Into<StdError>, 1712 + { 1713 + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; 1714 + Ok(Self::new(conn)) 1715 + } 1716 + } 1717 + impl<T> MetadataServiceClient<T> 1718 + where 1719 + T: tonic::client::GrpcService<tonic::body::BoxBody>, 1720 + T::Error: Into<StdError>, 1721 + T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static, 1722 + <T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send, 1723 + { 1724 + pub fn new(inner: T) -> Self { 1725 + let inner = tonic::client::Grpc::new(inner); 1726 + Self { inner } 1727 + } 1728 + pub fn with_origin(inner: T, origin: Uri) -> Self { 1729 + let inner = tonic::client::Grpc::with_origin(inner, origin); 1730 + Self { inner } 1731 + } 1732 + pub fn with_interceptor<F>( 1733 + inner: T, 1734 + interceptor: F, 1735 + ) -> MetadataServiceClient<InterceptedService<T, F>> 1736 + where 1737 + F: tonic::service::Interceptor, 1738 + T::ResponseBody: Default, 1739 + T: tonic::codegen::Service< 1740 + http::Request<tonic::body::BoxBody>, 1741 + Response = http::Response< 1742 + <T as tonic::client::GrpcService<tonic::body::BoxBody>>::ResponseBody, 1743 + >, 1744 + >, 1745 + <T as tonic::codegen::Service<http::Request<tonic::body::BoxBody>>>::Error: 1746 + Into<StdError> + std::marker::Send + std::marker::Sync, 1747 + { 1748 + MetadataServiceClient::new(InterceptedService::new(inner, interceptor)) 1749 + } 1750 + /// Compress requests with the given encoding. 1751 + /// 1752 + /// This requires the server to support it otherwise it might respond with an 1753 + /// error. 1754 + #[must_use] 1755 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 1756 + self.inner = self.inner.send_compressed(encoding); 1757 + self 1758 + } 1759 + /// Enable decompressing responses. 1760 + #[must_use] 1761 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 1762 + self.inner = self.inner.accept_compressed(encoding); 1763 + self 1764 + } 1765 + /// Limits the maximum size of a decoded message. 1766 + /// 1767 + /// Default: `4MB` 1768 + #[must_use] 1769 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 1770 + self.inner = self.inner.max_decoding_message_size(limit); 1771 + self 1772 + } 1773 + /// Limits the maximum size of an encoded message. 1774 + /// 1775 + /// Default: `usize::MAX` 1776 + #[must_use] 1777 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 1778 + self.inner = self.inner.max_encoding_message_size(limit); 1779 + self 1780 + } 1781 + } 1782 + } 1783 + /// Generated server implementations. 1784 + pub mod metadata_service_server { 1785 + #![allow( 1786 + unused_variables, 1787 + dead_code, 1788 + missing_docs, 1789 + clippy::wildcard_imports, 1790 + clippy::let_unit_value 1791 + )] 1792 + use tonic::codegen::*; 1793 + /// Generated trait containing gRPC methods that should be implemented for use with MetadataServiceServer. 1794 + #[async_trait] 1795 + pub trait MetadataService: std::marker::Send + std::marker::Sync + 'static {} 1796 + #[derive(Debug)] 1797 + pub struct MetadataServiceServer<T> { 1798 + inner: Arc<T>, 1799 + accept_compression_encodings: EnabledCompressionEncodings, 1800 + send_compression_encodings: EnabledCompressionEncodings, 1801 + max_decoding_message_size: Option<usize>, 1802 + max_encoding_message_size: Option<usize>, 1803 + } 1804 + impl<T> MetadataServiceServer<T> { 1805 + pub fn new(inner: T) -> Self { 1806 + Self::from_arc(Arc::new(inner)) 1807 + } 1808 + pub fn from_arc(inner: Arc<T>) -> Self { 1809 + Self { 1810 + inner, 1811 + accept_compression_encodings: Default::default(), 1812 + send_compression_encodings: Default::default(), 1813 + max_decoding_message_size: None, 1814 + max_encoding_message_size: None, 1815 + } 1816 + } 1817 + pub fn with_interceptor<F>(inner: T, interceptor: F) -> InterceptedService<Self, F> 1818 + where 1819 + F: tonic::service::Interceptor, 1820 + { 1821 + InterceptedService::new(Self::new(inner), interceptor) 1822 + } 1823 + /// Enable decompressing requests with the given encoding. 1824 + #[must_use] 1825 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 1826 + self.accept_compression_encodings.enable(encoding); 1827 + self 1828 + } 1829 + /// Compress responses with the given encoding, if the client supports it. 1830 + #[must_use] 1831 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 1832 + self.send_compression_encodings.enable(encoding); 1833 + self 1834 + } 1835 + /// Limits the maximum size of a decoded message. 1836 + /// 1837 + /// Default: `4MB` 1838 + #[must_use] 1839 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 1840 + self.max_decoding_message_size = Some(limit); 1841 + self 1842 + } 1843 + /// Limits the maximum size of an encoded message. 1844 + /// 1845 + /// Default: `usize::MAX` 1846 + #[must_use] 1847 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 1848 + self.max_encoding_message_size = Some(limit); 1849 + self 1850 + } 1851 + } 1852 + impl<T, B> tonic::codegen::Service<http::Request<B>> for MetadataServiceServer<T> 1853 + where 1854 + T: MetadataService, 1855 + B: Body + std::marker::Send + 'static, 1856 + B::Error: Into<StdError> + std::marker::Send + 'static, 1857 + { 1858 + type Response = http::Response<tonic::body::BoxBody>; 1859 + type Error = std::convert::Infallible; 1860 + type Future = BoxFuture<Self::Response, Self::Error>; 1861 + fn poll_ready( 1862 + &mut self, 1863 + _cx: &mut Context<'_>, 1864 + ) -> Poll<std::result::Result<(), Self::Error>> { 1865 + Poll::Ready(Ok(())) 1866 + } 1867 + fn call(&mut self, req: http::Request<B>) -> Self::Future { 1868 + match req.uri().path() { 1869 + _ => Box::pin(async move { 1870 + let mut response = http::Response::new(empty_body()); 1871 + let headers = response.headers_mut(); 1872 + headers.insert( 1873 + tonic::Status::GRPC_STATUS, 1874 + (tonic::Code::Unimplemented as i32).into(), 1875 + ); 1876 + headers.insert( 1877 + http::header::CONTENT_TYPE, 1878 + tonic::metadata::GRPC_CONTENT_TYPE, 1879 + ); 1880 + Ok(response) 1881 + }), 1882 + } 1883 + } 1884 + } 1885 + impl<T> Clone for MetadataServiceServer<T> { 1886 + fn clone(&self) -> Self { 1887 + let inner = self.inner.clone(); 1888 + Self { 1889 + inner, 1890 + accept_compression_encodings: self.accept_compression_encodings, 1891 + send_compression_encodings: self.send_compression_encodings, 1892 + max_decoding_message_size: self.max_decoding_message_size, 1893 + max_encoding_message_size: self.max_encoding_message_size, 1894 + } 1895 + } 1896 + } 1897 + /// Generated gRPC service name 1898 + pub const SERVICE_NAME: &str = "rockbox.v1alpha1.MetadataService"; 1899 + impl<T> tonic::server::NamedService for MetadataServiceServer<T> { 1900 + const NAME: &'static str = SERVICE_NAME; 1901 + } 1902 + } 1903 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 1904 + pub struct PlayRequest { 1905 + #[prost(int64, tag = "1")] 1906 + pub elapsed: i64, 1907 + #[prost(int64, tag = "2")] 1908 + pub offset: i64, 1909 + } 1910 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 1911 + pub struct PlayResponse {} 1912 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 1913 + pub struct PlayOrPauseRequest {} 1914 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 1915 + pub struct PlayOrPauseResponse {} 1916 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 1917 + pub struct PauseRequest {} 1918 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 1919 + pub struct PauseResponse {} 1920 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 1921 + pub struct ResumeRequest {} 1922 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 1923 + pub struct ResumeResponse {} 1924 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 1925 + pub struct NextRequest {} 1926 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 1927 + pub struct NextResponse {} 1928 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 1929 + pub struct PreviousRequest {} 1930 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 1931 + pub struct PreviousResponse {} 1932 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 1933 + pub struct FastForwardRewindRequest { 1934 + #[prost(int32, tag = "1")] 1935 + pub new_time: i32, 1936 + } 1937 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 1938 + pub struct FastForwardRewindResponse {} 1939 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 1940 + pub struct StatusRequest {} 1941 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 1942 + pub struct StreamStatusRequest {} 1943 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 1944 + pub struct StatusResponse { 1945 + #[prost(int32, tag = "1")] 1946 + pub status: i32, 1947 + } 1948 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 1949 + pub struct CurrentTrackRequest {} 1950 + #[derive(Clone, PartialEq, ::prost::Message)] 1951 + pub struct CurrentTrackResponse { 1952 + #[prost(string, tag = "1")] 1953 + pub title: ::prost::alloc::string::String, 1954 + #[prost(string, tag = "2")] 1955 + pub artist: ::prost::alloc::string::String, 1956 + #[prost(string, tag = "3")] 1957 + pub album: ::prost::alloc::string::String, 1958 + #[prost(string, tag = "4")] 1959 + pub genre: ::prost::alloc::string::String, 1960 + #[prost(string, tag = "5")] 1961 + pub disc: ::prost::alloc::string::String, 1962 + #[prost(string, tag = "6")] 1963 + pub track_string: ::prost::alloc::string::String, 1964 + #[prost(string, tag = "7")] 1965 + pub year_string: ::prost::alloc::string::String, 1966 + #[prost(string, tag = "8")] 1967 + pub composer: ::prost::alloc::string::String, 1968 + #[prost(string, tag = "9")] 1969 + pub comment: ::prost::alloc::string::String, 1970 + #[prost(string, tag = "10")] 1971 + pub album_artist: ::prost::alloc::string::String, 1972 + #[prost(string, tag = "11")] 1973 + pub grouping: ::prost::alloc::string::String, 1974 + #[prost(int32, tag = "12")] 1975 + pub discnum: i32, 1976 + #[prost(int32, tag = "13")] 1977 + pub tracknum: i32, 1978 + #[prost(int32, tag = "14")] 1979 + pub layer: i32, 1980 + #[prost(int32, tag = "15")] 1981 + pub year: i32, 1982 + #[prost(uint32, tag = "16")] 1983 + pub bitrate: u32, 1984 + #[prost(uint64, tag = "17")] 1985 + pub frequency: u64, 1986 + #[prost(uint64, tag = "18")] 1987 + pub filesize: u64, 1988 + #[prost(uint64, tag = "19")] 1989 + pub length: u64, 1990 + #[prost(uint64, tag = "20")] 1991 + pub elapsed: u64, 1992 + #[prost(string, tag = "21")] 1993 + pub path: ::prost::alloc::string::String, 1994 + #[prost(string, optional, tag = "22")] 1995 + pub album_art: ::core::option::Option<::prost::alloc::string::String>, 1996 + #[prost(string, tag = "23")] 1997 + pub album_id: ::prost::alloc::string::String, 1998 + #[prost(string, tag = "24")] 1999 + pub artist_id: ::prost::alloc::string::String, 2000 + #[prost(string, tag = "25")] 2001 + pub id: ::prost::alloc::string::String, 2002 + } 2003 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2004 + pub struct StreamCurrentTrackRequest {} 2005 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2006 + pub struct NextTrackRequest {} 2007 + #[derive(Clone, PartialEq, ::prost::Message)] 2008 + pub struct NextTrackResponse { 2009 + #[prost(string, tag = "1")] 2010 + pub title: ::prost::alloc::string::String, 2011 + #[prost(string, tag = "2")] 2012 + pub artist: ::prost::alloc::string::String, 2013 + #[prost(string, tag = "3")] 2014 + pub album: ::prost::alloc::string::String, 2015 + #[prost(string, tag = "4")] 2016 + pub genre: ::prost::alloc::string::String, 2017 + #[prost(string, tag = "5")] 2018 + pub disc: ::prost::alloc::string::String, 2019 + #[prost(string, tag = "6")] 2020 + pub track_string: ::prost::alloc::string::String, 2021 + #[prost(string, tag = "7")] 2022 + pub year_string: ::prost::alloc::string::String, 2023 + #[prost(string, tag = "8")] 2024 + pub composer: ::prost::alloc::string::String, 2025 + #[prost(string, tag = "9")] 2026 + pub comment: ::prost::alloc::string::String, 2027 + #[prost(string, tag = "10")] 2028 + pub album_artist: ::prost::alloc::string::String, 2029 + #[prost(string, tag = "11")] 2030 + pub grouping: ::prost::alloc::string::String, 2031 + #[prost(int32, tag = "12")] 2032 + pub discnum: i32, 2033 + #[prost(int32, tag = "13")] 2034 + pub tracknum: i32, 2035 + #[prost(int32, tag = "14")] 2036 + pub layer: i32, 2037 + #[prost(int32, tag = "15")] 2038 + pub year: i32, 2039 + #[prost(uint32, tag = "16")] 2040 + pub bitrate: u32, 2041 + #[prost(uint64, tag = "17")] 2042 + pub frequency: u64, 2043 + #[prost(uint64, tag = "18")] 2044 + pub filesize: u64, 2045 + #[prost(uint64, tag = "19")] 2046 + pub length: u64, 2047 + #[prost(uint64, tag = "20")] 2048 + pub elapsed: u64, 2049 + #[prost(string, tag = "21")] 2050 + pub path: ::prost::alloc::string::String, 2051 + } 2052 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2053 + pub struct FlushAndReloadTracksRequest {} 2054 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2055 + pub struct FlushAndReloadTracksResponse {} 2056 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2057 + pub struct GetFilePositionRequest {} 2058 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2059 + pub struct GetFilePositionResponse { 2060 + #[prost(int32, tag = "1")] 2061 + pub position: i32, 2062 + } 2063 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2064 + pub struct HardStopRequest {} 2065 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2066 + pub struct HardStopResponse {} 2067 + #[derive(Clone, PartialEq, ::prost::Message)] 2068 + pub struct PlayAlbumRequest { 2069 + #[prost(string, tag = "1")] 2070 + pub album_id: ::prost::alloc::string::String, 2071 + #[prost(bool, optional, tag = "2")] 2072 + pub shuffle: ::core::option::Option<bool>, 2073 + #[prost(int32, optional, tag = "3")] 2074 + pub position: ::core::option::Option<i32>, 2075 + } 2076 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2077 + pub struct PlayAlbumResponse {} 2078 + #[derive(Clone, PartialEq, ::prost::Message)] 2079 + pub struct PlayArtistTracksRequest { 2080 + #[prost(string, tag = "1")] 2081 + pub artist_id: ::prost::alloc::string::String, 2082 + #[prost(bool, optional, tag = "2")] 2083 + pub shuffle: ::core::option::Option<bool>, 2084 + #[prost(int32, optional, tag = "3")] 2085 + pub position: ::core::option::Option<i32>, 2086 + } 2087 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2088 + pub struct PlayArtistTracksResponse {} 2089 + #[derive(Clone, PartialEq, ::prost::Message)] 2090 + pub struct PlayPlaylistRequest { 2091 + #[prost(string, tag = "1")] 2092 + pub playlist_id: ::prost::alloc::string::String, 2093 + #[prost(bool, optional, tag = "2")] 2094 + pub shuffle: ::core::option::Option<bool>, 2095 + } 2096 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2097 + pub struct PlayPlaylistResponse {} 2098 + #[derive(Clone, PartialEq, ::prost::Message)] 2099 + pub struct PlayDirectoryRequest { 2100 + #[prost(string, tag = "1")] 2101 + pub path: ::prost::alloc::string::String, 2102 + #[prost(bool, optional, tag = "2")] 2103 + pub shuffle: ::core::option::Option<bool>, 2104 + #[prost(bool, optional, tag = "3")] 2105 + pub recurse: ::core::option::Option<bool>, 2106 + #[prost(int32, optional, tag = "4")] 2107 + pub position: ::core::option::Option<i32>, 2108 + } 2109 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2110 + pub struct PlayMusicDirectoryRequest { 2111 + #[prost(bool, optional, tag = "1")] 2112 + pub shuffle: ::core::option::Option<bool>, 2113 + #[prost(bool, optional, tag = "2")] 2114 + pub recurse: ::core::option::Option<bool>, 2115 + #[prost(int32, optional, tag = "3")] 2116 + pub position: ::core::option::Option<i32>, 2117 + } 2118 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2119 + pub struct PlayDirectoryResponse {} 2120 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2121 + pub struct PlayMusicDirectoryResponse {} 2122 + #[derive(Clone, PartialEq, ::prost::Message)] 2123 + pub struct PlayTrackRequest { 2124 + #[prost(string, tag = "1")] 2125 + pub path: ::prost::alloc::string::String, 2126 + } 2127 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2128 + pub struct PlayTrackResponse {} 2129 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2130 + pub struct PlayLikedTracksRequest { 2131 + #[prost(bool, optional, tag = "1")] 2132 + pub shuffle: ::core::option::Option<bool>, 2133 + #[prost(int32, optional, tag = "2")] 2134 + pub position: ::core::option::Option<i32>, 2135 + } 2136 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2137 + pub struct PlayLikedTracksResponse {} 2138 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2139 + pub struct PlayAllTracksRequest { 2140 + #[prost(bool, optional, tag = "1")] 2141 + pub shuffle: ::core::option::Option<bool>, 2142 + #[prost(int32, optional, tag = "2")] 2143 + pub position: ::core::option::Option<i32>, 2144 + } 2145 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2146 + pub struct PlayAllTracksResponse {} 2147 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2148 + pub struct StreamPlaylistRequest {} 2149 + #[derive(Clone, PartialEq, ::prost::Message)] 2150 + pub struct PlaylistResponse { 2151 + #[prost(int32, tag = "1")] 2152 + pub index: i32, 2153 + #[prost(int32, tag = "2")] 2154 + pub amount: i32, 2155 + #[prost(message, repeated, tag = "3")] 2156 + pub tracks: ::prost::alloc::vec::Vec<CurrentTrackResponse>, 2157 + } 2158 + /// Generated client implementations. 2159 + pub mod playback_service_client { 2160 + #![allow( 2161 + unused_variables, 2162 + dead_code, 2163 + missing_docs, 2164 + clippy::wildcard_imports, 2165 + clippy::let_unit_value 2166 + )] 2167 + use tonic::codegen::http::Uri; 2168 + use tonic::codegen::*; 2169 + #[derive(Debug, Clone)] 2170 + pub struct PlaybackServiceClient<T> { 2171 + inner: tonic::client::Grpc<T>, 2172 + } 2173 + impl PlaybackServiceClient<tonic::transport::Channel> { 2174 + /// Attempt to create a new client by connecting to a given endpoint. 2175 + pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error> 2176 + where 2177 + D: TryInto<tonic::transport::Endpoint>, 2178 + D::Error: Into<StdError>, 2179 + { 2180 + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; 2181 + Ok(Self::new(conn)) 2182 + } 2183 + } 2184 + impl<T> PlaybackServiceClient<T> 2185 + where 2186 + T: tonic::client::GrpcService<tonic::body::BoxBody>, 2187 + T::Error: Into<StdError>, 2188 + T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static, 2189 + <T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send, 2190 + { 2191 + pub fn new(inner: T) -> Self { 2192 + let inner = tonic::client::Grpc::new(inner); 2193 + Self { inner } 2194 + } 2195 + pub fn with_origin(inner: T, origin: Uri) -> Self { 2196 + let inner = tonic::client::Grpc::with_origin(inner, origin); 2197 + Self { inner } 2198 + } 2199 + pub fn with_interceptor<F>( 2200 + inner: T, 2201 + interceptor: F, 2202 + ) -> PlaybackServiceClient<InterceptedService<T, F>> 2203 + where 2204 + F: tonic::service::Interceptor, 2205 + T::ResponseBody: Default, 2206 + T: tonic::codegen::Service< 2207 + http::Request<tonic::body::BoxBody>, 2208 + Response = http::Response< 2209 + <T as tonic::client::GrpcService<tonic::body::BoxBody>>::ResponseBody, 2210 + >, 2211 + >, 2212 + <T as tonic::codegen::Service<http::Request<tonic::body::BoxBody>>>::Error: 2213 + Into<StdError> + std::marker::Send + std::marker::Sync, 2214 + { 2215 + PlaybackServiceClient::new(InterceptedService::new(inner, interceptor)) 2216 + } 2217 + /// Compress requests with the given encoding. 2218 + /// 2219 + /// This requires the server to support it otherwise it might respond with an 2220 + /// error. 2221 + #[must_use] 2222 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 2223 + self.inner = self.inner.send_compressed(encoding); 2224 + self 2225 + } 2226 + /// Enable decompressing responses. 2227 + #[must_use] 2228 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 2229 + self.inner = self.inner.accept_compressed(encoding); 2230 + self 2231 + } 2232 + /// Limits the maximum size of a decoded message. 2233 + /// 2234 + /// Default: `4MB` 2235 + #[must_use] 2236 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 2237 + self.inner = self.inner.max_decoding_message_size(limit); 2238 + self 2239 + } 2240 + /// Limits the maximum size of an encoded message. 2241 + /// 2242 + /// Default: `usize::MAX` 2243 + #[must_use] 2244 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 2245 + self.inner = self.inner.max_encoding_message_size(limit); 2246 + self 2247 + } 2248 + pub async fn play( 2249 + &mut self, 2250 + request: impl tonic::IntoRequest<super::PlayRequest>, 2251 + ) -> std::result::Result<tonic::Response<super::PlayResponse>, tonic::Status> { 2252 + self.inner.ready().await.map_err(|e| { 2253 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2254 + })?; 2255 + let codec = tonic::codec::ProstCodec::default(); 2256 + let path = 2257 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.PlaybackService/Play"); 2258 + let mut req = request.into_request(); 2259 + req.extensions_mut() 2260 + .insert(GrpcMethod::new("rockbox.v1alpha1.PlaybackService", "Play")); 2261 + self.inner.unary(req, path, codec).await 2262 + } 2263 + pub async fn pause( 2264 + &mut self, 2265 + request: impl tonic::IntoRequest<super::PauseRequest>, 2266 + ) -> std::result::Result<tonic::Response<super::PauseResponse>, tonic::Status> { 2267 + self.inner.ready().await.map_err(|e| { 2268 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2269 + })?; 2270 + let codec = tonic::codec::ProstCodec::default(); 2271 + let path = 2272 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.PlaybackService/Pause"); 2273 + let mut req = request.into_request(); 2274 + req.extensions_mut() 2275 + .insert(GrpcMethod::new("rockbox.v1alpha1.PlaybackService", "Pause")); 2276 + self.inner.unary(req, path, codec).await 2277 + } 2278 + pub async fn play_or_pause( 2279 + &mut self, 2280 + request: impl tonic::IntoRequest<super::PlayOrPauseRequest>, 2281 + ) -> std::result::Result<tonic::Response<super::PlayOrPauseResponse>, tonic::Status> 2282 + { 2283 + self.inner.ready().await.map_err(|e| { 2284 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2285 + })?; 2286 + let codec = tonic::codec::ProstCodec::default(); 2287 + let path = http::uri::PathAndQuery::from_static( 2288 + "/rockbox.v1alpha1.PlaybackService/PlayOrPause", 2289 + ); 2290 + let mut req = request.into_request(); 2291 + req.extensions_mut().insert(GrpcMethod::new( 2292 + "rockbox.v1alpha1.PlaybackService", 2293 + "PlayOrPause", 2294 + )); 2295 + self.inner.unary(req, path, codec).await 2296 + } 2297 + pub async fn resume( 2298 + &mut self, 2299 + request: impl tonic::IntoRequest<super::ResumeRequest>, 2300 + ) -> std::result::Result<tonic::Response<super::ResumeResponse>, tonic::Status> { 2301 + self.inner.ready().await.map_err(|e| { 2302 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2303 + })?; 2304 + let codec = tonic::codec::ProstCodec::default(); 2305 + let path = 2306 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.PlaybackService/Resume"); 2307 + let mut req = request.into_request(); 2308 + req.extensions_mut().insert(GrpcMethod::new( 2309 + "rockbox.v1alpha1.PlaybackService", 2310 + "Resume", 2311 + )); 2312 + self.inner.unary(req, path, codec).await 2313 + } 2314 + pub async fn next( 2315 + &mut self, 2316 + request: impl tonic::IntoRequest<super::NextRequest>, 2317 + ) -> std::result::Result<tonic::Response<super::NextResponse>, tonic::Status> { 2318 + self.inner.ready().await.map_err(|e| { 2319 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2320 + })?; 2321 + let codec = tonic::codec::ProstCodec::default(); 2322 + let path = 2323 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.PlaybackService/Next"); 2324 + let mut req = request.into_request(); 2325 + req.extensions_mut() 2326 + .insert(GrpcMethod::new("rockbox.v1alpha1.PlaybackService", "Next")); 2327 + self.inner.unary(req, path, codec).await 2328 + } 2329 + pub async fn previous( 2330 + &mut self, 2331 + request: impl tonic::IntoRequest<super::PreviousRequest>, 2332 + ) -> std::result::Result<tonic::Response<super::PreviousResponse>, tonic::Status> { 2333 + self.inner.ready().await.map_err(|e| { 2334 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2335 + })?; 2336 + let codec = tonic::codec::ProstCodec::default(); 2337 + let path = 2338 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.PlaybackService/Previous"); 2339 + let mut req = request.into_request(); 2340 + req.extensions_mut().insert(GrpcMethod::new( 2341 + "rockbox.v1alpha1.PlaybackService", 2342 + "Previous", 2343 + )); 2344 + self.inner.unary(req, path, codec).await 2345 + } 2346 + pub async fn fast_forward_rewind( 2347 + &mut self, 2348 + request: impl tonic::IntoRequest<super::FastForwardRewindRequest>, 2349 + ) -> std::result::Result<tonic::Response<super::FastForwardRewindResponse>, tonic::Status> 2350 + { 2351 + self.inner.ready().await.map_err(|e| { 2352 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2353 + })?; 2354 + let codec = tonic::codec::ProstCodec::default(); 2355 + let path = http::uri::PathAndQuery::from_static( 2356 + "/rockbox.v1alpha1.PlaybackService/FastForwardRewind", 2357 + ); 2358 + let mut req = request.into_request(); 2359 + req.extensions_mut().insert(GrpcMethod::new( 2360 + "rockbox.v1alpha1.PlaybackService", 2361 + "FastForwardRewind", 2362 + )); 2363 + self.inner.unary(req, path, codec).await 2364 + } 2365 + pub async fn status( 2366 + &mut self, 2367 + request: impl tonic::IntoRequest<super::StatusRequest>, 2368 + ) -> std::result::Result<tonic::Response<super::StatusResponse>, tonic::Status> { 2369 + self.inner.ready().await.map_err(|e| { 2370 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2371 + })?; 2372 + let codec = tonic::codec::ProstCodec::default(); 2373 + let path = 2374 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.PlaybackService/Status"); 2375 + let mut req = request.into_request(); 2376 + req.extensions_mut().insert(GrpcMethod::new( 2377 + "rockbox.v1alpha1.PlaybackService", 2378 + "Status", 2379 + )); 2380 + self.inner.unary(req, path, codec).await 2381 + } 2382 + pub async fn current_track( 2383 + &mut self, 2384 + request: impl tonic::IntoRequest<super::CurrentTrackRequest>, 2385 + ) -> std::result::Result<tonic::Response<super::CurrentTrackResponse>, tonic::Status> 2386 + { 2387 + self.inner.ready().await.map_err(|e| { 2388 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2389 + })?; 2390 + let codec = tonic::codec::ProstCodec::default(); 2391 + let path = http::uri::PathAndQuery::from_static( 2392 + "/rockbox.v1alpha1.PlaybackService/CurrentTrack", 2393 + ); 2394 + let mut req = request.into_request(); 2395 + req.extensions_mut().insert(GrpcMethod::new( 2396 + "rockbox.v1alpha1.PlaybackService", 2397 + "CurrentTrack", 2398 + )); 2399 + self.inner.unary(req, path, codec).await 2400 + } 2401 + pub async fn next_track( 2402 + &mut self, 2403 + request: impl tonic::IntoRequest<super::NextTrackRequest>, 2404 + ) -> std::result::Result<tonic::Response<super::NextTrackResponse>, tonic::Status> { 2405 + self.inner.ready().await.map_err(|e| { 2406 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2407 + })?; 2408 + let codec = tonic::codec::ProstCodec::default(); 2409 + let path = 2410 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.PlaybackService/NextTrack"); 2411 + let mut req = request.into_request(); 2412 + req.extensions_mut().insert(GrpcMethod::new( 2413 + "rockbox.v1alpha1.PlaybackService", 2414 + "NextTrack", 2415 + )); 2416 + self.inner.unary(req, path, codec).await 2417 + } 2418 + pub async fn flush_and_reload_tracks( 2419 + &mut self, 2420 + request: impl tonic::IntoRequest<super::FlushAndReloadTracksRequest>, 2421 + ) -> std::result::Result<tonic::Response<super::FlushAndReloadTracksResponse>, tonic::Status> 2422 + { 2423 + self.inner.ready().await.map_err(|e| { 2424 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2425 + })?; 2426 + let codec = tonic::codec::ProstCodec::default(); 2427 + let path = http::uri::PathAndQuery::from_static( 2428 + "/rockbox.v1alpha1.PlaybackService/FlushAndReloadTracks", 2429 + ); 2430 + let mut req = request.into_request(); 2431 + req.extensions_mut().insert(GrpcMethod::new( 2432 + "rockbox.v1alpha1.PlaybackService", 2433 + "FlushAndReloadTracks", 2434 + )); 2435 + self.inner.unary(req, path, codec).await 2436 + } 2437 + pub async fn get_file_position( 2438 + &mut self, 2439 + request: impl tonic::IntoRequest<super::GetFilePositionRequest>, 2440 + ) -> std::result::Result<tonic::Response<super::GetFilePositionResponse>, tonic::Status> 2441 + { 2442 + self.inner.ready().await.map_err(|e| { 2443 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2444 + })?; 2445 + let codec = tonic::codec::ProstCodec::default(); 2446 + let path = http::uri::PathAndQuery::from_static( 2447 + "/rockbox.v1alpha1.PlaybackService/GetFilePosition", 2448 + ); 2449 + let mut req = request.into_request(); 2450 + req.extensions_mut().insert(GrpcMethod::new( 2451 + "rockbox.v1alpha1.PlaybackService", 2452 + "GetFilePosition", 2453 + )); 2454 + self.inner.unary(req, path, codec).await 2455 + } 2456 + pub async fn hard_stop( 2457 + &mut self, 2458 + request: impl tonic::IntoRequest<super::HardStopRequest>, 2459 + ) -> std::result::Result<tonic::Response<super::HardStopResponse>, tonic::Status> { 2460 + self.inner.ready().await.map_err(|e| { 2461 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2462 + })?; 2463 + let codec = tonic::codec::ProstCodec::default(); 2464 + let path = 2465 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.PlaybackService/HardStop"); 2466 + let mut req = request.into_request(); 2467 + req.extensions_mut().insert(GrpcMethod::new( 2468 + "rockbox.v1alpha1.PlaybackService", 2469 + "HardStop", 2470 + )); 2471 + self.inner.unary(req, path, codec).await 2472 + } 2473 + pub async fn play_album( 2474 + &mut self, 2475 + request: impl tonic::IntoRequest<super::PlayAlbumRequest>, 2476 + ) -> std::result::Result<tonic::Response<super::PlayAlbumResponse>, tonic::Status> { 2477 + self.inner.ready().await.map_err(|e| { 2478 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2479 + })?; 2480 + let codec = tonic::codec::ProstCodec::default(); 2481 + let path = 2482 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.PlaybackService/PlayAlbum"); 2483 + let mut req = request.into_request(); 2484 + req.extensions_mut().insert(GrpcMethod::new( 2485 + "rockbox.v1alpha1.PlaybackService", 2486 + "PlayAlbum", 2487 + )); 2488 + self.inner.unary(req, path, codec).await 2489 + } 2490 + pub async fn play_artist_tracks( 2491 + &mut self, 2492 + request: impl tonic::IntoRequest<super::PlayArtistTracksRequest>, 2493 + ) -> std::result::Result<tonic::Response<super::PlayArtistTracksResponse>, tonic::Status> 2494 + { 2495 + self.inner.ready().await.map_err(|e| { 2496 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2497 + })?; 2498 + let codec = tonic::codec::ProstCodec::default(); 2499 + let path = http::uri::PathAndQuery::from_static( 2500 + "/rockbox.v1alpha1.PlaybackService/PlayArtistTracks", 2501 + ); 2502 + let mut req = request.into_request(); 2503 + req.extensions_mut().insert(GrpcMethod::new( 2504 + "rockbox.v1alpha1.PlaybackService", 2505 + "PlayArtistTracks", 2506 + )); 2507 + self.inner.unary(req, path, codec).await 2508 + } 2509 + pub async fn play_playlist( 2510 + &mut self, 2511 + request: impl tonic::IntoRequest<super::PlayPlaylistRequest>, 2512 + ) -> std::result::Result<tonic::Response<super::PlayPlaylistResponse>, tonic::Status> 2513 + { 2514 + self.inner.ready().await.map_err(|e| { 2515 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2516 + })?; 2517 + let codec = tonic::codec::ProstCodec::default(); 2518 + let path = http::uri::PathAndQuery::from_static( 2519 + "/rockbox.v1alpha1.PlaybackService/PlayPlaylist", 2520 + ); 2521 + let mut req = request.into_request(); 2522 + req.extensions_mut().insert(GrpcMethod::new( 2523 + "rockbox.v1alpha1.PlaybackService", 2524 + "PlayPlaylist", 2525 + )); 2526 + self.inner.unary(req, path, codec).await 2527 + } 2528 + pub async fn play_directory( 2529 + &mut self, 2530 + request: impl tonic::IntoRequest<super::PlayDirectoryRequest>, 2531 + ) -> std::result::Result<tonic::Response<super::PlayDirectoryResponse>, tonic::Status> 2532 + { 2533 + self.inner.ready().await.map_err(|e| { 2534 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2535 + })?; 2536 + let codec = tonic::codec::ProstCodec::default(); 2537 + let path = http::uri::PathAndQuery::from_static( 2538 + "/rockbox.v1alpha1.PlaybackService/PlayDirectory", 2539 + ); 2540 + let mut req = request.into_request(); 2541 + req.extensions_mut().insert(GrpcMethod::new( 2542 + "rockbox.v1alpha1.PlaybackService", 2543 + "PlayDirectory", 2544 + )); 2545 + self.inner.unary(req, path, codec).await 2546 + } 2547 + pub async fn play_music_directory( 2548 + &mut self, 2549 + request: impl tonic::IntoRequest<super::PlayMusicDirectoryRequest>, 2550 + ) -> std::result::Result<tonic::Response<super::PlayMusicDirectoryResponse>, tonic::Status> 2551 + { 2552 + self.inner.ready().await.map_err(|e| { 2553 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2554 + })?; 2555 + let codec = tonic::codec::ProstCodec::default(); 2556 + let path = http::uri::PathAndQuery::from_static( 2557 + "/rockbox.v1alpha1.PlaybackService/PlayMusicDirectory", 2558 + ); 2559 + let mut req = request.into_request(); 2560 + req.extensions_mut().insert(GrpcMethod::new( 2561 + "rockbox.v1alpha1.PlaybackService", 2562 + "PlayMusicDirectory", 2563 + )); 2564 + self.inner.unary(req, path, codec).await 2565 + } 2566 + pub async fn play_track( 2567 + &mut self, 2568 + request: impl tonic::IntoRequest<super::PlayTrackRequest>, 2569 + ) -> std::result::Result<tonic::Response<super::PlayTrackResponse>, tonic::Status> { 2570 + self.inner.ready().await.map_err(|e| { 2571 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2572 + })?; 2573 + let codec = tonic::codec::ProstCodec::default(); 2574 + let path = 2575 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.PlaybackService/PlayTrack"); 2576 + let mut req = request.into_request(); 2577 + req.extensions_mut().insert(GrpcMethod::new( 2578 + "rockbox.v1alpha1.PlaybackService", 2579 + "PlayTrack", 2580 + )); 2581 + self.inner.unary(req, path, codec).await 2582 + } 2583 + pub async fn play_liked_tracks( 2584 + &mut self, 2585 + request: impl tonic::IntoRequest<super::PlayLikedTracksRequest>, 2586 + ) -> std::result::Result<tonic::Response<super::PlayLikedTracksResponse>, tonic::Status> 2587 + { 2588 + self.inner.ready().await.map_err(|e| { 2589 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2590 + })?; 2591 + let codec = tonic::codec::ProstCodec::default(); 2592 + let path = http::uri::PathAndQuery::from_static( 2593 + "/rockbox.v1alpha1.PlaybackService/PlayLikedTracks", 2594 + ); 2595 + let mut req = request.into_request(); 2596 + req.extensions_mut().insert(GrpcMethod::new( 2597 + "rockbox.v1alpha1.PlaybackService", 2598 + "PlayLikedTracks", 2599 + )); 2600 + self.inner.unary(req, path, codec).await 2601 + } 2602 + pub async fn play_all_tracks( 2603 + &mut self, 2604 + request: impl tonic::IntoRequest<super::PlayAllTracksRequest>, 2605 + ) -> std::result::Result<tonic::Response<super::PlayAllTracksResponse>, tonic::Status> 2606 + { 2607 + self.inner.ready().await.map_err(|e| { 2608 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2609 + })?; 2610 + let codec = tonic::codec::ProstCodec::default(); 2611 + let path = http::uri::PathAndQuery::from_static( 2612 + "/rockbox.v1alpha1.PlaybackService/PlayAllTracks", 2613 + ); 2614 + let mut req = request.into_request(); 2615 + req.extensions_mut().insert(GrpcMethod::new( 2616 + "rockbox.v1alpha1.PlaybackService", 2617 + "PlayAllTracks", 2618 + )); 2619 + self.inner.unary(req, path, codec).await 2620 + } 2621 + pub async fn stream_current_track( 2622 + &mut self, 2623 + request: impl tonic::IntoRequest<super::StreamCurrentTrackRequest>, 2624 + ) -> std::result::Result< 2625 + tonic::Response<tonic::codec::Streaming<super::CurrentTrackResponse>>, 2626 + tonic::Status, 2627 + > { 2628 + self.inner.ready().await.map_err(|e| { 2629 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2630 + })?; 2631 + let codec = tonic::codec::ProstCodec::default(); 2632 + let path = http::uri::PathAndQuery::from_static( 2633 + "/rockbox.v1alpha1.PlaybackService/StreamCurrentTrack", 2634 + ); 2635 + let mut req = request.into_request(); 2636 + req.extensions_mut().insert(GrpcMethod::new( 2637 + "rockbox.v1alpha1.PlaybackService", 2638 + "StreamCurrentTrack", 2639 + )); 2640 + self.inner.server_streaming(req, path, codec).await 2641 + } 2642 + pub async fn stream_status( 2643 + &mut self, 2644 + request: impl tonic::IntoRequest<super::StreamStatusRequest>, 2645 + ) -> std::result::Result< 2646 + tonic::Response<tonic::codec::Streaming<super::StatusResponse>>, 2647 + tonic::Status, 2648 + > { 2649 + self.inner.ready().await.map_err(|e| { 2650 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2651 + })?; 2652 + let codec = tonic::codec::ProstCodec::default(); 2653 + let path = http::uri::PathAndQuery::from_static( 2654 + "/rockbox.v1alpha1.PlaybackService/StreamStatus", 2655 + ); 2656 + let mut req = request.into_request(); 2657 + req.extensions_mut().insert(GrpcMethod::new( 2658 + "rockbox.v1alpha1.PlaybackService", 2659 + "StreamStatus", 2660 + )); 2661 + self.inner.server_streaming(req, path, codec).await 2662 + } 2663 + pub async fn stream_playlist( 2664 + &mut self, 2665 + request: impl tonic::IntoRequest<super::StreamPlaylistRequest>, 2666 + ) -> std::result::Result< 2667 + tonic::Response<tonic::codec::Streaming<super::PlaylistResponse>>, 2668 + tonic::Status, 2669 + > { 2670 + self.inner.ready().await.map_err(|e| { 2671 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 2672 + })?; 2673 + let codec = tonic::codec::ProstCodec::default(); 2674 + let path = http::uri::PathAndQuery::from_static( 2675 + "/rockbox.v1alpha1.PlaybackService/StreamPlaylist", 2676 + ); 2677 + let mut req = request.into_request(); 2678 + req.extensions_mut().insert(GrpcMethod::new( 2679 + "rockbox.v1alpha1.PlaybackService", 2680 + "StreamPlaylist", 2681 + )); 2682 + self.inner.server_streaming(req, path, codec).await 2683 + } 2684 + } 2685 + } 2686 + /// Generated server implementations. 2687 + pub mod playback_service_server { 2688 + #![allow( 2689 + unused_variables, 2690 + dead_code, 2691 + missing_docs, 2692 + clippy::wildcard_imports, 2693 + clippy::let_unit_value 2694 + )] 2695 + use tonic::codegen::*; 2696 + /// Generated trait containing gRPC methods that should be implemented for use with PlaybackServiceServer. 2697 + #[async_trait] 2698 + pub trait PlaybackService: std::marker::Send + std::marker::Sync + 'static { 2699 + async fn play( 2700 + &self, 2701 + request: tonic::Request<super::PlayRequest>, 2702 + ) -> std::result::Result<tonic::Response<super::PlayResponse>, tonic::Status>; 2703 + async fn pause( 2704 + &self, 2705 + request: tonic::Request<super::PauseRequest>, 2706 + ) -> std::result::Result<tonic::Response<super::PauseResponse>, tonic::Status>; 2707 + async fn play_or_pause( 2708 + &self, 2709 + request: tonic::Request<super::PlayOrPauseRequest>, 2710 + ) -> std::result::Result<tonic::Response<super::PlayOrPauseResponse>, tonic::Status>; 2711 + async fn resume( 2712 + &self, 2713 + request: tonic::Request<super::ResumeRequest>, 2714 + ) -> std::result::Result<tonic::Response<super::ResumeResponse>, tonic::Status>; 2715 + async fn next( 2716 + &self, 2717 + request: tonic::Request<super::NextRequest>, 2718 + ) -> std::result::Result<tonic::Response<super::NextResponse>, tonic::Status>; 2719 + async fn previous( 2720 + &self, 2721 + request: tonic::Request<super::PreviousRequest>, 2722 + ) -> std::result::Result<tonic::Response<super::PreviousResponse>, tonic::Status>; 2723 + async fn fast_forward_rewind( 2724 + &self, 2725 + request: tonic::Request<super::FastForwardRewindRequest>, 2726 + ) -> std::result::Result<tonic::Response<super::FastForwardRewindResponse>, tonic::Status>; 2727 + async fn status( 2728 + &self, 2729 + request: tonic::Request<super::StatusRequest>, 2730 + ) -> std::result::Result<tonic::Response<super::StatusResponse>, tonic::Status>; 2731 + async fn current_track( 2732 + &self, 2733 + request: tonic::Request<super::CurrentTrackRequest>, 2734 + ) -> std::result::Result<tonic::Response<super::CurrentTrackResponse>, tonic::Status>; 2735 + async fn next_track( 2736 + &self, 2737 + request: tonic::Request<super::NextTrackRequest>, 2738 + ) -> std::result::Result<tonic::Response<super::NextTrackResponse>, tonic::Status>; 2739 + async fn flush_and_reload_tracks( 2740 + &self, 2741 + request: tonic::Request<super::FlushAndReloadTracksRequest>, 2742 + ) -> std::result::Result<tonic::Response<super::FlushAndReloadTracksResponse>, tonic::Status>; 2743 + async fn get_file_position( 2744 + &self, 2745 + request: tonic::Request<super::GetFilePositionRequest>, 2746 + ) -> std::result::Result<tonic::Response<super::GetFilePositionResponse>, tonic::Status>; 2747 + async fn hard_stop( 2748 + &self, 2749 + request: tonic::Request<super::HardStopRequest>, 2750 + ) -> std::result::Result<tonic::Response<super::HardStopResponse>, tonic::Status>; 2751 + async fn play_album( 2752 + &self, 2753 + request: tonic::Request<super::PlayAlbumRequest>, 2754 + ) -> std::result::Result<tonic::Response<super::PlayAlbumResponse>, tonic::Status>; 2755 + async fn play_artist_tracks( 2756 + &self, 2757 + request: tonic::Request<super::PlayArtistTracksRequest>, 2758 + ) -> std::result::Result<tonic::Response<super::PlayArtistTracksResponse>, tonic::Status>; 2759 + async fn play_playlist( 2760 + &self, 2761 + request: tonic::Request<super::PlayPlaylistRequest>, 2762 + ) -> std::result::Result<tonic::Response<super::PlayPlaylistResponse>, tonic::Status>; 2763 + async fn play_directory( 2764 + &self, 2765 + request: tonic::Request<super::PlayDirectoryRequest>, 2766 + ) -> std::result::Result<tonic::Response<super::PlayDirectoryResponse>, tonic::Status>; 2767 + async fn play_music_directory( 2768 + &self, 2769 + request: tonic::Request<super::PlayMusicDirectoryRequest>, 2770 + ) -> std::result::Result<tonic::Response<super::PlayMusicDirectoryResponse>, tonic::Status>; 2771 + async fn play_track( 2772 + &self, 2773 + request: tonic::Request<super::PlayTrackRequest>, 2774 + ) -> std::result::Result<tonic::Response<super::PlayTrackResponse>, tonic::Status>; 2775 + async fn play_liked_tracks( 2776 + &self, 2777 + request: tonic::Request<super::PlayLikedTracksRequest>, 2778 + ) -> std::result::Result<tonic::Response<super::PlayLikedTracksResponse>, tonic::Status>; 2779 + async fn play_all_tracks( 2780 + &self, 2781 + request: tonic::Request<super::PlayAllTracksRequest>, 2782 + ) -> std::result::Result<tonic::Response<super::PlayAllTracksResponse>, tonic::Status>; 2783 + /// Server streaming response type for the StreamCurrentTrack method. 2784 + type StreamCurrentTrackStream: tonic::codegen::tokio_stream::Stream< 2785 + Item = std::result::Result<super::CurrentTrackResponse, tonic::Status>, 2786 + > + std::marker::Send 2787 + + 'static; 2788 + async fn stream_current_track( 2789 + &self, 2790 + request: tonic::Request<super::StreamCurrentTrackRequest>, 2791 + ) -> std::result::Result<tonic::Response<Self::StreamCurrentTrackStream>, tonic::Status>; 2792 + /// Server streaming response type for the StreamStatus method. 2793 + type StreamStatusStream: tonic::codegen::tokio_stream::Stream< 2794 + Item = std::result::Result<super::StatusResponse, tonic::Status>, 2795 + > + std::marker::Send 2796 + + 'static; 2797 + async fn stream_status( 2798 + &self, 2799 + request: tonic::Request<super::StreamStatusRequest>, 2800 + ) -> std::result::Result<tonic::Response<Self::StreamStatusStream>, tonic::Status>; 2801 + /// Server streaming response type for the StreamPlaylist method. 2802 + type StreamPlaylistStream: tonic::codegen::tokio_stream::Stream< 2803 + Item = std::result::Result<super::PlaylistResponse, tonic::Status>, 2804 + > + std::marker::Send 2805 + + 'static; 2806 + async fn stream_playlist( 2807 + &self, 2808 + request: tonic::Request<super::StreamPlaylistRequest>, 2809 + ) -> std::result::Result<tonic::Response<Self::StreamPlaylistStream>, tonic::Status>; 2810 + } 2811 + #[derive(Debug)] 2812 + pub struct PlaybackServiceServer<T> { 2813 + inner: Arc<T>, 2814 + accept_compression_encodings: EnabledCompressionEncodings, 2815 + send_compression_encodings: EnabledCompressionEncodings, 2816 + max_decoding_message_size: Option<usize>, 2817 + max_encoding_message_size: Option<usize>, 2818 + } 2819 + impl<T> PlaybackServiceServer<T> { 2820 + pub fn new(inner: T) -> Self { 2821 + Self::from_arc(Arc::new(inner)) 2822 + } 2823 + pub fn from_arc(inner: Arc<T>) -> Self { 2824 + Self { 2825 + inner, 2826 + accept_compression_encodings: Default::default(), 2827 + send_compression_encodings: Default::default(), 2828 + max_decoding_message_size: None, 2829 + max_encoding_message_size: None, 2830 + } 2831 + } 2832 + pub fn with_interceptor<F>(inner: T, interceptor: F) -> InterceptedService<Self, F> 2833 + where 2834 + F: tonic::service::Interceptor, 2835 + { 2836 + InterceptedService::new(Self::new(inner), interceptor) 2837 + } 2838 + /// Enable decompressing requests with the given encoding. 2839 + #[must_use] 2840 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 2841 + self.accept_compression_encodings.enable(encoding); 2842 + self 2843 + } 2844 + /// Compress responses with the given encoding, if the client supports it. 2845 + #[must_use] 2846 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 2847 + self.send_compression_encodings.enable(encoding); 2848 + self 2849 + } 2850 + /// Limits the maximum size of a decoded message. 2851 + /// 2852 + /// Default: `4MB` 2853 + #[must_use] 2854 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 2855 + self.max_decoding_message_size = Some(limit); 2856 + self 2857 + } 2858 + /// Limits the maximum size of an encoded message. 2859 + /// 2860 + /// Default: `usize::MAX` 2861 + #[must_use] 2862 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 2863 + self.max_encoding_message_size = Some(limit); 2864 + self 2865 + } 2866 + } 2867 + impl<T, B> tonic::codegen::Service<http::Request<B>> for PlaybackServiceServer<T> 2868 + where 2869 + T: PlaybackService, 2870 + B: Body + std::marker::Send + 'static, 2871 + B::Error: Into<StdError> + std::marker::Send + 'static, 2872 + { 2873 + type Response = http::Response<tonic::body::BoxBody>; 2874 + type Error = std::convert::Infallible; 2875 + type Future = BoxFuture<Self::Response, Self::Error>; 2876 + fn poll_ready( 2877 + &mut self, 2878 + _cx: &mut Context<'_>, 2879 + ) -> Poll<std::result::Result<(), Self::Error>> { 2880 + Poll::Ready(Ok(())) 2881 + } 2882 + fn call(&mut self, req: http::Request<B>) -> Self::Future { 2883 + match req.uri().path() { 2884 + "/rockbox.v1alpha1.PlaybackService/Play" => { 2885 + #[allow(non_camel_case_types)] 2886 + struct PlaySvc<T: PlaybackService>(pub Arc<T>); 2887 + impl<T: PlaybackService> tonic::server::UnaryService<super::PlayRequest> for PlaySvc<T> { 2888 + type Response = super::PlayResponse; 2889 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 2890 + fn call( 2891 + &mut self, 2892 + request: tonic::Request<super::PlayRequest>, 2893 + ) -> Self::Future { 2894 + let inner = Arc::clone(&self.0); 2895 + let fut = 2896 + async move { <T as PlaybackService>::play(&inner, request).await }; 2897 + Box::pin(fut) 2898 + } 2899 + } 2900 + let accept_compression_encodings = self.accept_compression_encodings; 2901 + let send_compression_encodings = self.send_compression_encodings; 2902 + let max_decoding_message_size = self.max_decoding_message_size; 2903 + let max_encoding_message_size = self.max_encoding_message_size; 2904 + let inner = self.inner.clone(); 2905 + let fut = async move { 2906 + let method = PlaySvc(inner); 2907 + let codec = tonic::codec::ProstCodec::default(); 2908 + let mut grpc = tonic::server::Grpc::new(codec) 2909 + .apply_compression_config( 2910 + accept_compression_encodings, 2911 + send_compression_encodings, 2912 + ) 2913 + .apply_max_message_size_config( 2914 + max_decoding_message_size, 2915 + max_encoding_message_size, 2916 + ); 2917 + let res = grpc.unary(method, req).await; 2918 + Ok(res) 2919 + }; 2920 + Box::pin(fut) 2921 + } 2922 + "/rockbox.v1alpha1.PlaybackService/Pause" => { 2923 + #[allow(non_camel_case_types)] 2924 + struct PauseSvc<T: PlaybackService>(pub Arc<T>); 2925 + impl<T: PlaybackService> tonic::server::UnaryService<super::PauseRequest> for PauseSvc<T> { 2926 + type Response = super::PauseResponse; 2927 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 2928 + fn call( 2929 + &mut self, 2930 + request: tonic::Request<super::PauseRequest>, 2931 + ) -> Self::Future { 2932 + let inner = Arc::clone(&self.0); 2933 + let fut = 2934 + async move { <T as PlaybackService>::pause(&inner, request).await }; 2935 + Box::pin(fut) 2936 + } 2937 + } 2938 + let accept_compression_encodings = self.accept_compression_encodings; 2939 + let send_compression_encodings = self.send_compression_encodings; 2940 + let max_decoding_message_size = self.max_decoding_message_size; 2941 + let max_encoding_message_size = self.max_encoding_message_size; 2942 + let inner = self.inner.clone(); 2943 + let fut = async move { 2944 + let method = PauseSvc(inner); 2945 + let codec = tonic::codec::ProstCodec::default(); 2946 + let mut grpc = tonic::server::Grpc::new(codec) 2947 + .apply_compression_config( 2948 + accept_compression_encodings, 2949 + send_compression_encodings, 2950 + ) 2951 + .apply_max_message_size_config( 2952 + max_decoding_message_size, 2953 + max_encoding_message_size, 2954 + ); 2955 + let res = grpc.unary(method, req).await; 2956 + Ok(res) 2957 + }; 2958 + Box::pin(fut) 2959 + } 2960 + "/rockbox.v1alpha1.PlaybackService/PlayOrPause" => { 2961 + #[allow(non_camel_case_types)] 2962 + struct PlayOrPauseSvc<T: PlaybackService>(pub Arc<T>); 2963 + impl<T: PlaybackService> tonic::server::UnaryService<super::PlayOrPauseRequest> 2964 + for PlayOrPauseSvc<T> 2965 + { 2966 + type Response = super::PlayOrPauseResponse; 2967 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 2968 + fn call( 2969 + &mut self, 2970 + request: tonic::Request<super::PlayOrPauseRequest>, 2971 + ) -> Self::Future { 2972 + let inner = Arc::clone(&self.0); 2973 + let fut = async move { 2974 + <T as PlaybackService>::play_or_pause(&inner, request).await 2975 + }; 2976 + Box::pin(fut) 2977 + } 2978 + } 2979 + let accept_compression_encodings = self.accept_compression_encodings; 2980 + let send_compression_encodings = self.send_compression_encodings; 2981 + let max_decoding_message_size = self.max_decoding_message_size; 2982 + let max_encoding_message_size = self.max_encoding_message_size; 2983 + let inner = self.inner.clone(); 2984 + let fut = async move { 2985 + let method = PlayOrPauseSvc(inner); 2986 + let codec = tonic::codec::ProstCodec::default(); 2987 + let mut grpc = tonic::server::Grpc::new(codec) 2988 + .apply_compression_config( 2989 + accept_compression_encodings, 2990 + send_compression_encodings, 2991 + ) 2992 + .apply_max_message_size_config( 2993 + max_decoding_message_size, 2994 + max_encoding_message_size, 2995 + ); 2996 + let res = grpc.unary(method, req).await; 2997 + Ok(res) 2998 + }; 2999 + Box::pin(fut) 3000 + } 3001 + "/rockbox.v1alpha1.PlaybackService/Resume" => { 3002 + #[allow(non_camel_case_types)] 3003 + struct ResumeSvc<T: PlaybackService>(pub Arc<T>); 3004 + impl<T: PlaybackService> tonic::server::UnaryService<super::ResumeRequest> for ResumeSvc<T> { 3005 + type Response = super::ResumeResponse; 3006 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 3007 + fn call( 3008 + &mut self, 3009 + request: tonic::Request<super::ResumeRequest>, 3010 + ) -> Self::Future { 3011 + let inner = Arc::clone(&self.0); 3012 + let fut = async move { 3013 + <T as PlaybackService>::resume(&inner, request).await 3014 + }; 3015 + Box::pin(fut) 3016 + } 3017 + } 3018 + let accept_compression_encodings = self.accept_compression_encodings; 3019 + let send_compression_encodings = self.send_compression_encodings; 3020 + let max_decoding_message_size = self.max_decoding_message_size; 3021 + let max_encoding_message_size = self.max_encoding_message_size; 3022 + let inner = self.inner.clone(); 3023 + let fut = async move { 3024 + let method = ResumeSvc(inner); 3025 + let codec = tonic::codec::ProstCodec::default(); 3026 + let mut grpc = tonic::server::Grpc::new(codec) 3027 + .apply_compression_config( 3028 + accept_compression_encodings, 3029 + send_compression_encodings, 3030 + ) 3031 + .apply_max_message_size_config( 3032 + max_decoding_message_size, 3033 + max_encoding_message_size, 3034 + ); 3035 + let res = grpc.unary(method, req).await; 3036 + Ok(res) 3037 + }; 3038 + Box::pin(fut) 3039 + } 3040 + "/rockbox.v1alpha1.PlaybackService/Next" => { 3041 + #[allow(non_camel_case_types)] 3042 + struct NextSvc<T: PlaybackService>(pub Arc<T>); 3043 + impl<T: PlaybackService> tonic::server::UnaryService<super::NextRequest> for NextSvc<T> { 3044 + type Response = super::NextResponse; 3045 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 3046 + fn call( 3047 + &mut self, 3048 + request: tonic::Request<super::NextRequest>, 3049 + ) -> Self::Future { 3050 + let inner = Arc::clone(&self.0); 3051 + let fut = 3052 + async move { <T as PlaybackService>::next(&inner, request).await }; 3053 + Box::pin(fut) 3054 + } 3055 + } 3056 + let accept_compression_encodings = self.accept_compression_encodings; 3057 + let send_compression_encodings = self.send_compression_encodings; 3058 + let max_decoding_message_size = self.max_decoding_message_size; 3059 + let max_encoding_message_size = self.max_encoding_message_size; 3060 + let inner = self.inner.clone(); 3061 + let fut = async move { 3062 + let method = NextSvc(inner); 3063 + let codec = tonic::codec::ProstCodec::default(); 3064 + let mut grpc = tonic::server::Grpc::new(codec) 3065 + .apply_compression_config( 3066 + accept_compression_encodings, 3067 + send_compression_encodings, 3068 + ) 3069 + .apply_max_message_size_config( 3070 + max_decoding_message_size, 3071 + max_encoding_message_size, 3072 + ); 3073 + let res = grpc.unary(method, req).await; 3074 + Ok(res) 3075 + }; 3076 + Box::pin(fut) 3077 + } 3078 + "/rockbox.v1alpha1.PlaybackService/Previous" => { 3079 + #[allow(non_camel_case_types)] 3080 + struct PreviousSvc<T: PlaybackService>(pub Arc<T>); 3081 + impl<T: PlaybackService> tonic::server::UnaryService<super::PreviousRequest> for PreviousSvc<T> { 3082 + type Response = super::PreviousResponse; 3083 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 3084 + fn call( 3085 + &mut self, 3086 + request: tonic::Request<super::PreviousRequest>, 3087 + ) -> Self::Future { 3088 + let inner = Arc::clone(&self.0); 3089 + let fut = async move { 3090 + <T as PlaybackService>::previous(&inner, request).await 3091 + }; 3092 + Box::pin(fut) 3093 + } 3094 + } 3095 + let accept_compression_encodings = self.accept_compression_encodings; 3096 + let send_compression_encodings = self.send_compression_encodings; 3097 + let max_decoding_message_size = self.max_decoding_message_size; 3098 + let max_encoding_message_size = self.max_encoding_message_size; 3099 + let inner = self.inner.clone(); 3100 + let fut = async move { 3101 + let method = PreviousSvc(inner); 3102 + let codec = tonic::codec::ProstCodec::default(); 3103 + let mut grpc = tonic::server::Grpc::new(codec) 3104 + .apply_compression_config( 3105 + accept_compression_encodings, 3106 + send_compression_encodings, 3107 + ) 3108 + .apply_max_message_size_config( 3109 + max_decoding_message_size, 3110 + max_encoding_message_size, 3111 + ); 3112 + let res = grpc.unary(method, req).await; 3113 + Ok(res) 3114 + }; 3115 + Box::pin(fut) 3116 + } 3117 + "/rockbox.v1alpha1.PlaybackService/FastForwardRewind" => { 3118 + #[allow(non_camel_case_types)] 3119 + struct FastForwardRewindSvc<T: PlaybackService>(pub Arc<T>); 3120 + impl<T: PlaybackService> 3121 + tonic::server::UnaryService<super::FastForwardRewindRequest> 3122 + for FastForwardRewindSvc<T> 3123 + { 3124 + type Response = super::FastForwardRewindResponse; 3125 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 3126 + fn call( 3127 + &mut self, 3128 + request: tonic::Request<super::FastForwardRewindRequest>, 3129 + ) -> Self::Future { 3130 + let inner = Arc::clone(&self.0); 3131 + let fut = async move { 3132 + <T as PlaybackService>::fast_forward_rewind(&inner, request).await 3133 + }; 3134 + Box::pin(fut) 3135 + } 3136 + } 3137 + let accept_compression_encodings = self.accept_compression_encodings; 3138 + let send_compression_encodings = self.send_compression_encodings; 3139 + let max_decoding_message_size = self.max_decoding_message_size; 3140 + let max_encoding_message_size = self.max_encoding_message_size; 3141 + let inner = self.inner.clone(); 3142 + let fut = async move { 3143 + let method = FastForwardRewindSvc(inner); 3144 + let codec = tonic::codec::ProstCodec::default(); 3145 + let mut grpc = tonic::server::Grpc::new(codec) 3146 + .apply_compression_config( 3147 + accept_compression_encodings, 3148 + send_compression_encodings, 3149 + ) 3150 + .apply_max_message_size_config( 3151 + max_decoding_message_size, 3152 + max_encoding_message_size, 3153 + ); 3154 + let res = grpc.unary(method, req).await; 3155 + Ok(res) 3156 + }; 3157 + Box::pin(fut) 3158 + } 3159 + "/rockbox.v1alpha1.PlaybackService/Status" => { 3160 + #[allow(non_camel_case_types)] 3161 + struct StatusSvc<T: PlaybackService>(pub Arc<T>); 3162 + impl<T: PlaybackService> tonic::server::UnaryService<super::StatusRequest> for StatusSvc<T> { 3163 + type Response = super::StatusResponse; 3164 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 3165 + fn call( 3166 + &mut self, 3167 + request: tonic::Request<super::StatusRequest>, 3168 + ) -> Self::Future { 3169 + let inner = Arc::clone(&self.0); 3170 + let fut = async move { 3171 + <T as PlaybackService>::status(&inner, request).await 3172 + }; 3173 + Box::pin(fut) 3174 + } 3175 + } 3176 + let accept_compression_encodings = self.accept_compression_encodings; 3177 + let send_compression_encodings = self.send_compression_encodings; 3178 + let max_decoding_message_size = self.max_decoding_message_size; 3179 + let max_encoding_message_size = self.max_encoding_message_size; 3180 + let inner = self.inner.clone(); 3181 + let fut = async move { 3182 + let method = StatusSvc(inner); 3183 + let codec = tonic::codec::ProstCodec::default(); 3184 + let mut grpc = tonic::server::Grpc::new(codec) 3185 + .apply_compression_config( 3186 + accept_compression_encodings, 3187 + send_compression_encodings, 3188 + ) 3189 + .apply_max_message_size_config( 3190 + max_decoding_message_size, 3191 + max_encoding_message_size, 3192 + ); 3193 + let res = grpc.unary(method, req).await; 3194 + Ok(res) 3195 + }; 3196 + Box::pin(fut) 3197 + } 3198 + "/rockbox.v1alpha1.PlaybackService/CurrentTrack" => { 3199 + #[allow(non_camel_case_types)] 3200 + struct CurrentTrackSvc<T: PlaybackService>(pub Arc<T>); 3201 + impl<T: PlaybackService> tonic::server::UnaryService<super::CurrentTrackRequest> 3202 + for CurrentTrackSvc<T> 3203 + { 3204 + type Response = super::CurrentTrackResponse; 3205 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 3206 + fn call( 3207 + &mut self, 3208 + request: tonic::Request<super::CurrentTrackRequest>, 3209 + ) -> Self::Future { 3210 + let inner = Arc::clone(&self.0); 3211 + let fut = async move { 3212 + <T as PlaybackService>::current_track(&inner, request).await 3213 + }; 3214 + Box::pin(fut) 3215 + } 3216 + } 3217 + let accept_compression_encodings = self.accept_compression_encodings; 3218 + let send_compression_encodings = self.send_compression_encodings; 3219 + let max_decoding_message_size = self.max_decoding_message_size; 3220 + let max_encoding_message_size = self.max_encoding_message_size; 3221 + let inner = self.inner.clone(); 3222 + let fut = async move { 3223 + let method = CurrentTrackSvc(inner); 3224 + let codec = tonic::codec::ProstCodec::default(); 3225 + let mut grpc = tonic::server::Grpc::new(codec) 3226 + .apply_compression_config( 3227 + accept_compression_encodings, 3228 + send_compression_encodings, 3229 + ) 3230 + .apply_max_message_size_config( 3231 + max_decoding_message_size, 3232 + max_encoding_message_size, 3233 + ); 3234 + let res = grpc.unary(method, req).await; 3235 + Ok(res) 3236 + }; 3237 + Box::pin(fut) 3238 + } 3239 + "/rockbox.v1alpha1.PlaybackService/NextTrack" => { 3240 + #[allow(non_camel_case_types)] 3241 + struct NextTrackSvc<T: PlaybackService>(pub Arc<T>); 3242 + impl<T: PlaybackService> tonic::server::UnaryService<super::NextTrackRequest> for NextTrackSvc<T> { 3243 + type Response = super::NextTrackResponse; 3244 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 3245 + fn call( 3246 + &mut self, 3247 + request: tonic::Request<super::NextTrackRequest>, 3248 + ) -> Self::Future { 3249 + let inner = Arc::clone(&self.0); 3250 + let fut = async move { 3251 + <T as PlaybackService>::next_track(&inner, request).await 3252 + }; 3253 + Box::pin(fut) 3254 + } 3255 + } 3256 + let accept_compression_encodings = self.accept_compression_encodings; 3257 + let send_compression_encodings = self.send_compression_encodings; 3258 + let max_decoding_message_size = self.max_decoding_message_size; 3259 + let max_encoding_message_size = self.max_encoding_message_size; 3260 + let inner = self.inner.clone(); 3261 + let fut = async move { 3262 + let method = NextTrackSvc(inner); 3263 + let codec = tonic::codec::ProstCodec::default(); 3264 + let mut grpc = tonic::server::Grpc::new(codec) 3265 + .apply_compression_config( 3266 + accept_compression_encodings, 3267 + send_compression_encodings, 3268 + ) 3269 + .apply_max_message_size_config( 3270 + max_decoding_message_size, 3271 + max_encoding_message_size, 3272 + ); 3273 + let res = grpc.unary(method, req).await; 3274 + Ok(res) 3275 + }; 3276 + Box::pin(fut) 3277 + } 3278 + "/rockbox.v1alpha1.PlaybackService/FlushAndReloadTracks" => { 3279 + #[allow(non_camel_case_types)] 3280 + struct FlushAndReloadTracksSvc<T: PlaybackService>(pub Arc<T>); 3281 + impl<T: PlaybackService> 3282 + tonic::server::UnaryService<super::FlushAndReloadTracksRequest> 3283 + for FlushAndReloadTracksSvc<T> 3284 + { 3285 + type Response = super::FlushAndReloadTracksResponse; 3286 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 3287 + fn call( 3288 + &mut self, 3289 + request: tonic::Request<super::FlushAndReloadTracksRequest>, 3290 + ) -> Self::Future { 3291 + let inner = Arc::clone(&self.0); 3292 + let fut = async move { 3293 + <T as PlaybackService>::flush_and_reload_tracks(&inner, request) 3294 + .await 3295 + }; 3296 + Box::pin(fut) 3297 + } 3298 + } 3299 + let accept_compression_encodings = self.accept_compression_encodings; 3300 + let send_compression_encodings = self.send_compression_encodings; 3301 + let max_decoding_message_size = self.max_decoding_message_size; 3302 + let max_encoding_message_size = self.max_encoding_message_size; 3303 + let inner = self.inner.clone(); 3304 + let fut = async move { 3305 + let method = FlushAndReloadTracksSvc(inner); 3306 + let codec = tonic::codec::ProstCodec::default(); 3307 + let mut grpc = tonic::server::Grpc::new(codec) 3308 + .apply_compression_config( 3309 + accept_compression_encodings, 3310 + send_compression_encodings, 3311 + ) 3312 + .apply_max_message_size_config( 3313 + max_decoding_message_size, 3314 + max_encoding_message_size, 3315 + ); 3316 + let res = grpc.unary(method, req).await; 3317 + Ok(res) 3318 + }; 3319 + Box::pin(fut) 3320 + } 3321 + "/rockbox.v1alpha1.PlaybackService/GetFilePosition" => { 3322 + #[allow(non_camel_case_types)] 3323 + struct GetFilePositionSvc<T: PlaybackService>(pub Arc<T>); 3324 + impl<T: PlaybackService> 3325 + tonic::server::UnaryService<super::GetFilePositionRequest> 3326 + for GetFilePositionSvc<T> 3327 + { 3328 + type Response = super::GetFilePositionResponse; 3329 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 3330 + fn call( 3331 + &mut self, 3332 + request: tonic::Request<super::GetFilePositionRequest>, 3333 + ) -> Self::Future { 3334 + let inner = Arc::clone(&self.0); 3335 + let fut = async move { 3336 + <T as PlaybackService>::get_file_position(&inner, request).await 3337 + }; 3338 + Box::pin(fut) 3339 + } 3340 + } 3341 + let accept_compression_encodings = self.accept_compression_encodings; 3342 + let send_compression_encodings = self.send_compression_encodings; 3343 + let max_decoding_message_size = self.max_decoding_message_size; 3344 + let max_encoding_message_size = self.max_encoding_message_size; 3345 + let inner = self.inner.clone(); 3346 + let fut = async move { 3347 + let method = GetFilePositionSvc(inner); 3348 + let codec = tonic::codec::ProstCodec::default(); 3349 + let mut grpc = tonic::server::Grpc::new(codec) 3350 + .apply_compression_config( 3351 + accept_compression_encodings, 3352 + send_compression_encodings, 3353 + ) 3354 + .apply_max_message_size_config( 3355 + max_decoding_message_size, 3356 + max_encoding_message_size, 3357 + ); 3358 + let res = grpc.unary(method, req).await; 3359 + Ok(res) 3360 + }; 3361 + Box::pin(fut) 3362 + } 3363 + "/rockbox.v1alpha1.PlaybackService/HardStop" => { 3364 + #[allow(non_camel_case_types)] 3365 + struct HardStopSvc<T: PlaybackService>(pub Arc<T>); 3366 + impl<T: PlaybackService> tonic::server::UnaryService<super::HardStopRequest> for HardStopSvc<T> { 3367 + type Response = super::HardStopResponse; 3368 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 3369 + fn call( 3370 + &mut self, 3371 + request: tonic::Request<super::HardStopRequest>, 3372 + ) -> Self::Future { 3373 + let inner = Arc::clone(&self.0); 3374 + let fut = async move { 3375 + <T as PlaybackService>::hard_stop(&inner, request).await 3376 + }; 3377 + Box::pin(fut) 3378 + } 3379 + } 3380 + let accept_compression_encodings = self.accept_compression_encodings; 3381 + let send_compression_encodings = self.send_compression_encodings; 3382 + let max_decoding_message_size = self.max_decoding_message_size; 3383 + let max_encoding_message_size = self.max_encoding_message_size; 3384 + let inner = self.inner.clone(); 3385 + let fut = async move { 3386 + let method = HardStopSvc(inner); 3387 + let codec = tonic::codec::ProstCodec::default(); 3388 + let mut grpc = tonic::server::Grpc::new(codec) 3389 + .apply_compression_config( 3390 + accept_compression_encodings, 3391 + send_compression_encodings, 3392 + ) 3393 + .apply_max_message_size_config( 3394 + max_decoding_message_size, 3395 + max_encoding_message_size, 3396 + ); 3397 + let res = grpc.unary(method, req).await; 3398 + Ok(res) 3399 + }; 3400 + Box::pin(fut) 3401 + } 3402 + "/rockbox.v1alpha1.PlaybackService/PlayAlbum" => { 3403 + #[allow(non_camel_case_types)] 3404 + struct PlayAlbumSvc<T: PlaybackService>(pub Arc<T>); 3405 + impl<T: PlaybackService> tonic::server::UnaryService<super::PlayAlbumRequest> for PlayAlbumSvc<T> { 3406 + type Response = super::PlayAlbumResponse; 3407 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 3408 + fn call( 3409 + &mut self, 3410 + request: tonic::Request<super::PlayAlbumRequest>, 3411 + ) -> Self::Future { 3412 + let inner = Arc::clone(&self.0); 3413 + let fut = async move { 3414 + <T as PlaybackService>::play_album(&inner, request).await 3415 + }; 3416 + Box::pin(fut) 3417 + } 3418 + } 3419 + let accept_compression_encodings = self.accept_compression_encodings; 3420 + let send_compression_encodings = self.send_compression_encodings; 3421 + let max_decoding_message_size = self.max_decoding_message_size; 3422 + let max_encoding_message_size = self.max_encoding_message_size; 3423 + let inner = self.inner.clone(); 3424 + let fut = async move { 3425 + let method = PlayAlbumSvc(inner); 3426 + let codec = tonic::codec::ProstCodec::default(); 3427 + let mut grpc = tonic::server::Grpc::new(codec) 3428 + .apply_compression_config( 3429 + accept_compression_encodings, 3430 + send_compression_encodings, 3431 + ) 3432 + .apply_max_message_size_config( 3433 + max_decoding_message_size, 3434 + max_encoding_message_size, 3435 + ); 3436 + let res = grpc.unary(method, req).await; 3437 + Ok(res) 3438 + }; 3439 + Box::pin(fut) 3440 + } 3441 + "/rockbox.v1alpha1.PlaybackService/PlayArtistTracks" => { 3442 + #[allow(non_camel_case_types)] 3443 + struct PlayArtistTracksSvc<T: PlaybackService>(pub Arc<T>); 3444 + impl<T: PlaybackService> 3445 + tonic::server::UnaryService<super::PlayArtistTracksRequest> 3446 + for PlayArtistTracksSvc<T> 3447 + { 3448 + type Response = super::PlayArtistTracksResponse; 3449 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 3450 + fn call( 3451 + &mut self, 3452 + request: tonic::Request<super::PlayArtistTracksRequest>, 3453 + ) -> Self::Future { 3454 + let inner = Arc::clone(&self.0); 3455 + let fut = async move { 3456 + <T as PlaybackService>::play_artist_tracks(&inner, request).await 3457 + }; 3458 + Box::pin(fut) 3459 + } 3460 + } 3461 + let accept_compression_encodings = self.accept_compression_encodings; 3462 + let send_compression_encodings = self.send_compression_encodings; 3463 + let max_decoding_message_size = self.max_decoding_message_size; 3464 + let max_encoding_message_size = self.max_encoding_message_size; 3465 + let inner = self.inner.clone(); 3466 + let fut = async move { 3467 + let method = PlayArtistTracksSvc(inner); 3468 + let codec = tonic::codec::ProstCodec::default(); 3469 + let mut grpc = tonic::server::Grpc::new(codec) 3470 + .apply_compression_config( 3471 + accept_compression_encodings, 3472 + send_compression_encodings, 3473 + ) 3474 + .apply_max_message_size_config( 3475 + max_decoding_message_size, 3476 + max_encoding_message_size, 3477 + ); 3478 + let res = grpc.unary(method, req).await; 3479 + Ok(res) 3480 + }; 3481 + Box::pin(fut) 3482 + } 3483 + "/rockbox.v1alpha1.PlaybackService/PlayPlaylist" => { 3484 + #[allow(non_camel_case_types)] 3485 + struct PlayPlaylistSvc<T: PlaybackService>(pub Arc<T>); 3486 + impl<T: PlaybackService> tonic::server::UnaryService<super::PlayPlaylistRequest> 3487 + for PlayPlaylistSvc<T> 3488 + { 3489 + type Response = super::PlayPlaylistResponse; 3490 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 3491 + fn call( 3492 + &mut self, 3493 + request: tonic::Request<super::PlayPlaylistRequest>, 3494 + ) -> Self::Future { 3495 + let inner = Arc::clone(&self.0); 3496 + let fut = async move { 3497 + <T as PlaybackService>::play_playlist(&inner, request).await 3498 + }; 3499 + Box::pin(fut) 3500 + } 3501 + } 3502 + let accept_compression_encodings = self.accept_compression_encodings; 3503 + let send_compression_encodings = self.send_compression_encodings; 3504 + let max_decoding_message_size = self.max_decoding_message_size; 3505 + let max_encoding_message_size = self.max_encoding_message_size; 3506 + let inner = self.inner.clone(); 3507 + let fut = async move { 3508 + let method = PlayPlaylistSvc(inner); 3509 + let codec = tonic::codec::ProstCodec::default(); 3510 + let mut grpc = tonic::server::Grpc::new(codec) 3511 + .apply_compression_config( 3512 + accept_compression_encodings, 3513 + send_compression_encodings, 3514 + ) 3515 + .apply_max_message_size_config( 3516 + max_decoding_message_size, 3517 + max_encoding_message_size, 3518 + ); 3519 + let res = grpc.unary(method, req).await; 3520 + Ok(res) 3521 + }; 3522 + Box::pin(fut) 3523 + } 3524 + "/rockbox.v1alpha1.PlaybackService/PlayDirectory" => { 3525 + #[allow(non_camel_case_types)] 3526 + struct PlayDirectorySvc<T: PlaybackService>(pub Arc<T>); 3527 + impl<T: PlaybackService> 3528 + tonic::server::UnaryService<super::PlayDirectoryRequest> 3529 + for PlayDirectorySvc<T> 3530 + { 3531 + type Response = super::PlayDirectoryResponse; 3532 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 3533 + fn call( 3534 + &mut self, 3535 + request: tonic::Request<super::PlayDirectoryRequest>, 3536 + ) -> Self::Future { 3537 + let inner = Arc::clone(&self.0); 3538 + let fut = async move { 3539 + <T as PlaybackService>::play_directory(&inner, request).await 3540 + }; 3541 + Box::pin(fut) 3542 + } 3543 + } 3544 + let accept_compression_encodings = self.accept_compression_encodings; 3545 + let send_compression_encodings = self.send_compression_encodings; 3546 + let max_decoding_message_size = self.max_decoding_message_size; 3547 + let max_encoding_message_size = self.max_encoding_message_size; 3548 + let inner = self.inner.clone(); 3549 + let fut = async move { 3550 + let method = PlayDirectorySvc(inner); 3551 + let codec = tonic::codec::ProstCodec::default(); 3552 + let mut grpc = tonic::server::Grpc::new(codec) 3553 + .apply_compression_config( 3554 + accept_compression_encodings, 3555 + send_compression_encodings, 3556 + ) 3557 + .apply_max_message_size_config( 3558 + max_decoding_message_size, 3559 + max_encoding_message_size, 3560 + ); 3561 + let res = grpc.unary(method, req).await; 3562 + Ok(res) 3563 + }; 3564 + Box::pin(fut) 3565 + } 3566 + "/rockbox.v1alpha1.PlaybackService/PlayMusicDirectory" => { 3567 + #[allow(non_camel_case_types)] 3568 + struct PlayMusicDirectorySvc<T: PlaybackService>(pub Arc<T>); 3569 + impl<T: PlaybackService> 3570 + tonic::server::UnaryService<super::PlayMusicDirectoryRequest> 3571 + for PlayMusicDirectorySvc<T> 3572 + { 3573 + type Response = super::PlayMusicDirectoryResponse; 3574 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 3575 + fn call( 3576 + &mut self, 3577 + request: tonic::Request<super::PlayMusicDirectoryRequest>, 3578 + ) -> Self::Future { 3579 + let inner = Arc::clone(&self.0); 3580 + let fut = async move { 3581 + <T as PlaybackService>::play_music_directory(&inner, request).await 3582 + }; 3583 + Box::pin(fut) 3584 + } 3585 + } 3586 + let accept_compression_encodings = self.accept_compression_encodings; 3587 + let send_compression_encodings = self.send_compression_encodings; 3588 + let max_decoding_message_size = self.max_decoding_message_size; 3589 + let max_encoding_message_size = self.max_encoding_message_size; 3590 + let inner = self.inner.clone(); 3591 + let fut = async move { 3592 + let method = PlayMusicDirectorySvc(inner); 3593 + let codec = tonic::codec::ProstCodec::default(); 3594 + let mut grpc = tonic::server::Grpc::new(codec) 3595 + .apply_compression_config( 3596 + accept_compression_encodings, 3597 + send_compression_encodings, 3598 + ) 3599 + .apply_max_message_size_config( 3600 + max_decoding_message_size, 3601 + max_encoding_message_size, 3602 + ); 3603 + let res = grpc.unary(method, req).await; 3604 + Ok(res) 3605 + }; 3606 + Box::pin(fut) 3607 + } 3608 + "/rockbox.v1alpha1.PlaybackService/PlayTrack" => { 3609 + #[allow(non_camel_case_types)] 3610 + struct PlayTrackSvc<T: PlaybackService>(pub Arc<T>); 3611 + impl<T: PlaybackService> tonic::server::UnaryService<super::PlayTrackRequest> for PlayTrackSvc<T> { 3612 + type Response = super::PlayTrackResponse; 3613 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 3614 + fn call( 3615 + &mut self, 3616 + request: tonic::Request<super::PlayTrackRequest>, 3617 + ) -> Self::Future { 3618 + let inner = Arc::clone(&self.0); 3619 + let fut = async move { 3620 + <T as PlaybackService>::play_track(&inner, request).await 3621 + }; 3622 + Box::pin(fut) 3623 + } 3624 + } 3625 + let accept_compression_encodings = self.accept_compression_encodings; 3626 + let send_compression_encodings = self.send_compression_encodings; 3627 + let max_decoding_message_size = self.max_decoding_message_size; 3628 + let max_encoding_message_size = self.max_encoding_message_size; 3629 + let inner = self.inner.clone(); 3630 + let fut = async move { 3631 + let method = PlayTrackSvc(inner); 3632 + let codec = tonic::codec::ProstCodec::default(); 3633 + let mut grpc = tonic::server::Grpc::new(codec) 3634 + .apply_compression_config( 3635 + accept_compression_encodings, 3636 + send_compression_encodings, 3637 + ) 3638 + .apply_max_message_size_config( 3639 + max_decoding_message_size, 3640 + max_encoding_message_size, 3641 + ); 3642 + let res = grpc.unary(method, req).await; 3643 + Ok(res) 3644 + }; 3645 + Box::pin(fut) 3646 + } 3647 + "/rockbox.v1alpha1.PlaybackService/PlayLikedTracks" => { 3648 + #[allow(non_camel_case_types)] 3649 + struct PlayLikedTracksSvc<T: PlaybackService>(pub Arc<T>); 3650 + impl<T: PlaybackService> 3651 + tonic::server::UnaryService<super::PlayLikedTracksRequest> 3652 + for PlayLikedTracksSvc<T> 3653 + { 3654 + type Response = super::PlayLikedTracksResponse; 3655 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 3656 + fn call( 3657 + &mut self, 3658 + request: tonic::Request<super::PlayLikedTracksRequest>, 3659 + ) -> Self::Future { 3660 + let inner = Arc::clone(&self.0); 3661 + let fut = async move { 3662 + <T as PlaybackService>::play_liked_tracks(&inner, request).await 3663 + }; 3664 + Box::pin(fut) 3665 + } 3666 + } 3667 + let accept_compression_encodings = self.accept_compression_encodings; 3668 + let send_compression_encodings = self.send_compression_encodings; 3669 + let max_decoding_message_size = self.max_decoding_message_size; 3670 + let max_encoding_message_size = self.max_encoding_message_size; 3671 + let inner = self.inner.clone(); 3672 + let fut = async move { 3673 + let method = PlayLikedTracksSvc(inner); 3674 + let codec = tonic::codec::ProstCodec::default(); 3675 + let mut grpc = tonic::server::Grpc::new(codec) 3676 + .apply_compression_config( 3677 + accept_compression_encodings, 3678 + send_compression_encodings, 3679 + ) 3680 + .apply_max_message_size_config( 3681 + max_decoding_message_size, 3682 + max_encoding_message_size, 3683 + ); 3684 + let res = grpc.unary(method, req).await; 3685 + Ok(res) 3686 + }; 3687 + Box::pin(fut) 3688 + } 3689 + "/rockbox.v1alpha1.PlaybackService/PlayAllTracks" => { 3690 + #[allow(non_camel_case_types)] 3691 + struct PlayAllTracksSvc<T: PlaybackService>(pub Arc<T>); 3692 + impl<T: PlaybackService> 3693 + tonic::server::UnaryService<super::PlayAllTracksRequest> 3694 + for PlayAllTracksSvc<T> 3695 + { 3696 + type Response = super::PlayAllTracksResponse; 3697 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 3698 + fn call( 3699 + &mut self, 3700 + request: tonic::Request<super::PlayAllTracksRequest>, 3701 + ) -> Self::Future { 3702 + let inner = Arc::clone(&self.0); 3703 + let fut = async move { 3704 + <T as PlaybackService>::play_all_tracks(&inner, request).await 3705 + }; 3706 + Box::pin(fut) 3707 + } 3708 + } 3709 + let accept_compression_encodings = self.accept_compression_encodings; 3710 + let send_compression_encodings = self.send_compression_encodings; 3711 + let max_decoding_message_size = self.max_decoding_message_size; 3712 + let max_encoding_message_size = self.max_encoding_message_size; 3713 + let inner = self.inner.clone(); 3714 + let fut = async move { 3715 + let method = PlayAllTracksSvc(inner); 3716 + let codec = tonic::codec::ProstCodec::default(); 3717 + let mut grpc = tonic::server::Grpc::new(codec) 3718 + .apply_compression_config( 3719 + accept_compression_encodings, 3720 + send_compression_encodings, 3721 + ) 3722 + .apply_max_message_size_config( 3723 + max_decoding_message_size, 3724 + max_encoding_message_size, 3725 + ); 3726 + let res = grpc.unary(method, req).await; 3727 + Ok(res) 3728 + }; 3729 + Box::pin(fut) 3730 + } 3731 + "/rockbox.v1alpha1.PlaybackService/StreamCurrentTrack" => { 3732 + #[allow(non_camel_case_types)] 3733 + struct StreamCurrentTrackSvc<T: PlaybackService>(pub Arc<T>); 3734 + impl<T: PlaybackService> 3735 + tonic::server::ServerStreamingService<super::StreamCurrentTrackRequest> 3736 + for StreamCurrentTrackSvc<T> 3737 + { 3738 + type Response = super::CurrentTrackResponse; 3739 + type ResponseStream = T::StreamCurrentTrackStream; 3740 + type Future = 3741 + BoxFuture<tonic::Response<Self::ResponseStream>, tonic::Status>; 3742 + fn call( 3743 + &mut self, 3744 + request: tonic::Request<super::StreamCurrentTrackRequest>, 3745 + ) -> Self::Future { 3746 + let inner = Arc::clone(&self.0); 3747 + let fut = async move { 3748 + <T as PlaybackService>::stream_current_track(&inner, request).await 3749 + }; 3750 + Box::pin(fut) 3751 + } 3752 + } 3753 + let accept_compression_encodings = self.accept_compression_encodings; 3754 + let send_compression_encodings = self.send_compression_encodings; 3755 + let max_decoding_message_size = self.max_decoding_message_size; 3756 + let max_encoding_message_size = self.max_encoding_message_size; 3757 + let inner = self.inner.clone(); 3758 + let fut = async move { 3759 + let method = StreamCurrentTrackSvc(inner); 3760 + let codec = tonic::codec::ProstCodec::default(); 3761 + let mut grpc = tonic::server::Grpc::new(codec) 3762 + .apply_compression_config( 3763 + accept_compression_encodings, 3764 + send_compression_encodings, 3765 + ) 3766 + .apply_max_message_size_config( 3767 + max_decoding_message_size, 3768 + max_encoding_message_size, 3769 + ); 3770 + let res = grpc.server_streaming(method, req).await; 3771 + Ok(res) 3772 + }; 3773 + Box::pin(fut) 3774 + } 3775 + "/rockbox.v1alpha1.PlaybackService/StreamStatus" => { 3776 + #[allow(non_camel_case_types)] 3777 + struct StreamStatusSvc<T: PlaybackService>(pub Arc<T>); 3778 + impl<T: PlaybackService> 3779 + tonic::server::ServerStreamingService<super::StreamStatusRequest> 3780 + for StreamStatusSvc<T> 3781 + { 3782 + type Response = super::StatusResponse; 3783 + type ResponseStream = T::StreamStatusStream; 3784 + type Future = 3785 + BoxFuture<tonic::Response<Self::ResponseStream>, tonic::Status>; 3786 + fn call( 3787 + &mut self, 3788 + request: tonic::Request<super::StreamStatusRequest>, 3789 + ) -> Self::Future { 3790 + let inner = Arc::clone(&self.0); 3791 + let fut = async move { 3792 + <T as PlaybackService>::stream_status(&inner, request).await 3793 + }; 3794 + Box::pin(fut) 3795 + } 3796 + } 3797 + let accept_compression_encodings = self.accept_compression_encodings; 3798 + let send_compression_encodings = self.send_compression_encodings; 3799 + let max_decoding_message_size = self.max_decoding_message_size; 3800 + let max_encoding_message_size = self.max_encoding_message_size; 3801 + let inner = self.inner.clone(); 3802 + let fut = async move { 3803 + let method = StreamStatusSvc(inner); 3804 + let codec = tonic::codec::ProstCodec::default(); 3805 + let mut grpc = tonic::server::Grpc::new(codec) 3806 + .apply_compression_config( 3807 + accept_compression_encodings, 3808 + send_compression_encodings, 3809 + ) 3810 + .apply_max_message_size_config( 3811 + max_decoding_message_size, 3812 + max_encoding_message_size, 3813 + ); 3814 + let res = grpc.server_streaming(method, req).await; 3815 + Ok(res) 3816 + }; 3817 + Box::pin(fut) 3818 + } 3819 + "/rockbox.v1alpha1.PlaybackService/StreamPlaylist" => { 3820 + #[allow(non_camel_case_types)] 3821 + struct StreamPlaylistSvc<T: PlaybackService>(pub Arc<T>); 3822 + impl<T: PlaybackService> 3823 + tonic::server::ServerStreamingService<super::StreamPlaylistRequest> 3824 + for StreamPlaylistSvc<T> 3825 + { 3826 + type Response = super::PlaylistResponse; 3827 + type ResponseStream = T::StreamPlaylistStream; 3828 + type Future = 3829 + BoxFuture<tonic::Response<Self::ResponseStream>, tonic::Status>; 3830 + fn call( 3831 + &mut self, 3832 + request: tonic::Request<super::StreamPlaylistRequest>, 3833 + ) -> Self::Future { 3834 + let inner = Arc::clone(&self.0); 3835 + let fut = async move { 3836 + <T as PlaybackService>::stream_playlist(&inner, request).await 3837 + }; 3838 + Box::pin(fut) 3839 + } 3840 + } 3841 + let accept_compression_encodings = self.accept_compression_encodings; 3842 + let send_compression_encodings = self.send_compression_encodings; 3843 + let max_decoding_message_size = self.max_decoding_message_size; 3844 + let max_encoding_message_size = self.max_encoding_message_size; 3845 + let inner = self.inner.clone(); 3846 + let fut = async move { 3847 + let method = StreamPlaylistSvc(inner); 3848 + let codec = tonic::codec::ProstCodec::default(); 3849 + let mut grpc = tonic::server::Grpc::new(codec) 3850 + .apply_compression_config( 3851 + accept_compression_encodings, 3852 + send_compression_encodings, 3853 + ) 3854 + .apply_max_message_size_config( 3855 + max_decoding_message_size, 3856 + max_encoding_message_size, 3857 + ); 3858 + let res = grpc.server_streaming(method, req).await; 3859 + Ok(res) 3860 + }; 3861 + Box::pin(fut) 3862 + } 3863 + _ => Box::pin(async move { 3864 + let mut response = http::Response::new(empty_body()); 3865 + let headers = response.headers_mut(); 3866 + headers.insert( 3867 + tonic::Status::GRPC_STATUS, 3868 + (tonic::Code::Unimplemented as i32).into(), 3869 + ); 3870 + headers.insert( 3871 + http::header::CONTENT_TYPE, 3872 + tonic::metadata::GRPC_CONTENT_TYPE, 3873 + ); 3874 + Ok(response) 3875 + }), 3876 + } 3877 + } 3878 + } 3879 + impl<T> Clone for PlaybackServiceServer<T> { 3880 + fn clone(&self) -> Self { 3881 + let inner = self.inner.clone(); 3882 + Self { 3883 + inner, 3884 + accept_compression_encodings: self.accept_compression_encodings, 3885 + send_compression_encodings: self.send_compression_encodings, 3886 + max_decoding_message_size: self.max_decoding_message_size, 3887 + max_encoding_message_size: self.max_encoding_message_size, 3888 + } 3889 + } 3890 + } 3891 + /// Generated gRPC service name 3892 + pub const SERVICE_NAME: &str = "rockbox.v1alpha1.PlaybackService"; 3893 + impl<T> tonic::server::NamedService for PlaybackServiceServer<T> { 3894 + const NAME: &'static str = SERVICE_NAME; 3895 + } 3896 + } 3897 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3898 + pub struct GetCurrentRequest {} 3899 + #[derive(Clone, PartialEq, ::prost::Message)] 3900 + pub struct GetCurrentResponse { 3901 + #[prost(int32, tag = "1")] 3902 + pub index: i32, 3903 + #[prost(int32, tag = "2")] 3904 + pub amount: i32, 3905 + #[prost(int32, tag = "3")] 3906 + pub max_playlist_size: i32, 3907 + #[prost(int32, tag = "4")] 3908 + pub first_index: i32, 3909 + #[prost(int32, tag = "5")] 3910 + pub last_insert_pos: i32, 3911 + #[prost(int32, tag = "6")] 3912 + pub seed: i32, 3913 + #[prost(int32, tag = "7")] 3914 + pub last_shuffled_start: i32, 3915 + #[prost(message, repeated, tag = "8")] 3916 + pub tracks: ::prost::alloc::vec::Vec<CurrentTrackResponse>, 3917 + } 3918 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3919 + pub struct GetResumeInfoRequest {} 3920 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3921 + pub struct GetResumeInfoResponse {} 3922 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3923 + pub struct GetTrackInfoRequest {} 3924 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3925 + pub struct GetTrackInfoResponse {} 3926 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3927 + pub struct GetFirstIndexRequest {} 3928 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3929 + pub struct GetFirstIndexResponse {} 3930 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3931 + pub struct GetDisplayIndexRequest {} 3932 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3933 + pub struct GetDisplayIndexResponse {} 3934 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3935 + pub struct AmountRequest {} 3936 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3937 + pub struct AmountResponse { 3938 + #[prost(int32, tag = "1")] 3939 + pub amount: i32, 3940 + } 3941 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3942 + pub struct PlaylistResumeRequest {} 3943 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3944 + pub struct PlaylistResumeResponse { 3945 + #[prost(int32, tag = "1")] 3946 + pub code: i32, 3947 + } 3948 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3949 + pub struct ResumeTrackRequest { 3950 + #[prost(int32, tag = "1")] 3951 + pub start_index: i32, 3952 + #[prost(uint32, tag = "2")] 3953 + pub crc: u32, 3954 + #[prost(uint64, tag = "3")] 3955 + pub elapsed: u64, 3956 + #[prost(uint64, tag = "4")] 3957 + pub offset: u64, 3958 + } 3959 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3960 + pub struct ResumeTrackResponse {} 3961 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3962 + pub struct SetModifiedRequest {} 3963 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3964 + pub struct SetModifiedResponse {} 3965 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3966 + pub struct StartRequest { 3967 + #[prost(int32, optional, tag = "1")] 3968 + pub start_index: ::core::option::Option<i32>, 3969 + #[prost(int32, optional, tag = "2")] 3970 + pub elapsed: ::core::option::Option<i32>, 3971 + #[prost(int32, optional, tag = "3")] 3972 + pub offset: ::core::option::Option<i32>, 3973 + } 3974 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3975 + pub struct StartResponse {} 3976 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3977 + pub struct SyncRequest {} 3978 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3979 + pub struct SyncResponse {} 3980 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3981 + pub struct RemoveAllTracksRequest {} 3982 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3983 + pub struct RemoveAllTracksResponse {} 3984 + #[derive(Clone, PartialEq, ::prost::Message)] 3985 + pub struct RemoveTracksRequest { 3986 + #[prost(int32, repeated, tag = "1")] 3987 + pub positions: ::prost::alloc::vec::Vec<i32>, 3988 + } 3989 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 3990 + pub struct RemoveTracksResponse {} 3991 + #[derive(Clone, PartialEq, ::prost::Message)] 3992 + pub struct CreatePlaylistRequest { 3993 + #[prost(string, tag = "1")] 3994 + pub name: ::prost::alloc::string::String, 3995 + #[prost(string, repeated, tag = "2")] 3996 + pub tracks: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, 3997 + #[prost(string, optional, tag = "3")] 3998 + pub folder_id: ::core::option::Option<::prost::alloc::string::String>, 3999 + } 4000 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 4001 + pub struct CreatePlaylistResponse { 4002 + #[prost(int32, tag = "1")] 4003 + pub start_index: i32, 4004 + } 4005 + #[derive(Clone, PartialEq, ::prost::Message)] 4006 + pub struct InsertTracksRequest { 4007 + #[prost(string, optional, tag = "1")] 4008 + pub playlist_id: ::core::option::Option<::prost::alloc::string::String>, 4009 + #[prost(int32, tag = "2")] 4010 + pub position: i32, 4011 + #[prost(string, repeated, tag = "3")] 4012 + pub tracks: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, 4013 + #[prost(bool, optional, tag = "4")] 4014 + pub shuffle: ::core::option::Option<bool>, 4015 + } 4016 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 4017 + pub struct InsertTracksResponse {} 4018 + #[derive(Clone, PartialEq, ::prost::Message)] 4019 + pub struct InsertDirectoryRequest { 4020 + #[prost(string, optional, tag = "1")] 4021 + pub playlist_id: ::core::option::Option<::prost::alloc::string::String>, 4022 + #[prost(int32, tag = "2")] 4023 + pub position: i32, 4024 + #[prost(string, tag = "3")] 4025 + pub directory: ::prost::alloc::string::String, 4026 + #[prost(bool, optional, tag = "4")] 4027 + pub recurse: ::core::option::Option<bool>, 4028 + #[prost(bool, optional, tag = "5")] 4029 + pub shuffle: ::core::option::Option<bool>, 4030 + } 4031 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 4032 + pub struct InsertDirectoryResponse {} 4033 + #[derive(Clone, PartialEq, ::prost::Message)] 4034 + pub struct InsertPlaylistRequest { 4035 + #[prost(int32, tag = "1")] 4036 + pub position: i32, 4037 + #[prost(string, tag = "2")] 4038 + pub target_playlist_id: ::prost::alloc::string::String, 4039 + #[prost(string, tag = "3")] 4040 + pub playlist_id: ::prost::alloc::string::String, 4041 + #[prost(bool, optional, tag = "4")] 4042 + pub shuffle: ::core::option::Option<bool>, 4043 + } 4044 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 4045 + pub struct InsertPlaylistResponse {} 4046 + #[derive(Clone, PartialEq, ::prost::Message)] 4047 + pub struct InsertAlbumRequest { 4048 + #[prost(int32, tag = "1")] 4049 + pub position: i32, 4050 + #[prost(string, tag = "2")] 4051 + pub album_id: ::prost::alloc::string::String, 4052 + #[prost(bool, optional, tag = "3")] 4053 + pub shuffle: ::core::option::Option<bool>, 4054 + } 4055 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 4056 + pub struct InsertAlbumResponse {} 4057 + #[derive(Clone, PartialEq, ::prost::Message)] 4058 + pub struct InsertArtistTracksRequest { 4059 + #[prost(int32, tag = "1")] 4060 + pub position: i32, 4061 + #[prost(string, tag = "2")] 4062 + pub artist_id: ::prost::alloc::string::String, 4063 + #[prost(bool, optional, tag = "3")] 4064 + pub shuffle: ::core::option::Option<bool>, 4065 + } 4066 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 4067 + pub struct InsertArtistTracksResponse {} 4068 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 4069 + pub struct ShufflePlaylistRequest { 4070 + #[prost(int32, tag = "1")] 4071 + pub start_index: i32, 4072 + } 4073 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 4074 + pub struct ShufflePlaylistResponse {} 4075 + /// Generated client implementations. 4076 + pub mod playlist_service_client { 4077 + #![allow( 4078 + unused_variables, 4079 + dead_code, 4080 + missing_docs, 4081 + clippy::wildcard_imports, 4082 + clippy::let_unit_value 4083 + )] 4084 + use tonic::codegen::http::Uri; 4085 + use tonic::codegen::*; 4086 + #[derive(Debug, Clone)] 4087 + pub struct PlaylistServiceClient<T> { 4088 + inner: tonic::client::Grpc<T>, 4089 + } 4090 + impl PlaylistServiceClient<tonic::transport::Channel> { 4091 + /// Attempt to create a new client by connecting to a given endpoint. 4092 + pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error> 4093 + where 4094 + D: TryInto<tonic::transport::Endpoint>, 4095 + D::Error: Into<StdError>, 4096 + { 4097 + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; 4098 + Ok(Self::new(conn)) 4099 + } 4100 + } 4101 + impl<T> PlaylistServiceClient<T> 4102 + where 4103 + T: tonic::client::GrpcService<tonic::body::BoxBody>, 4104 + T::Error: Into<StdError>, 4105 + T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static, 4106 + <T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send, 4107 + { 4108 + pub fn new(inner: T) -> Self { 4109 + let inner = tonic::client::Grpc::new(inner); 4110 + Self { inner } 4111 + } 4112 + pub fn with_origin(inner: T, origin: Uri) -> Self { 4113 + let inner = tonic::client::Grpc::with_origin(inner, origin); 4114 + Self { inner } 4115 + } 4116 + pub fn with_interceptor<F>( 4117 + inner: T, 4118 + interceptor: F, 4119 + ) -> PlaylistServiceClient<InterceptedService<T, F>> 4120 + where 4121 + F: tonic::service::Interceptor, 4122 + T::ResponseBody: Default, 4123 + T: tonic::codegen::Service< 4124 + http::Request<tonic::body::BoxBody>, 4125 + Response = http::Response< 4126 + <T as tonic::client::GrpcService<tonic::body::BoxBody>>::ResponseBody, 4127 + >, 4128 + >, 4129 + <T as tonic::codegen::Service<http::Request<tonic::body::BoxBody>>>::Error: 4130 + Into<StdError> + std::marker::Send + std::marker::Sync, 4131 + { 4132 + PlaylistServiceClient::new(InterceptedService::new(inner, interceptor)) 4133 + } 4134 + /// Compress requests with the given encoding. 4135 + /// 4136 + /// This requires the server to support it otherwise it might respond with an 4137 + /// error. 4138 + #[must_use] 4139 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 4140 + self.inner = self.inner.send_compressed(encoding); 4141 + self 4142 + } 4143 + /// Enable decompressing responses. 4144 + #[must_use] 4145 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 4146 + self.inner = self.inner.accept_compressed(encoding); 4147 + self 4148 + } 4149 + /// Limits the maximum size of a decoded message. 4150 + /// 4151 + /// Default: `4MB` 4152 + #[must_use] 4153 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 4154 + self.inner = self.inner.max_decoding_message_size(limit); 4155 + self 4156 + } 4157 + /// Limits the maximum size of an encoded message. 4158 + /// 4159 + /// Default: `usize::MAX` 4160 + #[must_use] 4161 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 4162 + self.inner = self.inner.max_encoding_message_size(limit); 4163 + self 4164 + } 4165 + pub async fn get_current( 4166 + &mut self, 4167 + request: impl tonic::IntoRequest<super::GetCurrentRequest>, 4168 + ) -> std::result::Result<tonic::Response<super::GetCurrentResponse>, tonic::Status> 4169 + { 4170 + self.inner.ready().await.map_err(|e| { 4171 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 4172 + })?; 4173 + let codec = tonic::codec::ProstCodec::default(); 4174 + let path = http::uri::PathAndQuery::from_static( 4175 + "/rockbox.v1alpha1.PlaylistService/GetCurrent", 4176 + ); 4177 + let mut req = request.into_request(); 4178 + req.extensions_mut().insert(GrpcMethod::new( 4179 + "rockbox.v1alpha1.PlaylistService", 4180 + "GetCurrent", 4181 + )); 4182 + self.inner.unary(req, path, codec).await 4183 + } 4184 + pub async fn get_resume_info( 4185 + &mut self, 4186 + request: impl tonic::IntoRequest<super::GetResumeInfoRequest>, 4187 + ) -> std::result::Result<tonic::Response<super::GetResumeInfoResponse>, tonic::Status> 4188 + { 4189 + self.inner.ready().await.map_err(|e| { 4190 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 4191 + })?; 4192 + let codec = tonic::codec::ProstCodec::default(); 4193 + let path = http::uri::PathAndQuery::from_static( 4194 + "/rockbox.v1alpha1.PlaylistService/GetResumeInfo", 4195 + ); 4196 + let mut req = request.into_request(); 4197 + req.extensions_mut().insert(GrpcMethod::new( 4198 + "rockbox.v1alpha1.PlaylistService", 4199 + "GetResumeInfo", 4200 + )); 4201 + self.inner.unary(req, path, codec).await 4202 + } 4203 + pub async fn get_track_info( 4204 + &mut self, 4205 + request: impl tonic::IntoRequest<super::GetTrackInfoRequest>, 4206 + ) -> std::result::Result<tonic::Response<super::GetTrackInfoResponse>, tonic::Status> 4207 + { 4208 + self.inner.ready().await.map_err(|e| { 4209 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 4210 + })?; 4211 + let codec = tonic::codec::ProstCodec::default(); 4212 + let path = http::uri::PathAndQuery::from_static( 4213 + "/rockbox.v1alpha1.PlaylistService/GetTrackInfo", 4214 + ); 4215 + let mut req = request.into_request(); 4216 + req.extensions_mut().insert(GrpcMethod::new( 4217 + "rockbox.v1alpha1.PlaylistService", 4218 + "GetTrackInfo", 4219 + )); 4220 + self.inner.unary(req, path, codec).await 4221 + } 4222 + pub async fn get_first_index( 4223 + &mut self, 4224 + request: impl tonic::IntoRequest<super::GetFirstIndexRequest>, 4225 + ) -> std::result::Result<tonic::Response<super::GetFirstIndexResponse>, tonic::Status> 4226 + { 4227 + self.inner.ready().await.map_err(|e| { 4228 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 4229 + })?; 4230 + let codec = tonic::codec::ProstCodec::default(); 4231 + let path = http::uri::PathAndQuery::from_static( 4232 + "/rockbox.v1alpha1.PlaylistService/GetFirstIndex", 4233 + ); 4234 + let mut req = request.into_request(); 4235 + req.extensions_mut().insert(GrpcMethod::new( 4236 + "rockbox.v1alpha1.PlaylistService", 4237 + "GetFirstIndex", 4238 + )); 4239 + self.inner.unary(req, path, codec).await 4240 + } 4241 + pub async fn get_display_index( 4242 + &mut self, 4243 + request: impl tonic::IntoRequest<super::GetDisplayIndexRequest>, 4244 + ) -> std::result::Result<tonic::Response<super::GetDisplayIndexResponse>, tonic::Status> 4245 + { 4246 + self.inner.ready().await.map_err(|e| { 4247 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 4248 + })?; 4249 + let codec = tonic::codec::ProstCodec::default(); 4250 + let path = http::uri::PathAndQuery::from_static( 4251 + "/rockbox.v1alpha1.PlaylistService/GetDisplayIndex", 4252 + ); 4253 + let mut req = request.into_request(); 4254 + req.extensions_mut().insert(GrpcMethod::new( 4255 + "rockbox.v1alpha1.PlaylistService", 4256 + "GetDisplayIndex", 4257 + )); 4258 + self.inner.unary(req, path, codec).await 4259 + } 4260 + pub async fn amount( 4261 + &mut self, 4262 + request: impl tonic::IntoRequest<super::AmountRequest>, 4263 + ) -> std::result::Result<tonic::Response<super::AmountResponse>, tonic::Status> { 4264 + self.inner.ready().await.map_err(|e| { 4265 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 4266 + })?; 4267 + let codec = tonic::codec::ProstCodec::default(); 4268 + let path = 4269 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.PlaylistService/Amount"); 4270 + let mut req = request.into_request(); 4271 + req.extensions_mut().insert(GrpcMethod::new( 4272 + "rockbox.v1alpha1.PlaylistService", 4273 + "Amount", 4274 + )); 4275 + self.inner.unary(req, path, codec).await 4276 + } 4277 + pub async fn playlist_resume( 4278 + &mut self, 4279 + request: impl tonic::IntoRequest<super::PlaylistResumeRequest>, 4280 + ) -> std::result::Result<tonic::Response<super::PlaylistResumeResponse>, tonic::Status> 4281 + { 4282 + self.inner.ready().await.map_err(|e| { 4283 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 4284 + })?; 4285 + let codec = tonic::codec::ProstCodec::default(); 4286 + let path = http::uri::PathAndQuery::from_static( 4287 + "/rockbox.v1alpha1.PlaylistService/PlaylistResume", 4288 + ); 4289 + let mut req = request.into_request(); 4290 + req.extensions_mut().insert(GrpcMethod::new( 4291 + "rockbox.v1alpha1.PlaylistService", 4292 + "PlaylistResume", 4293 + )); 4294 + self.inner.unary(req, path, codec).await 4295 + } 4296 + pub async fn resume_track( 4297 + &mut self, 4298 + request: impl tonic::IntoRequest<super::ResumeTrackRequest>, 4299 + ) -> std::result::Result<tonic::Response<super::ResumeTrackResponse>, tonic::Status> 4300 + { 4301 + self.inner.ready().await.map_err(|e| { 4302 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 4303 + })?; 4304 + let codec = tonic::codec::ProstCodec::default(); 4305 + let path = http::uri::PathAndQuery::from_static( 4306 + "/rockbox.v1alpha1.PlaylistService/ResumeTrack", 4307 + ); 4308 + let mut req = request.into_request(); 4309 + req.extensions_mut().insert(GrpcMethod::new( 4310 + "rockbox.v1alpha1.PlaylistService", 4311 + "ResumeTrack", 4312 + )); 4313 + self.inner.unary(req, path, codec).await 4314 + } 4315 + pub async fn set_modified( 4316 + &mut self, 4317 + request: impl tonic::IntoRequest<super::SetModifiedRequest>, 4318 + ) -> std::result::Result<tonic::Response<super::SetModifiedResponse>, tonic::Status> 4319 + { 4320 + self.inner.ready().await.map_err(|e| { 4321 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 4322 + })?; 4323 + let codec = tonic::codec::ProstCodec::default(); 4324 + let path = http::uri::PathAndQuery::from_static( 4325 + "/rockbox.v1alpha1.PlaylistService/SetModified", 4326 + ); 4327 + let mut req = request.into_request(); 4328 + req.extensions_mut().insert(GrpcMethod::new( 4329 + "rockbox.v1alpha1.PlaylistService", 4330 + "SetModified", 4331 + )); 4332 + self.inner.unary(req, path, codec).await 4333 + } 4334 + pub async fn start( 4335 + &mut self, 4336 + request: impl tonic::IntoRequest<super::StartRequest>, 4337 + ) -> std::result::Result<tonic::Response<super::StartResponse>, tonic::Status> { 4338 + self.inner.ready().await.map_err(|e| { 4339 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 4340 + })?; 4341 + let codec = tonic::codec::ProstCodec::default(); 4342 + let path = 4343 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.PlaylistService/Start"); 4344 + let mut req = request.into_request(); 4345 + req.extensions_mut() 4346 + .insert(GrpcMethod::new("rockbox.v1alpha1.PlaylistService", "Start")); 4347 + self.inner.unary(req, path, codec).await 4348 + } 4349 + pub async fn sync( 4350 + &mut self, 4351 + request: impl tonic::IntoRequest<super::SyncRequest>, 4352 + ) -> std::result::Result<tonic::Response<super::SyncResponse>, tonic::Status> { 4353 + self.inner.ready().await.map_err(|e| { 4354 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 4355 + })?; 4356 + let codec = tonic::codec::ProstCodec::default(); 4357 + let path = 4358 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.PlaylistService/Sync"); 4359 + let mut req = request.into_request(); 4360 + req.extensions_mut() 4361 + .insert(GrpcMethod::new("rockbox.v1alpha1.PlaylistService", "Sync")); 4362 + self.inner.unary(req, path, codec).await 4363 + } 4364 + pub async fn remove_all_tracks( 4365 + &mut self, 4366 + request: impl tonic::IntoRequest<super::RemoveAllTracksRequest>, 4367 + ) -> std::result::Result<tonic::Response<super::RemoveAllTracksResponse>, tonic::Status> 4368 + { 4369 + self.inner.ready().await.map_err(|e| { 4370 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 4371 + })?; 4372 + let codec = tonic::codec::ProstCodec::default(); 4373 + let path = http::uri::PathAndQuery::from_static( 4374 + "/rockbox.v1alpha1.PlaylistService/RemoveAllTracks", 4375 + ); 4376 + let mut req = request.into_request(); 4377 + req.extensions_mut().insert(GrpcMethod::new( 4378 + "rockbox.v1alpha1.PlaylistService", 4379 + "RemoveAllTracks", 4380 + )); 4381 + self.inner.unary(req, path, codec).await 4382 + } 4383 + pub async fn remove_tracks( 4384 + &mut self, 4385 + request: impl tonic::IntoRequest<super::RemoveTracksRequest>, 4386 + ) -> std::result::Result<tonic::Response<super::RemoveTracksResponse>, tonic::Status> 4387 + { 4388 + self.inner.ready().await.map_err(|e| { 4389 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 4390 + })?; 4391 + let codec = tonic::codec::ProstCodec::default(); 4392 + let path = http::uri::PathAndQuery::from_static( 4393 + "/rockbox.v1alpha1.PlaylistService/RemoveTracks", 4394 + ); 4395 + let mut req = request.into_request(); 4396 + req.extensions_mut().insert(GrpcMethod::new( 4397 + "rockbox.v1alpha1.PlaylistService", 4398 + "RemoveTracks", 4399 + )); 4400 + self.inner.unary(req, path, codec).await 4401 + } 4402 + pub async fn create_playlist( 4403 + &mut self, 4404 + request: impl tonic::IntoRequest<super::CreatePlaylistRequest>, 4405 + ) -> std::result::Result<tonic::Response<super::CreatePlaylistResponse>, tonic::Status> 4406 + { 4407 + self.inner.ready().await.map_err(|e| { 4408 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 4409 + })?; 4410 + let codec = tonic::codec::ProstCodec::default(); 4411 + let path = http::uri::PathAndQuery::from_static( 4412 + "/rockbox.v1alpha1.PlaylistService/CreatePlaylist", 4413 + ); 4414 + let mut req = request.into_request(); 4415 + req.extensions_mut().insert(GrpcMethod::new( 4416 + "rockbox.v1alpha1.PlaylistService", 4417 + "CreatePlaylist", 4418 + )); 4419 + self.inner.unary(req, path, codec).await 4420 + } 4421 + pub async fn insert_tracks( 4422 + &mut self, 4423 + request: impl tonic::IntoRequest<super::InsertTracksRequest>, 4424 + ) -> std::result::Result<tonic::Response<super::InsertTracksResponse>, tonic::Status> 4425 + { 4426 + self.inner.ready().await.map_err(|e| { 4427 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 4428 + })?; 4429 + let codec = tonic::codec::ProstCodec::default(); 4430 + let path = http::uri::PathAndQuery::from_static( 4431 + "/rockbox.v1alpha1.PlaylistService/InsertTracks", 4432 + ); 4433 + let mut req = request.into_request(); 4434 + req.extensions_mut().insert(GrpcMethod::new( 4435 + "rockbox.v1alpha1.PlaylistService", 4436 + "InsertTracks", 4437 + )); 4438 + self.inner.unary(req, path, codec).await 4439 + } 4440 + pub async fn insert_directory( 4441 + &mut self, 4442 + request: impl tonic::IntoRequest<super::InsertDirectoryRequest>, 4443 + ) -> std::result::Result<tonic::Response<super::InsertDirectoryResponse>, tonic::Status> 4444 + { 4445 + self.inner.ready().await.map_err(|e| { 4446 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 4447 + })?; 4448 + let codec = tonic::codec::ProstCodec::default(); 4449 + let path = http::uri::PathAndQuery::from_static( 4450 + "/rockbox.v1alpha1.PlaylistService/InsertDirectory", 4451 + ); 4452 + let mut req = request.into_request(); 4453 + req.extensions_mut().insert(GrpcMethod::new( 4454 + "rockbox.v1alpha1.PlaylistService", 4455 + "InsertDirectory", 4456 + )); 4457 + self.inner.unary(req, path, codec).await 4458 + } 4459 + pub async fn insert_playlist( 4460 + &mut self, 4461 + request: impl tonic::IntoRequest<super::InsertPlaylistRequest>, 4462 + ) -> std::result::Result<tonic::Response<super::InsertPlaylistResponse>, tonic::Status> 4463 + { 4464 + self.inner.ready().await.map_err(|e| { 4465 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 4466 + })?; 4467 + let codec = tonic::codec::ProstCodec::default(); 4468 + let path = http::uri::PathAndQuery::from_static( 4469 + "/rockbox.v1alpha1.PlaylistService/InsertPlaylist", 4470 + ); 4471 + let mut req = request.into_request(); 4472 + req.extensions_mut().insert(GrpcMethod::new( 4473 + "rockbox.v1alpha1.PlaylistService", 4474 + "InsertPlaylist", 4475 + )); 4476 + self.inner.unary(req, path, codec).await 4477 + } 4478 + pub async fn insert_album( 4479 + &mut self, 4480 + request: impl tonic::IntoRequest<super::InsertAlbumRequest>, 4481 + ) -> std::result::Result<tonic::Response<super::InsertAlbumResponse>, tonic::Status> 4482 + { 4483 + self.inner.ready().await.map_err(|e| { 4484 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 4485 + })?; 4486 + let codec = tonic::codec::ProstCodec::default(); 4487 + let path = http::uri::PathAndQuery::from_static( 4488 + "/rockbox.v1alpha1.PlaylistService/InsertAlbum", 4489 + ); 4490 + let mut req = request.into_request(); 4491 + req.extensions_mut().insert(GrpcMethod::new( 4492 + "rockbox.v1alpha1.PlaylistService", 4493 + "InsertAlbum", 4494 + )); 4495 + self.inner.unary(req, path, codec).await 4496 + } 4497 + pub async fn insert_artist_tracks( 4498 + &mut self, 4499 + request: impl tonic::IntoRequest<super::InsertArtistTracksRequest>, 4500 + ) -> std::result::Result<tonic::Response<super::InsertArtistTracksResponse>, tonic::Status> 4501 + { 4502 + self.inner.ready().await.map_err(|e| { 4503 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 4504 + })?; 4505 + let codec = tonic::codec::ProstCodec::default(); 4506 + let path = http::uri::PathAndQuery::from_static( 4507 + "/rockbox.v1alpha1.PlaylistService/InsertArtistTracks", 4508 + ); 4509 + let mut req = request.into_request(); 4510 + req.extensions_mut().insert(GrpcMethod::new( 4511 + "rockbox.v1alpha1.PlaylistService", 4512 + "InsertArtistTracks", 4513 + )); 4514 + self.inner.unary(req, path, codec).await 4515 + } 4516 + pub async fn shuffle_playlist( 4517 + &mut self, 4518 + request: impl tonic::IntoRequest<super::ShufflePlaylistRequest>, 4519 + ) -> std::result::Result<tonic::Response<super::ShufflePlaylistResponse>, tonic::Status> 4520 + { 4521 + self.inner.ready().await.map_err(|e| { 4522 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 4523 + })?; 4524 + let codec = tonic::codec::ProstCodec::default(); 4525 + let path = http::uri::PathAndQuery::from_static( 4526 + "/rockbox.v1alpha1.PlaylistService/ShufflePlaylist", 4527 + ); 4528 + let mut req = request.into_request(); 4529 + req.extensions_mut().insert(GrpcMethod::new( 4530 + "rockbox.v1alpha1.PlaylistService", 4531 + "ShufflePlaylist", 4532 + )); 4533 + self.inner.unary(req, path, codec).await 4534 + } 4535 + } 4536 + } 4537 + /// Generated server implementations. 4538 + pub mod playlist_service_server { 4539 + #![allow( 4540 + unused_variables, 4541 + dead_code, 4542 + missing_docs, 4543 + clippy::wildcard_imports, 4544 + clippy::let_unit_value 4545 + )] 4546 + use tonic::codegen::*; 4547 + /// Generated trait containing gRPC methods that should be implemented for use with PlaylistServiceServer. 4548 + #[async_trait] 4549 + pub trait PlaylistService: std::marker::Send + std::marker::Sync + 'static { 4550 + async fn get_current( 4551 + &self, 4552 + request: tonic::Request<super::GetCurrentRequest>, 4553 + ) -> std::result::Result<tonic::Response<super::GetCurrentResponse>, tonic::Status>; 4554 + async fn get_resume_info( 4555 + &self, 4556 + request: tonic::Request<super::GetResumeInfoRequest>, 4557 + ) -> std::result::Result<tonic::Response<super::GetResumeInfoResponse>, tonic::Status>; 4558 + async fn get_track_info( 4559 + &self, 4560 + request: tonic::Request<super::GetTrackInfoRequest>, 4561 + ) -> std::result::Result<tonic::Response<super::GetTrackInfoResponse>, tonic::Status>; 4562 + async fn get_first_index( 4563 + &self, 4564 + request: tonic::Request<super::GetFirstIndexRequest>, 4565 + ) -> std::result::Result<tonic::Response<super::GetFirstIndexResponse>, tonic::Status>; 4566 + async fn get_display_index( 4567 + &self, 4568 + request: tonic::Request<super::GetDisplayIndexRequest>, 4569 + ) -> std::result::Result<tonic::Response<super::GetDisplayIndexResponse>, tonic::Status>; 4570 + async fn amount( 4571 + &self, 4572 + request: tonic::Request<super::AmountRequest>, 4573 + ) -> std::result::Result<tonic::Response<super::AmountResponse>, tonic::Status>; 4574 + async fn playlist_resume( 4575 + &self, 4576 + request: tonic::Request<super::PlaylistResumeRequest>, 4577 + ) -> std::result::Result<tonic::Response<super::PlaylistResumeResponse>, tonic::Status>; 4578 + async fn resume_track( 4579 + &self, 4580 + request: tonic::Request<super::ResumeTrackRequest>, 4581 + ) -> std::result::Result<tonic::Response<super::ResumeTrackResponse>, tonic::Status>; 4582 + async fn set_modified( 4583 + &self, 4584 + request: tonic::Request<super::SetModifiedRequest>, 4585 + ) -> std::result::Result<tonic::Response<super::SetModifiedResponse>, tonic::Status>; 4586 + async fn start( 4587 + &self, 4588 + request: tonic::Request<super::StartRequest>, 4589 + ) -> std::result::Result<tonic::Response<super::StartResponse>, tonic::Status>; 4590 + async fn sync( 4591 + &self, 4592 + request: tonic::Request<super::SyncRequest>, 4593 + ) -> std::result::Result<tonic::Response<super::SyncResponse>, tonic::Status>; 4594 + async fn remove_all_tracks( 4595 + &self, 4596 + request: tonic::Request<super::RemoveAllTracksRequest>, 4597 + ) -> std::result::Result<tonic::Response<super::RemoveAllTracksResponse>, tonic::Status>; 4598 + async fn remove_tracks( 4599 + &self, 4600 + request: tonic::Request<super::RemoveTracksRequest>, 4601 + ) -> std::result::Result<tonic::Response<super::RemoveTracksResponse>, tonic::Status>; 4602 + async fn create_playlist( 4603 + &self, 4604 + request: tonic::Request<super::CreatePlaylistRequest>, 4605 + ) -> std::result::Result<tonic::Response<super::CreatePlaylistResponse>, tonic::Status>; 4606 + async fn insert_tracks( 4607 + &self, 4608 + request: tonic::Request<super::InsertTracksRequest>, 4609 + ) -> std::result::Result<tonic::Response<super::InsertTracksResponse>, tonic::Status>; 4610 + async fn insert_directory( 4611 + &self, 4612 + request: tonic::Request<super::InsertDirectoryRequest>, 4613 + ) -> std::result::Result<tonic::Response<super::InsertDirectoryResponse>, tonic::Status>; 4614 + async fn insert_playlist( 4615 + &self, 4616 + request: tonic::Request<super::InsertPlaylistRequest>, 4617 + ) -> std::result::Result<tonic::Response<super::InsertPlaylistResponse>, tonic::Status>; 4618 + async fn insert_album( 4619 + &self, 4620 + request: tonic::Request<super::InsertAlbumRequest>, 4621 + ) -> std::result::Result<tonic::Response<super::InsertAlbumResponse>, tonic::Status>; 4622 + async fn insert_artist_tracks( 4623 + &self, 4624 + request: tonic::Request<super::InsertArtistTracksRequest>, 4625 + ) -> std::result::Result<tonic::Response<super::InsertArtistTracksResponse>, tonic::Status>; 4626 + async fn shuffle_playlist( 4627 + &self, 4628 + request: tonic::Request<super::ShufflePlaylistRequest>, 4629 + ) -> std::result::Result<tonic::Response<super::ShufflePlaylistResponse>, tonic::Status>; 4630 + } 4631 + #[derive(Debug)] 4632 + pub struct PlaylistServiceServer<T> { 4633 + inner: Arc<T>, 4634 + accept_compression_encodings: EnabledCompressionEncodings, 4635 + send_compression_encodings: EnabledCompressionEncodings, 4636 + max_decoding_message_size: Option<usize>, 4637 + max_encoding_message_size: Option<usize>, 4638 + } 4639 + impl<T> PlaylistServiceServer<T> { 4640 + pub fn new(inner: T) -> Self { 4641 + Self::from_arc(Arc::new(inner)) 4642 + } 4643 + pub fn from_arc(inner: Arc<T>) -> Self { 4644 + Self { 4645 + inner, 4646 + accept_compression_encodings: Default::default(), 4647 + send_compression_encodings: Default::default(), 4648 + max_decoding_message_size: None, 4649 + max_encoding_message_size: None, 4650 + } 4651 + } 4652 + pub fn with_interceptor<F>(inner: T, interceptor: F) -> InterceptedService<Self, F> 4653 + where 4654 + F: tonic::service::Interceptor, 4655 + { 4656 + InterceptedService::new(Self::new(inner), interceptor) 4657 + } 4658 + /// Enable decompressing requests with the given encoding. 4659 + #[must_use] 4660 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 4661 + self.accept_compression_encodings.enable(encoding); 4662 + self 4663 + } 4664 + /// Compress responses with the given encoding, if the client supports it. 4665 + #[must_use] 4666 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 4667 + self.send_compression_encodings.enable(encoding); 4668 + self 4669 + } 4670 + /// Limits the maximum size of a decoded message. 4671 + /// 4672 + /// Default: `4MB` 4673 + #[must_use] 4674 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 4675 + self.max_decoding_message_size = Some(limit); 4676 + self 4677 + } 4678 + /// Limits the maximum size of an encoded message. 4679 + /// 4680 + /// Default: `usize::MAX` 4681 + #[must_use] 4682 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 4683 + self.max_encoding_message_size = Some(limit); 4684 + self 4685 + } 4686 + } 4687 + impl<T, B> tonic::codegen::Service<http::Request<B>> for PlaylistServiceServer<T> 4688 + where 4689 + T: PlaylistService, 4690 + B: Body + std::marker::Send + 'static, 4691 + B::Error: Into<StdError> + std::marker::Send + 'static, 4692 + { 4693 + type Response = http::Response<tonic::body::BoxBody>; 4694 + type Error = std::convert::Infallible; 4695 + type Future = BoxFuture<Self::Response, Self::Error>; 4696 + fn poll_ready( 4697 + &mut self, 4698 + _cx: &mut Context<'_>, 4699 + ) -> Poll<std::result::Result<(), Self::Error>> { 4700 + Poll::Ready(Ok(())) 4701 + } 4702 + fn call(&mut self, req: http::Request<B>) -> Self::Future { 4703 + match req.uri().path() { 4704 + "/rockbox.v1alpha1.PlaylistService/GetCurrent" => { 4705 + #[allow(non_camel_case_types)] 4706 + struct GetCurrentSvc<T: PlaylistService>(pub Arc<T>); 4707 + impl<T: PlaylistService> tonic::server::UnaryService<super::GetCurrentRequest> 4708 + for GetCurrentSvc<T> 4709 + { 4710 + type Response = super::GetCurrentResponse; 4711 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 4712 + fn call( 4713 + &mut self, 4714 + request: tonic::Request<super::GetCurrentRequest>, 4715 + ) -> Self::Future { 4716 + let inner = Arc::clone(&self.0); 4717 + let fut = async move { 4718 + <T as PlaylistService>::get_current(&inner, request).await 4719 + }; 4720 + Box::pin(fut) 4721 + } 4722 + } 4723 + let accept_compression_encodings = self.accept_compression_encodings; 4724 + let send_compression_encodings = self.send_compression_encodings; 4725 + let max_decoding_message_size = self.max_decoding_message_size; 4726 + let max_encoding_message_size = self.max_encoding_message_size; 4727 + let inner = self.inner.clone(); 4728 + let fut = async move { 4729 + let method = GetCurrentSvc(inner); 4730 + let codec = tonic::codec::ProstCodec::default(); 4731 + let mut grpc = tonic::server::Grpc::new(codec) 4732 + .apply_compression_config( 4733 + accept_compression_encodings, 4734 + send_compression_encodings, 4735 + ) 4736 + .apply_max_message_size_config( 4737 + max_decoding_message_size, 4738 + max_encoding_message_size, 4739 + ); 4740 + let res = grpc.unary(method, req).await; 4741 + Ok(res) 4742 + }; 4743 + Box::pin(fut) 4744 + } 4745 + "/rockbox.v1alpha1.PlaylistService/GetResumeInfo" => { 4746 + #[allow(non_camel_case_types)] 4747 + struct GetResumeInfoSvc<T: PlaylistService>(pub Arc<T>); 4748 + impl<T: PlaylistService> 4749 + tonic::server::UnaryService<super::GetResumeInfoRequest> 4750 + for GetResumeInfoSvc<T> 4751 + { 4752 + type Response = super::GetResumeInfoResponse; 4753 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 4754 + fn call( 4755 + &mut self, 4756 + request: tonic::Request<super::GetResumeInfoRequest>, 4757 + ) -> Self::Future { 4758 + let inner = Arc::clone(&self.0); 4759 + let fut = async move { 4760 + <T as PlaylistService>::get_resume_info(&inner, request).await 4761 + }; 4762 + Box::pin(fut) 4763 + } 4764 + } 4765 + let accept_compression_encodings = self.accept_compression_encodings; 4766 + let send_compression_encodings = self.send_compression_encodings; 4767 + let max_decoding_message_size = self.max_decoding_message_size; 4768 + let max_encoding_message_size = self.max_encoding_message_size; 4769 + let inner = self.inner.clone(); 4770 + let fut = async move { 4771 + let method = GetResumeInfoSvc(inner); 4772 + let codec = tonic::codec::ProstCodec::default(); 4773 + let mut grpc = tonic::server::Grpc::new(codec) 4774 + .apply_compression_config( 4775 + accept_compression_encodings, 4776 + send_compression_encodings, 4777 + ) 4778 + .apply_max_message_size_config( 4779 + max_decoding_message_size, 4780 + max_encoding_message_size, 4781 + ); 4782 + let res = grpc.unary(method, req).await; 4783 + Ok(res) 4784 + }; 4785 + Box::pin(fut) 4786 + } 4787 + "/rockbox.v1alpha1.PlaylistService/GetTrackInfo" => { 4788 + #[allow(non_camel_case_types)] 4789 + struct GetTrackInfoSvc<T: PlaylistService>(pub Arc<T>); 4790 + impl<T: PlaylistService> tonic::server::UnaryService<super::GetTrackInfoRequest> 4791 + for GetTrackInfoSvc<T> 4792 + { 4793 + type Response = super::GetTrackInfoResponse; 4794 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 4795 + fn call( 4796 + &mut self, 4797 + request: tonic::Request<super::GetTrackInfoRequest>, 4798 + ) -> Self::Future { 4799 + let inner = Arc::clone(&self.0); 4800 + let fut = async move { 4801 + <T as PlaylistService>::get_track_info(&inner, request).await 4802 + }; 4803 + Box::pin(fut) 4804 + } 4805 + } 4806 + let accept_compression_encodings = self.accept_compression_encodings; 4807 + let send_compression_encodings = self.send_compression_encodings; 4808 + let max_decoding_message_size = self.max_decoding_message_size; 4809 + let max_encoding_message_size = self.max_encoding_message_size; 4810 + let inner = self.inner.clone(); 4811 + let fut = async move { 4812 + let method = GetTrackInfoSvc(inner); 4813 + let codec = tonic::codec::ProstCodec::default(); 4814 + let mut grpc = tonic::server::Grpc::new(codec) 4815 + .apply_compression_config( 4816 + accept_compression_encodings, 4817 + send_compression_encodings, 4818 + ) 4819 + .apply_max_message_size_config( 4820 + max_decoding_message_size, 4821 + max_encoding_message_size, 4822 + ); 4823 + let res = grpc.unary(method, req).await; 4824 + Ok(res) 4825 + }; 4826 + Box::pin(fut) 4827 + } 4828 + "/rockbox.v1alpha1.PlaylistService/GetFirstIndex" => { 4829 + #[allow(non_camel_case_types)] 4830 + struct GetFirstIndexSvc<T: PlaylistService>(pub Arc<T>); 4831 + impl<T: PlaylistService> 4832 + tonic::server::UnaryService<super::GetFirstIndexRequest> 4833 + for GetFirstIndexSvc<T> 4834 + { 4835 + type Response = super::GetFirstIndexResponse; 4836 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 4837 + fn call( 4838 + &mut self, 4839 + request: tonic::Request<super::GetFirstIndexRequest>, 4840 + ) -> Self::Future { 4841 + let inner = Arc::clone(&self.0); 4842 + let fut = async move { 4843 + <T as PlaylistService>::get_first_index(&inner, request).await 4844 + }; 4845 + Box::pin(fut) 4846 + } 4847 + } 4848 + let accept_compression_encodings = self.accept_compression_encodings; 4849 + let send_compression_encodings = self.send_compression_encodings; 4850 + let max_decoding_message_size = self.max_decoding_message_size; 4851 + let max_encoding_message_size = self.max_encoding_message_size; 4852 + let inner = self.inner.clone(); 4853 + let fut = async move { 4854 + let method = GetFirstIndexSvc(inner); 4855 + let codec = tonic::codec::ProstCodec::default(); 4856 + let mut grpc = tonic::server::Grpc::new(codec) 4857 + .apply_compression_config( 4858 + accept_compression_encodings, 4859 + send_compression_encodings, 4860 + ) 4861 + .apply_max_message_size_config( 4862 + max_decoding_message_size, 4863 + max_encoding_message_size, 4864 + ); 4865 + let res = grpc.unary(method, req).await; 4866 + Ok(res) 4867 + }; 4868 + Box::pin(fut) 4869 + } 4870 + "/rockbox.v1alpha1.PlaylistService/GetDisplayIndex" => { 4871 + #[allow(non_camel_case_types)] 4872 + struct GetDisplayIndexSvc<T: PlaylistService>(pub Arc<T>); 4873 + impl<T: PlaylistService> 4874 + tonic::server::UnaryService<super::GetDisplayIndexRequest> 4875 + for GetDisplayIndexSvc<T> 4876 + { 4877 + type Response = super::GetDisplayIndexResponse; 4878 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 4879 + fn call( 4880 + &mut self, 4881 + request: tonic::Request<super::GetDisplayIndexRequest>, 4882 + ) -> Self::Future { 4883 + let inner = Arc::clone(&self.0); 4884 + let fut = async move { 4885 + <T as PlaylistService>::get_display_index(&inner, request).await 4886 + }; 4887 + Box::pin(fut) 4888 + } 4889 + } 4890 + let accept_compression_encodings = self.accept_compression_encodings; 4891 + let send_compression_encodings = self.send_compression_encodings; 4892 + let max_decoding_message_size = self.max_decoding_message_size; 4893 + let max_encoding_message_size = self.max_encoding_message_size; 4894 + let inner = self.inner.clone(); 4895 + let fut = async move { 4896 + let method = GetDisplayIndexSvc(inner); 4897 + let codec = tonic::codec::ProstCodec::default(); 4898 + let mut grpc = tonic::server::Grpc::new(codec) 4899 + .apply_compression_config( 4900 + accept_compression_encodings, 4901 + send_compression_encodings, 4902 + ) 4903 + .apply_max_message_size_config( 4904 + max_decoding_message_size, 4905 + max_encoding_message_size, 4906 + ); 4907 + let res = grpc.unary(method, req).await; 4908 + Ok(res) 4909 + }; 4910 + Box::pin(fut) 4911 + } 4912 + "/rockbox.v1alpha1.PlaylistService/Amount" => { 4913 + #[allow(non_camel_case_types)] 4914 + struct AmountSvc<T: PlaylistService>(pub Arc<T>); 4915 + impl<T: PlaylistService> tonic::server::UnaryService<super::AmountRequest> for AmountSvc<T> { 4916 + type Response = super::AmountResponse; 4917 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 4918 + fn call( 4919 + &mut self, 4920 + request: tonic::Request<super::AmountRequest>, 4921 + ) -> Self::Future { 4922 + let inner = Arc::clone(&self.0); 4923 + let fut = async move { 4924 + <T as PlaylistService>::amount(&inner, request).await 4925 + }; 4926 + Box::pin(fut) 4927 + } 4928 + } 4929 + let accept_compression_encodings = self.accept_compression_encodings; 4930 + let send_compression_encodings = self.send_compression_encodings; 4931 + let max_decoding_message_size = self.max_decoding_message_size; 4932 + let max_encoding_message_size = self.max_encoding_message_size; 4933 + let inner = self.inner.clone(); 4934 + let fut = async move { 4935 + let method = AmountSvc(inner); 4936 + let codec = tonic::codec::ProstCodec::default(); 4937 + let mut grpc = tonic::server::Grpc::new(codec) 4938 + .apply_compression_config( 4939 + accept_compression_encodings, 4940 + send_compression_encodings, 4941 + ) 4942 + .apply_max_message_size_config( 4943 + max_decoding_message_size, 4944 + max_encoding_message_size, 4945 + ); 4946 + let res = grpc.unary(method, req).await; 4947 + Ok(res) 4948 + }; 4949 + Box::pin(fut) 4950 + } 4951 + "/rockbox.v1alpha1.PlaylistService/PlaylistResume" => { 4952 + #[allow(non_camel_case_types)] 4953 + struct PlaylistResumeSvc<T: PlaylistService>(pub Arc<T>); 4954 + impl<T: PlaylistService> 4955 + tonic::server::UnaryService<super::PlaylistResumeRequest> 4956 + for PlaylistResumeSvc<T> 4957 + { 4958 + type Response = super::PlaylistResumeResponse; 4959 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 4960 + fn call( 4961 + &mut self, 4962 + request: tonic::Request<super::PlaylistResumeRequest>, 4963 + ) -> Self::Future { 4964 + let inner = Arc::clone(&self.0); 4965 + let fut = async move { 4966 + <T as PlaylistService>::playlist_resume(&inner, request).await 4967 + }; 4968 + Box::pin(fut) 4969 + } 4970 + } 4971 + let accept_compression_encodings = self.accept_compression_encodings; 4972 + let send_compression_encodings = self.send_compression_encodings; 4973 + let max_decoding_message_size = self.max_decoding_message_size; 4974 + let max_encoding_message_size = self.max_encoding_message_size; 4975 + let inner = self.inner.clone(); 4976 + let fut = async move { 4977 + let method = PlaylistResumeSvc(inner); 4978 + let codec = tonic::codec::ProstCodec::default(); 4979 + let mut grpc = tonic::server::Grpc::new(codec) 4980 + .apply_compression_config( 4981 + accept_compression_encodings, 4982 + send_compression_encodings, 4983 + ) 4984 + .apply_max_message_size_config( 4985 + max_decoding_message_size, 4986 + max_encoding_message_size, 4987 + ); 4988 + let res = grpc.unary(method, req).await; 4989 + Ok(res) 4990 + }; 4991 + Box::pin(fut) 4992 + } 4993 + "/rockbox.v1alpha1.PlaylistService/ResumeTrack" => { 4994 + #[allow(non_camel_case_types)] 4995 + struct ResumeTrackSvc<T: PlaylistService>(pub Arc<T>); 4996 + impl<T: PlaylistService> tonic::server::UnaryService<super::ResumeTrackRequest> 4997 + for ResumeTrackSvc<T> 4998 + { 4999 + type Response = super::ResumeTrackResponse; 5000 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 5001 + fn call( 5002 + &mut self, 5003 + request: tonic::Request<super::ResumeTrackRequest>, 5004 + ) -> Self::Future { 5005 + let inner = Arc::clone(&self.0); 5006 + let fut = async move { 5007 + <T as PlaylistService>::resume_track(&inner, request).await 5008 + }; 5009 + Box::pin(fut) 5010 + } 5011 + } 5012 + let accept_compression_encodings = self.accept_compression_encodings; 5013 + let send_compression_encodings = self.send_compression_encodings; 5014 + let max_decoding_message_size = self.max_decoding_message_size; 5015 + let max_encoding_message_size = self.max_encoding_message_size; 5016 + let inner = self.inner.clone(); 5017 + let fut = async move { 5018 + let method = ResumeTrackSvc(inner); 5019 + let codec = tonic::codec::ProstCodec::default(); 5020 + let mut grpc = tonic::server::Grpc::new(codec) 5021 + .apply_compression_config( 5022 + accept_compression_encodings, 5023 + send_compression_encodings, 5024 + ) 5025 + .apply_max_message_size_config( 5026 + max_decoding_message_size, 5027 + max_encoding_message_size, 5028 + ); 5029 + let res = grpc.unary(method, req).await; 5030 + Ok(res) 5031 + }; 5032 + Box::pin(fut) 5033 + } 5034 + "/rockbox.v1alpha1.PlaylistService/SetModified" => { 5035 + #[allow(non_camel_case_types)] 5036 + struct SetModifiedSvc<T: PlaylistService>(pub Arc<T>); 5037 + impl<T: PlaylistService> tonic::server::UnaryService<super::SetModifiedRequest> 5038 + for SetModifiedSvc<T> 5039 + { 5040 + type Response = super::SetModifiedResponse; 5041 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 5042 + fn call( 5043 + &mut self, 5044 + request: tonic::Request<super::SetModifiedRequest>, 5045 + ) -> Self::Future { 5046 + let inner = Arc::clone(&self.0); 5047 + let fut = async move { 5048 + <T as PlaylistService>::set_modified(&inner, request).await 5049 + }; 5050 + Box::pin(fut) 5051 + } 5052 + } 5053 + let accept_compression_encodings = self.accept_compression_encodings; 5054 + let send_compression_encodings = self.send_compression_encodings; 5055 + let max_decoding_message_size = self.max_decoding_message_size; 5056 + let max_encoding_message_size = self.max_encoding_message_size; 5057 + let inner = self.inner.clone(); 5058 + let fut = async move { 5059 + let method = SetModifiedSvc(inner); 5060 + let codec = tonic::codec::ProstCodec::default(); 5061 + let mut grpc = tonic::server::Grpc::new(codec) 5062 + .apply_compression_config( 5063 + accept_compression_encodings, 5064 + send_compression_encodings, 5065 + ) 5066 + .apply_max_message_size_config( 5067 + max_decoding_message_size, 5068 + max_encoding_message_size, 5069 + ); 5070 + let res = grpc.unary(method, req).await; 5071 + Ok(res) 5072 + }; 5073 + Box::pin(fut) 5074 + } 5075 + "/rockbox.v1alpha1.PlaylistService/Start" => { 5076 + #[allow(non_camel_case_types)] 5077 + struct StartSvc<T: PlaylistService>(pub Arc<T>); 5078 + impl<T: PlaylistService> tonic::server::UnaryService<super::StartRequest> for StartSvc<T> { 5079 + type Response = super::StartResponse; 5080 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 5081 + fn call( 5082 + &mut self, 5083 + request: tonic::Request<super::StartRequest>, 5084 + ) -> Self::Future { 5085 + let inner = Arc::clone(&self.0); 5086 + let fut = 5087 + async move { <T as PlaylistService>::start(&inner, request).await }; 5088 + Box::pin(fut) 5089 + } 5090 + } 5091 + let accept_compression_encodings = self.accept_compression_encodings; 5092 + let send_compression_encodings = self.send_compression_encodings; 5093 + let max_decoding_message_size = self.max_decoding_message_size; 5094 + let max_encoding_message_size = self.max_encoding_message_size; 5095 + let inner = self.inner.clone(); 5096 + let fut = async move { 5097 + let method = StartSvc(inner); 5098 + let codec = tonic::codec::ProstCodec::default(); 5099 + let mut grpc = tonic::server::Grpc::new(codec) 5100 + .apply_compression_config( 5101 + accept_compression_encodings, 5102 + send_compression_encodings, 5103 + ) 5104 + .apply_max_message_size_config( 5105 + max_decoding_message_size, 5106 + max_encoding_message_size, 5107 + ); 5108 + let res = grpc.unary(method, req).await; 5109 + Ok(res) 5110 + }; 5111 + Box::pin(fut) 5112 + } 5113 + "/rockbox.v1alpha1.PlaylistService/Sync" => { 5114 + #[allow(non_camel_case_types)] 5115 + struct SyncSvc<T: PlaylistService>(pub Arc<T>); 5116 + impl<T: PlaylistService> tonic::server::UnaryService<super::SyncRequest> for SyncSvc<T> { 5117 + type Response = super::SyncResponse; 5118 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 5119 + fn call( 5120 + &mut self, 5121 + request: tonic::Request<super::SyncRequest>, 5122 + ) -> Self::Future { 5123 + let inner = Arc::clone(&self.0); 5124 + let fut = 5125 + async move { <T as PlaylistService>::sync(&inner, request).await }; 5126 + Box::pin(fut) 5127 + } 5128 + } 5129 + let accept_compression_encodings = self.accept_compression_encodings; 5130 + let send_compression_encodings = self.send_compression_encodings; 5131 + let max_decoding_message_size = self.max_decoding_message_size; 5132 + let max_encoding_message_size = self.max_encoding_message_size; 5133 + let inner = self.inner.clone(); 5134 + let fut = async move { 5135 + let method = SyncSvc(inner); 5136 + let codec = tonic::codec::ProstCodec::default(); 5137 + let mut grpc = tonic::server::Grpc::new(codec) 5138 + .apply_compression_config( 5139 + accept_compression_encodings, 5140 + send_compression_encodings, 5141 + ) 5142 + .apply_max_message_size_config( 5143 + max_decoding_message_size, 5144 + max_encoding_message_size, 5145 + ); 5146 + let res = grpc.unary(method, req).await; 5147 + Ok(res) 5148 + }; 5149 + Box::pin(fut) 5150 + } 5151 + "/rockbox.v1alpha1.PlaylistService/RemoveAllTracks" => { 5152 + #[allow(non_camel_case_types)] 5153 + struct RemoveAllTracksSvc<T: PlaylistService>(pub Arc<T>); 5154 + impl<T: PlaylistService> 5155 + tonic::server::UnaryService<super::RemoveAllTracksRequest> 5156 + for RemoveAllTracksSvc<T> 5157 + { 5158 + type Response = super::RemoveAllTracksResponse; 5159 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 5160 + fn call( 5161 + &mut self, 5162 + request: tonic::Request<super::RemoveAllTracksRequest>, 5163 + ) -> Self::Future { 5164 + let inner = Arc::clone(&self.0); 5165 + let fut = async move { 5166 + <T as PlaylistService>::remove_all_tracks(&inner, request).await 5167 + }; 5168 + Box::pin(fut) 5169 + } 5170 + } 5171 + let accept_compression_encodings = self.accept_compression_encodings; 5172 + let send_compression_encodings = self.send_compression_encodings; 5173 + let max_decoding_message_size = self.max_decoding_message_size; 5174 + let max_encoding_message_size = self.max_encoding_message_size; 5175 + let inner = self.inner.clone(); 5176 + let fut = async move { 5177 + let method = RemoveAllTracksSvc(inner); 5178 + let codec = tonic::codec::ProstCodec::default(); 5179 + let mut grpc = tonic::server::Grpc::new(codec) 5180 + .apply_compression_config( 5181 + accept_compression_encodings, 5182 + send_compression_encodings, 5183 + ) 5184 + .apply_max_message_size_config( 5185 + max_decoding_message_size, 5186 + max_encoding_message_size, 5187 + ); 5188 + let res = grpc.unary(method, req).await; 5189 + Ok(res) 5190 + }; 5191 + Box::pin(fut) 5192 + } 5193 + "/rockbox.v1alpha1.PlaylistService/RemoveTracks" => { 5194 + #[allow(non_camel_case_types)] 5195 + struct RemoveTracksSvc<T: PlaylistService>(pub Arc<T>); 5196 + impl<T: PlaylistService> tonic::server::UnaryService<super::RemoveTracksRequest> 5197 + for RemoveTracksSvc<T> 5198 + { 5199 + type Response = super::RemoveTracksResponse; 5200 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 5201 + fn call( 5202 + &mut self, 5203 + request: tonic::Request<super::RemoveTracksRequest>, 5204 + ) -> Self::Future { 5205 + let inner = Arc::clone(&self.0); 5206 + let fut = async move { 5207 + <T as PlaylistService>::remove_tracks(&inner, request).await 5208 + }; 5209 + Box::pin(fut) 5210 + } 5211 + } 5212 + let accept_compression_encodings = self.accept_compression_encodings; 5213 + let send_compression_encodings = self.send_compression_encodings; 5214 + let max_decoding_message_size = self.max_decoding_message_size; 5215 + let max_encoding_message_size = self.max_encoding_message_size; 5216 + let inner = self.inner.clone(); 5217 + let fut = async move { 5218 + let method = RemoveTracksSvc(inner); 5219 + let codec = tonic::codec::ProstCodec::default(); 5220 + let mut grpc = tonic::server::Grpc::new(codec) 5221 + .apply_compression_config( 5222 + accept_compression_encodings, 5223 + send_compression_encodings, 5224 + ) 5225 + .apply_max_message_size_config( 5226 + max_decoding_message_size, 5227 + max_encoding_message_size, 5228 + ); 5229 + let res = grpc.unary(method, req).await; 5230 + Ok(res) 5231 + }; 5232 + Box::pin(fut) 5233 + } 5234 + "/rockbox.v1alpha1.PlaylistService/CreatePlaylist" => { 5235 + #[allow(non_camel_case_types)] 5236 + struct CreatePlaylistSvc<T: PlaylistService>(pub Arc<T>); 5237 + impl<T: PlaylistService> 5238 + tonic::server::UnaryService<super::CreatePlaylistRequest> 5239 + for CreatePlaylistSvc<T> 5240 + { 5241 + type Response = super::CreatePlaylistResponse; 5242 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 5243 + fn call( 5244 + &mut self, 5245 + request: tonic::Request<super::CreatePlaylistRequest>, 5246 + ) -> Self::Future { 5247 + let inner = Arc::clone(&self.0); 5248 + let fut = async move { 5249 + <T as PlaylistService>::create_playlist(&inner, request).await 5250 + }; 5251 + Box::pin(fut) 5252 + } 5253 + } 5254 + let accept_compression_encodings = self.accept_compression_encodings; 5255 + let send_compression_encodings = self.send_compression_encodings; 5256 + let max_decoding_message_size = self.max_decoding_message_size; 5257 + let max_encoding_message_size = self.max_encoding_message_size; 5258 + let inner = self.inner.clone(); 5259 + let fut = async move { 5260 + let method = CreatePlaylistSvc(inner); 5261 + let codec = tonic::codec::ProstCodec::default(); 5262 + let mut grpc = tonic::server::Grpc::new(codec) 5263 + .apply_compression_config( 5264 + accept_compression_encodings, 5265 + send_compression_encodings, 5266 + ) 5267 + .apply_max_message_size_config( 5268 + max_decoding_message_size, 5269 + max_encoding_message_size, 5270 + ); 5271 + let res = grpc.unary(method, req).await; 5272 + Ok(res) 5273 + }; 5274 + Box::pin(fut) 5275 + } 5276 + "/rockbox.v1alpha1.PlaylistService/InsertTracks" => { 5277 + #[allow(non_camel_case_types)] 5278 + struct InsertTracksSvc<T: PlaylistService>(pub Arc<T>); 5279 + impl<T: PlaylistService> tonic::server::UnaryService<super::InsertTracksRequest> 5280 + for InsertTracksSvc<T> 5281 + { 5282 + type Response = super::InsertTracksResponse; 5283 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 5284 + fn call( 5285 + &mut self, 5286 + request: tonic::Request<super::InsertTracksRequest>, 5287 + ) -> Self::Future { 5288 + let inner = Arc::clone(&self.0); 5289 + let fut = async move { 5290 + <T as PlaylistService>::insert_tracks(&inner, request).await 5291 + }; 5292 + Box::pin(fut) 5293 + } 5294 + } 5295 + let accept_compression_encodings = self.accept_compression_encodings; 5296 + let send_compression_encodings = self.send_compression_encodings; 5297 + let max_decoding_message_size = self.max_decoding_message_size; 5298 + let max_encoding_message_size = self.max_encoding_message_size; 5299 + let inner = self.inner.clone(); 5300 + let fut = async move { 5301 + let method = InsertTracksSvc(inner); 5302 + let codec = tonic::codec::ProstCodec::default(); 5303 + let mut grpc = tonic::server::Grpc::new(codec) 5304 + .apply_compression_config( 5305 + accept_compression_encodings, 5306 + send_compression_encodings, 5307 + ) 5308 + .apply_max_message_size_config( 5309 + max_decoding_message_size, 5310 + max_encoding_message_size, 5311 + ); 5312 + let res = grpc.unary(method, req).await; 5313 + Ok(res) 5314 + }; 5315 + Box::pin(fut) 5316 + } 5317 + "/rockbox.v1alpha1.PlaylistService/InsertDirectory" => { 5318 + #[allow(non_camel_case_types)] 5319 + struct InsertDirectorySvc<T: PlaylistService>(pub Arc<T>); 5320 + impl<T: PlaylistService> 5321 + tonic::server::UnaryService<super::InsertDirectoryRequest> 5322 + for InsertDirectorySvc<T> 5323 + { 5324 + type Response = super::InsertDirectoryResponse; 5325 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 5326 + fn call( 5327 + &mut self, 5328 + request: tonic::Request<super::InsertDirectoryRequest>, 5329 + ) -> Self::Future { 5330 + let inner = Arc::clone(&self.0); 5331 + let fut = async move { 5332 + <T as PlaylistService>::insert_directory(&inner, request).await 5333 + }; 5334 + Box::pin(fut) 5335 + } 5336 + } 5337 + let accept_compression_encodings = self.accept_compression_encodings; 5338 + let send_compression_encodings = self.send_compression_encodings; 5339 + let max_decoding_message_size = self.max_decoding_message_size; 5340 + let max_encoding_message_size = self.max_encoding_message_size; 5341 + let inner = self.inner.clone(); 5342 + let fut = async move { 5343 + let method = InsertDirectorySvc(inner); 5344 + let codec = tonic::codec::ProstCodec::default(); 5345 + let mut grpc = tonic::server::Grpc::new(codec) 5346 + .apply_compression_config( 5347 + accept_compression_encodings, 5348 + send_compression_encodings, 5349 + ) 5350 + .apply_max_message_size_config( 5351 + max_decoding_message_size, 5352 + max_encoding_message_size, 5353 + ); 5354 + let res = grpc.unary(method, req).await; 5355 + Ok(res) 5356 + }; 5357 + Box::pin(fut) 5358 + } 5359 + "/rockbox.v1alpha1.PlaylistService/InsertPlaylist" => { 5360 + #[allow(non_camel_case_types)] 5361 + struct InsertPlaylistSvc<T: PlaylistService>(pub Arc<T>); 5362 + impl<T: PlaylistService> 5363 + tonic::server::UnaryService<super::InsertPlaylistRequest> 5364 + for InsertPlaylistSvc<T> 5365 + { 5366 + type Response = super::InsertPlaylistResponse; 5367 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 5368 + fn call( 5369 + &mut self, 5370 + request: tonic::Request<super::InsertPlaylistRequest>, 5371 + ) -> Self::Future { 5372 + let inner = Arc::clone(&self.0); 5373 + let fut = async move { 5374 + <T as PlaylistService>::insert_playlist(&inner, request).await 5375 + }; 5376 + Box::pin(fut) 5377 + } 5378 + } 5379 + let accept_compression_encodings = self.accept_compression_encodings; 5380 + let send_compression_encodings = self.send_compression_encodings; 5381 + let max_decoding_message_size = self.max_decoding_message_size; 5382 + let max_encoding_message_size = self.max_encoding_message_size; 5383 + let inner = self.inner.clone(); 5384 + let fut = async move { 5385 + let method = InsertPlaylistSvc(inner); 5386 + let codec = tonic::codec::ProstCodec::default(); 5387 + let mut grpc = tonic::server::Grpc::new(codec) 5388 + .apply_compression_config( 5389 + accept_compression_encodings, 5390 + send_compression_encodings, 5391 + ) 5392 + .apply_max_message_size_config( 5393 + max_decoding_message_size, 5394 + max_encoding_message_size, 5395 + ); 5396 + let res = grpc.unary(method, req).await; 5397 + Ok(res) 5398 + }; 5399 + Box::pin(fut) 5400 + } 5401 + "/rockbox.v1alpha1.PlaylistService/InsertAlbum" => { 5402 + #[allow(non_camel_case_types)] 5403 + struct InsertAlbumSvc<T: PlaylistService>(pub Arc<T>); 5404 + impl<T: PlaylistService> tonic::server::UnaryService<super::InsertAlbumRequest> 5405 + for InsertAlbumSvc<T> 5406 + { 5407 + type Response = super::InsertAlbumResponse; 5408 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 5409 + fn call( 5410 + &mut self, 5411 + request: tonic::Request<super::InsertAlbumRequest>, 5412 + ) -> Self::Future { 5413 + let inner = Arc::clone(&self.0); 5414 + let fut = async move { 5415 + <T as PlaylistService>::insert_album(&inner, request).await 5416 + }; 5417 + Box::pin(fut) 5418 + } 5419 + } 5420 + let accept_compression_encodings = self.accept_compression_encodings; 5421 + let send_compression_encodings = self.send_compression_encodings; 5422 + let max_decoding_message_size = self.max_decoding_message_size; 5423 + let max_encoding_message_size = self.max_encoding_message_size; 5424 + let inner = self.inner.clone(); 5425 + let fut = async move { 5426 + let method = InsertAlbumSvc(inner); 5427 + let codec = tonic::codec::ProstCodec::default(); 5428 + let mut grpc = tonic::server::Grpc::new(codec) 5429 + .apply_compression_config( 5430 + accept_compression_encodings, 5431 + send_compression_encodings, 5432 + ) 5433 + .apply_max_message_size_config( 5434 + max_decoding_message_size, 5435 + max_encoding_message_size, 5436 + ); 5437 + let res = grpc.unary(method, req).await; 5438 + Ok(res) 5439 + }; 5440 + Box::pin(fut) 5441 + } 5442 + "/rockbox.v1alpha1.PlaylistService/InsertArtistTracks" => { 5443 + #[allow(non_camel_case_types)] 5444 + struct InsertArtistTracksSvc<T: PlaylistService>(pub Arc<T>); 5445 + impl<T: PlaylistService> 5446 + tonic::server::UnaryService<super::InsertArtistTracksRequest> 5447 + for InsertArtistTracksSvc<T> 5448 + { 5449 + type Response = super::InsertArtistTracksResponse; 5450 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 5451 + fn call( 5452 + &mut self, 5453 + request: tonic::Request<super::InsertArtistTracksRequest>, 5454 + ) -> Self::Future { 5455 + let inner = Arc::clone(&self.0); 5456 + let fut = async move { 5457 + <T as PlaylistService>::insert_artist_tracks(&inner, request).await 5458 + }; 5459 + Box::pin(fut) 5460 + } 5461 + } 5462 + let accept_compression_encodings = self.accept_compression_encodings; 5463 + let send_compression_encodings = self.send_compression_encodings; 5464 + let max_decoding_message_size = self.max_decoding_message_size; 5465 + let max_encoding_message_size = self.max_encoding_message_size; 5466 + let inner = self.inner.clone(); 5467 + let fut = async move { 5468 + let method = InsertArtistTracksSvc(inner); 5469 + let codec = tonic::codec::ProstCodec::default(); 5470 + let mut grpc = tonic::server::Grpc::new(codec) 5471 + .apply_compression_config( 5472 + accept_compression_encodings, 5473 + send_compression_encodings, 5474 + ) 5475 + .apply_max_message_size_config( 5476 + max_decoding_message_size, 5477 + max_encoding_message_size, 5478 + ); 5479 + let res = grpc.unary(method, req).await; 5480 + Ok(res) 5481 + }; 5482 + Box::pin(fut) 5483 + } 5484 + "/rockbox.v1alpha1.PlaylistService/ShufflePlaylist" => { 5485 + #[allow(non_camel_case_types)] 5486 + struct ShufflePlaylistSvc<T: PlaylistService>(pub Arc<T>); 5487 + impl<T: PlaylistService> 5488 + tonic::server::UnaryService<super::ShufflePlaylistRequest> 5489 + for ShufflePlaylistSvc<T> 5490 + { 5491 + type Response = super::ShufflePlaylistResponse; 5492 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 5493 + fn call( 5494 + &mut self, 5495 + request: tonic::Request<super::ShufflePlaylistRequest>, 5496 + ) -> Self::Future { 5497 + let inner = Arc::clone(&self.0); 5498 + let fut = async move { 5499 + <T as PlaylistService>::shuffle_playlist(&inner, request).await 5500 + }; 5501 + Box::pin(fut) 5502 + } 5503 + } 5504 + let accept_compression_encodings = self.accept_compression_encodings; 5505 + let send_compression_encodings = self.send_compression_encodings; 5506 + let max_decoding_message_size = self.max_decoding_message_size; 5507 + let max_encoding_message_size = self.max_encoding_message_size; 5508 + let inner = self.inner.clone(); 5509 + let fut = async move { 5510 + let method = ShufflePlaylistSvc(inner); 5511 + let codec = tonic::codec::ProstCodec::default(); 5512 + let mut grpc = tonic::server::Grpc::new(codec) 5513 + .apply_compression_config( 5514 + accept_compression_encodings, 5515 + send_compression_encodings, 5516 + ) 5517 + .apply_max_message_size_config( 5518 + max_decoding_message_size, 5519 + max_encoding_message_size, 5520 + ); 5521 + let res = grpc.unary(method, req).await; 5522 + Ok(res) 5523 + }; 5524 + Box::pin(fut) 5525 + } 5526 + _ => Box::pin(async move { 5527 + let mut response = http::Response::new(empty_body()); 5528 + let headers = response.headers_mut(); 5529 + headers.insert( 5530 + tonic::Status::GRPC_STATUS, 5531 + (tonic::Code::Unimplemented as i32).into(), 5532 + ); 5533 + headers.insert( 5534 + http::header::CONTENT_TYPE, 5535 + tonic::metadata::GRPC_CONTENT_TYPE, 5536 + ); 5537 + Ok(response) 5538 + }), 5539 + } 5540 + } 5541 + } 5542 + impl<T> Clone for PlaylistServiceServer<T> { 5543 + fn clone(&self) -> Self { 5544 + let inner = self.inner.clone(); 5545 + Self { 5546 + inner, 5547 + accept_compression_encodings: self.accept_compression_encodings, 5548 + send_compression_encodings: self.send_compression_encodings, 5549 + max_decoding_message_size: self.max_decoding_message_size, 5550 + max_encoding_message_size: self.max_encoding_message_size, 5551 + } 5552 + } 5553 + } 5554 + /// Generated gRPC service name 5555 + pub const SERVICE_NAME: &str = "rockbox.v1alpha1.PlaylistService"; 5556 + impl<T> tonic::server::NamedService for PlaylistServiceServer<T> { 5557 + const NAME: &'static str = SERVICE_NAME; 5558 + } 5559 + } 5560 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 5561 + pub struct GetSettingsListRequest { 5562 + #[prost(int32, tag = "1")] 5563 + pub count: i32, 5564 + } 5565 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 5566 + pub struct GetSettingsListResponse {} 5567 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 5568 + pub struct GetGlobalSettingsRequest {} 5569 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 5570 + pub struct ReplaygainSettings { 5571 + #[prost(bool, tag = "1")] 5572 + pub noclip: bool, 5573 + #[prost(int32, tag = "2")] 5574 + pub r#type: i32, 5575 + #[prost(int32, tag = "3")] 5576 + pub preamp: i32, 5577 + } 5578 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 5579 + pub struct EqBandSetting { 5580 + #[prost(int32, tag = "1")] 5581 + pub cutoff: i32, 5582 + #[prost(int32, tag = "2")] 5583 + pub q: i32, 5584 + #[prost(int32, tag = "3")] 5585 + pub gain: i32, 5586 + } 5587 + #[derive(Clone, PartialEq, ::prost::Message)] 5588 + pub struct SettingsList { 5589 + #[prost(uint32, tag = "1")] 5590 + pub flags: u32, 5591 + #[prost(int32, tag = "2")] 5592 + pub lang_id: i32, 5593 + #[prost(string, tag = "3")] 5594 + pub cfg_name: ::prost::alloc::string::String, 5595 + #[prost(string, tag = "4")] 5596 + pub cfg_vals: ::prost::alloc::string::String, 5597 + } 5598 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 5599 + pub struct CompressorSettings { 5600 + #[prost(int32, tag = "1")] 5601 + pub threshold: i32, 5602 + #[prost(int32, tag = "2")] 5603 + pub makeup_gain: i32, 5604 + #[prost(int32, tag = "3")] 5605 + pub ratio: i32, 5606 + #[prost(int32, tag = "4")] 5607 + pub knee: i32, 5608 + #[prost(int32, tag = "5")] 5609 + pub release_time: i32, 5610 + #[prost(int32, tag = "6")] 5611 + pub attack_time: i32, 5612 + } 5613 + #[derive(Clone, PartialEq, ::prost::Message)] 5614 + pub struct GetGlobalSettingsResponse { 5615 + #[prost(int32, tag = "1")] 5616 + pub volume: i32, 5617 + #[prost(int32, tag = "2")] 5618 + pub balance: i32, 5619 + #[prost(int32, tag = "3")] 5620 + pub bass: i32, 5621 + #[prost(int32, tag = "4")] 5622 + pub treble: i32, 5623 + #[prost(int32, tag = "5")] 5624 + pub channel_config: i32, 5625 + #[prost(int32, tag = "6")] 5626 + pub stereo_width: i32, 5627 + #[prost(int32, tag = "7")] 5628 + pub bass_cutoff: i32, 5629 + #[prost(int32, tag = "8")] 5630 + pub treble_cutoff: i32, 5631 + #[prost(int32, tag = "9")] 5632 + pub crossfade: i32, 5633 + #[prost(int32, tag = "10")] 5634 + pub crossfade_fade_in_delay: i32, 5635 + #[prost(int32, tag = "11")] 5636 + pub crossfade_fade_out_delay: i32, 5637 + #[prost(int32, tag = "12")] 5638 + pub crossfade_fade_in_duration: i32, 5639 + #[prost(int32, tag = "13")] 5640 + pub crossfade_fade_out_duration: i32, 5641 + #[prost(int32, tag = "14")] 5642 + pub crossfade_fade_out_mixmode: i32, 5643 + #[prost(message, optional, tag = "15")] 5644 + pub replaygain_settings: ::core::option::Option<ReplaygainSettings>, 5645 + #[prost(int32, tag = "16")] 5646 + pub crossfeed: i32, 5647 + #[prost(uint32, tag = "17")] 5648 + pub crossfeed_direct_gain: u32, 5649 + #[prost(uint32, tag = "18")] 5650 + pub crossfeed_cross_gain: u32, 5651 + #[prost(uint32, tag = "19")] 5652 + pub crossfeed_hf_attenuation: u32, 5653 + #[prost(uint32, tag = "20")] 5654 + pub crossfeed_hf_cutoff: u32, 5655 + #[prost(bool, tag = "21")] 5656 + pub eq_enabled: bool, 5657 + #[prost(uint32, tag = "22")] 5658 + pub eq_precut: u32, 5659 + #[prost(message, repeated, tag = "23")] 5660 + pub eq_band_settings: ::prost::alloc::vec::Vec<EqBandSetting>, 5661 + #[prost(int32, tag = "24")] 5662 + pub beep: i32, 5663 + #[prost(int32, tag = "25")] 5664 + pub keyclick: i32, 5665 + #[prost(int32, tag = "26")] 5666 + pub keyclick_repeats: i32, 5667 + #[prost(bool, tag = "27")] 5668 + pub dithering_enabled: bool, 5669 + #[prost(bool, tag = "28")] 5670 + pub timestretch_enabled: bool, 5671 + #[prost(int32, tag = "29")] 5672 + pub list_accel_start_delay: i32, 5673 + #[prost(int32, tag = "30")] 5674 + pub list_accel_wait: i32, 5675 + #[prost(int32, tag = "31")] 5676 + pub touchpad_sensitivity: i32, 5677 + #[prost(int32, tag = "32")] 5678 + pub touchpad_deadzone: i32, 5679 + #[prost(int32, tag = "33")] 5680 + pub pause_rewind: i32, 5681 + #[prost(int32, tag = "34")] 5682 + pub unplug_mode: i32, 5683 + #[prost(bool, tag = "35")] 5684 + pub unplug_autoresume: bool, 5685 + #[prost(int32, tag = "37")] 5686 + pub timeformat: i32, 5687 + #[prost(int32, tag = "38")] 5688 + pub disk_spindown: i32, 5689 + #[prost(int32, tag = "39")] 5690 + pub buffer_margin: i32, 5691 + #[prost(int32, tag = "40")] 5692 + pub dirfilter: i32, 5693 + #[prost(int32, tag = "41")] 5694 + pub show_filename_ext: i32, 5695 + #[prost(int32, tag = "42")] 5696 + pub default_codepage: i32, 5697 + #[prost(bool, tag = "43")] 5698 + pub hold_lr_for_scroll_in_list: bool, 5699 + #[prost(bool, tag = "44")] 5700 + pub play_selected: bool, 5701 + #[prost(int32, tag = "45")] 5702 + pub single_mode: i32, 5703 + #[prost(bool, tag = "46")] 5704 + pub party_mode: bool, 5705 + #[prost(bool, tag = "48")] 5706 + pub car_adapter_mode: bool, 5707 + #[prost(int32, tag = "49")] 5708 + pub car_adapter_mode_delay: i32, 5709 + #[prost(int32, tag = "50")] 5710 + pub start_in_screen: i32, 5711 + #[prost(int32, tag = "51")] 5712 + pub ff_rewind_min_step: i32, 5713 + #[prost(int32, tag = "52")] 5714 + pub ff_rewind_accel: i32, 5715 + #[prost(int32, tag = "53")] 5716 + pub peak_meter_release: i32, 5717 + #[prost(int32, tag = "54")] 5718 + pub peak_meter_hold: i32, 5719 + #[prost(int32, tag = "55")] 5720 + pub peak_meter_clip_hold: i32, 5721 + #[prost(bool, tag = "56")] 5722 + pub peak_meter_dbfs: bool, 5723 + #[prost(int32, tag = "57")] 5724 + pub peak_meter_min: i32, 5725 + #[prost(int32, tag = "58")] 5726 + pub peak_meter_max: i32, 5727 + #[prost(string, tag = "59")] 5728 + pub wps_file: ::prost::alloc::string::String, 5729 + #[prost(string, tag = "60")] 5730 + pub sbs_file: ::prost::alloc::string::String, 5731 + #[prost(string, tag = "61")] 5732 + pub lang_file: ::prost::alloc::string::String, 5733 + #[prost(string, tag = "62")] 5734 + pub playlist_catalog_dir: ::prost::alloc::string::String, 5735 + #[prost(int32, tag = "63")] 5736 + pub skip_length: i32, 5737 + #[prost(int32, tag = "64")] 5738 + pub max_files_in_dir: i32, 5739 + #[prost(int32, tag = "65")] 5740 + pub max_files_in_playlist: i32, 5741 + #[prost(int32, tag = "66")] 5742 + pub volume_type: i32, 5743 + #[prost(int32, tag = "67")] 5744 + pub battery_display: i32, 5745 + #[prost(bool, tag = "68")] 5746 + pub show_icons: bool, 5747 + #[prost(int32, tag = "69")] 5748 + pub statusbar: i32, 5749 + #[prost(int32, tag = "70")] 5750 + pub scrollbar: i32, 5751 + #[prost(int32, tag = "71")] 5752 + pub scrollbar_width: i32, 5753 + #[prost(int32, tag = "72")] 5754 + pub list_line_padding: i32, 5755 + #[prost(int32, tag = "73")] 5756 + pub list_separator_color: i32, 5757 + #[prost(bool, tag = "74")] 5758 + pub browse_current: bool, 5759 + #[prost(bool, tag = "75")] 5760 + pub scroll_paginated: bool, 5761 + #[prost(bool, tag = "76")] 5762 + pub list_wraparound: bool, 5763 + #[prost(int32, tag = "77")] 5764 + pub list_order: i32, 5765 + #[prost(int32, tag = "78")] 5766 + pub scroll_speed: i32, 5767 + #[prost(int32, tag = "79")] 5768 + pub bidir_limit: i32, 5769 + #[prost(int32, tag = "80")] 5770 + pub scroll_delay: i32, 5771 + #[prost(int32, tag = "81")] 5772 + pub scroll_step: i32, 5773 + #[prost(int32, tag = "82")] 5774 + pub autoloadbookmark: i32, 5775 + #[prost(int32, tag = "83")] 5776 + pub autocreatebookmark: i32, 5777 + #[prost(bool, tag = "84")] 5778 + pub autoupdatebookmark: bool, 5779 + #[prost(int32, tag = "85")] 5780 + pub usemrb: i32, 5781 + #[prost(bool, tag = "86")] 5782 + pub dircache: bool, 5783 + #[prost(int32, tag = "87")] 5784 + pub tagcache_ram: i32, 5785 + #[prost(bool, tag = "88")] 5786 + pub tagcache_autoupdate: bool, 5787 + #[prost(bool, tag = "89")] 5788 + pub autoresume_enable: bool, 5789 + #[prost(int32, tag = "90")] 5790 + pub autoresume_automatic: i32, 5791 + #[prost(string, tag = "91")] 5792 + pub autoresume_paths: ::prost::alloc::string::String, 5793 + #[prost(bool, tag = "92")] 5794 + pub runtimedb: bool, 5795 + #[prost(string, tag = "93")] 5796 + pub tagcache_scan_paths: ::prost::alloc::string::String, 5797 + #[prost(string, tag = "94")] 5798 + pub tagcache_db_path: ::prost::alloc::string::String, 5799 + #[prost(string, tag = "95")] 5800 + pub backdrop_file: ::prost::alloc::string::String, 5801 + #[prost(int32, tag = "96")] 5802 + pub bg_color: i32, 5803 + #[prost(int32, tag = "97")] 5804 + pub fg_color: i32, 5805 + #[prost(int32, tag = "98")] 5806 + pub lss_color: i32, 5807 + #[prost(int32, tag = "99")] 5808 + pub lse_color: i32, 5809 + #[prost(int32, tag = "100")] 5810 + pub lst_color: i32, 5811 + #[prost(string, tag = "101")] 5812 + pub colors_file: ::prost::alloc::string::String, 5813 + #[prost(int32, tag = "102")] 5814 + pub browser_default: i32, 5815 + #[prost(int32, tag = "103")] 5816 + pub repeat_mode: i32, 5817 + #[prost(int32, tag = "104")] 5818 + pub next_folder: i32, 5819 + #[prost(bool, tag = "105")] 5820 + pub constrain_next_folder: bool, 5821 + #[prost(int32, tag = "106")] 5822 + pub recursive_dir_insert: i32, 5823 + #[prost(bool, tag = "107")] 5824 + pub fade_on_stop: bool, 5825 + #[prost(bool, tag = "108")] 5826 + pub playlist_shuffle: bool, 5827 + #[prost(bool, tag = "109")] 5828 + pub warnon_erase_dynplaylist: bool, 5829 + #[prost(bool, tag = "110")] 5830 + pub keep_current_track_on_replace_playlist: bool, 5831 + #[prost(bool, tag = "111")] 5832 + pub show_shuffled_adding_options: bool, 5833 + #[prost(int32, tag = "112")] 5834 + pub show_queue_options: i32, 5835 + #[prost(int32, tag = "113")] 5836 + pub album_art: i32, 5837 + #[prost(bool, tag = "114")] 5838 + pub rewind_across_tracks: bool, 5839 + #[prost(bool, tag = "115")] 5840 + pub playlist_viewer_icons: bool, 5841 + #[prost(bool, tag = "116")] 5842 + pub playlist_viewer_indices: bool, 5843 + #[prost(int32, tag = "117")] 5844 + pub playlist_viewer_track_display: i32, 5845 + #[prost(bool, tag = "118")] 5846 + pub sort_case: bool, 5847 + #[prost(int32, tag = "119")] 5848 + pub sort_dir: i32, 5849 + #[prost(int32, tag = "120")] 5850 + pub sort_file: i32, 5851 + #[prost(int32, tag = "121")] 5852 + pub interpret_numbers: i32, 5853 + #[prost(int32, tag = "122")] 5854 + pub poweroff: i32, 5855 + #[prost(bool, tag = "123")] 5856 + pub spdif_enable: bool, 5857 + #[prost(int32, tag = "124")] 5858 + pub contrast: i32, 5859 + #[prost(bool, tag = "125")] 5860 + pub invert: bool, 5861 + #[prost(bool, tag = "126")] 5862 + pub flip_display: bool, 5863 + #[prost(int32, tag = "127")] 5864 + pub cursor_style: i32, 5865 + #[prost(int32, tag = "128")] 5866 + pub screen_scroll_step: i32, 5867 + #[prost(int32, tag = "129")] 5868 + pub show_path_in_browser: i32, 5869 + #[prost(bool, tag = "130")] 5870 + pub offset_out_of_view: bool, 5871 + #[prost(bool, tag = "131")] 5872 + pub disable_mainmenu_scrolling: bool, 5873 + #[prost(string, tag = "132")] 5874 + pub icon_file: ::prost::alloc::string::String, 5875 + #[prost(string, tag = "133")] 5876 + pub viewers_icon_file: ::prost::alloc::string::String, 5877 + #[prost(string, tag = "134")] 5878 + pub font_file: ::prost::alloc::string::String, 5879 + #[prost(int32, tag = "135")] 5880 + pub glyphs_to_cache: i32, 5881 + #[prost(string, tag = "136")] 5882 + pub kbd_file: ::prost::alloc::string::String, 5883 + #[prost(int32, tag = "137")] 5884 + pub backlight_timeout: i32, 5885 + #[prost(bool, tag = "138")] 5886 + pub caption_backlight: bool, 5887 + #[prost(bool, tag = "139")] 5888 + pub bl_filter_first_keypress: bool, 5889 + #[prost(int32, tag = "140")] 5890 + pub backlight_timeout_plugged: i32, 5891 + #[prost(bool, tag = "141")] 5892 + pub bt_selective_softlock_actions: bool, 5893 + #[prost(int32, tag = "142")] 5894 + pub bt_selective_softlock_actions_mask: i32, 5895 + #[prost(bool, tag = "143")] 5896 + pub bl_selective_actions: bool, 5897 + #[prost(int32, tag = "144")] 5898 + pub bl_selective_actions_mask: i32, 5899 + #[prost(int32, tag = "145")] 5900 + pub backlight_on_button_hold: i32, 5901 + #[prost(int32, tag = "146")] 5902 + pub lcd_sleep_after_backlight_off: i32, 5903 + #[prost(int32, tag = "147")] 5904 + pub brightness: i32, 5905 + #[prost(int32, tag = "148")] 5906 + pub speaker_mode: i32, 5907 + #[prost(bool, tag = "149")] 5908 + pub prevent_skip: bool, 5909 + #[prost(int32, tag = "150")] 5910 + pub touch_mode: i32, 5911 + #[prost(bool, tag = "151")] 5912 + pub pitch_mode_semitone: bool, 5913 + #[prost(bool, tag = "152")] 5914 + pub pitch_mode_timestretch: bool, 5915 + #[prost(string, tag = "153")] 5916 + pub player_name: ::prost::alloc::string::String, 5917 + #[prost(message, optional, tag = "154")] 5918 + pub compressor_settings: ::core::option::Option<CompressorSettings>, 5919 + #[prost(int32, tag = "155")] 5920 + pub sleeptimer_duration: i32, 5921 + #[prost(bool, tag = "156")] 5922 + pub sleeptimer_on_startup: bool, 5923 + #[prost(bool, tag = "157")] 5924 + pub keypress_restarts_sleeptimer: bool, 5925 + #[prost(bool, tag = "158")] 5926 + pub show_shutdown_message: bool, 5927 + #[prost(int32, tag = "159")] 5928 + pub hotkey_wps: i32, 5929 + #[prost(int32, tag = "160")] 5930 + pub hotkey_tree: i32, 5931 + #[prost(int32, tag = "161")] 5932 + pub resume_rewind: i32, 5933 + #[prost(int32, tag = "162")] 5934 + pub depth_3d: i32, 5935 + #[prost(int32, tag = "163")] 5936 + pub roll_off: i32, 5937 + #[prost(int32, tag = "164")] 5938 + pub power_mode: i32, 5939 + #[prost(bool, tag = "165")] 5940 + pub keyclick_hardware: bool, 5941 + #[prost(string, tag = "166")] 5942 + pub start_directory: ::prost::alloc::string::String, 5943 + #[prost(bool, tag = "167")] 5944 + pub root_menu_customized: bool, 5945 + #[prost(bool, tag = "168")] 5946 + pub shortcuts_replaces_qs: bool, 5947 + #[prost(int32, tag = "169")] 5948 + pub play_frequency: i32, 5949 + #[prost(int32, tag = "170")] 5950 + pub volume_limit: i32, 5951 + #[prost(int32, tag = "171")] 5952 + pub volume_adjust_mode: i32, 5953 + #[prost(int32, tag = "172")] 5954 + pub volume_adjust_norm_steps: i32, 5955 + #[prost(int32, tag = "173")] 5956 + pub surround_enabled: i32, 5957 + #[prost(int32, tag = "174")] 5958 + pub surround_balance: i32, 5959 + #[prost(int32, tag = "175")] 5960 + pub surround_fx1: i32, 5961 + #[prost(int32, tag = "176")] 5962 + pub surround_fx2: i32, 5963 + #[prost(bool, tag = "177")] 5964 + pub surround_method2: bool, 5965 + #[prost(int32, tag = "178")] 5966 + pub surround_mix: i32, 5967 + #[prost(int32, tag = "179")] 5968 + pub pbe: i32, 5969 + #[prost(int32, tag = "180")] 5970 + pub pbe_precut: i32, 5971 + #[prost(int32, tag = "181")] 5972 + pub afr_enabled: i32, 5973 + #[prost(int32, tag = "182")] 5974 + pub governor: i32, 5975 + #[prost(int32, tag = "183")] 5976 + pub stereosw_mode: i32, 5977 + #[prost(string, tag = "184")] 5978 + pub music_dir: ::prost::alloc::string::String, 5979 + } 5980 + #[derive(Clone, PartialEq, ::prost::Message)] 5981 + pub struct SaveSettingsRequest { 5982 + #[prost(string, optional, tag = "1")] 5983 + pub music_dir: ::core::option::Option<::prost::alloc::string::String>, 5984 + #[prost(bool, optional, tag = "2")] 5985 + pub playlist_shuffle: ::core::option::Option<bool>, 5986 + #[prost(int32, optional, tag = "3")] 5987 + pub repeat_mode: ::core::option::Option<i32>, 5988 + #[prost(int32, optional, tag = "4")] 5989 + pub bass: ::core::option::Option<i32>, 5990 + #[prost(int32, optional, tag = "5")] 5991 + pub treble: ::core::option::Option<i32>, 5992 + #[prost(int32, optional, tag = "6")] 5993 + pub bass_cutoff: ::core::option::Option<i32>, 5994 + #[prost(int32, optional, tag = "7")] 5995 + pub treble_cutoff: ::core::option::Option<i32>, 5996 + #[prost(int32, optional, tag = "8")] 5997 + pub crossfade: ::core::option::Option<i32>, 5998 + #[prost(bool, optional, tag = "9")] 5999 + pub fade_on_stop: ::core::option::Option<bool>, 6000 + #[prost(int32, optional, tag = "10")] 6001 + pub fade_in_delay: ::core::option::Option<i32>, 6002 + #[prost(int32, optional, tag = "11")] 6003 + pub fade_in_duration: ::core::option::Option<i32>, 6004 + #[prost(int32, optional, tag = "12")] 6005 + pub fade_out_delay: ::core::option::Option<i32>, 6006 + #[prost(int32, optional, tag = "13")] 6007 + pub fade_out_duration: ::core::option::Option<i32>, 6008 + #[prost(int32, optional, tag = "14")] 6009 + pub fade_out_mixmode: ::core::option::Option<i32>, 6010 + #[prost(int32, optional, tag = "15")] 6011 + pub balance: ::core::option::Option<i32>, 6012 + #[prost(int32, optional, tag = "16")] 6013 + pub stereo_width: ::core::option::Option<i32>, 6014 + #[prost(int32, optional, tag = "17")] 6015 + pub stereosw_mode: ::core::option::Option<i32>, 6016 + #[prost(int32, optional, tag = "18")] 6017 + pub surround_enabled: ::core::option::Option<i32>, 6018 + #[prost(int32, optional, tag = "19")] 6019 + pub surround_balance: ::core::option::Option<i32>, 6020 + #[prost(int32, optional, tag = "20")] 6021 + pub surround_fx1: ::core::option::Option<i32>, 6022 + #[prost(int32, optional, tag = "21")] 6023 + pub surround_fx2: ::core::option::Option<i32>, 6024 + #[prost(bool, optional, tag = "22")] 6025 + pub party_mode: ::core::option::Option<bool>, 6026 + #[prost(int32, optional, tag = "23")] 6027 + pub channel_config: ::core::option::Option<i32>, 6028 + #[prost(string, optional, tag = "24")] 6029 + pub player_name: ::core::option::Option<::prost::alloc::string::String>, 6030 + #[prost(bool, optional, tag = "25")] 6031 + pub eq_enabled: ::core::option::Option<bool>, 6032 + #[prost(message, repeated, tag = "26")] 6033 + pub eq_band_settings: ::prost::alloc::vec::Vec<EqBandSetting>, 6034 + #[prost(message, optional, tag = "27")] 6035 + pub replaygain_settings: ::core::option::Option<ReplaygainSettings>, 6036 + #[prost(message, optional, tag = "28")] 6037 + pub compressor_settings: ::core::option::Option<CompressorSettings>, 6038 + } 6039 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6040 + pub struct SaveSettingsResponse {} 6041 + /// Generated client implementations. 6042 + pub mod settings_service_client { 6043 + #![allow( 6044 + unused_variables, 6045 + dead_code, 6046 + missing_docs, 6047 + clippy::wildcard_imports, 6048 + clippy::let_unit_value 6049 + )] 6050 + use tonic::codegen::http::Uri; 6051 + use tonic::codegen::*; 6052 + #[derive(Debug, Clone)] 6053 + pub struct SettingsServiceClient<T> { 6054 + inner: tonic::client::Grpc<T>, 6055 + } 6056 + impl SettingsServiceClient<tonic::transport::Channel> { 6057 + /// Attempt to create a new client by connecting to a given endpoint. 6058 + pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error> 6059 + where 6060 + D: TryInto<tonic::transport::Endpoint>, 6061 + D::Error: Into<StdError>, 6062 + { 6063 + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; 6064 + Ok(Self::new(conn)) 6065 + } 6066 + } 6067 + impl<T> SettingsServiceClient<T> 6068 + where 6069 + T: tonic::client::GrpcService<tonic::body::BoxBody>, 6070 + T::Error: Into<StdError>, 6071 + T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static, 6072 + <T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send, 6073 + { 6074 + pub fn new(inner: T) -> Self { 6075 + let inner = tonic::client::Grpc::new(inner); 6076 + Self { inner } 6077 + } 6078 + pub fn with_origin(inner: T, origin: Uri) -> Self { 6079 + let inner = tonic::client::Grpc::with_origin(inner, origin); 6080 + Self { inner } 6081 + } 6082 + pub fn with_interceptor<F>( 6083 + inner: T, 6084 + interceptor: F, 6085 + ) -> SettingsServiceClient<InterceptedService<T, F>> 6086 + where 6087 + F: tonic::service::Interceptor, 6088 + T::ResponseBody: Default, 6089 + T: tonic::codegen::Service< 6090 + http::Request<tonic::body::BoxBody>, 6091 + Response = http::Response< 6092 + <T as tonic::client::GrpcService<tonic::body::BoxBody>>::ResponseBody, 6093 + >, 6094 + >, 6095 + <T as tonic::codegen::Service<http::Request<tonic::body::BoxBody>>>::Error: 6096 + Into<StdError> + std::marker::Send + std::marker::Sync, 6097 + { 6098 + SettingsServiceClient::new(InterceptedService::new(inner, interceptor)) 6099 + } 6100 + /// Compress requests with the given encoding. 6101 + /// 6102 + /// This requires the server to support it otherwise it might respond with an 6103 + /// error. 6104 + #[must_use] 6105 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 6106 + self.inner = self.inner.send_compressed(encoding); 6107 + self 6108 + } 6109 + /// Enable decompressing responses. 6110 + #[must_use] 6111 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 6112 + self.inner = self.inner.accept_compressed(encoding); 6113 + self 6114 + } 6115 + /// Limits the maximum size of a decoded message. 6116 + /// 6117 + /// Default: `4MB` 6118 + #[must_use] 6119 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 6120 + self.inner = self.inner.max_decoding_message_size(limit); 6121 + self 6122 + } 6123 + /// Limits the maximum size of an encoded message. 6124 + /// 6125 + /// Default: `usize::MAX` 6126 + #[must_use] 6127 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 6128 + self.inner = self.inner.max_encoding_message_size(limit); 6129 + self 6130 + } 6131 + pub async fn get_settings_list( 6132 + &mut self, 6133 + request: impl tonic::IntoRequest<super::GetSettingsListRequest>, 6134 + ) -> std::result::Result<tonic::Response<super::GetSettingsListResponse>, tonic::Status> 6135 + { 6136 + self.inner.ready().await.map_err(|e| { 6137 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 6138 + })?; 6139 + let codec = tonic::codec::ProstCodec::default(); 6140 + let path = http::uri::PathAndQuery::from_static( 6141 + "/rockbox.v1alpha1.SettingsService/GetSettingsList", 6142 + ); 6143 + let mut req = request.into_request(); 6144 + req.extensions_mut().insert(GrpcMethod::new( 6145 + "rockbox.v1alpha1.SettingsService", 6146 + "GetSettingsList", 6147 + )); 6148 + self.inner.unary(req, path, codec).await 6149 + } 6150 + pub async fn get_global_settings( 6151 + &mut self, 6152 + request: impl tonic::IntoRequest<super::GetGlobalSettingsRequest>, 6153 + ) -> std::result::Result<tonic::Response<super::GetGlobalSettingsResponse>, tonic::Status> 6154 + { 6155 + self.inner.ready().await.map_err(|e| { 6156 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 6157 + })?; 6158 + let codec = tonic::codec::ProstCodec::default(); 6159 + let path = http::uri::PathAndQuery::from_static( 6160 + "/rockbox.v1alpha1.SettingsService/GetGlobalSettings", 6161 + ); 6162 + let mut req = request.into_request(); 6163 + req.extensions_mut().insert(GrpcMethod::new( 6164 + "rockbox.v1alpha1.SettingsService", 6165 + "GetGlobalSettings", 6166 + )); 6167 + self.inner.unary(req, path, codec).await 6168 + } 6169 + pub async fn save_settings( 6170 + &mut self, 6171 + request: impl tonic::IntoRequest<super::SaveSettingsRequest>, 6172 + ) -> std::result::Result<tonic::Response<super::SaveSettingsResponse>, tonic::Status> 6173 + { 6174 + self.inner.ready().await.map_err(|e| { 6175 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 6176 + })?; 6177 + let codec = tonic::codec::ProstCodec::default(); 6178 + let path = http::uri::PathAndQuery::from_static( 6179 + "/rockbox.v1alpha1.SettingsService/SaveSettings", 6180 + ); 6181 + let mut req = request.into_request(); 6182 + req.extensions_mut().insert(GrpcMethod::new( 6183 + "rockbox.v1alpha1.SettingsService", 6184 + "SaveSettings", 6185 + )); 6186 + self.inner.unary(req, path, codec).await 6187 + } 6188 + } 6189 + } 6190 + /// Generated server implementations. 6191 + pub mod settings_service_server { 6192 + #![allow( 6193 + unused_variables, 6194 + dead_code, 6195 + missing_docs, 6196 + clippy::wildcard_imports, 6197 + clippy::let_unit_value 6198 + )] 6199 + use tonic::codegen::*; 6200 + /// Generated trait containing gRPC methods that should be implemented for use with SettingsServiceServer. 6201 + #[async_trait] 6202 + pub trait SettingsService: std::marker::Send + std::marker::Sync + 'static { 6203 + async fn get_settings_list( 6204 + &self, 6205 + request: tonic::Request<super::GetSettingsListRequest>, 6206 + ) -> std::result::Result<tonic::Response<super::GetSettingsListResponse>, tonic::Status>; 6207 + async fn get_global_settings( 6208 + &self, 6209 + request: tonic::Request<super::GetGlobalSettingsRequest>, 6210 + ) -> std::result::Result<tonic::Response<super::GetGlobalSettingsResponse>, tonic::Status>; 6211 + async fn save_settings( 6212 + &self, 6213 + request: tonic::Request<super::SaveSettingsRequest>, 6214 + ) -> std::result::Result<tonic::Response<super::SaveSettingsResponse>, tonic::Status>; 6215 + } 6216 + #[derive(Debug)] 6217 + pub struct SettingsServiceServer<T> { 6218 + inner: Arc<T>, 6219 + accept_compression_encodings: EnabledCompressionEncodings, 6220 + send_compression_encodings: EnabledCompressionEncodings, 6221 + max_decoding_message_size: Option<usize>, 6222 + max_encoding_message_size: Option<usize>, 6223 + } 6224 + impl<T> SettingsServiceServer<T> { 6225 + pub fn new(inner: T) -> Self { 6226 + Self::from_arc(Arc::new(inner)) 6227 + } 6228 + pub fn from_arc(inner: Arc<T>) -> Self { 6229 + Self { 6230 + inner, 6231 + accept_compression_encodings: Default::default(), 6232 + send_compression_encodings: Default::default(), 6233 + max_decoding_message_size: None, 6234 + max_encoding_message_size: None, 6235 + } 6236 + } 6237 + pub fn with_interceptor<F>(inner: T, interceptor: F) -> InterceptedService<Self, F> 6238 + where 6239 + F: tonic::service::Interceptor, 6240 + { 6241 + InterceptedService::new(Self::new(inner), interceptor) 6242 + } 6243 + /// Enable decompressing requests with the given encoding. 6244 + #[must_use] 6245 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 6246 + self.accept_compression_encodings.enable(encoding); 6247 + self 6248 + } 6249 + /// Compress responses with the given encoding, if the client supports it. 6250 + #[must_use] 6251 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 6252 + self.send_compression_encodings.enable(encoding); 6253 + self 6254 + } 6255 + /// Limits the maximum size of a decoded message. 6256 + /// 6257 + /// Default: `4MB` 6258 + #[must_use] 6259 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 6260 + self.max_decoding_message_size = Some(limit); 6261 + self 6262 + } 6263 + /// Limits the maximum size of an encoded message. 6264 + /// 6265 + /// Default: `usize::MAX` 6266 + #[must_use] 6267 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 6268 + self.max_encoding_message_size = Some(limit); 6269 + self 6270 + } 6271 + } 6272 + impl<T, B> tonic::codegen::Service<http::Request<B>> for SettingsServiceServer<T> 6273 + where 6274 + T: SettingsService, 6275 + B: Body + std::marker::Send + 'static, 6276 + B::Error: Into<StdError> + std::marker::Send + 'static, 6277 + { 6278 + type Response = http::Response<tonic::body::BoxBody>; 6279 + type Error = std::convert::Infallible; 6280 + type Future = BoxFuture<Self::Response, Self::Error>; 6281 + fn poll_ready( 6282 + &mut self, 6283 + _cx: &mut Context<'_>, 6284 + ) -> Poll<std::result::Result<(), Self::Error>> { 6285 + Poll::Ready(Ok(())) 6286 + } 6287 + fn call(&mut self, req: http::Request<B>) -> Self::Future { 6288 + match req.uri().path() { 6289 + "/rockbox.v1alpha1.SettingsService/GetSettingsList" => { 6290 + #[allow(non_camel_case_types)] 6291 + struct GetSettingsListSvc<T: SettingsService>(pub Arc<T>); 6292 + impl<T: SettingsService> 6293 + tonic::server::UnaryService<super::GetSettingsListRequest> 6294 + for GetSettingsListSvc<T> 6295 + { 6296 + type Response = super::GetSettingsListResponse; 6297 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 6298 + fn call( 6299 + &mut self, 6300 + request: tonic::Request<super::GetSettingsListRequest>, 6301 + ) -> Self::Future { 6302 + let inner = Arc::clone(&self.0); 6303 + let fut = async move { 6304 + <T as SettingsService>::get_settings_list(&inner, request).await 6305 + }; 6306 + Box::pin(fut) 6307 + } 6308 + } 6309 + let accept_compression_encodings = self.accept_compression_encodings; 6310 + let send_compression_encodings = self.send_compression_encodings; 6311 + let max_decoding_message_size = self.max_decoding_message_size; 6312 + let max_encoding_message_size = self.max_encoding_message_size; 6313 + let inner = self.inner.clone(); 6314 + let fut = async move { 6315 + let method = GetSettingsListSvc(inner); 6316 + let codec = tonic::codec::ProstCodec::default(); 6317 + let mut grpc = tonic::server::Grpc::new(codec) 6318 + .apply_compression_config( 6319 + accept_compression_encodings, 6320 + send_compression_encodings, 6321 + ) 6322 + .apply_max_message_size_config( 6323 + max_decoding_message_size, 6324 + max_encoding_message_size, 6325 + ); 6326 + let res = grpc.unary(method, req).await; 6327 + Ok(res) 6328 + }; 6329 + Box::pin(fut) 6330 + } 6331 + "/rockbox.v1alpha1.SettingsService/GetGlobalSettings" => { 6332 + #[allow(non_camel_case_types)] 6333 + struct GetGlobalSettingsSvc<T: SettingsService>(pub Arc<T>); 6334 + impl<T: SettingsService> 6335 + tonic::server::UnaryService<super::GetGlobalSettingsRequest> 6336 + for GetGlobalSettingsSvc<T> 6337 + { 6338 + type Response = super::GetGlobalSettingsResponse; 6339 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 6340 + fn call( 6341 + &mut self, 6342 + request: tonic::Request<super::GetGlobalSettingsRequest>, 6343 + ) -> Self::Future { 6344 + let inner = Arc::clone(&self.0); 6345 + let fut = async move { 6346 + <T as SettingsService>::get_global_settings(&inner, request).await 6347 + }; 6348 + Box::pin(fut) 6349 + } 6350 + } 6351 + let accept_compression_encodings = self.accept_compression_encodings; 6352 + let send_compression_encodings = self.send_compression_encodings; 6353 + let max_decoding_message_size = self.max_decoding_message_size; 6354 + let max_encoding_message_size = self.max_encoding_message_size; 6355 + let inner = self.inner.clone(); 6356 + let fut = async move { 6357 + let method = GetGlobalSettingsSvc(inner); 6358 + let codec = tonic::codec::ProstCodec::default(); 6359 + let mut grpc = tonic::server::Grpc::new(codec) 6360 + .apply_compression_config( 6361 + accept_compression_encodings, 6362 + send_compression_encodings, 6363 + ) 6364 + .apply_max_message_size_config( 6365 + max_decoding_message_size, 6366 + max_encoding_message_size, 6367 + ); 6368 + let res = grpc.unary(method, req).await; 6369 + Ok(res) 6370 + }; 6371 + Box::pin(fut) 6372 + } 6373 + "/rockbox.v1alpha1.SettingsService/SaveSettings" => { 6374 + #[allow(non_camel_case_types)] 6375 + struct SaveSettingsSvc<T: SettingsService>(pub Arc<T>); 6376 + impl<T: SettingsService> tonic::server::UnaryService<super::SaveSettingsRequest> 6377 + for SaveSettingsSvc<T> 6378 + { 6379 + type Response = super::SaveSettingsResponse; 6380 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 6381 + fn call( 6382 + &mut self, 6383 + request: tonic::Request<super::SaveSettingsRequest>, 6384 + ) -> Self::Future { 6385 + let inner = Arc::clone(&self.0); 6386 + let fut = async move { 6387 + <T as SettingsService>::save_settings(&inner, request).await 6388 + }; 6389 + Box::pin(fut) 6390 + } 6391 + } 6392 + let accept_compression_encodings = self.accept_compression_encodings; 6393 + let send_compression_encodings = self.send_compression_encodings; 6394 + let max_decoding_message_size = self.max_decoding_message_size; 6395 + let max_encoding_message_size = self.max_encoding_message_size; 6396 + let inner = self.inner.clone(); 6397 + let fut = async move { 6398 + let method = SaveSettingsSvc(inner); 6399 + let codec = tonic::codec::ProstCodec::default(); 6400 + let mut grpc = tonic::server::Grpc::new(codec) 6401 + .apply_compression_config( 6402 + accept_compression_encodings, 6403 + send_compression_encodings, 6404 + ) 6405 + .apply_max_message_size_config( 6406 + max_decoding_message_size, 6407 + max_encoding_message_size, 6408 + ); 6409 + let res = grpc.unary(method, req).await; 6410 + Ok(res) 6411 + }; 6412 + Box::pin(fut) 6413 + } 6414 + _ => Box::pin(async move { 6415 + let mut response = http::Response::new(empty_body()); 6416 + let headers = response.headers_mut(); 6417 + headers.insert( 6418 + tonic::Status::GRPC_STATUS, 6419 + (tonic::Code::Unimplemented as i32).into(), 6420 + ); 6421 + headers.insert( 6422 + http::header::CONTENT_TYPE, 6423 + tonic::metadata::GRPC_CONTENT_TYPE, 6424 + ); 6425 + Ok(response) 6426 + }), 6427 + } 6428 + } 6429 + } 6430 + impl<T> Clone for SettingsServiceServer<T> { 6431 + fn clone(&self) -> Self { 6432 + let inner = self.inner.clone(); 6433 + Self { 6434 + inner, 6435 + accept_compression_encodings: self.accept_compression_encodings, 6436 + send_compression_encodings: self.send_compression_encodings, 6437 + max_decoding_message_size: self.max_decoding_message_size, 6438 + max_encoding_message_size: self.max_encoding_message_size, 6439 + } 6440 + } 6441 + } 6442 + /// Generated gRPC service name 6443 + pub const SERVICE_NAME: &str = "rockbox.v1alpha1.SettingsService"; 6444 + impl<T> tonic::server::NamedService for SettingsServiceServer<T> { 6445 + const NAME: &'static str = SERVICE_NAME; 6446 + } 6447 + } 6448 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6449 + pub struct AdjustVolumeRequest { 6450 + #[prost(int32, tag = "1")] 6451 + pub steps: i32, 6452 + } 6453 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6454 + pub struct AdjustVolumeResponse {} 6455 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6456 + pub struct SoundSetRequest { 6457 + #[prost(int32, tag = "1")] 6458 + pub setting: i32, 6459 + #[prost(int32, tag = "2")] 6460 + pub value: i32, 6461 + } 6462 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6463 + pub struct SoundSetResponse {} 6464 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6465 + pub struct SoundCurrentRequest { 6466 + #[prost(int32, tag = "1")] 6467 + pub setting: i32, 6468 + } 6469 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6470 + pub struct SoundCurrentResponse { 6471 + #[prost(int32, tag = "1")] 6472 + pub value: i32, 6473 + } 6474 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6475 + pub struct SoundDefaultRequest { 6476 + #[prost(int32, tag = "1")] 6477 + pub setting: i32, 6478 + } 6479 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6480 + pub struct SoundDefaultResponse { 6481 + #[prost(int32, tag = "1")] 6482 + pub value: i32, 6483 + } 6484 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6485 + pub struct SoundMinRequest { 6486 + #[prost(int32, tag = "1")] 6487 + pub setting: i32, 6488 + } 6489 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6490 + pub struct SoundMinResponse { 6491 + #[prost(int32, tag = "1")] 6492 + pub value: i32, 6493 + } 6494 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6495 + pub struct SoundMaxRequest { 6496 + #[prost(int32, tag = "1")] 6497 + pub setting: i32, 6498 + } 6499 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6500 + pub struct SoundMaxResponse { 6501 + #[prost(int32, tag = "1")] 6502 + pub value: i32, 6503 + } 6504 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6505 + pub struct SoundUnitRequest {} 6506 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6507 + pub struct SoundUnitResponse {} 6508 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6509 + pub struct SoundVal2PhysRequest { 6510 + #[prost(int32, tag = "1")] 6511 + pub setting: i32, 6512 + #[prost(int32, tag = "2")] 6513 + pub value: i32, 6514 + } 6515 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6516 + pub struct SoundVal2PhysResponse { 6517 + #[prost(int32, tag = "1")] 6518 + pub value: i32, 6519 + } 6520 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6521 + pub struct GetPitchRequest {} 6522 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6523 + pub struct GetPitchResponse { 6524 + #[prost(int32, tag = "1")] 6525 + pub value: i32, 6526 + } 6527 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6528 + pub struct SetPitchRequest { 6529 + #[prost(int32, tag = "1")] 6530 + pub value: i32, 6531 + } 6532 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6533 + pub struct SetPitchResponse {} 6534 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6535 + pub struct BeepPlayRequest { 6536 + #[prost(uint32, tag = "1")] 6537 + pub frequency: u32, 6538 + #[prost(uint32, tag = "2")] 6539 + pub duration: u32, 6540 + #[prost(uint32, tag = "3")] 6541 + pub amplitude: u32, 6542 + } 6543 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6544 + pub struct BeepPlayResponse {} 6545 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6546 + pub struct PcmbufFadeRequest { 6547 + #[prost(int32, tag = "1")] 6548 + pub fade: i32, 6549 + #[prost(bool, tag = "2")] 6550 + pub r#in: bool, 6551 + } 6552 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6553 + pub struct PcmbufFadeResponse {} 6554 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6555 + pub struct PcmbufSetLowLatencyRequest { 6556 + #[prost(bool, tag = "1")] 6557 + pub state: bool, 6558 + } 6559 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6560 + pub struct PcmbufSetLowLatencyResponse {} 6561 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6562 + pub struct SystemSoundPlayRequest { 6563 + #[prost(uint32, tag = "1")] 6564 + pub sound: u32, 6565 + } 6566 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6567 + pub struct SystemSoundPlayResponse {} 6568 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6569 + pub struct KeyclickClickRequest { 6570 + #[prost(bool, tag = "1")] 6571 + pub rawbutton: bool, 6572 + #[prost(int32, tag = "2")] 6573 + pub action: i32, 6574 + } 6575 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 6576 + pub struct KeyclickClickResponse {} 6577 + /// Generated client implementations. 6578 + pub mod sound_service_client { 6579 + #![allow( 6580 + unused_variables, 6581 + dead_code, 6582 + missing_docs, 6583 + clippy::wildcard_imports, 6584 + clippy::let_unit_value 6585 + )] 6586 + use tonic::codegen::http::Uri; 6587 + use tonic::codegen::*; 6588 + #[derive(Debug, Clone)] 6589 + pub struct SoundServiceClient<T> { 6590 + inner: tonic::client::Grpc<T>, 6591 + } 6592 + impl SoundServiceClient<tonic::transport::Channel> { 6593 + /// Attempt to create a new client by connecting to a given endpoint. 6594 + pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error> 6595 + where 6596 + D: TryInto<tonic::transport::Endpoint>, 6597 + D::Error: Into<StdError>, 6598 + { 6599 + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; 6600 + Ok(Self::new(conn)) 6601 + } 6602 + } 6603 + impl<T> SoundServiceClient<T> 6604 + where 6605 + T: tonic::client::GrpcService<tonic::body::BoxBody>, 6606 + T::Error: Into<StdError>, 6607 + T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static, 6608 + <T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send, 6609 + { 6610 + pub fn new(inner: T) -> Self { 6611 + let inner = tonic::client::Grpc::new(inner); 6612 + Self { inner } 6613 + } 6614 + pub fn with_origin(inner: T, origin: Uri) -> Self { 6615 + let inner = tonic::client::Grpc::with_origin(inner, origin); 6616 + Self { inner } 6617 + } 6618 + pub fn with_interceptor<F>( 6619 + inner: T, 6620 + interceptor: F, 6621 + ) -> SoundServiceClient<InterceptedService<T, F>> 6622 + where 6623 + F: tonic::service::Interceptor, 6624 + T::ResponseBody: Default, 6625 + T: tonic::codegen::Service< 6626 + http::Request<tonic::body::BoxBody>, 6627 + Response = http::Response< 6628 + <T as tonic::client::GrpcService<tonic::body::BoxBody>>::ResponseBody, 6629 + >, 6630 + >, 6631 + <T as tonic::codegen::Service<http::Request<tonic::body::BoxBody>>>::Error: 6632 + Into<StdError> + std::marker::Send + std::marker::Sync, 6633 + { 6634 + SoundServiceClient::new(InterceptedService::new(inner, interceptor)) 6635 + } 6636 + /// Compress requests with the given encoding. 6637 + /// 6638 + /// This requires the server to support it otherwise it might respond with an 6639 + /// error. 6640 + #[must_use] 6641 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 6642 + self.inner = self.inner.send_compressed(encoding); 6643 + self 6644 + } 6645 + /// Enable decompressing responses. 6646 + #[must_use] 6647 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 6648 + self.inner = self.inner.accept_compressed(encoding); 6649 + self 6650 + } 6651 + /// Limits the maximum size of a decoded message. 6652 + /// 6653 + /// Default: `4MB` 6654 + #[must_use] 6655 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 6656 + self.inner = self.inner.max_decoding_message_size(limit); 6657 + self 6658 + } 6659 + /// Limits the maximum size of an encoded message. 6660 + /// 6661 + /// Default: `usize::MAX` 6662 + #[must_use] 6663 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 6664 + self.inner = self.inner.max_encoding_message_size(limit); 6665 + self 6666 + } 6667 + pub async fn adjust_volume( 6668 + &mut self, 6669 + request: impl tonic::IntoRequest<super::AdjustVolumeRequest>, 6670 + ) -> std::result::Result<tonic::Response<super::AdjustVolumeResponse>, tonic::Status> 6671 + { 6672 + self.inner.ready().await.map_err(|e| { 6673 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 6674 + })?; 6675 + let codec = tonic::codec::ProstCodec::default(); 6676 + let path = 6677 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.SoundService/AdjustVolume"); 6678 + let mut req = request.into_request(); 6679 + req.extensions_mut().insert(GrpcMethod::new( 6680 + "rockbox.v1alpha1.SoundService", 6681 + "AdjustVolume", 6682 + )); 6683 + self.inner.unary(req, path, codec).await 6684 + } 6685 + pub async fn sound_set( 6686 + &mut self, 6687 + request: impl tonic::IntoRequest<super::SoundSetRequest>, 6688 + ) -> std::result::Result<tonic::Response<super::SoundSetResponse>, tonic::Status> { 6689 + self.inner.ready().await.map_err(|e| { 6690 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 6691 + })?; 6692 + let codec = tonic::codec::ProstCodec::default(); 6693 + let path = 6694 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.SoundService/SoundSet"); 6695 + let mut req = request.into_request(); 6696 + req.extensions_mut() 6697 + .insert(GrpcMethod::new("rockbox.v1alpha1.SoundService", "SoundSet")); 6698 + self.inner.unary(req, path, codec).await 6699 + } 6700 + pub async fn sound_current( 6701 + &mut self, 6702 + request: impl tonic::IntoRequest<super::SoundCurrentRequest>, 6703 + ) -> std::result::Result<tonic::Response<super::SoundCurrentResponse>, tonic::Status> 6704 + { 6705 + self.inner.ready().await.map_err(|e| { 6706 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 6707 + })?; 6708 + let codec = tonic::codec::ProstCodec::default(); 6709 + let path = 6710 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.SoundService/SoundCurrent"); 6711 + let mut req = request.into_request(); 6712 + req.extensions_mut().insert(GrpcMethod::new( 6713 + "rockbox.v1alpha1.SoundService", 6714 + "SoundCurrent", 6715 + )); 6716 + self.inner.unary(req, path, codec).await 6717 + } 6718 + pub async fn sound_default( 6719 + &mut self, 6720 + request: impl tonic::IntoRequest<super::SoundDefaultRequest>, 6721 + ) -> std::result::Result<tonic::Response<super::SoundDefaultResponse>, tonic::Status> 6722 + { 6723 + self.inner.ready().await.map_err(|e| { 6724 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 6725 + })?; 6726 + let codec = tonic::codec::ProstCodec::default(); 6727 + let path = 6728 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.SoundService/SoundDefault"); 6729 + let mut req = request.into_request(); 6730 + req.extensions_mut().insert(GrpcMethod::new( 6731 + "rockbox.v1alpha1.SoundService", 6732 + "SoundDefault", 6733 + )); 6734 + self.inner.unary(req, path, codec).await 6735 + } 6736 + pub async fn sound_min( 6737 + &mut self, 6738 + request: impl tonic::IntoRequest<super::SoundMinRequest>, 6739 + ) -> std::result::Result<tonic::Response<super::SoundMinResponse>, tonic::Status> { 6740 + self.inner.ready().await.map_err(|e| { 6741 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 6742 + })?; 6743 + let codec = tonic::codec::ProstCodec::default(); 6744 + let path = 6745 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.SoundService/SoundMin"); 6746 + let mut req = request.into_request(); 6747 + req.extensions_mut() 6748 + .insert(GrpcMethod::new("rockbox.v1alpha1.SoundService", "SoundMin")); 6749 + self.inner.unary(req, path, codec).await 6750 + } 6751 + pub async fn sound_max( 6752 + &mut self, 6753 + request: impl tonic::IntoRequest<super::SoundMaxRequest>, 6754 + ) -> std::result::Result<tonic::Response<super::SoundMaxResponse>, tonic::Status> { 6755 + self.inner.ready().await.map_err(|e| { 6756 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 6757 + })?; 6758 + let codec = tonic::codec::ProstCodec::default(); 6759 + let path = 6760 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.SoundService/SoundMax"); 6761 + let mut req = request.into_request(); 6762 + req.extensions_mut() 6763 + .insert(GrpcMethod::new("rockbox.v1alpha1.SoundService", "SoundMax")); 6764 + self.inner.unary(req, path, codec).await 6765 + } 6766 + pub async fn sound_unit( 6767 + &mut self, 6768 + request: impl tonic::IntoRequest<super::SoundUnitRequest>, 6769 + ) -> std::result::Result<tonic::Response<super::SoundUnitResponse>, tonic::Status> { 6770 + self.inner.ready().await.map_err(|e| { 6771 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 6772 + })?; 6773 + let codec = tonic::codec::ProstCodec::default(); 6774 + let path = 6775 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.SoundService/SoundUnit"); 6776 + let mut req = request.into_request(); 6777 + req.extensions_mut().insert(GrpcMethod::new( 6778 + "rockbox.v1alpha1.SoundService", 6779 + "SoundUnit", 6780 + )); 6781 + self.inner.unary(req, path, codec).await 6782 + } 6783 + pub async fn sound_val2_phys( 6784 + &mut self, 6785 + request: impl tonic::IntoRequest<super::SoundVal2PhysRequest>, 6786 + ) -> std::result::Result<tonic::Response<super::SoundVal2PhysResponse>, tonic::Status> 6787 + { 6788 + self.inner.ready().await.map_err(|e| { 6789 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 6790 + })?; 6791 + let codec = tonic::codec::ProstCodec::default(); 6792 + let path = http::uri::PathAndQuery::from_static( 6793 + "/rockbox.v1alpha1.SoundService/SoundVal2Phys", 6794 + ); 6795 + let mut req = request.into_request(); 6796 + req.extensions_mut().insert(GrpcMethod::new( 6797 + "rockbox.v1alpha1.SoundService", 6798 + "SoundVal2Phys", 6799 + )); 6800 + self.inner.unary(req, path, codec).await 6801 + } 6802 + pub async fn get_pitch( 6803 + &mut self, 6804 + request: impl tonic::IntoRequest<super::GetPitchRequest>, 6805 + ) -> std::result::Result<tonic::Response<super::GetPitchResponse>, tonic::Status> { 6806 + self.inner.ready().await.map_err(|e| { 6807 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 6808 + })?; 6809 + let codec = tonic::codec::ProstCodec::default(); 6810 + let path = 6811 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.SoundService/GetPitch"); 6812 + let mut req = request.into_request(); 6813 + req.extensions_mut() 6814 + .insert(GrpcMethod::new("rockbox.v1alpha1.SoundService", "GetPitch")); 6815 + self.inner.unary(req, path, codec).await 6816 + } 6817 + pub async fn set_pitch( 6818 + &mut self, 6819 + request: impl tonic::IntoRequest<super::SetPitchRequest>, 6820 + ) -> std::result::Result<tonic::Response<super::SetPitchResponse>, tonic::Status> { 6821 + self.inner.ready().await.map_err(|e| { 6822 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 6823 + })?; 6824 + let codec = tonic::codec::ProstCodec::default(); 6825 + let path = 6826 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.SoundService/SetPitch"); 6827 + let mut req = request.into_request(); 6828 + req.extensions_mut() 6829 + .insert(GrpcMethod::new("rockbox.v1alpha1.SoundService", "SetPitch")); 6830 + self.inner.unary(req, path, codec).await 6831 + } 6832 + pub async fn beep_play( 6833 + &mut self, 6834 + request: impl tonic::IntoRequest<super::BeepPlayRequest>, 6835 + ) -> std::result::Result<tonic::Response<super::BeepPlayResponse>, tonic::Status> { 6836 + self.inner.ready().await.map_err(|e| { 6837 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 6838 + })?; 6839 + let codec = tonic::codec::ProstCodec::default(); 6840 + let path = 6841 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.SoundService/BeepPlay"); 6842 + let mut req = request.into_request(); 6843 + req.extensions_mut() 6844 + .insert(GrpcMethod::new("rockbox.v1alpha1.SoundService", "BeepPlay")); 6845 + self.inner.unary(req, path, codec).await 6846 + } 6847 + pub async fn pcmbuf_fade( 6848 + &mut self, 6849 + request: impl tonic::IntoRequest<super::PcmbufFadeRequest>, 6850 + ) -> std::result::Result<tonic::Response<super::PcmbufFadeResponse>, tonic::Status> 6851 + { 6852 + self.inner.ready().await.map_err(|e| { 6853 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 6854 + })?; 6855 + let codec = tonic::codec::ProstCodec::default(); 6856 + let path = 6857 + http::uri::PathAndQuery::from_static("/rockbox.v1alpha1.SoundService/PcmbufFade"); 6858 + let mut req = request.into_request(); 6859 + req.extensions_mut().insert(GrpcMethod::new( 6860 + "rockbox.v1alpha1.SoundService", 6861 + "PcmbufFade", 6862 + )); 6863 + self.inner.unary(req, path, codec).await 6864 + } 6865 + pub async fn pcmbuf_set_low_latency( 6866 + &mut self, 6867 + request: impl tonic::IntoRequest<super::PcmbufSetLowLatencyRequest>, 6868 + ) -> std::result::Result<tonic::Response<super::PcmbufSetLowLatencyResponse>, tonic::Status> 6869 + { 6870 + self.inner.ready().await.map_err(|e| { 6871 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 6872 + })?; 6873 + let codec = tonic::codec::ProstCodec::default(); 6874 + let path = http::uri::PathAndQuery::from_static( 6875 + "/rockbox.v1alpha1.SoundService/PcmbufSetLowLatency", 6876 + ); 6877 + let mut req = request.into_request(); 6878 + req.extensions_mut().insert(GrpcMethod::new( 6879 + "rockbox.v1alpha1.SoundService", 6880 + "PcmbufSetLowLatency", 6881 + )); 6882 + self.inner.unary(req, path, codec).await 6883 + } 6884 + pub async fn system_sound_play( 6885 + &mut self, 6886 + request: impl tonic::IntoRequest<super::SystemSoundPlayRequest>, 6887 + ) -> std::result::Result<tonic::Response<super::SystemSoundPlayResponse>, tonic::Status> 6888 + { 6889 + self.inner.ready().await.map_err(|e| { 6890 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 6891 + })?; 6892 + let codec = tonic::codec::ProstCodec::default(); 6893 + let path = http::uri::PathAndQuery::from_static( 6894 + "/rockbox.v1alpha1.SoundService/SystemSoundPlay", 6895 + ); 6896 + let mut req = request.into_request(); 6897 + req.extensions_mut().insert(GrpcMethod::new( 6898 + "rockbox.v1alpha1.SoundService", 6899 + "SystemSoundPlay", 6900 + )); 6901 + self.inner.unary(req, path, codec).await 6902 + } 6903 + pub async fn keyclick_click( 6904 + &mut self, 6905 + request: impl tonic::IntoRequest<super::KeyclickClickRequest>, 6906 + ) -> std::result::Result<tonic::Response<super::KeyclickClickResponse>, tonic::Status> 6907 + { 6908 + self.inner.ready().await.map_err(|e| { 6909 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 6910 + })?; 6911 + let codec = tonic::codec::ProstCodec::default(); 6912 + let path = http::uri::PathAndQuery::from_static( 6913 + "/rockbox.v1alpha1.SoundService/KeyclickClick", 6914 + ); 6915 + let mut req = request.into_request(); 6916 + req.extensions_mut().insert(GrpcMethod::new( 6917 + "rockbox.v1alpha1.SoundService", 6918 + "KeyclickClick", 6919 + )); 6920 + self.inner.unary(req, path, codec).await 6921 + } 6922 + } 6923 + } 6924 + /// Generated server implementations. 6925 + pub mod sound_service_server { 6926 + #![allow( 6927 + unused_variables, 6928 + dead_code, 6929 + missing_docs, 6930 + clippy::wildcard_imports, 6931 + clippy::let_unit_value 6932 + )] 6933 + use tonic::codegen::*; 6934 + /// Generated trait containing gRPC methods that should be implemented for use with SoundServiceServer. 6935 + #[async_trait] 6936 + pub trait SoundService: std::marker::Send + std::marker::Sync + 'static { 6937 + async fn adjust_volume( 6938 + &self, 6939 + request: tonic::Request<super::AdjustVolumeRequest>, 6940 + ) -> std::result::Result<tonic::Response<super::AdjustVolumeResponse>, tonic::Status>; 6941 + async fn sound_set( 6942 + &self, 6943 + request: tonic::Request<super::SoundSetRequest>, 6944 + ) -> std::result::Result<tonic::Response<super::SoundSetResponse>, tonic::Status>; 6945 + async fn sound_current( 6946 + &self, 6947 + request: tonic::Request<super::SoundCurrentRequest>, 6948 + ) -> std::result::Result<tonic::Response<super::SoundCurrentResponse>, tonic::Status>; 6949 + async fn sound_default( 6950 + &self, 6951 + request: tonic::Request<super::SoundDefaultRequest>, 6952 + ) -> std::result::Result<tonic::Response<super::SoundDefaultResponse>, tonic::Status>; 6953 + async fn sound_min( 6954 + &self, 6955 + request: tonic::Request<super::SoundMinRequest>, 6956 + ) -> std::result::Result<tonic::Response<super::SoundMinResponse>, tonic::Status>; 6957 + async fn sound_max( 6958 + &self, 6959 + request: tonic::Request<super::SoundMaxRequest>, 6960 + ) -> std::result::Result<tonic::Response<super::SoundMaxResponse>, tonic::Status>; 6961 + async fn sound_unit( 6962 + &self, 6963 + request: tonic::Request<super::SoundUnitRequest>, 6964 + ) -> std::result::Result<tonic::Response<super::SoundUnitResponse>, tonic::Status>; 6965 + async fn sound_val2_phys( 6966 + &self, 6967 + request: tonic::Request<super::SoundVal2PhysRequest>, 6968 + ) -> std::result::Result<tonic::Response<super::SoundVal2PhysResponse>, tonic::Status>; 6969 + async fn get_pitch( 6970 + &self, 6971 + request: tonic::Request<super::GetPitchRequest>, 6972 + ) -> std::result::Result<tonic::Response<super::GetPitchResponse>, tonic::Status>; 6973 + async fn set_pitch( 6974 + &self, 6975 + request: tonic::Request<super::SetPitchRequest>, 6976 + ) -> std::result::Result<tonic::Response<super::SetPitchResponse>, tonic::Status>; 6977 + async fn beep_play( 6978 + &self, 6979 + request: tonic::Request<super::BeepPlayRequest>, 6980 + ) -> std::result::Result<tonic::Response<super::BeepPlayResponse>, tonic::Status>; 6981 + async fn pcmbuf_fade( 6982 + &self, 6983 + request: tonic::Request<super::PcmbufFadeRequest>, 6984 + ) -> std::result::Result<tonic::Response<super::PcmbufFadeResponse>, tonic::Status>; 6985 + async fn pcmbuf_set_low_latency( 6986 + &self, 6987 + request: tonic::Request<super::PcmbufSetLowLatencyRequest>, 6988 + ) -> std::result::Result<tonic::Response<super::PcmbufSetLowLatencyResponse>, tonic::Status>; 6989 + async fn system_sound_play( 6990 + &self, 6991 + request: tonic::Request<super::SystemSoundPlayRequest>, 6992 + ) -> std::result::Result<tonic::Response<super::SystemSoundPlayResponse>, tonic::Status>; 6993 + async fn keyclick_click( 6994 + &self, 6995 + request: tonic::Request<super::KeyclickClickRequest>, 6996 + ) -> std::result::Result<tonic::Response<super::KeyclickClickResponse>, tonic::Status>; 6997 + } 6998 + #[derive(Debug)] 6999 + pub struct SoundServiceServer<T> { 7000 + inner: Arc<T>, 7001 + accept_compression_encodings: EnabledCompressionEncodings, 7002 + send_compression_encodings: EnabledCompressionEncodings, 7003 + max_decoding_message_size: Option<usize>, 7004 + max_encoding_message_size: Option<usize>, 7005 + } 7006 + impl<T> SoundServiceServer<T> { 7007 + pub fn new(inner: T) -> Self { 7008 + Self::from_arc(Arc::new(inner)) 7009 + } 7010 + pub fn from_arc(inner: Arc<T>) -> Self { 7011 + Self { 7012 + inner, 7013 + accept_compression_encodings: Default::default(), 7014 + send_compression_encodings: Default::default(), 7015 + max_decoding_message_size: None, 7016 + max_encoding_message_size: None, 7017 + } 7018 + } 7019 + pub fn with_interceptor<F>(inner: T, interceptor: F) -> InterceptedService<Self, F> 7020 + where 7021 + F: tonic::service::Interceptor, 7022 + { 7023 + InterceptedService::new(Self::new(inner), interceptor) 7024 + } 7025 + /// Enable decompressing requests with the given encoding. 7026 + #[must_use] 7027 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 7028 + self.accept_compression_encodings.enable(encoding); 7029 + self 7030 + } 7031 + /// Compress responses with the given encoding, if the client supports it. 7032 + #[must_use] 7033 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 7034 + self.send_compression_encodings.enable(encoding); 7035 + self 7036 + } 7037 + /// Limits the maximum size of a decoded message. 7038 + /// 7039 + /// Default: `4MB` 7040 + #[must_use] 7041 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 7042 + self.max_decoding_message_size = Some(limit); 7043 + self 7044 + } 7045 + /// Limits the maximum size of an encoded message. 7046 + /// 7047 + /// Default: `usize::MAX` 7048 + #[must_use] 7049 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 7050 + self.max_encoding_message_size = Some(limit); 7051 + self 7052 + } 7053 + } 7054 + impl<T, B> tonic::codegen::Service<http::Request<B>> for SoundServiceServer<T> 7055 + where 7056 + T: SoundService, 7057 + B: Body + std::marker::Send + 'static, 7058 + B::Error: Into<StdError> + std::marker::Send + 'static, 7059 + { 7060 + type Response = http::Response<tonic::body::BoxBody>; 7061 + type Error = std::convert::Infallible; 7062 + type Future = BoxFuture<Self::Response, Self::Error>; 7063 + fn poll_ready( 7064 + &mut self, 7065 + _cx: &mut Context<'_>, 7066 + ) -> Poll<std::result::Result<(), Self::Error>> { 7067 + Poll::Ready(Ok(())) 7068 + } 7069 + fn call(&mut self, req: http::Request<B>) -> Self::Future { 7070 + match req.uri().path() { 7071 + "/rockbox.v1alpha1.SoundService/AdjustVolume" => { 7072 + #[allow(non_camel_case_types)] 7073 + struct AdjustVolumeSvc<T: SoundService>(pub Arc<T>); 7074 + impl<T: SoundService> tonic::server::UnaryService<super::AdjustVolumeRequest> 7075 + for AdjustVolumeSvc<T> 7076 + { 7077 + type Response = super::AdjustVolumeResponse; 7078 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 7079 + fn call( 7080 + &mut self, 7081 + request: tonic::Request<super::AdjustVolumeRequest>, 7082 + ) -> Self::Future { 7083 + let inner = Arc::clone(&self.0); 7084 + let fut = async move { 7085 + <T as SoundService>::adjust_volume(&inner, request).await 7086 + }; 7087 + Box::pin(fut) 7088 + } 7089 + } 7090 + let accept_compression_encodings = self.accept_compression_encodings; 7091 + let send_compression_encodings = self.send_compression_encodings; 7092 + let max_decoding_message_size = self.max_decoding_message_size; 7093 + let max_encoding_message_size = self.max_encoding_message_size; 7094 + let inner = self.inner.clone(); 7095 + let fut = async move { 7096 + let method = AdjustVolumeSvc(inner); 7097 + let codec = tonic::codec::ProstCodec::default(); 7098 + let mut grpc = tonic::server::Grpc::new(codec) 7099 + .apply_compression_config( 7100 + accept_compression_encodings, 7101 + send_compression_encodings, 7102 + ) 7103 + .apply_max_message_size_config( 7104 + max_decoding_message_size, 7105 + max_encoding_message_size, 7106 + ); 7107 + let res = grpc.unary(method, req).await; 7108 + Ok(res) 7109 + }; 7110 + Box::pin(fut) 7111 + } 7112 + "/rockbox.v1alpha1.SoundService/SoundSet" => { 7113 + #[allow(non_camel_case_types)] 7114 + struct SoundSetSvc<T: SoundService>(pub Arc<T>); 7115 + impl<T: SoundService> tonic::server::UnaryService<super::SoundSetRequest> for SoundSetSvc<T> { 7116 + type Response = super::SoundSetResponse; 7117 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 7118 + fn call( 7119 + &mut self, 7120 + request: tonic::Request<super::SoundSetRequest>, 7121 + ) -> Self::Future { 7122 + let inner = Arc::clone(&self.0); 7123 + let fut = async move { 7124 + <T as SoundService>::sound_set(&inner, request).await 7125 + }; 7126 + Box::pin(fut) 7127 + } 7128 + } 7129 + let accept_compression_encodings = self.accept_compression_encodings; 7130 + let send_compression_encodings = self.send_compression_encodings; 7131 + let max_decoding_message_size = self.max_decoding_message_size; 7132 + let max_encoding_message_size = self.max_encoding_message_size; 7133 + let inner = self.inner.clone(); 7134 + let fut = async move { 7135 + let method = SoundSetSvc(inner); 7136 + let codec = tonic::codec::ProstCodec::default(); 7137 + let mut grpc = tonic::server::Grpc::new(codec) 7138 + .apply_compression_config( 7139 + accept_compression_encodings, 7140 + send_compression_encodings, 7141 + ) 7142 + .apply_max_message_size_config( 7143 + max_decoding_message_size, 7144 + max_encoding_message_size, 7145 + ); 7146 + let res = grpc.unary(method, req).await; 7147 + Ok(res) 7148 + }; 7149 + Box::pin(fut) 7150 + } 7151 + "/rockbox.v1alpha1.SoundService/SoundCurrent" => { 7152 + #[allow(non_camel_case_types)] 7153 + struct SoundCurrentSvc<T: SoundService>(pub Arc<T>); 7154 + impl<T: SoundService> tonic::server::UnaryService<super::SoundCurrentRequest> 7155 + for SoundCurrentSvc<T> 7156 + { 7157 + type Response = super::SoundCurrentResponse; 7158 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 7159 + fn call( 7160 + &mut self, 7161 + request: tonic::Request<super::SoundCurrentRequest>, 7162 + ) -> Self::Future { 7163 + let inner = Arc::clone(&self.0); 7164 + let fut = async move { 7165 + <T as SoundService>::sound_current(&inner, request).await 7166 + }; 7167 + Box::pin(fut) 7168 + } 7169 + } 7170 + let accept_compression_encodings = self.accept_compression_encodings; 7171 + let send_compression_encodings = self.send_compression_encodings; 7172 + let max_decoding_message_size = self.max_decoding_message_size; 7173 + let max_encoding_message_size = self.max_encoding_message_size; 7174 + let inner = self.inner.clone(); 7175 + let fut = async move { 7176 + let method = SoundCurrentSvc(inner); 7177 + let codec = tonic::codec::ProstCodec::default(); 7178 + let mut grpc = tonic::server::Grpc::new(codec) 7179 + .apply_compression_config( 7180 + accept_compression_encodings, 7181 + send_compression_encodings, 7182 + ) 7183 + .apply_max_message_size_config( 7184 + max_decoding_message_size, 7185 + max_encoding_message_size, 7186 + ); 7187 + let res = grpc.unary(method, req).await; 7188 + Ok(res) 7189 + }; 7190 + Box::pin(fut) 7191 + } 7192 + "/rockbox.v1alpha1.SoundService/SoundDefault" => { 7193 + #[allow(non_camel_case_types)] 7194 + struct SoundDefaultSvc<T: SoundService>(pub Arc<T>); 7195 + impl<T: SoundService> tonic::server::UnaryService<super::SoundDefaultRequest> 7196 + for SoundDefaultSvc<T> 7197 + { 7198 + type Response = super::SoundDefaultResponse; 7199 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 7200 + fn call( 7201 + &mut self, 7202 + request: tonic::Request<super::SoundDefaultRequest>, 7203 + ) -> Self::Future { 7204 + let inner = Arc::clone(&self.0); 7205 + let fut = async move { 7206 + <T as SoundService>::sound_default(&inner, request).await 7207 + }; 7208 + Box::pin(fut) 7209 + } 7210 + } 7211 + let accept_compression_encodings = self.accept_compression_encodings; 7212 + let send_compression_encodings = self.send_compression_encodings; 7213 + let max_decoding_message_size = self.max_decoding_message_size; 7214 + let max_encoding_message_size = self.max_encoding_message_size; 7215 + let inner = self.inner.clone(); 7216 + let fut = async move { 7217 + let method = SoundDefaultSvc(inner); 7218 + let codec = tonic::codec::ProstCodec::default(); 7219 + let mut grpc = tonic::server::Grpc::new(codec) 7220 + .apply_compression_config( 7221 + accept_compression_encodings, 7222 + send_compression_encodings, 7223 + ) 7224 + .apply_max_message_size_config( 7225 + max_decoding_message_size, 7226 + max_encoding_message_size, 7227 + ); 7228 + let res = grpc.unary(method, req).await; 7229 + Ok(res) 7230 + }; 7231 + Box::pin(fut) 7232 + } 7233 + "/rockbox.v1alpha1.SoundService/SoundMin" => { 7234 + #[allow(non_camel_case_types)] 7235 + struct SoundMinSvc<T: SoundService>(pub Arc<T>); 7236 + impl<T: SoundService> tonic::server::UnaryService<super::SoundMinRequest> for SoundMinSvc<T> { 7237 + type Response = super::SoundMinResponse; 7238 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 7239 + fn call( 7240 + &mut self, 7241 + request: tonic::Request<super::SoundMinRequest>, 7242 + ) -> Self::Future { 7243 + let inner = Arc::clone(&self.0); 7244 + let fut = async move { 7245 + <T as SoundService>::sound_min(&inner, request).await 7246 + }; 7247 + Box::pin(fut) 7248 + } 7249 + } 7250 + let accept_compression_encodings = self.accept_compression_encodings; 7251 + let send_compression_encodings = self.send_compression_encodings; 7252 + let max_decoding_message_size = self.max_decoding_message_size; 7253 + let max_encoding_message_size = self.max_encoding_message_size; 7254 + let inner = self.inner.clone(); 7255 + let fut = async move { 7256 + let method = SoundMinSvc(inner); 7257 + let codec = tonic::codec::ProstCodec::default(); 7258 + let mut grpc = tonic::server::Grpc::new(codec) 7259 + .apply_compression_config( 7260 + accept_compression_encodings, 7261 + send_compression_encodings, 7262 + ) 7263 + .apply_max_message_size_config( 7264 + max_decoding_message_size, 7265 + max_encoding_message_size, 7266 + ); 7267 + let res = grpc.unary(method, req).await; 7268 + Ok(res) 7269 + }; 7270 + Box::pin(fut) 7271 + } 7272 + "/rockbox.v1alpha1.SoundService/SoundMax" => { 7273 + #[allow(non_camel_case_types)] 7274 + struct SoundMaxSvc<T: SoundService>(pub Arc<T>); 7275 + impl<T: SoundService> tonic::server::UnaryService<super::SoundMaxRequest> for SoundMaxSvc<T> { 7276 + type Response = super::SoundMaxResponse; 7277 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 7278 + fn call( 7279 + &mut self, 7280 + request: tonic::Request<super::SoundMaxRequest>, 7281 + ) -> Self::Future { 7282 + let inner = Arc::clone(&self.0); 7283 + let fut = async move { 7284 + <T as SoundService>::sound_max(&inner, request).await 7285 + }; 7286 + Box::pin(fut) 7287 + } 7288 + } 7289 + let accept_compression_encodings = self.accept_compression_encodings; 7290 + let send_compression_encodings = self.send_compression_encodings; 7291 + let max_decoding_message_size = self.max_decoding_message_size; 7292 + let max_encoding_message_size = self.max_encoding_message_size; 7293 + let inner = self.inner.clone(); 7294 + let fut = async move { 7295 + let method = SoundMaxSvc(inner); 7296 + let codec = tonic::codec::ProstCodec::default(); 7297 + let mut grpc = tonic::server::Grpc::new(codec) 7298 + .apply_compression_config( 7299 + accept_compression_encodings, 7300 + send_compression_encodings, 7301 + ) 7302 + .apply_max_message_size_config( 7303 + max_decoding_message_size, 7304 + max_encoding_message_size, 7305 + ); 7306 + let res = grpc.unary(method, req).await; 7307 + Ok(res) 7308 + }; 7309 + Box::pin(fut) 7310 + } 7311 + "/rockbox.v1alpha1.SoundService/SoundUnit" => { 7312 + #[allow(non_camel_case_types)] 7313 + struct SoundUnitSvc<T: SoundService>(pub Arc<T>); 7314 + impl<T: SoundService> tonic::server::UnaryService<super::SoundUnitRequest> for SoundUnitSvc<T> { 7315 + type Response = super::SoundUnitResponse; 7316 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 7317 + fn call( 7318 + &mut self, 7319 + request: tonic::Request<super::SoundUnitRequest>, 7320 + ) -> Self::Future { 7321 + let inner = Arc::clone(&self.0); 7322 + let fut = async move { 7323 + <T as SoundService>::sound_unit(&inner, request).await 7324 + }; 7325 + Box::pin(fut) 7326 + } 7327 + } 7328 + let accept_compression_encodings = self.accept_compression_encodings; 7329 + let send_compression_encodings = self.send_compression_encodings; 7330 + let max_decoding_message_size = self.max_decoding_message_size; 7331 + let max_encoding_message_size = self.max_encoding_message_size; 7332 + let inner = self.inner.clone(); 7333 + let fut = async move { 7334 + let method = SoundUnitSvc(inner); 7335 + let codec = tonic::codec::ProstCodec::default(); 7336 + let mut grpc = tonic::server::Grpc::new(codec) 7337 + .apply_compression_config( 7338 + accept_compression_encodings, 7339 + send_compression_encodings, 7340 + ) 7341 + .apply_max_message_size_config( 7342 + max_decoding_message_size, 7343 + max_encoding_message_size, 7344 + ); 7345 + let res = grpc.unary(method, req).await; 7346 + Ok(res) 7347 + }; 7348 + Box::pin(fut) 7349 + } 7350 + "/rockbox.v1alpha1.SoundService/SoundVal2Phys" => { 7351 + #[allow(non_camel_case_types)] 7352 + struct SoundVal2PhysSvc<T: SoundService>(pub Arc<T>); 7353 + impl<T: SoundService> tonic::server::UnaryService<super::SoundVal2PhysRequest> 7354 + for SoundVal2PhysSvc<T> 7355 + { 7356 + type Response = super::SoundVal2PhysResponse; 7357 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 7358 + fn call( 7359 + &mut self, 7360 + request: tonic::Request<super::SoundVal2PhysRequest>, 7361 + ) -> Self::Future { 7362 + let inner = Arc::clone(&self.0); 7363 + let fut = async move { 7364 + <T as SoundService>::sound_val2_phys(&inner, request).await 7365 + }; 7366 + Box::pin(fut) 7367 + } 7368 + } 7369 + let accept_compression_encodings = self.accept_compression_encodings; 7370 + let send_compression_encodings = self.send_compression_encodings; 7371 + let max_decoding_message_size = self.max_decoding_message_size; 7372 + let max_encoding_message_size = self.max_encoding_message_size; 7373 + let inner = self.inner.clone(); 7374 + let fut = async move { 7375 + let method = SoundVal2PhysSvc(inner); 7376 + let codec = tonic::codec::ProstCodec::default(); 7377 + let mut grpc = tonic::server::Grpc::new(codec) 7378 + .apply_compression_config( 7379 + accept_compression_encodings, 7380 + send_compression_encodings, 7381 + ) 7382 + .apply_max_message_size_config( 7383 + max_decoding_message_size, 7384 + max_encoding_message_size, 7385 + ); 7386 + let res = grpc.unary(method, req).await; 7387 + Ok(res) 7388 + }; 7389 + Box::pin(fut) 7390 + } 7391 + "/rockbox.v1alpha1.SoundService/GetPitch" => { 7392 + #[allow(non_camel_case_types)] 7393 + struct GetPitchSvc<T: SoundService>(pub Arc<T>); 7394 + impl<T: SoundService> tonic::server::UnaryService<super::GetPitchRequest> for GetPitchSvc<T> { 7395 + type Response = super::GetPitchResponse; 7396 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 7397 + fn call( 7398 + &mut self, 7399 + request: tonic::Request<super::GetPitchRequest>, 7400 + ) -> Self::Future { 7401 + let inner = Arc::clone(&self.0); 7402 + let fut = async move { 7403 + <T as SoundService>::get_pitch(&inner, request).await 7404 + }; 7405 + Box::pin(fut) 7406 + } 7407 + } 7408 + let accept_compression_encodings = self.accept_compression_encodings; 7409 + let send_compression_encodings = self.send_compression_encodings; 7410 + let max_decoding_message_size = self.max_decoding_message_size; 7411 + let max_encoding_message_size = self.max_encoding_message_size; 7412 + let inner = self.inner.clone(); 7413 + let fut = async move { 7414 + let method = GetPitchSvc(inner); 7415 + let codec = tonic::codec::ProstCodec::default(); 7416 + let mut grpc = tonic::server::Grpc::new(codec) 7417 + .apply_compression_config( 7418 + accept_compression_encodings, 7419 + send_compression_encodings, 7420 + ) 7421 + .apply_max_message_size_config( 7422 + max_decoding_message_size, 7423 + max_encoding_message_size, 7424 + ); 7425 + let res = grpc.unary(method, req).await; 7426 + Ok(res) 7427 + }; 7428 + Box::pin(fut) 7429 + } 7430 + "/rockbox.v1alpha1.SoundService/SetPitch" => { 7431 + #[allow(non_camel_case_types)] 7432 + struct SetPitchSvc<T: SoundService>(pub Arc<T>); 7433 + impl<T: SoundService> tonic::server::UnaryService<super::SetPitchRequest> for SetPitchSvc<T> { 7434 + type Response = super::SetPitchResponse; 7435 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 7436 + fn call( 7437 + &mut self, 7438 + request: tonic::Request<super::SetPitchRequest>, 7439 + ) -> Self::Future { 7440 + let inner = Arc::clone(&self.0); 7441 + let fut = async move { 7442 + <T as SoundService>::set_pitch(&inner, request).await 7443 + }; 7444 + Box::pin(fut) 7445 + } 7446 + } 7447 + let accept_compression_encodings = self.accept_compression_encodings; 7448 + let send_compression_encodings = self.send_compression_encodings; 7449 + let max_decoding_message_size = self.max_decoding_message_size; 7450 + let max_encoding_message_size = self.max_encoding_message_size; 7451 + let inner = self.inner.clone(); 7452 + let fut = async move { 7453 + let method = SetPitchSvc(inner); 7454 + let codec = tonic::codec::ProstCodec::default(); 7455 + let mut grpc = tonic::server::Grpc::new(codec) 7456 + .apply_compression_config( 7457 + accept_compression_encodings, 7458 + send_compression_encodings, 7459 + ) 7460 + .apply_max_message_size_config( 7461 + max_decoding_message_size, 7462 + max_encoding_message_size, 7463 + ); 7464 + let res = grpc.unary(method, req).await; 7465 + Ok(res) 7466 + }; 7467 + Box::pin(fut) 7468 + } 7469 + "/rockbox.v1alpha1.SoundService/BeepPlay" => { 7470 + #[allow(non_camel_case_types)] 7471 + struct BeepPlaySvc<T: SoundService>(pub Arc<T>); 7472 + impl<T: SoundService> tonic::server::UnaryService<super::BeepPlayRequest> for BeepPlaySvc<T> { 7473 + type Response = super::BeepPlayResponse; 7474 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 7475 + fn call( 7476 + &mut self, 7477 + request: tonic::Request<super::BeepPlayRequest>, 7478 + ) -> Self::Future { 7479 + let inner = Arc::clone(&self.0); 7480 + let fut = async move { 7481 + <T as SoundService>::beep_play(&inner, request).await 7482 + }; 7483 + Box::pin(fut) 7484 + } 7485 + } 7486 + let accept_compression_encodings = self.accept_compression_encodings; 7487 + let send_compression_encodings = self.send_compression_encodings; 7488 + let max_decoding_message_size = self.max_decoding_message_size; 7489 + let max_encoding_message_size = self.max_encoding_message_size; 7490 + let inner = self.inner.clone(); 7491 + let fut = async move { 7492 + let method = BeepPlaySvc(inner); 7493 + let codec = tonic::codec::ProstCodec::default(); 7494 + let mut grpc = tonic::server::Grpc::new(codec) 7495 + .apply_compression_config( 7496 + accept_compression_encodings, 7497 + send_compression_encodings, 7498 + ) 7499 + .apply_max_message_size_config( 7500 + max_decoding_message_size, 7501 + max_encoding_message_size, 7502 + ); 7503 + let res = grpc.unary(method, req).await; 7504 + Ok(res) 7505 + }; 7506 + Box::pin(fut) 7507 + } 7508 + "/rockbox.v1alpha1.SoundService/PcmbufFade" => { 7509 + #[allow(non_camel_case_types)] 7510 + struct PcmbufFadeSvc<T: SoundService>(pub Arc<T>); 7511 + impl<T: SoundService> tonic::server::UnaryService<super::PcmbufFadeRequest> for PcmbufFadeSvc<T> { 7512 + type Response = super::PcmbufFadeResponse; 7513 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 7514 + fn call( 7515 + &mut self, 7516 + request: tonic::Request<super::PcmbufFadeRequest>, 7517 + ) -> Self::Future { 7518 + let inner = Arc::clone(&self.0); 7519 + let fut = async move { 7520 + <T as SoundService>::pcmbuf_fade(&inner, request).await 7521 + }; 7522 + Box::pin(fut) 7523 + } 7524 + } 7525 + let accept_compression_encodings = self.accept_compression_encodings; 7526 + let send_compression_encodings = self.send_compression_encodings; 7527 + let max_decoding_message_size = self.max_decoding_message_size; 7528 + let max_encoding_message_size = self.max_encoding_message_size; 7529 + let inner = self.inner.clone(); 7530 + let fut = async move { 7531 + let method = PcmbufFadeSvc(inner); 7532 + let codec = tonic::codec::ProstCodec::default(); 7533 + let mut grpc = tonic::server::Grpc::new(codec) 7534 + .apply_compression_config( 7535 + accept_compression_encodings, 7536 + send_compression_encodings, 7537 + ) 7538 + .apply_max_message_size_config( 7539 + max_decoding_message_size, 7540 + max_encoding_message_size, 7541 + ); 7542 + let res = grpc.unary(method, req).await; 7543 + Ok(res) 7544 + }; 7545 + Box::pin(fut) 7546 + } 7547 + "/rockbox.v1alpha1.SoundService/PcmbufSetLowLatency" => { 7548 + #[allow(non_camel_case_types)] 7549 + struct PcmbufSetLowLatencySvc<T: SoundService>(pub Arc<T>); 7550 + impl<T: SoundService> 7551 + tonic::server::UnaryService<super::PcmbufSetLowLatencyRequest> 7552 + for PcmbufSetLowLatencySvc<T> 7553 + { 7554 + type Response = super::PcmbufSetLowLatencyResponse; 7555 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 7556 + fn call( 7557 + &mut self, 7558 + request: tonic::Request<super::PcmbufSetLowLatencyRequest>, 7559 + ) -> Self::Future { 7560 + let inner = Arc::clone(&self.0); 7561 + let fut = async move { 7562 + <T as SoundService>::pcmbuf_set_low_latency(&inner, request).await 7563 + }; 7564 + Box::pin(fut) 7565 + } 7566 + } 7567 + let accept_compression_encodings = self.accept_compression_encodings; 7568 + let send_compression_encodings = self.send_compression_encodings; 7569 + let max_decoding_message_size = self.max_decoding_message_size; 7570 + let max_encoding_message_size = self.max_encoding_message_size; 7571 + let inner = self.inner.clone(); 7572 + let fut = async move { 7573 + let method = PcmbufSetLowLatencySvc(inner); 7574 + let codec = tonic::codec::ProstCodec::default(); 7575 + let mut grpc = tonic::server::Grpc::new(codec) 7576 + .apply_compression_config( 7577 + accept_compression_encodings, 7578 + send_compression_encodings, 7579 + ) 7580 + .apply_max_message_size_config( 7581 + max_decoding_message_size, 7582 + max_encoding_message_size, 7583 + ); 7584 + let res = grpc.unary(method, req).await; 7585 + Ok(res) 7586 + }; 7587 + Box::pin(fut) 7588 + } 7589 + "/rockbox.v1alpha1.SoundService/SystemSoundPlay" => { 7590 + #[allow(non_camel_case_types)] 7591 + struct SystemSoundPlaySvc<T: SoundService>(pub Arc<T>); 7592 + impl<T: SoundService> tonic::server::UnaryService<super::SystemSoundPlayRequest> 7593 + for SystemSoundPlaySvc<T> 7594 + { 7595 + type Response = super::SystemSoundPlayResponse; 7596 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 7597 + fn call( 7598 + &mut self, 7599 + request: tonic::Request<super::SystemSoundPlayRequest>, 7600 + ) -> Self::Future { 7601 + let inner = Arc::clone(&self.0); 7602 + let fut = async move { 7603 + <T as SoundService>::system_sound_play(&inner, request).await 7604 + }; 7605 + Box::pin(fut) 7606 + } 7607 + } 7608 + let accept_compression_encodings = self.accept_compression_encodings; 7609 + let send_compression_encodings = self.send_compression_encodings; 7610 + let max_decoding_message_size = self.max_decoding_message_size; 7611 + let max_encoding_message_size = self.max_encoding_message_size; 7612 + let inner = self.inner.clone(); 7613 + let fut = async move { 7614 + let method = SystemSoundPlaySvc(inner); 7615 + let codec = tonic::codec::ProstCodec::default(); 7616 + let mut grpc = tonic::server::Grpc::new(codec) 7617 + .apply_compression_config( 7618 + accept_compression_encodings, 7619 + send_compression_encodings, 7620 + ) 7621 + .apply_max_message_size_config( 7622 + max_decoding_message_size, 7623 + max_encoding_message_size, 7624 + ); 7625 + let res = grpc.unary(method, req).await; 7626 + Ok(res) 7627 + }; 7628 + Box::pin(fut) 7629 + } 7630 + "/rockbox.v1alpha1.SoundService/KeyclickClick" => { 7631 + #[allow(non_camel_case_types)] 7632 + struct KeyclickClickSvc<T: SoundService>(pub Arc<T>); 7633 + impl<T: SoundService> tonic::server::UnaryService<super::KeyclickClickRequest> 7634 + for KeyclickClickSvc<T> 7635 + { 7636 + type Response = super::KeyclickClickResponse; 7637 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 7638 + fn call( 7639 + &mut self, 7640 + request: tonic::Request<super::KeyclickClickRequest>, 7641 + ) -> Self::Future { 7642 + let inner = Arc::clone(&self.0); 7643 + let fut = async move { 7644 + <T as SoundService>::keyclick_click(&inner, request).await 7645 + }; 7646 + Box::pin(fut) 7647 + } 7648 + } 7649 + let accept_compression_encodings = self.accept_compression_encodings; 7650 + let send_compression_encodings = self.send_compression_encodings; 7651 + let max_decoding_message_size = self.max_decoding_message_size; 7652 + let max_encoding_message_size = self.max_encoding_message_size; 7653 + let inner = self.inner.clone(); 7654 + let fut = async move { 7655 + let method = KeyclickClickSvc(inner); 7656 + let codec = tonic::codec::ProstCodec::default(); 7657 + let mut grpc = tonic::server::Grpc::new(codec) 7658 + .apply_compression_config( 7659 + accept_compression_encodings, 7660 + send_compression_encodings, 7661 + ) 7662 + .apply_max_message_size_config( 7663 + max_decoding_message_size, 7664 + max_encoding_message_size, 7665 + ); 7666 + let res = grpc.unary(method, req).await; 7667 + Ok(res) 7668 + }; 7669 + Box::pin(fut) 7670 + } 7671 + _ => Box::pin(async move { 7672 + let mut response = http::Response::new(empty_body()); 7673 + let headers = response.headers_mut(); 7674 + headers.insert( 7675 + tonic::Status::GRPC_STATUS, 7676 + (tonic::Code::Unimplemented as i32).into(), 7677 + ); 7678 + headers.insert( 7679 + http::header::CONTENT_TYPE, 7680 + tonic::metadata::GRPC_CONTENT_TYPE, 7681 + ); 7682 + Ok(response) 7683 + }), 7684 + } 7685 + } 7686 + } 7687 + impl<T> Clone for SoundServiceServer<T> { 7688 + fn clone(&self) -> Self { 7689 + let inner = self.inner.clone(); 7690 + Self { 7691 + inner, 7692 + accept_compression_encodings: self.accept_compression_encodings, 7693 + send_compression_encodings: self.send_compression_encodings, 7694 + max_decoding_message_size: self.max_decoding_message_size, 7695 + max_encoding_message_size: self.max_encoding_message_size, 7696 + } 7697 + } 7698 + } 7699 + /// Generated gRPC service name 7700 + pub const SERVICE_NAME: &str = "rockbox.v1alpha1.SoundService"; 7701 + impl<T> tonic::server::NamedService for SoundServiceServer<T> { 7702 + const NAME: &'static str = SERVICE_NAME; 7703 + } 7704 + } 7705 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 7706 + pub struct GetRockboxVersionRequest {} 7707 + #[derive(Clone, PartialEq, ::prost::Message)] 7708 + pub struct GetRockboxVersionResponse { 7709 + #[prost(string, tag = "1")] 7710 + pub version: ::prost::alloc::string::String, 7711 + } 7712 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 7713 + pub struct GetGlobalStatusRequest {} 7714 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 7715 + pub struct GetGlobalStatusResponse { 7716 + #[prost(int32, tag = "1")] 7717 + pub resume_index: i32, 7718 + #[prost(uint32, tag = "2")] 7719 + pub resume_crc32: u32, 7720 + #[prost(uint32, tag = "3")] 7721 + pub resume_elapsed: u32, 7722 + #[prost(uint32, tag = "4")] 7723 + pub resume_offset: u32, 7724 + #[prost(int32, tag = "5")] 7725 + pub runtime: i32, 7726 + #[prost(int32, tag = "6")] 7727 + pub topruntime: i32, 7728 + #[prost(int32, tag = "7")] 7729 + pub dircache_size: i32, 7730 + #[prost(int32, tag = "8")] 7731 + pub last_screen: i32, 7732 + #[prost(int32, tag = "9")] 7733 + pub viewer_icon_count: i32, 7734 + #[prost(int32, tag = "10")] 7735 + pub last_volume_change: i32, 7736 + } 7737 + /// Generated client implementations. 7738 + pub mod system_service_client { 7739 + #![allow( 7740 + unused_variables, 7741 + dead_code, 7742 + missing_docs, 7743 + clippy::wildcard_imports, 7744 + clippy::let_unit_value 7745 + )] 7746 + use tonic::codegen::http::Uri; 7747 + use tonic::codegen::*; 7748 + #[derive(Debug, Clone)] 7749 + pub struct SystemServiceClient<T> { 7750 + inner: tonic::client::Grpc<T>, 7751 + } 7752 + impl SystemServiceClient<tonic::transport::Channel> { 7753 + /// Attempt to create a new client by connecting to a given endpoint. 7754 + pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error> 7755 + where 7756 + D: TryInto<tonic::transport::Endpoint>, 7757 + D::Error: Into<StdError>, 7758 + { 7759 + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; 7760 + Ok(Self::new(conn)) 7761 + } 7762 + } 7763 + impl<T> SystemServiceClient<T> 7764 + where 7765 + T: tonic::client::GrpcService<tonic::body::BoxBody>, 7766 + T::Error: Into<StdError>, 7767 + T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static, 7768 + <T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send, 7769 + { 7770 + pub fn new(inner: T) -> Self { 7771 + let inner = tonic::client::Grpc::new(inner); 7772 + Self { inner } 7773 + } 7774 + pub fn with_origin(inner: T, origin: Uri) -> Self { 7775 + let inner = tonic::client::Grpc::with_origin(inner, origin); 7776 + Self { inner } 7777 + } 7778 + pub fn with_interceptor<F>( 7779 + inner: T, 7780 + interceptor: F, 7781 + ) -> SystemServiceClient<InterceptedService<T, F>> 7782 + where 7783 + F: tonic::service::Interceptor, 7784 + T::ResponseBody: Default, 7785 + T: tonic::codegen::Service< 7786 + http::Request<tonic::body::BoxBody>, 7787 + Response = http::Response< 7788 + <T as tonic::client::GrpcService<tonic::body::BoxBody>>::ResponseBody, 7789 + >, 7790 + >, 7791 + <T as tonic::codegen::Service<http::Request<tonic::body::BoxBody>>>::Error: 7792 + Into<StdError> + std::marker::Send + std::marker::Sync, 7793 + { 7794 + SystemServiceClient::new(InterceptedService::new(inner, interceptor)) 7795 + } 7796 + /// Compress requests with the given encoding. 7797 + /// 7798 + /// This requires the server to support it otherwise it might respond with an 7799 + /// error. 7800 + #[must_use] 7801 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 7802 + self.inner = self.inner.send_compressed(encoding); 7803 + self 7804 + } 7805 + /// Enable decompressing responses. 7806 + #[must_use] 7807 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 7808 + self.inner = self.inner.accept_compressed(encoding); 7809 + self 7810 + } 7811 + /// Limits the maximum size of a decoded message. 7812 + /// 7813 + /// Default: `4MB` 7814 + #[must_use] 7815 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 7816 + self.inner = self.inner.max_decoding_message_size(limit); 7817 + self 7818 + } 7819 + /// Limits the maximum size of an encoded message. 7820 + /// 7821 + /// Default: `usize::MAX` 7822 + #[must_use] 7823 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 7824 + self.inner = self.inner.max_encoding_message_size(limit); 7825 + self 7826 + } 7827 + pub async fn get_rockbox_version( 7828 + &mut self, 7829 + request: impl tonic::IntoRequest<super::GetRockboxVersionRequest>, 7830 + ) -> std::result::Result<tonic::Response<super::GetRockboxVersionResponse>, tonic::Status> 7831 + { 7832 + self.inner.ready().await.map_err(|e| { 7833 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 7834 + })?; 7835 + let codec = tonic::codec::ProstCodec::default(); 7836 + let path = http::uri::PathAndQuery::from_static( 7837 + "/rockbox.v1alpha1.SystemService/GetRockboxVersion", 7838 + ); 7839 + let mut req = request.into_request(); 7840 + req.extensions_mut().insert(GrpcMethod::new( 7841 + "rockbox.v1alpha1.SystemService", 7842 + "GetRockboxVersion", 7843 + )); 7844 + self.inner.unary(req, path, codec).await 7845 + } 7846 + pub async fn get_global_status( 7847 + &mut self, 7848 + request: impl tonic::IntoRequest<super::GetGlobalStatusRequest>, 7849 + ) -> std::result::Result<tonic::Response<super::GetGlobalStatusResponse>, tonic::Status> 7850 + { 7851 + self.inner.ready().await.map_err(|e| { 7852 + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 7853 + })?; 7854 + let codec = tonic::codec::ProstCodec::default(); 7855 + let path = http::uri::PathAndQuery::from_static( 7856 + "/rockbox.v1alpha1.SystemService/GetGlobalStatus", 7857 + ); 7858 + let mut req = request.into_request(); 7859 + req.extensions_mut().insert(GrpcMethod::new( 7860 + "rockbox.v1alpha1.SystemService", 7861 + "GetGlobalStatus", 7862 + )); 7863 + self.inner.unary(req, path, codec).await 7864 + } 7865 + } 7866 + } 7867 + /// Generated server implementations. 7868 + pub mod system_service_server { 7869 + #![allow( 7870 + unused_variables, 7871 + dead_code, 7872 + missing_docs, 7873 + clippy::wildcard_imports, 7874 + clippy::let_unit_value 7875 + )] 7876 + use tonic::codegen::*; 7877 + /// Generated trait containing gRPC methods that should be implemented for use with SystemServiceServer. 7878 + #[async_trait] 7879 + pub trait SystemService: std::marker::Send + std::marker::Sync + 'static { 7880 + async fn get_rockbox_version( 7881 + &self, 7882 + request: tonic::Request<super::GetRockboxVersionRequest>, 7883 + ) -> std::result::Result<tonic::Response<super::GetRockboxVersionResponse>, tonic::Status>; 7884 + async fn get_global_status( 7885 + &self, 7886 + request: tonic::Request<super::GetGlobalStatusRequest>, 7887 + ) -> std::result::Result<tonic::Response<super::GetGlobalStatusResponse>, tonic::Status>; 7888 + } 7889 + #[derive(Debug)] 7890 + pub struct SystemServiceServer<T> { 7891 + inner: Arc<T>, 7892 + accept_compression_encodings: EnabledCompressionEncodings, 7893 + send_compression_encodings: EnabledCompressionEncodings, 7894 + max_decoding_message_size: Option<usize>, 7895 + max_encoding_message_size: Option<usize>, 7896 + } 7897 + impl<T> SystemServiceServer<T> { 7898 + pub fn new(inner: T) -> Self { 7899 + Self::from_arc(Arc::new(inner)) 7900 + } 7901 + pub fn from_arc(inner: Arc<T>) -> Self { 7902 + Self { 7903 + inner, 7904 + accept_compression_encodings: Default::default(), 7905 + send_compression_encodings: Default::default(), 7906 + max_decoding_message_size: None, 7907 + max_encoding_message_size: None, 7908 + } 7909 + } 7910 + pub fn with_interceptor<F>(inner: T, interceptor: F) -> InterceptedService<Self, F> 7911 + where 7912 + F: tonic::service::Interceptor, 7913 + { 7914 + InterceptedService::new(Self::new(inner), interceptor) 7915 + } 7916 + /// Enable decompressing requests with the given encoding. 7917 + #[must_use] 7918 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 7919 + self.accept_compression_encodings.enable(encoding); 7920 + self 7921 + } 7922 + /// Compress responses with the given encoding, if the client supports it. 7923 + #[must_use] 7924 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 7925 + self.send_compression_encodings.enable(encoding); 7926 + self 7927 + } 7928 + /// Limits the maximum size of a decoded message. 7929 + /// 7930 + /// Default: `4MB` 7931 + #[must_use] 7932 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 7933 + self.max_decoding_message_size = Some(limit); 7934 + self 7935 + } 7936 + /// Limits the maximum size of an encoded message. 7937 + /// 7938 + /// Default: `usize::MAX` 7939 + #[must_use] 7940 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 7941 + self.max_encoding_message_size = Some(limit); 7942 + self 7943 + } 7944 + } 7945 + impl<T, B> tonic::codegen::Service<http::Request<B>> for SystemServiceServer<T> 7946 + where 7947 + T: SystemService, 7948 + B: Body + std::marker::Send + 'static, 7949 + B::Error: Into<StdError> + std::marker::Send + 'static, 7950 + { 7951 + type Response = http::Response<tonic::body::BoxBody>; 7952 + type Error = std::convert::Infallible; 7953 + type Future = BoxFuture<Self::Response, Self::Error>; 7954 + fn poll_ready( 7955 + &mut self, 7956 + _cx: &mut Context<'_>, 7957 + ) -> Poll<std::result::Result<(), Self::Error>> { 7958 + Poll::Ready(Ok(())) 7959 + } 7960 + fn call(&mut self, req: http::Request<B>) -> Self::Future { 7961 + match req.uri().path() { 7962 + "/rockbox.v1alpha1.SystemService/GetRockboxVersion" => { 7963 + #[allow(non_camel_case_types)] 7964 + struct GetRockboxVersionSvc<T: SystemService>(pub Arc<T>); 7965 + impl<T: SystemService> 7966 + tonic::server::UnaryService<super::GetRockboxVersionRequest> 7967 + for GetRockboxVersionSvc<T> 7968 + { 7969 + type Response = super::GetRockboxVersionResponse; 7970 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 7971 + fn call( 7972 + &mut self, 7973 + request: tonic::Request<super::GetRockboxVersionRequest>, 7974 + ) -> Self::Future { 7975 + let inner = Arc::clone(&self.0); 7976 + let fut = async move { 7977 + <T as SystemService>::get_rockbox_version(&inner, request).await 7978 + }; 7979 + Box::pin(fut) 7980 + } 7981 + } 7982 + let accept_compression_encodings = self.accept_compression_encodings; 7983 + let send_compression_encodings = self.send_compression_encodings; 7984 + let max_decoding_message_size = self.max_decoding_message_size; 7985 + let max_encoding_message_size = self.max_encoding_message_size; 7986 + let inner = self.inner.clone(); 7987 + let fut = async move { 7988 + let method = GetRockboxVersionSvc(inner); 7989 + let codec = tonic::codec::ProstCodec::default(); 7990 + let mut grpc = tonic::server::Grpc::new(codec) 7991 + .apply_compression_config( 7992 + accept_compression_encodings, 7993 + send_compression_encodings, 7994 + ) 7995 + .apply_max_message_size_config( 7996 + max_decoding_message_size, 7997 + max_encoding_message_size, 7998 + ); 7999 + let res = grpc.unary(method, req).await; 8000 + Ok(res) 8001 + }; 8002 + Box::pin(fut) 8003 + } 8004 + "/rockbox.v1alpha1.SystemService/GetGlobalStatus" => { 8005 + #[allow(non_camel_case_types)] 8006 + struct GetGlobalStatusSvc<T: SystemService>(pub Arc<T>); 8007 + impl<T: SystemService> 8008 + tonic::server::UnaryService<super::GetGlobalStatusRequest> 8009 + for GetGlobalStatusSvc<T> 8010 + { 8011 + type Response = super::GetGlobalStatusResponse; 8012 + type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 8013 + fn call( 8014 + &mut self, 8015 + request: tonic::Request<super::GetGlobalStatusRequest>, 8016 + ) -> Self::Future { 8017 + let inner = Arc::clone(&self.0); 8018 + let fut = async move { 8019 + <T as SystemService>::get_global_status(&inner, request).await 8020 + }; 8021 + Box::pin(fut) 8022 + } 8023 + } 8024 + let accept_compression_encodings = self.accept_compression_encodings; 8025 + let send_compression_encodings = self.send_compression_encodings; 8026 + let max_decoding_message_size = self.max_decoding_message_size; 8027 + let max_encoding_message_size = self.max_encoding_message_size; 8028 + let inner = self.inner.clone(); 8029 + let fut = async move { 8030 + let method = GetGlobalStatusSvc(inner); 8031 + let codec = tonic::codec::ProstCodec::default(); 8032 + let mut grpc = tonic::server::Grpc::new(codec) 8033 + .apply_compression_config( 8034 + accept_compression_encodings, 8035 + send_compression_encodings, 8036 + ) 8037 + .apply_max_message_size_config( 8038 + max_decoding_message_size, 8039 + max_encoding_message_size, 8040 + ); 8041 + let res = grpc.unary(method, req).await; 8042 + Ok(res) 8043 + }; 8044 + Box::pin(fut) 8045 + } 8046 + _ => Box::pin(async move { 8047 + let mut response = http::Response::new(empty_body()); 8048 + let headers = response.headers_mut(); 8049 + headers.insert( 8050 + tonic::Status::GRPC_STATUS, 8051 + (tonic::Code::Unimplemented as i32).into(), 8052 + ); 8053 + headers.insert( 8054 + http::header::CONTENT_TYPE, 8055 + tonic::metadata::GRPC_CONTENT_TYPE, 8056 + ); 8057 + Ok(response) 8058 + }), 8059 + } 8060 + } 8061 + } 8062 + impl<T> Clone for SystemServiceServer<T> { 8063 + fn clone(&self) -> Self { 8064 + let inner = self.inner.clone(); 8065 + Self { 8066 + inner, 8067 + accept_compression_encodings: self.accept_compression_encodings, 8068 + send_compression_encodings: self.send_compression_encodings, 8069 + max_decoding_message_size: self.max_decoding_message_size, 8070 + max_encoding_message_size: self.max_encoding_message_size, 8071 + } 8072 + } 8073 + } 8074 + /// Generated gRPC service name 8075 + pub const SERVICE_NAME: &str = "rockbox.v1alpha1.SystemService"; 8076 + impl<T> tonic::server::NamedService for SystemServiceServer<T> { 8077 + const NAME: &'static str = SERVICE_NAME; 8078 + } 8079 + }
crates/upnp/src/api/rockbox_descriptor.bin

This is a binary file and will not be displayed.

+147
crates/upnp/src/db.rs
··· 1 + use sqlx::{sqlite::SqliteConnectOptions, Pool, Sqlite, SqlitePool}; 2 + 3 + #[derive(sqlx::FromRow, Debug)] 4 + pub struct Track { 5 + pub id: String, 6 + pub path: String, 7 + pub title: String, 8 + pub artist: String, 9 + pub album: String, 10 + pub album_id: String, 11 + pub artist_id: String, 12 + pub track_number: Option<i64>, 13 + pub length: i64, 14 + pub filesize: i64, 15 + pub album_art: Option<String>, 16 + pub genre: Option<String>, 17 + pub year: Option<i64>, 18 + } 19 + 20 + #[derive(sqlx::FromRow, Debug)] 21 + pub struct Album { 22 + pub id: String, 23 + pub title: String, 24 + pub artist: String, 25 + pub album_art: Option<String>, 26 + pub track_count: i64, 27 + } 28 + 29 + #[derive(sqlx::FromRow, Debug)] 30 + pub struct Artist { 31 + pub id: String, 32 + pub name: String, 33 + pub track_count: i64, 34 + } 35 + 36 + pub async fn open_pool() -> anyhow::Result<Pool<Sqlite>> { 37 + let home = std::env::var("HOME").unwrap_or_default(); 38 + let db_path = std::env::var("DATABASE_URL") 39 + .unwrap_or_else(|_| format!("{}/.config/rockbox.org/rockbox-library.db", home)); 40 + 41 + if !std::path::Path::new(&db_path).exists() { 42 + anyhow::bail!("library database not found at {}", db_path); 43 + } 44 + 45 + let options = SqliteConnectOptions::new() 46 + .filename(&db_path) 47 + .read_only(true); 48 + Ok(SqlitePool::connect_with(options).await?) 49 + } 50 + 51 + pub async fn all_tracks(pool: &Pool<Sqlite>) -> anyhow::Result<Vec<Track>> { 52 + let rows = sqlx::query_as::<_, Track>( 53 + "SELECT id, path, title, artist, album, album_id, artist_id, \ 54 + track_number, length, filesize, album_art, genre, year FROM track \ 55 + ORDER BY artist, album, track_number, title", 56 + ) 57 + .fetch_all(pool) 58 + .await?; 59 + Ok(rows) 60 + } 61 + 62 + pub async fn track_by_path(pool: &Pool<Sqlite>, path: &str) -> anyhow::Result<Option<Track>> { 63 + let row = sqlx::query_as::<_, Track>( 64 + "SELECT id, path, title, artist, album, album_id, artist_id, \ 65 + track_number, length, filesize, album_art, genre, year FROM track WHERE path = ?", 66 + ) 67 + .bind(path) 68 + .fetch_optional(pool) 69 + .await?; 70 + Ok(row) 71 + } 72 + 73 + pub async fn track_by_id(pool: &Pool<Sqlite>, id: &str) -> anyhow::Result<Option<Track>> { 74 + let row = sqlx::query_as::<_, Track>( 75 + "SELECT id, path, title, artist, album, album_id, artist_id, \ 76 + track_number, length, filesize, album_art, genre, year FROM track WHERE id = ?", 77 + ) 78 + .bind(id) 79 + .fetch_optional(pool) 80 + .await?; 81 + Ok(row) 82 + } 83 + 84 + pub async fn tracks_by_album(pool: &Pool<Sqlite>, album_id: &str) -> anyhow::Result<Vec<Track>> { 85 + let rows = sqlx::query_as::<_, Track>( 86 + "SELECT id, path, title, artist, album, album_id, artist_id, \ 87 + track_number, length, filesize, album_art, genre, year FROM track \ 88 + WHERE album_id = ? ORDER BY track_number, title", 89 + ) 90 + .bind(album_id) 91 + .fetch_all(pool) 92 + .await?; 93 + Ok(rows) 94 + } 95 + 96 + pub async fn tracks_by_artist(pool: &Pool<Sqlite>, artist_id: &str) -> anyhow::Result<Vec<Track>> { 97 + let rows = sqlx::query_as::<_, Track>( 98 + "SELECT id, path, title, artist, album, album_id, artist_id, \ 99 + track_number, length, filesize, album_art, genre, year FROM track \ 100 + WHERE artist_id = ? ORDER BY album, track_number, title", 101 + ) 102 + .bind(artist_id) 103 + .fetch_all(pool) 104 + .await?; 105 + Ok(rows) 106 + } 107 + 108 + pub async fn all_albums(pool: &Pool<Sqlite>) -> anyhow::Result<Vec<Album>> { 109 + let rows = sqlx::query_as::<_, Album>( 110 + "SELECT album_id AS id, album AS title, artist, album_art, COUNT(*) AS track_count \ 111 + FROM track GROUP BY album_id ORDER BY artist, title", 112 + ) 113 + .fetch_all(pool) 114 + .await?; 115 + Ok(rows) 116 + } 117 + 118 + pub async fn all_artists(pool: &Pool<Sqlite>) -> anyhow::Result<Vec<Artist>> { 119 + let rows = sqlx::query_as::<_, Artist>( 120 + "SELECT artist_id AS id, artist AS name, COUNT(*) AS track_count \ 121 + FROM track GROUP BY artist_id ORDER BY name", 122 + ) 123 + .fetch_all(pool) 124 + .await?; 125 + Ok(rows) 126 + } 127 + 128 + pub async fn count_tracks(pool: &Pool<Sqlite>) -> i64 { 129 + sqlx::query_scalar("SELECT COUNT(*) FROM track") 130 + .fetch_one(pool) 131 + .await 132 + .unwrap_or(0) 133 + } 134 + 135 + pub async fn count_albums(pool: &Pool<Sqlite>) -> i64 { 136 + sqlx::query_scalar("SELECT COUNT(DISTINCT album_id) FROM track") 137 + .fetch_one(pool) 138 + .await 139 + .unwrap_or(0) 140 + } 141 + 142 + pub async fn count_artists(pool: &Pool<Sqlite>) -> i64 { 143 + sqlx::query_scalar("SELECT COUNT(DISTINCT artist_id) FROM track") 144 + .fetch_one(pool) 145 + .await 146 + .unwrap_or(0) 147 + }
+114
crates/upnp/src/didl.rs
··· 1 + use crate::db::{Album, Artist, Track}; 2 + use crate::format::protocol_info_for_path; 3 + 4 + fn xml_escape(s: &str) -> String { 5 + s.replace('&', "&amp;") 6 + .replace('<', "&lt;") 7 + .replace('>', "&gt;") 8 + .replace('"', "&quot;") 9 + } 10 + 11 + /// Format track duration (milliseconds) as `h:mm:ss.mmm`. 12 + fn fmt_duration(ms: i64) -> String { 13 + let ms = ms.max(0) as u64; 14 + let secs = ms / 1000; 15 + let millis = ms % 1000; 16 + let minutes = secs / 60; 17 + let secs = secs % 60; 18 + let hours = minutes / 60; 19 + let minutes = minutes % 60; 20 + format!("{hours}:{minutes:02}:{secs:02}.{millis:03}") 21 + } 22 + 23 + pub fn track_item(track: &Track, parent_id: &str, base_url: &str) -> String { 24 + let protocol_info = protocol_info_for_path(&track.path); 25 + let duration = fmt_duration(track.length); 26 + let res_url = format!("{base_url}/audio/{}", track.id); 27 + let art_tag = track.album_art.as_deref().map(|_| { 28 + format!( 29 + "<upnp:albumArtURI>{base_url}/art/{}</upnp:albumArtURI>", 30 + xml_escape(&track.album_id) 31 + ) 32 + }); 33 + let track_num_tag = track 34 + .track_number 35 + .map(|n| format!("<upnp:originalTrackNumber>{n}</upnp:originalTrackNumber>")); 36 + let genre_tag = track 37 + .genre 38 + .as_deref() 39 + .map(|g| format!("<upnp:genre>{}</upnp:genre>", xml_escape(g))); 40 + 41 + format!( 42 + r#"<item id="track:{tid}" parentID="{parent_id}" restricted="1"> 43 + <dc:title>{title}</dc:title> 44 + <dc:creator>{artist}</dc:creator> 45 + <upnp:class>object.item.audioItem.musicTrack</upnp:class> 46 + <upnp:artist>{artist}</upnp:artist> 47 + <upnp:album>{album}</upnp:album> 48 + {art}{track_num}{genre}<res protocolInfo="{protocol_info}" size="{size}" duration="{duration}">{res_url}</res> 49 + </item>"#, 50 + tid = xml_escape(&track.id), 51 + parent_id = xml_escape(parent_id), 52 + title = xml_escape(&track.title), 53 + artist = xml_escape(&track.artist), 54 + album = xml_escape(&track.album), 55 + art = art_tag.unwrap_or_default(), 56 + track_num = track_num_tag.unwrap_or_default(), 57 + genre = genre_tag.unwrap_or_default(), 58 + size = track.filesize, 59 + ) 60 + } 61 + 62 + pub fn album_container(album: &Album, parent_id: &str) -> String { 63 + format!( 64 + r#"<container id="album:{id}" parentID="{parent_id}" restricted="1" childCount="{count}" searchable="0"> 65 + <dc:title>{title}</dc:title> 66 + <dc:creator>{artist}</dc:creator> 67 + <upnp:class>object.container.album.musicAlbum</upnp:class> 68 + </container>"#, 69 + id = xml_escape(&album.id), 70 + parent_id = xml_escape(parent_id), 71 + title = xml_escape(&album.title), 72 + artist = xml_escape(&album.artist), 73 + count = album.track_count, 74 + ) 75 + } 76 + 77 + pub fn artist_container(artist: &Artist, parent_id: &str) -> String { 78 + format!( 79 + r#"<container id="artist:{id}" parentID="{parent_id}" restricted="1" childCount="{count}" searchable="0"> 80 + <dc:title>{name}</dc:title> 81 + <upnp:class>object.container.person.musicArtist</upnp:class> 82 + </container>"#, 83 + id = xml_escape(&artist.id), 84 + parent_id = xml_escape(parent_id), 85 + name = xml_escape(&artist.name), 86 + count = artist.track_count, 87 + ) 88 + } 89 + 90 + pub fn simple_container(id: &str, parent_id: &str, title: &str, child_count: i64) -> String { 91 + format!( 92 + r#"<container id="{id}" parentID="{parent_id}" restricted="1" childCount="{child_count}" searchable="1"> 93 + <dc:title>{title}</dc:title> 94 + <upnp:class>object.container</upnp:class> 95 + </container>"#, 96 + id = xml_escape(id), 97 + parent_id = xml_escape(parent_id), 98 + title = xml_escape(title), 99 + ) 100 + } 101 + 102 + pub fn wrap_didl(items: &[String]) -> String { 103 + let body = items.join("\n"); 104 + format!( 105 + r#"<DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/">{body}</DIDL-Lite>"# 106 + ) 107 + } 108 + 109 + /// XML-escape the DIDL-Lite string for embedding inside a SOAP <Result> element. 110 + pub fn escape_for_result(s: &str) -> String { 111 + s.replace('&', "&amp;") 112 + .replace('<', "&lt;") 113 + .replace('>', "&gt;") 114 + }
+158
crates/upnp/src/format.rs
··· 1 + /// Return the HTTP Content-Type for a given file path, based on the extension. 2 + /// 3 + /// Covers every format in Rockbox's `audio_formats[]` array 4 + /// (lib/rbcodec/metadata/metadata.c). 5 + pub fn content_type_for_path(path: &str) -> &'static str { 6 + let ext = path 7 + .rsplit('.') 8 + .next() 9 + .map(|e| e.to_ascii_lowercase()) 10 + .unwrap_or_default(); 11 + content_type_for_ext(&ext) 12 + } 13 + 14 + /// Return the UPnP `protocolInfo` source string for a given file path. 15 + /// Format: `http-get:*:<mime>:*` 16 + pub fn protocol_info_for_path(path: &str) -> String { 17 + format!("http-get:*:{}:*", content_type_for_path(path)) 18 + } 19 + 20 + fn content_type_for_ext(ext: &str) -> &'static str { 21 + match ext { 22 + // ── MPEG Audio (MP1 / MP2 / MP3) ───────────────────────────────── 23 + "mp3" | "mp2" | "mp1" | "mpa" => "audio/mpeg", 24 + 25 + // ── AIFF ────────────────────────────────────────────────────────── 26 + "aiff" | "aif" => "audio/aiff", 27 + 28 + // ── WAV / WAVE64 / ATRAC3-in-WAV ───────────────────────────────── 29 + "wav" | "at3" => "audio/wav", 30 + "w64" => "audio/x-w64", 31 + 32 + // ── Ogg Vorbis ──────────────────────────────────────────────────── 33 + "ogg" | "oga" => "audio/ogg", 34 + 35 + // ── Opus ────────────────────────────────────────────────────────── 36 + "opus" => "audio/opus", 37 + 38 + // ── Speex (Ogg container) ───────────────────────────────────────── 39 + "spx" => "audio/ogg", 40 + 41 + // ── FLAC ────────────────────────────────────────────────────────── 42 + "flac" => "audio/flac", 43 + 44 + // ── AAC / ALAC / MP4 ────────────────────────────────────────────── 45 + "m4a" | "m4b" | "mp4" => "audio/mp4", 46 + "aac" => "audio/aac", 47 + 48 + // ── WavPack ─────────────────────────────────────────────────────── 49 + "wv" => "audio/x-wavpack", 50 + 51 + // ── Monkey's Audio ──────────────────────────────────────────────── 52 + "ape" | "mac" => "audio/x-ape", 53 + 54 + // ── Musepack ────────────────────────────────────────────────────── 55 + "mpc" => "audio/x-musepack", 56 + 57 + // ── AC3 / A52 ───────────────────────────────────────────────────── 58 + "ac3" | "a52" => "audio/ac3", 59 + 60 + // ── WMA / WMV / ASF ─────────────────────────────────────────────── 61 + "wma" | "wmv" | "asf" => "audio/x-ms-wma", 62 + 63 + // ── RealMedia ───────────────────────────────────────────────────── 64 + "rm" | "ra" | "rmvb" => "audio/x-pn-realaudio", 65 + 66 + // ── True Audio ──────────────────────────────────────────────────── 67 + "tta" => "audio/x-tta", 68 + 69 + // ── Shorten ─────────────────────────────────────────────────────── 70 + "shn" => "audio/x-shorten", 71 + 72 + // ── Sun Audio / AU ──────────────────────────────────────────────── 73 + "au" | "snd" => "audio/basic", 74 + 75 + // ── Sony ATRAC3 in OMA container ───────────────────────────────── 76 + "oma" | "aa3" => "audio/x-sony-oma", 77 + 78 + // ── SMAF (mobile ringtone) ──────────────────────────────────────── 79 + "mmf" => "application/x-smaf", 80 + 81 + // ── Dialogic VOX ───────────────────────────────────────────────── 82 + "vox" => "audio/vox", 83 + 84 + // ── ADX (CRI Middleware) ────────────────────────────────────────── 85 + "adx" => "audio/x-adx", 86 + 87 + // ── MOD (Amiga tracker) ─────────────────────────────────────────── 88 + "mod" => "audio/mod", 89 + 90 + // ── Chiptune / game music formats ───────────────────────────────── 91 + // SID (C64) 92 + "sid" => "audio/prs.sid", 93 + // NES Sound Format 94 + "nsf" | "nsfe" => "audio/x-nsf", 95 + // SPC (SNES) 96 + "spc" => "audio/x-spc", 97 + // Atari SAP / ASAP family 98 + "sap" | "cmc" | "cm3" | "cmr" | "cms" | "dmc" | "dlt" | "mpt" | "mpd" | "rmt" | "tmc" 99 + | "tm8" | "tm2" => "audio/x-asap", 100 + // AY (ZX Spectrum / Amstrad) 101 + "ay" => "audio/x-ay", 102 + // VTX (ZX Spectrum) 103 + "vtx" => "audio/x-vtx", 104 + // GBS (Game Boy) 105 + "gbs" => "audio/x-gbs", 106 + // HES (PC Engine / TurboGrafx) 107 + "hes" => "audio/x-hes", 108 + // SGC (Sega Master System / Game Gear / Coleco) 109 + "sgc" => "audio/x-sgc", 110 + // VGM (Video Game Music) 111 + "vgm" | "vgz" => "audio/x-vgm", 112 + // KSS (MSX) 113 + "kss" => "audio/x-kss", 114 + 115 + _ => "application/octet-stream", 116 + } 117 + } 118 + 119 + #[cfg(test)] 120 + mod tests { 121 + use super::*; 122 + 123 + #[test] 124 + fn common_formats() { 125 + assert_eq!(content_type_for_path("song.mp3"), "audio/mpeg"); 126 + assert_eq!(content_type_for_path("song.flac"), "audio/flac"); 127 + assert_eq!(content_type_for_path("song.ogg"), "audio/ogg"); 128 + assert_eq!(content_type_for_path("song.opus"), "audio/opus"); 129 + assert_eq!(content_type_for_path("song.m4a"), "audio/mp4"); 130 + assert_eq!(content_type_for_path("song.wav"), "audio/wav"); 131 + assert_eq!(content_type_for_path("song.wv"), "audio/x-wavpack"); 132 + assert_eq!(content_type_for_path("song.ape"), "audio/x-ape"); 133 + } 134 + 135 + #[test] 136 + fn mpeg_variants() { 137 + assert_eq!(content_type_for_path("song.mp1"), "audio/mpeg"); 138 + assert_eq!(content_type_for_path("song.mp2"), "audio/mpeg"); 139 + assert_eq!(content_type_for_path("song.mpa"), "audio/mpeg"); 140 + } 141 + 142 + #[test] 143 + fn game_formats() { 144 + assert_eq!(content_type_for_path("game.sid"), "audio/prs.sid"); 145 + assert_eq!(content_type_for_path("game.nsf"), "audio/x-nsf"); 146 + assert_eq!(content_type_for_path("game.spc"), "audio/x-spc"); 147 + assert_eq!(content_type_for_path("game.vgm"), "audio/x-vgm"); 148 + assert_eq!(content_type_for_path("game.kss"), "audio/x-kss"); 149 + } 150 + 151 + #[test] 152 + fn unknown_falls_back() { 153 + assert_eq!( 154 + content_type_for_path("file.xyz"), 155 + "application/octet-stream" 156 + ); 157 + } 158 + }
+603
crates/upnp/src/lib.rs
··· 1 + pub mod api { 2 + #[path = ""] 3 + pub mod rockbox { 4 + #[path = "rockbox.v1alpha1.rs"] 5 + pub mod v1alpha1; 6 + } 7 + } 8 + 9 + pub mod db; 10 + pub(crate) mod didl; 11 + pub mod format; 12 + pub(crate) mod pcm_server; 13 + pub mod renderer; 14 + pub mod scan; 15 + pub mod server; 16 + pub(crate) mod ssdp; 17 + 18 + // Called from rockbox-cli to force this crate's symbols into librockbox_cli.a 19 + #[doc(hidden)] 20 + pub fn _link_upnp() {} 21 + 22 + use std::collections::VecDeque; 23 + use std::ffi::CStr; 24 + use std::os::raw::{c_char, c_int}; 25 + use std::sync::atomic::{AtomicU64, Ordering}; 26 + use std::sync::{Arc, Condvar, Mutex, OnceLock}; 27 + 28 + // --------------------------------------------------------------------------- 29 + // Broadcast buffer — one writer, N independent readers (WAV PCM stream). 30 + // Follows the same pattern as the rockbox-slim crate. 31 + // --------------------------------------------------------------------------- 32 + 33 + pub(crate) enum RecvResult { 34 + Data(Vec<u8>), 35 + Closed, 36 + } 37 + 38 + pub(crate) struct BroadcastBuffer { 39 + inner: Mutex<BroadcastInner>, 40 + condvar: Condvar, 41 + } 42 + 43 + struct BroadcastInner { 44 + chunks: VecDeque<(u64, Vec<u8>)>, 45 + next_seq: u64, 46 + total_bytes: usize, 47 + closed: bool, 48 + } 49 + 50 + // 4 MB — about 23 s of S16LE stereo at 44100 Hz 51 + const MAX_BUFFERED: usize = 4 * 1024 * 1024; 52 + 53 + impl BroadcastBuffer { 54 + fn new() -> Self { 55 + BroadcastBuffer { 56 + inner: Mutex::new(BroadcastInner { 57 + chunks: VecDeque::new(), 58 + next_seq: 0, 59 + total_bytes: 0, 60 + closed: false, 61 + }), 62 + condvar: Condvar::new(), 63 + } 64 + } 65 + 66 + pub(crate) fn push(&self, data: &[u8]) { 67 + let mut g = self.inner.lock().unwrap(); 68 + if g.closed { 69 + return; 70 + } 71 + let seq = g.next_seq; 72 + g.next_seq += 1; 73 + g.total_bytes += data.len(); 74 + g.chunks.push_back((seq, data.to_vec())); 75 + while g.total_bytes > MAX_BUFFERED { 76 + if let Some((_, old)) = g.chunks.pop_front() { 77 + g.total_bytes -= old.len(); 78 + } else { 79 + break; 80 + } 81 + } 82 + self.condvar.notify_all(); 83 + } 84 + 85 + pub(crate) fn subscribe(self: &Arc<Self>) -> BroadcastReceiver { 86 + let next_seq = self.inner.lock().unwrap().next_seq; 87 + BroadcastReceiver { 88 + buf: Arc::clone(self), 89 + next_seq, 90 + } 91 + } 92 + 93 + fn reset(&self) { 94 + let mut g = self.inner.lock().unwrap(); 95 + g.chunks.clear(); 96 + g.total_bytes = 0; 97 + g.closed = false; 98 + // next_seq is intentionally NOT reset so existing receivers skip forward. 99 + } 100 + 101 + fn close(&self) { 102 + let mut g = self.inner.lock().unwrap(); 103 + g.closed = true; 104 + self.condvar.notify_all(); 105 + } 106 + } 107 + 108 + pub(crate) struct BroadcastReceiver { 109 + buf: Arc<BroadcastBuffer>, 110 + next_seq: u64, 111 + } 112 + 113 + impl BroadcastReceiver { 114 + pub(crate) fn recv_blocking(&mut self) -> RecvResult { 115 + let mut g = self.buf.inner.lock().unwrap(); 116 + loop { 117 + if g.closed { 118 + return RecvResult::Closed; 119 + } 120 + if let Some(&(front_seq, _)) = g.chunks.front() { 121 + if self.next_seq < front_seq { 122 + tracing::debug!( 123 + "upnp/pcm: receiver lagging, skipping {} → {}", 124 + self.next_seq, 125 + front_seq 126 + ); 127 + self.next_seq = front_seq; 128 + } 129 + if self.next_seq < g.next_seq { 130 + let idx = (self.next_seq - front_seq) as usize; 131 + let chunk = g.chunks[idx].1.clone(); 132 + self.next_seq += 1; 133 + return RecvResult::Data(chunk); 134 + } 135 + } 136 + g = self.buf.condvar.wait(g).unwrap(); 137 + } 138 + } 139 + } 140 + 141 + // --------------------------------------------------------------------------- 142 + // Global state 143 + // --------------------------------------------------------------------------- 144 + 145 + static BUFFER: OnceLock<Arc<BroadcastBuffer>> = OnceLock::new(); 146 + static PCM_STARTED: Mutex<bool> = Mutex::new(false); 147 + static RENDERER_PLAYING: Mutex<bool> = Mutex::new(false); 148 + static SERVER_STARTED: Mutex<bool> = Mutex::new(false); 149 + static DEVICE_UUID: OnceLock<String> = OnceLock::new(); 150 + static RUNTIME: OnceLock<tokio::runtime::Runtime> = OnceLock::new(); 151 + static MONITOR_GEN: AtomicU64 = AtomicU64::new(0); 152 + 153 + pub(crate) struct UpnpConfig { 154 + pub server_port: u16, 155 + pub pcm_port: u16, 156 + pub friendly_name: String, 157 + pub renderer_url: Option<String>, 158 + pub sample_rate: u32, 159 + } 160 + 161 + static CONFIG: Mutex<UpnpConfig> = Mutex::new(UpnpConfig { 162 + server_port: 7878, 163 + pcm_port: 7879, 164 + friendly_name: String::new(), 165 + renderer_url: None, 166 + sample_rate: 44100, 167 + }); 168 + 169 + pub(crate) fn get_buffer() -> Arc<BroadcastBuffer> { 170 + BUFFER 171 + .get_or_init(|| Arc::new(BroadcastBuffer::new())) 172 + .clone() 173 + } 174 + 175 + pub(crate) fn get_runtime() -> &'static tokio::runtime::Runtime { 176 + RUNTIME.get_or_init(|| { 177 + tokio::runtime::Runtime::new().expect("failed to create UPnP tokio runtime") 178 + }) 179 + } 180 + 181 + pub(crate) fn device_uuid() -> &'static str { 182 + DEVICE_UUID.get_or_init(|| uuid::Uuid::new_v4().to_string()) 183 + } 184 + 185 + pub(crate) fn get_local_ip() -> std::net::Ipv4Addr { 186 + if let Ok(socket) = std::net::UdpSocket::bind("0.0.0.0:0") { 187 + if socket.connect("8.8.8.8:80").is_ok() { 188 + if let Ok(addr) = socket.local_addr() { 189 + if let std::net::IpAddr::V4(ip) = addr.ip() { 190 + return ip; 191 + } 192 + } 193 + } 194 + } 195 + std::net::Ipv4Addr::LOCALHOST 196 + } 197 + 198 + // --------------------------------------------------------------------------- 199 + // Public API — UPnP Media Server (ContentDirectory + SSDP) 200 + // --------------------------------------------------------------------------- 201 + 202 + /// Start the UPnP/DLNA Media Server so control points can browse and stream 203 + /// the music library. Idempotent. 204 + pub fn start_media_server(port: u16, friendly_name: &str) { 205 + let mut started = SERVER_STARTED.lock().unwrap(); 206 + if *started { 207 + return; 208 + } 209 + let name = if friendly_name.is_empty() { 210 + "Rockbox".to_string() 211 + } else { 212 + friendly_name.to_string() 213 + }; 214 + { 215 + let mut cfg = CONFIG.lock().unwrap(); 216 + cfg.server_port = port; 217 + cfg.friendly_name = name; 218 + } 219 + let _ = device_uuid(); // initialise UUID before spawning threads 220 + let rt = get_runtime(); 221 + rt.spawn(async move { 222 + if let Err(e) = server::run(port).await { 223 + tracing::error!("UPnP HTTP server error: {}", e); 224 + } 225 + }); 226 + rt.spawn(async move { 227 + ssdp::run(port).await; 228 + }); 229 + *started = true; 230 + tracing::info!("UPnP media server started on :{port}"); 231 + } 232 + 233 + /// Start the UPnP/DLNA MediaRenderer:1 so control points can push media to 234 + /// this device. Idempotent (tracked by the renderer module itself). 235 + pub fn start_renderer(port: u16, friendly_name: &str) { 236 + renderer::start(port, friendly_name); 237 + } 238 + 239 + // --------------------------------------------------------------------------- 240 + // FFI exports — UPnP PCM sink (WAV streaming to UPnP/DLNA renderers) 241 + // --------------------------------------------------------------------------- 242 + 243 + /// Set the HTTP port for the WAV PCM stream (default: 7879). 244 + #[no_mangle] 245 + pub extern "C" fn pcm_upnp_set_http_port(port: u16) { 246 + CONFIG.lock().unwrap().pcm_port = port; 247 + } 248 + 249 + /// Set the AVTransport control URL of a UPnP renderer to auto-command on start. 250 + /// Pass NULL to clear. 251 + #[no_mangle] 252 + pub extern "C" fn pcm_upnp_set_renderer_url(url: *const c_char) { 253 + let mut cfg = CONFIG.lock().unwrap(); 254 + if url.is_null() { 255 + cfg.renderer_url = None; 256 + return; 257 + } 258 + let s = unsafe { CStr::from_ptr(url) } 259 + .to_str() 260 + .unwrap_or("") 261 + .to_string(); 262 + cfg.renderer_url = if s.is_empty() { None } else { Some(s) }; 263 + } 264 + 265 + /// Inform the PCM sink of the current sample rate (called by set_freq). 266 + #[no_mangle] 267 + pub extern "C" fn pcm_upnp_set_sample_rate(rate: u32) { 268 + CONFIG.lock().unwrap().sample_rate = rate; 269 + } 270 + 271 + /// Start the WAV PCM stream HTTP server. The HTTP server starts once; the 272 + /// renderer is notified with fresh DIDL-Lite metadata on every call (track 273 + /// change), but Play is only issued on the first call after a full stop. 274 + #[no_mangle] 275 + pub extern "C" fn pcm_upnp_start() -> c_int { 276 + // --- Start the HTTP broadcast server once --- 277 + { 278 + let mut started = PCM_STARTED.lock().unwrap(); 279 + if !*started { 280 + let buf = get_buffer(); 281 + buf.reset(); 282 + let (port, sample_rate) = { 283 + let cfg = CONFIG.lock().unwrap(); 284 + (cfg.pcm_port, cfg.sample_rate) 285 + }; 286 + let buf_http = buf.clone(); 287 + std::thread::spawn(move || pcm_server::serve(port, sample_rate, buf_http)); 288 + *started = true; 289 + tracing::info!("UPnP PCM sink: WAV stream on :{port}"); 290 + } 291 + } 292 + 293 + // --- Notify the renderer with current track metadata --- 294 + let (port, sample_rate, renderer_url) = { 295 + let cfg = CONFIG.lock().unwrap(); 296 + (cfg.pcm_port, cfg.sample_rate, cfg.renderer_url.clone()) 297 + }; 298 + if let Some(url) = renderer_url { 299 + let need_play = { 300 + let mut rp = RENDERER_PLAYING.lock().unwrap(); 301 + let was = *rp; 302 + *rp = true; 303 + !was 304 + }; 305 + let local_ip = get_local_ip(); 306 + let track = rockbox_sys::playback::current_track(); 307 + let rt = get_runtime(); 308 + 309 + // Start (or replace) the track-change monitor. 310 + ensure_track_monitor(url.clone(), port); 311 + 312 + rt.spawn(async move { 313 + let stream_url = format!("http://{}:{}/stream.wav", local_ip, port); 314 + let album_art_url = if let Some(ref t) = track { 315 + get_album_art_url(&t.path, local_ip).await 316 + } else { 317 + None 318 + }; 319 + if let Err(e) = avtransport_play( 320 + &url, 321 + &stream_url, 322 + track.as_ref(), 323 + sample_rate, 324 + need_play, 325 + album_art_url.as_deref(), 326 + ) 327 + .await 328 + { 329 + tracing::warn!("UPnP AVTransport play failed: {}", e); 330 + } 331 + }); 332 + } 333 + 0 334 + } 335 + 336 + /// Push raw S16LE stereo PCM into the WAV broadcast buffer. 337 + #[no_mangle] 338 + pub extern "C" fn pcm_upnp_write(data: *const u8, len: usize) -> c_int { 339 + if data.is_null() || len == 0 { 340 + return 0; 341 + } 342 + let slice = unsafe { std::slice::from_raw_parts(data, len) }; 343 + get_buffer().push(slice); 344 + 0 345 + } 346 + 347 + /// No-op between tracks — HTTP connections stay alive. 348 + #[no_mangle] 349 + pub extern "C" fn pcm_upnp_stop() {} 350 + 351 + /// Shut down the PCM stream server (called on daemon exit). 352 + #[no_mangle] 353 + pub extern "C" fn pcm_upnp_close() { 354 + let mut started = PCM_STARTED.lock().unwrap(); 355 + let mut rp = RENDERER_PLAYING.lock().unwrap(); 356 + get_buffer().close(); 357 + *started = false; 358 + *rp = false; 359 + } 360 + 361 + // --------------------------------------------------------------------------- 362 + // Album art DB lookup 363 + // --------------------------------------------------------------------------- 364 + 365 + async fn get_album_art_url(path: &str, local_ip: std::net::Ipv4Addr) -> Option<String> { 366 + let pool = db::open_pool().await.ok()?; 367 + let db_track = db::track_by_path(&pool, path).await.ok()??; 368 + let filename = db_track.album_art?; 369 + if filename.is_empty() { 370 + return None; 371 + } 372 + let graphql_port = std::env::var("ROCKBOX_GRAPHQL_PORT").unwrap_or_else(|_| "6062".to_string()); 373 + Some(format!( 374 + "http://{}:{}/covers/{}", 375 + local_ip, graphql_port, filename 376 + )) 377 + } 378 + 379 + // --------------------------------------------------------------------------- 380 + // Track-change monitor — sends updated SetAVTransportURI when track changes 381 + // --------------------------------------------------------------------------- 382 + 383 + fn ensure_track_monitor(renderer_url: String, port: u16) { 384 + let gen = MONITOR_GEN.fetch_add(1, Ordering::SeqCst) + 1; 385 + let rt = get_runtime(); 386 + rt.spawn(async move { 387 + let mut last_path = String::new(); 388 + loop { 389 + tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; 390 + 391 + // Exit if a newer monitor was started (e.g. renderer changed). 392 + if MONITOR_GEN.load(Ordering::SeqCst) != gen { 393 + return; 394 + } 395 + 396 + if !*RENDERER_PLAYING.lock().unwrap() { 397 + return; 398 + } 399 + 400 + let track = rockbox_sys::playback::current_track(); 401 + let current_path = track.as_ref().map(|t| t.path.clone()).unwrap_or_default(); 402 + 403 + if current_path.is_empty() || current_path == last_path { 404 + continue; 405 + } 406 + last_path = current_path.clone(); 407 + 408 + let local_ip = get_local_ip(); 409 + let stream_url = format!("http://{}:{}/stream.wav", local_ip, port); 410 + let sample_rate = CONFIG.lock().unwrap().sample_rate; 411 + let album_art_url = get_album_art_url(&current_path, local_ip).await; 412 + 413 + if let Err(e) = avtransport_play( 414 + &renderer_url, 415 + &stream_url, 416 + track.as_ref(), 417 + sample_rate, 418 + false, 419 + album_art_url.as_deref(), 420 + ) 421 + .await 422 + { 423 + tracing::warn!("UPnP track monitor: failed to update metadata: {}", e); 424 + } 425 + } 426 + }); 427 + } 428 + 429 + // --------------------------------------------------------------------------- 430 + // AVTransport SOAP client — tells a UPnP renderer to play our WAV stream 431 + // --------------------------------------------------------------------------- 432 + 433 + async fn avtransport_play( 434 + control_url: &str, 435 + stream_url: &str, 436 + track: Option<&rockbox_sys::types::mp3_entry::Mp3Entry>, 437 + sample_rate: u32, 438 + send_play: bool, 439 + album_art_url: Option<&str>, 440 + ) -> anyhow::Result<()> { 441 + use bytes::Bytes; 442 + use http_body_util::Full; 443 + use hyper::Request; 444 + use hyper_util::client::legacy::Client; 445 + use hyper_util::rt::TokioExecutor; 446 + 447 + let client: Client<_, Full<Bytes>> = Client::builder(TokioExecutor::new()).build_http(); 448 + 449 + let metadata = build_didl_metadata(track, stream_url, sample_rate, album_art_url); 450 + let metadata_escaped = xml_escape(&metadata); 451 + tracing::info!("UPnP AVTransport: setting URI to {stream_url} with metadata:\n{metadata}"); 452 + 453 + let set_uri_body = format!( 454 + r#"<?xml version="1.0" encoding="utf-8"?> 455 + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" 456 + s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 457 + <s:Body> 458 + <u:SetAVTransportURI xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"> 459 + <InstanceID>0</InstanceID> 460 + <CurrentURI>{stream_url}</CurrentURI> 461 + <CurrentURIMetaData>{metadata_escaped}</CurrentURIMetaData> 462 + </u:SetAVTransportURI> 463 + </s:Body> 464 + </s:Envelope>"# 465 + ); 466 + 467 + let req = Request::builder() 468 + .method("POST") 469 + .uri(control_url) 470 + .header("Content-Type", "text/xml; charset=\"utf-8\"") 471 + .header( 472 + "SOAPAction", 473 + "\"urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI\"", 474 + ) 475 + .header("Content-Length", set_uri_body.len().to_string()) 476 + .body(Full::from(Bytes::from(set_uri_body)))?; 477 + 478 + let resp: hyper::Response<hyper::body::Incoming> = client.request(req).await?; 479 + if !resp.status().is_success() { 480 + anyhow::bail!("SetAVTransportURI returned HTTP {}", resp.status().as_u16()); 481 + } 482 + tracing::debug!("UPnP AVTransport: SetAVTransportURI OK"); 483 + 484 + if !send_play { 485 + return Ok(()); 486 + } 487 + 488 + let play_body = r#"<?xml version="1.0" encoding="utf-8"?> 489 + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" 490 + s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 491 + <s:Body> 492 + <u:Play xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"> 493 + <InstanceID>0</InstanceID> 494 + <Speed>1</Speed> 495 + </u:Play> 496 + </s:Body> 497 + </s:Envelope>"#; 498 + 499 + let req = Request::builder() 500 + .method("POST") 501 + .uri(control_url) 502 + .header("Content-Type", "text/xml; charset=\"utf-8\"") 503 + .header( 504 + "SOAPAction", 505 + "\"urn:schemas-upnp-org:service:AVTransport:1#Play\"", 506 + ) 507 + .header("Content-Length", play_body.len().to_string()) 508 + .body(Full::from(Bytes::from(play_body)))?; 509 + 510 + let resp: hyper::Response<hyper::body::Incoming> = client.request(req).await?; 511 + if !resp.status().is_success() { 512 + anyhow::bail!("Play returned HTTP {}", resp.status().as_u16()); 513 + } 514 + tracing::info!("UPnP AVTransport: renderer playing WAV stream at {stream_url}"); 515 + Ok(()) 516 + } 517 + 518 + // --------------------------------------------------------------------------- 519 + // DIDL-Lite metadata helpers 520 + // --------------------------------------------------------------------------- 521 + 522 + fn ms_to_upnp_duration(ms: u64) -> String { 523 + let total_secs = ms / 1000; 524 + let frac_ms = ms % 1000; 525 + let h = total_secs / 3600; 526 + let m = (total_secs % 3600) / 60; 527 + let s = total_secs % 60; 528 + format!("{}:{:02}:{:02}.{:03}", h, m, s, frac_ms) 529 + } 530 + 531 + fn xml_escape(s: &str) -> String { 532 + s.replace('&', "&amp;") 533 + .replace('<', "&lt;") 534 + .replace('>', "&gt;") 535 + .replace('"', "&quot;") 536 + .replace('\'', "&apos;") 537 + } 538 + 539 + fn build_didl_metadata( 540 + track: Option<&rockbox_sys::types::mp3_entry::Mp3Entry>, 541 + stream_url: &str, 542 + sample_rate: u32, 543 + album_art_url: Option<&str>, 544 + ) -> String { 545 + let (title, artist, album, duration_ms) = match track { 546 + Some(t) => { 547 + let title = if t.title.trim().is_empty() { 548 + t.path 549 + .rsplit('/') 550 + .next() 551 + .and_then(|f| { 552 + f.rsplit('.') 553 + .nth(1) 554 + .map(|_| f.rsplit('.').skip(1).collect::<Vec<_>>().join(".")) 555 + }) 556 + .unwrap_or_else(|| t.path.clone()) 557 + } else { 558 + t.title.clone() 559 + }; 560 + (title, t.artist.clone(), t.album.clone(), t.length) 561 + } 562 + None => ("stream.wav".to_string(), String::new(), String::new(), 0u64), 563 + }; 564 + 565 + let duration_str = if duration_ms > 0 { 566 + ms_to_upnp_duration(duration_ms) 567 + } else { 568 + "0:00:00.000".to_string() 569 + }; 570 + 571 + let t = xml_escape(&title); 572 + let ar = xml_escape(&artist); 573 + let al = xml_escape(&album); 574 + let url_esc = xml_escape(stream_url); 575 + 576 + let artist_elem = if ar.is_empty() { 577 + String::new() 578 + } else { 579 + format!("\n <upnp:artist>{ar}</upnp:artist>") 580 + }; 581 + let album_elem = if al.is_empty() { 582 + String::new() 583 + } else { 584 + format!("\n <upnp:album>{al}</upnp:album>") 585 + }; 586 + let art_elem = match album_art_url.filter(|s| !s.is_empty()) { 587 + Some(url) => { 588 + let art_url = xml_escape(url); 589 + format!("\n <upnp:albumArtURI>{art_url}</upnp:albumArtURI>") 590 + } 591 + None => String::new(), 592 + }; 593 + 594 + format!( 595 + r#"<DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/"> 596 + <item id="1" parentID="0" restricted="1"> 597 + <dc:title>{t}</dc:title>{artist_elem}{album_elem}{art_elem} 598 + <upnp:class>object.item.audioItem.musicTrack</upnp:class> 599 + <res protocolInfo="http-get:*:audio/wav:DLNA.ORG_PN=LPCM;DLNA.ORG_FLAGS=01700000000000000000000000000000" duration="{duration_str}" sampleFrequency="{sample_rate}" nrAudioChannels="2" bitsPerSample="16">{url_esc}</res> 600 + </item> 601 + </DIDL-Lite>"# 602 + ) 603 + }
+511
crates/upnp/src/pcm_server.rs
··· 1 + use crate::{BroadcastBuffer, RecvResult}; 2 + use std::io::Write; 3 + use std::net::TcpListener; 4 + use std::path::Path; 5 + use std::sync::Arc; 6 + 7 + // ICY metadata is inserted every ICY_METAINT audio bytes. 8 + // 16 384 bytes ≈ 93 ms of S16LE stereo @ 44100 Hz. 9 + const ICY_METAINT: usize = 16384; 10 + 11 + // ─── WAV header ────────────────────────────────────────────────────────────── 12 + 13 + fn wav_header(sample_rate: u32) -> [u8; 44] { 14 + let channels: u32 = 2; 15 + let bits: u32 = 16; 16 + let byte_rate = sample_rate * channels * bits / 8; 17 + let block_align = (channels * bits / 8) as u16; 18 + let mut h = [0u8; 44]; 19 + h[0..4].copy_from_slice(b"RIFF"); 20 + h[4..8].copy_from_slice(&0xFFFF_FFFFu32.to_le_bytes()); // streaming size 21 + h[8..12].copy_from_slice(b"WAVE"); 22 + h[12..16].copy_from_slice(b"fmt "); 23 + h[16..20].copy_from_slice(&16u32.to_le_bytes()); 24 + h[20..22].copy_from_slice(&1u16.to_le_bytes()); // PCM 25 + h[22..24].copy_from_slice(&(channels as u16).to_le_bytes()); 26 + h[24..28].copy_from_slice(&sample_rate.to_le_bytes()); 27 + h[28..32].copy_from_slice(&byte_rate.to_le_bytes()); 28 + h[32..34].copy_from_slice(&block_align.to_le_bytes()); 29 + h[34..36].copy_from_slice(&(bits as u16).to_le_bytes()); 30 + h[36..40].copy_from_slice(b"data"); 31 + h[40..44].copy_from_slice(&0xFFFF_FFFFu32.to_le_bytes()); // streaming size 32 + h 33 + } 34 + 35 + // ─── Track metadata ────────────────────────────────────────────────────────── 36 + 37 + struct TrackMeta { 38 + title: String, 39 + artist: String, 40 + album: String, 41 + albumartist: String, 42 + year: i32, 43 + genre: String, 44 + tracknum: i32, 45 + path: String, 46 + elapsed_ms: u64, 47 + duration_ms: u64, 48 + } 49 + 50 + impl TrackMeta { 51 + fn from_current() -> Option<Self> { 52 + rockbox_sys::playback::current_track().map(|t| Self { 53 + title: t.title, 54 + artist: t.artist, 55 + album: t.album, 56 + albumartist: t.albumartist, 57 + year: t.year, 58 + genre: t.genre_string, 59 + tracknum: t.tracknum, 60 + path: t.path, 61 + elapsed_ms: t.elapsed, 62 + duration_ms: t.length, 63 + }) 64 + } 65 + 66 + fn display_title(&self) -> String { 67 + match (!self.artist.is_empty(), !self.title.is_empty()) { 68 + (true, true) => format!("{} - {}", self.artist, self.title), 69 + (false, true) => self.title.clone(), 70 + (true, false) => self.artist.clone(), 71 + (false, false) => String::new(), 72 + } 73 + } 74 + 75 + /// Key used to detect track changes (ignores playback position). 76 + fn track_key(&self) -> String { 77 + format!("{}\x00{}\x00{}", self.artist, self.title, self.path) 78 + } 79 + } 80 + 81 + // ─── ICY metadata ──────────────────────────────────────────────────────────── 82 + 83 + /// Build a full ICY metadata block with all available track fields. 84 + /// 85 + /// Wire format: [1-byte length_in_16s][length * 16 bytes: Key='Val'; pairs, zero-padded] 86 + /// A single zero byte is an empty/no-update block. 87 + /// 88 + /// Players that understand extended ICY (VLC, foobar2000, many DLNA renderers) will 89 + /// display artist, album, year, genre and fetch album art from `art_url`. 90 + fn icy_block(meta: &TrackMeta, art_url: &str) -> Vec<u8> { 91 + if meta.title.is_empty() && meta.artist.is_empty() { 92 + return vec![0]; 93 + } 94 + 95 + let mut parts = Vec::<String>::new(); 96 + 97 + // Standard field — always present when there is something to show. 98 + parts.push(format!( 99 + "StreamTitle='{}';", 100 + icy_escape(&meta.display_title()) 101 + )); 102 + 103 + if !meta.artist.is_empty() { 104 + parts.push(format!("StreamArtist='{}';", icy_escape(&meta.artist))); 105 + } 106 + if !meta.album.is_empty() { 107 + parts.push(format!("StreamAlbum='{}';", icy_escape(&meta.album))); 108 + } 109 + // Only include albumartist when it differs from track artist to save space. 110 + if !meta.albumartist.is_empty() && meta.albumartist != meta.artist { 111 + parts.push(format!( 112 + "StreamAlbumArtist='{}';", 113 + icy_escape(&meta.albumartist) 114 + )); 115 + } 116 + if meta.year > 0 { 117 + parts.push(format!("StreamYear='{}';", meta.year)); 118 + } 119 + if !meta.genre.is_empty() { 120 + parts.push(format!("StreamGenre='{}';", icy_escape(&meta.genre))); 121 + } 122 + if meta.tracknum > 0 { 123 + parts.push(format!("StreamTrackNum='{}';", meta.tracknum)); 124 + } 125 + // StreamUrl is the standard ICY field many players use to fetch artwork. 126 + if !art_url.is_empty() { 127 + parts.push(format!("StreamUrl='{}';", art_url)); 128 + } 129 + 130 + let s = parts.concat(); 131 + let b = s.as_bytes(); 132 + let chunks = (b.len() + 15) / 16; 133 + let mut out = vec![0u8; 1 + chunks * 16]; 134 + out[0] = chunks as u8; 135 + out[1..1 + b.len()].copy_from_slice(b); 136 + out 137 + } 138 + 139 + /// Replace characters that break ICY `Key='Value';` parsing. 140 + fn icy_escape(s: &str) -> String { 141 + s.replace('\'', "\u{2019}") // right single quotation mark 142 + .replace(';', ",") 143 + } 144 + 145 + // ─── Album art ─────────────────────────────────────────────────────────────── 146 + 147 + /// Search the track's parent directory for common cover art filenames. 148 + /// Returns `(image_bytes, mime_type)` for the first match found. 149 + fn find_album_art(track_path: &str) -> Option<(Vec<u8>, &'static str)> { 150 + let dir = Path::new(track_path).parent()?; 151 + const CANDIDATES: &[(&str, &'static str)] = &[ 152 + ("cover.jpg", "image/jpeg"), 153 + ("cover.jpeg", "image/jpeg"), 154 + ("cover.png", "image/png"), 155 + ("cover.webp", "image/webp"), 156 + ("folder.jpg", "image/jpeg"), 157 + ("folder.jpeg", "image/jpeg"), 158 + ("folder.png", "image/png"), 159 + ("album.jpg", "image/jpeg"), 160 + ("album.png", "image/png"), 161 + ("front.jpg", "image/jpeg"), 162 + ("front.jpeg", "image/jpeg"), 163 + ("front.png", "image/png"), 164 + ("artwork.jpg", "image/jpeg"), 165 + ("artwork.png", "image/png"), 166 + ("AlbumArt.jpg", "image/jpeg"), 167 + ("AlbumArt.jpeg", "image/jpeg"), 168 + ("AlbumArt.png", "image/png"), 169 + ]; 170 + for (name, mime) in CANDIDATES { 171 + let p = dir.join(name); 172 + if let Ok(data) = std::fs::read(&p) { 173 + tracing::debug!("upnp/pcm: album art → {}", p.display()); 174 + return Some((data, mime)); 175 + } 176 + } 177 + None 178 + } 179 + 180 + // ─── JSON helpers ──────────────────────────────────────────────────────────── 181 + 182 + fn json_escape(s: &str) -> String { 183 + let mut out = String::with_capacity(s.len() + 2); 184 + out.push('"'); 185 + for c in s.chars() { 186 + match c { 187 + '"' => out.push_str("\\\""), 188 + '\\' => out.push_str("\\\\"), 189 + '\n' => out.push_str("\\n"), 190 + '\r' => out.push_str("\\r"), 191 + '\t' => out.push_str("\\t"), 192 + c if (c as u32) < 0x20 => { 193 + let _ = std::fmt::Write::write_fmt(&mut out, format_args!("\\u{:04x}", c as u32)); 194 + } 195 + c => out.push(c), 196 + } 197 + } 198 + out.push('"'); 199 + out 200 + } 201 + 202 + fn build_now_playing_json(meta: &TrackMeta, art_url: &str) -> String { 203 + format!( 204 + concat!( 205 + r#"{{"title":{title},"artist":{artist},"album":{album},"albumartist":{albumartist},"#, 206 + r#""year":{year},"genre":{genre},"tracknum":{tracknum},"#, 207 + r#""duration_ms":{duration},"elapsed_ms":{elapsed},"art_url":{art_url}}}"#, 208 + ), 209 + title = json_escape(&meta.title), 210 + artist = json_escape(&meta.artist), 211 + album = json_escape(&meta.album), 212 + albumartist = json_escape(&meta.albumartist), 213 + year = meta.year, 214 + genre = json_escape(&meta.genre), 215 + tracknum = meta.tracknum, 216 + duration = meta.duration_ms, 217 + elapsed = meta.elapsed_ms, 218 + art_url = json_escape(art_url), 219 + ) 220 + } 221 + 222 + // ─── HTTP request parsing ───────────────────────────────────────────────────── 223 + 224 + struct Request { 225 + /// URL path, e.g. "/stream.wav" or "/now-playing/art" 226 + path: String, 227 + wants_icy: bool, 228 + } 229 + 230 + fn parse_request(stream: &mut std::net::TcpStream) -> std::io::Result<Request> { 231 + use std::io::Read; 232 + let mut buf: Vec<u8> = Vec::with_capacity(1024); 233 + let mut byte = [0u8; 1]; 234 + loop { 235 + stream.read_exact(&mut byte)?; 236 + buf.push(byte[0]); 237 + if buf.ends_with(b"\r\n\r\n") || buf.ends_with(b"\n\n") { 238 + break; 239 + } 240 + if buf.len() > 8192 { 241 + break; 242 + } 243 + } 244 + let raw = String::from_utf8_lossy(&buf); 245 + 246 + // First line: "GET /path HTTP/1.x" 247 + let path = raw 248 + .lines() 249 + .next() 250 + .and_then(|l| l.split_whitespace().nth(1)) 251 + .unwrap_or("/") 252 + .to_string(); 253 + 254 + let wants_icy = raw.lines().any(|l| { 255 + let l = l.to_ascii_lowercase(); 256 + l.strip_prefix("icy-metadata:") 257 + .map(|v| v.trim() == "1") 258 + .unwrap_or(false) 259 + }); 260 + 261 + Ok(Request { path, wants_icy }) 262 + } 263 + 264 + // ─── Endpoint helpers ───────────────────────────────────────────────────────── 265 + 266 + fn art_base_url(local_ip: std::net::Ipv4Addr, port: u16) -> String { 267 + format!("http://{}:{}/now-playing/art", local_ip, port) 268 + } 269 + 270 + fn send_404(stream: &mut std::net::TcpStream) { 271 + let _ = stream.write_all(b"HTTP/1.0 404 Not Found\r\nContent-Length: 0\r\n\r\n"); 272 + } 273 + 274 + fn serve_now_playing_json( 275 + stream: &mut std::net::TcpStream, 276 + local_ip: std::net::Ipv4Addr, 277 + port: u16, 278 + ) { 279 + let art_url = art_base_url(local_ip, port); 280 + let body = match TrackMeta::from_current() { 281 + Some(meta) => build_now_playing_json(&meta, &art_url), 282 + None => r#"{"error":"no track playing"}"#.to_string(), 283 + }; 284 + let hdr = format!( 285 + "HTTP/1.0 200 OK\r\n\ 286 + Content-Type: application/json\r\n\ 287 + Access-Control-Allow-Origin: *\r\n\ 288 + Cache-Control: no-cache\r\n\ 289 + Content-Length: {}\r\n\ 290 + \r\n", 291 + body.len() 292 + ); 293 + let _ = stream.write_all(hdr.as_bytes()); 294 + let _ = stream.write_all(body.as_bytes()); 295 + } 296 + 297 + fn serve_now_playing_art(stream: &mut std::net::TcpStream) { 298 + let art = TrackMeta::from_current() 299 + .filter(|m| !m.path.is_empty()) 300 + .and_then(|m| find_album_art(&m.path)); 301 + 302 + match art { 303 + Some((data, mime)) => { 304 + let hdr = format!( 305 + "HTTP/1.0 200 OK\r\n\ 306 + Content-Type: {mime}\r\n\ 307 + Content-Length: {}\r\n\ 308 + Cache-Control: no-cache\r\n\ 309 + Access-Control-Allow-Origin: *\r\n\ 310 + \r\n", 311 + data.len() 312 + ); 313 + let _ = stream.write_all(hdr.as_bytes()); 314 + let _ = stream.write_all(&data); 315 + } 316 + None => send_404(stream), 317 + } 318 + } 319 + 320 + // ─── WAV stream with ICY metadata ──────────────────────────────────────────── 321 + 322 + fn serve_wav_stream( 323 + stream: &mut std::net::TcpStream, 324 + req: &Request, 325 + sample_rate: u32, 326 + buf: Arc<BroadcastBuffer>, 327 + port: u16, 328 + peer: &str, 329 + ) { 330 + let wav_hdr = wav_header(sample_rate); 331 + let local_ip = crate::get_local_ip(); 332 + 333 + let http_hdr = if req.wants_icy { 334 + format!( 335 + "HTTP/1.0 200 OK\r\n\ 336 + Content-Type: audio/wav\r\n\ 337 + Cache-Control: no-cache\r\n\ 338 + icy-metaint: {ICY_METAINT}\r\n\ 339 + icy-name: Rockbox\r\n\ 340 + TransferMode.DLNA.ORG: Streaming\r\n\ 341 + Content-Features.DLNA.ORG: DLNA.ORG_OP=00;DLNA.ORG_CI=0\r\n\ 342 + \r\n" 343 + ) 344 + } else { 345 + "HTTP/1.0 200 OK\r\n\ 346 + Content-Type: audio/wav\r\n\ 347 + Cache-Control: no-cache\r\n\ 348 + TransferMode.DLNA.ORG: Streaming\r\n\ 349 + Content-Features.DLNA.ORG: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n\ 350 + \r\n" 351 + .to_string() 352 + }; 353 + 354 + if stream.write_all(http_hdr.as_bytes()).is_err() || stream.write_all(&wav_hdr).is_err() { 355 + tracing::warn!("upnp/pcm: header write error to {peer}"); 356 + return; 357 + } 358 + 359 + tracing::info!( 360 + "upnp/pcm: streaming WAV{} to {peer}", 361 + if req.wants_icy { " (ICY metadata)" } else { "" } 362 + ); 363 + 364 + let mut rx = buf.subscribe(); 365 + 366 + if req.wants_icy { 367 + let art_url = art_base_url(local_ip, port); 368 + // The WAV header bytes already occupy the start of the body, so the first 369 + // ICY boundary arrives after (ICY_METAINT - 44) bytes of PCM data. 370 + let mut bytes_since_meta: usize = wav_hdr.len(); 371 + let mut last_track_key = String::new(); 372 + 373 + loop { 374 + match rx.recv_blocking() { 375 + RecvResult::Data(chunk) => { 376 + if write_icy_chunk( 377 + stream, 378 + &chunk, 379 + &mut bytes_since_meta, 380 + &mut last_track_key, 381 + &art_url, 382 + ) 383 + .is_err() 384 + { 385 + tracing::debug!("upnp/pcm: {peer} disconnected"); 386 + break; 387 + } 388 + } 389 + RecvResult::Closed => break, 390 + } 391 + } 392 + } else { 393 + loop { 394 + match rx.recv_blocking() { 395 + RecvResult::Data(chunk) => { 396 + if stream.write_all(&chunk).is_err() { 397 + tracing::debug!("upnp/pcm: {peer} disconnected"); 398 + break; 399 + } 400 + } 401 + RecvResult::Closed => break, 402 + } 403 + } 404 + } 405 + } 406 + 407 + /// Write `data` into `stream`, inserting an ICY metadata block every `ICY_METAINT` bytes. 408 + /// 409 + /// Sends a populated block only when the track changes; otherwise sends a single 410 + /// zero byte (empty block) to satisfy the protocol framing without cluttering logs. 411 + fn write_icy_chunk( 412 + stream: &mut std::net::TcpStream, 413 + data: &[u8], 414 + bytes_since_meta: &mut usize, 415 + last_track_key: &mut String, 416 + art_url: &str, 417 + ) -> std::io::Result<()> { 418 + let mut pos = 0; 419 + while pos < data.len() { 420 + let space = ICY_METAINT - *bytes_since_meta; 421 + let to_write = space.min(data.len() - pos); 422 + 423 + stream.write_all(&data[pos..pos + to_write])?; 424 + pos += to_write; 425 + *bytes_since_meta += to_write; 426 + 427 + if *bytes_since_meta >= ICY_METAINT { 428 + *bytes_since_meta = 0; 429 + 430 + match TrackMeta::from_current() { 431 + Some(meta) => { 432 + let key = meta.track_key(); 433 + if key != *last_track_key { 434 + *last_track_key = key; 435 + let block = icy_block(&meta, art_url); 436 + stream.write_all(&block)?; 437 + tracing::debug!( 438 + "upnp/pcm: ICY → {} / {} / {}", 439 + meta.artist, 440 + meta.title, 441 + meta.album 442 + ); 443 + } else { 444 + stream.write_all(&[0u8])?; // same track — empty block 445 + } 446 + } 447 + None => { 448 + stream.write_all(&[0u8])?; // nothing playing 449 + } 450 + } 451 + } 452 + } 453 + Ok(()) 454 + } 455 + 456 + // ─── Main server ───────────────────────────────────────────────────────────── 457 + 458 + /// HTTP server that serves live PCM audio as a streaming WAV file plus 459 + /// companion metadata endpoints. 460 + /// 461 + /// Endpoints: 462 + /// GET /stream.wav — live WAV stream (supports ICY metadata) 463 + /// GET /now-playing.json — current track metadata as JSON 464 + /// GET /now-playing/art — current track album art (JPEG/PNG) 465 + /// (any other path) — also serves the WAV stream (for compatibility) 466 + pub fn serve(port: u16, sample_rate: u32, buf: Arc<BroadcastBuffer>) { 467 + let listener = match TcpListener::bind(("0.0.0.0", port)) { 468 + Ok(l) => l, 469 + Err(e) => { 470 + tracing::error!("upnp/pcm: bind :{port} failed: {e}"); 471 + return; 472 + } 473 + }; 474 + tracing::info!( 475 + "upnp/pcm: WAV stream on :{port} metadata on /now-playing.json and /now-playing/art" 476 + ); 477 + 478 + for stream in listener.incoming() { 479 + match stream { 480 + Ok(mut tcp) => { 481 + let buf = buf.clone(); 482 + std::thread::spawn(move || { 483 + let peer = tcp.peer_addr().map(|a| a.to_string()).unwrap_or_default(); 484 + 485 + let req = match parse_request(&mut tcp) { 486 + Ok(r) => r, 487 + Err(e) => { 488 + tracing::warn!("upnp/pcm: request read error from {peer}: {e}"); 489 + return; 490 + } 491 + }; 492 + 493 + let local_ip = crate::get_local_ip(); 494 + 495 + match req.path.as_str() { 496 + "/now-playing.json" => { 497 + serve_now_playing_json(&mut tcp, local_ip, port); 498 + } 499 + "/now-playing/art" | "/now-playing/art.jpg" | "/now-playing/art.png" => { 500 + serve_now_playing_art(&mut tcp); 501 + } 502 + _ => { 503 + serve_wav_stream(&mut tcp, &req, sample_rate, buf, port, &peer); 504 + } 505 + } 506 + }); 507 + } 508 + Err(e) => tracing::warn!("upnp/pcm: accept error: {e}"), 509 + } 510 + } 511 + }
+1038
crates/upnp/src/renderer.rs
··· 1 + use crate::{get_local_ip, get_runtime, CONFIG}; 2 + use bytes::Bytes; 3 + use http::{Method, Request, Response, StatusCode}; 4 + use http_body_util::{BodyExt, Full}; 5 + use hyper::body::Incoming; 6 + use hyper::service::service_fn; 7 + use hyper_util::rt::{TokioExecutor, TokioIo}; 8 + use hyper_util::server::conn::auto::Builder; 9 + use socket2::{Domain, Protocol, Socket, Type}; 10 + use std::net::{Ipv4Addr, SocketAddr}; 11 + use std::sync::{Arc, Mutex, OnceLock}; 12 + use tokio::net::TcpListener; 13 + use tokio::time::Duration; 14 + 15 + // --------------------------------------------------------------------------- 16 + // Renderer state 17 + // --------------------------------------------------------------------------- 18 + 19 + #[derive(Debug, Clone, PartialEq)] 20 + enum TransportState { 21 + NoMediaPresent, 22 + Stopped, 23 + Playing, 24 + PausedPlayback, 25 + } 26 + 27 + impl TransportState { 28 + fn as_str(&self) -> &'static str { 29 + match self { 30 + Self::NoMediaPresent => "NO_MEDIA_PRESENT", 31 + Self::Stopped => "STOPPED", 32 + Self::Playing => "PLAYING", 33 + Self::PausedPlayback => "PAUSED_PLAYBACK", 34 + } 35 + } 36 + } 37 + 38 + struct RendererState { 39 + current_uri: Option<String>, 40 + /// Raw (unescaped) DIDL-Lite XML received in CurrentURIMetaData. 41 + current_metadata: String, 42 + transport_state: TransportState, 43 + mute: bool, 44 + } 45 + 46 + static RENDERER_STATE: Mutex<RendererState> = Mutex::new(RendererState { 47 + current_uri: None, 48 + current_metadata: String::new(), 49 + transport_state: TransportState::NoMediaPresent, 50 + mute: false, 51 + }); 52 + 53 + static RENDERER_UUID: OnceLock<String> = OnceLock::new(); 54 + 55 + fn renderer_uuid() -> &'static str { 56 + RENDERER_UUID.get_or_init(|| uuid::Uuid::new_v4().to_string()) 57 + } 58 + 59 + // --------------------------------------------------------------------------- 60 + // Public API 61 + // --------------------------------------------------------------------------- 62 + 63 + pub fn start(port: u16, friendly_name: &str) { 64 + let name = if friendly_name.is_empty() { 65 + "Rockbox".to_string() 66 + } else { 67 + friendly_name.to_string() 68 + }; 69 + let _ = renderer_uuid(); 70 + let rt = get_runtime(); 71 + rt.spawn(async move { 72 + if let Err(e) = run_http(port, name).await { 73 + tracing::error!("UPnP renderer HTTP server error: {e}"); 74 + } 75 + }); 76 + rt.spawn(async move { 77 + run_ssdp(port).await; 78 + }); 79 + tracing::info!("UPnP media renderer started on :{port}"); 80 + } 81 + 82 + // --------------------------------------------------------------------------- 83 + // HTTP server 84 + // --------------------------------------------------------------------------- 85 + 86 + struct State { 87 + friendly_name: String, 88 + uuid: String, 89 + } 90 + 91 + async fn run_http(port: u16, friendly_name: String) -> anyhow::Result<()> { 92 + let state = Arc::new(State { 93 + friendly_name, 94 + uuid: renderer_uuid().to_string(), 95 + }); 96 + 97 + let listener = TcpListener::bind(SocketAddr::from(([0, 0, 0, 0], port))).await?; 98 + tracing::info!("UPnP renderer listening on :{port}"); 99 + 100 + loop { 101 + let (stream, _) = listener.accept().await?; 102 + let io = TokioIo::new(stream); 103 + let state = state.clone(); 104 + tokio::spawn(async move { 105 + let svc = service_fn(move |req| { 106 + let state = state.clone(); 107 + async move { handle(req, state).await } 108 + }); 109 + if let Err(e) = Builder::new(TokioExecutor::new()) 110 + .serve_connection(io, svc) 111 + .await 112 + { 113 + tracing::debug!("UPnP renderer: connection error: {e}"); 114 + } 115 + }); 116 + } 117 + } 118 + 119 + async fn handle( 120 + req: Request<Incoming>, 121 + state: Arc<State>, 122 + ) -> Result<Response<Full<Bytes>>, hyper::Error> { 123 + let path = req.uri().path().to_string(); 124 + let method = req.method().clone(); 125 + 126 + // Extract SOAPAction header before consuming the body. 127 + let header_action = soap_action_from_header(req.headers()); 128 + 129 + let resp = match (method, path.as_str()) { 130 + (Method::GET, "/renderer/desc.xml") => renderer_description(&state), 131 + (Method::GET, "/AVTransport/desc.xml") => avtransport_scpd(), 132 + (Method::GET, "/RenderingControl/desc.xml") => rendering_control_scpd(), 133 + (Method::GET, "/ConnectionManager/desc.xml") => renderer_connection_manager_scpd(), 134 + (Method::POST, "/AVTransport/control") => { 135 + let body = req.collect().await?.to_bytes(); 136 + let body_str = std::str::from_utf8(&body).unwrap_or("").to_string(); 137 + avtransport_control(body_str, header_action).await 138 + } 139 + (Method::POST, "/RenderingControl/control") => { 140 + let body = req.collect().await?.to_bytes(); 141 + let body_str = std::str::from_utf8(&body).unwrap_or("").to_string(); 142 + rendering_control(body_str, header_action) 143 + } 144 + (Method::POST, "/ConnectionManager/control") => { 145 + let body = req.collect().await?.to_bytes(); 146 + let body_str = std::str::from_utf8(&body).unwrap_or("").to_string(); 147 + renderer_connection_manager(body_str, header_action) 148 + } 149 + (m, _) if m.as_str() == "SUBSCRIBE" || m.as_str() == "UNSUBSCRIBE" => Response::builder() 150 + .status(200) 151 + .header("SID", "uuid:rockbox-renderer-event-sid") 152 + .header("TIMEOUT", "Second-1800") 153 + .body(Full::from(Bytes::new())) 154 + .unwrap(), 155 + _ => not_found(), 156 + }; 157 + Ok(resp) 158 + } 159 + 160 + // --------------------------------------------------------------------------- 161 + // Device description 162 + // --------------------------------------------------------------------------- 163 + 164 + fn renderer_description(state: &State) -> Response<Full<Bytes>> { 165 + let xml = format!( 166 + r#"<?xml version="1.0" encoding="UTF-8"?> 167 + <root xmlns="urn:schemas-upnp-org:device-1-0"> 168 + <specVersion><major>1</major><minor>0</minor></specVersion> 169 + <device> 170 + <deviceType>urn:schemas-upnp-org:device:MediaRenderer:1</deviceType> 171 + <friendlyName>{name} (Renderer)</friendlyName> 172 + <manufacturer>Rockbox</manufacturer> 173 + <manufacturerURL>https://www.rockbox.org</manufacturerURL> 174 + <modelDescription>Rockbox UPnP/DLNA Media Renderer</modelDescription> 175 + <modelName>Rockbox</modelName> 176 + <modelNumber>1.0</modelNumber> 177 + <UDN>uuid:{uuid}</UDN> 178 + <dlna:X_DLNADOC xmlns:dlna="urn:schemas-dlna-org:device-1-0">DMR-1.50</dlna:X_DLNADOC> 179 + <serviceList> 180 + <service> 181 + <serviceType>urn:schemas-upnp-org:service:AVTransport:1</serviceType> 182 + <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId> 183 + <SCPDURL>/AVTransport/desc.xml</SCPDURL> 184 + <controlURL>/AVTransport/control</controlURL> 185 + <eventSubURL>/AVTransport/events</eventSubURL> 186 + </service> 187 + <service> 188 + <serviceType>urn:schemas-upnp-org:service:RenderingControl:1</serviceType> 189 + <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId> 190 + <SCPDURL>/RenderingControl/desc.xml</SCPDURL> 191 + <controlURL>/RenderingControl/control</controlURL> 192 + <eventSubURL>/RenderingControl/events</eventSubURL> 193 + </service> 194 + <service> 195 + <serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType> 196 + <serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId> 197 + <SCPDURL>/ConnectionManager/desc.xml</SCPDURL> 198 + <controlURL>/ConnectionManager/control</controlURL> 199 + <eventSubURL>/ConnectionManager/events</eventSubURL> 200 + </service> 201 + </serviceList> 202 + </device> 203 + </root>"#, 204 + name = xml_escape(&state.friendly_name), 205 + uuid = state.uuid, 206 + ); 207 + xml_response(xml) 208 + } 209 + 210 + fn avtransport_scpd() -> Response<Full<Bytes>> { 211 + xml_response(r#"<?xml version="1.0" encoding="UTF-8"?> 212 + <scpd xmlns="urn:schemas-upnp-org:service-1-0"> 213 + <specVersion><major>1</major><minor>0</minor></specVersion> 214 + <actionList> 215 + <action><name>SetAVTransportURI</name><argumentList> 216 + <argument><name>InstanceID</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable></argument> 217 + <argument><name>CurrentURI</name><direction>in</direction><relatedStateVariable>AVTransportURI</relatedStateVariable></argument> 218 + <argument><name>CurrentURIMetaData</name><direction>in</direction><relatedStateVariable>AVTransportURIMetaData</relatedStateVariable></argument> 219 + </argumentList></action> 220 + <action><name>Play</name><argumentList> 221 + <argument><name>InstanceID</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable></argument> 222 + <argument><name>Speed</name><direction>in</direction><relatedStateVariable>TransportPlaySpeed</relatedStateVariable></argument> 223 + </argumentList></action> 224 + <action><name>Stop</name><argumentList> 225 + <argument><name>InstanceID</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable></argument> 226 + </argumentList></action> 227 + <action><name>Pause</name><argumentList> 228 + <argument><name>InstanceID</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable></argument> 229 + </argumentList></action> 230 + <action><name>Seek</name><argumentList> 231 + <argument><name>InstanceID</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable></argument> 232 + <argument><name>Unit</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_SeekMode</relatedStateVariable></argument> 233 + <argument><name>Target</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_SeekTarget</relatedStateVariable></argument> 234 + </argumentList></action> 235 + <action><name>GetTransportInfo</name><argumentList> 236 + <argument><name>InstanceID</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable></argument> 237 + <argument><name>CurrentTransportState</name><direction>out</direction><relatedStateVariable>TransportState</relatedStateVariable></argument> 238 + <argument><name>CurrentTransportStatus</name><direction>out</direction><relatedStateVariable>TransportStatus</relatedStateVariable></argument> 239 + <argument><name>CurrentSpeed</name><direction>out</direction><relatedStateVariable>TransportPlaySpeed</relatedStateVariable></argument> 240 + </argumentList></action> 241 + <action><name>GetPositionInfo</name><argumentList> 242 + <argument><name>InstanceID</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable></argument> 243 + <argument><name>Track</name><direction>out</direction><relatedStateVariable>CurrentTrack</relatedStateVariable></argument> 244 + <argument><name>TrackDuration</name><direction>out</direction><relatedStateVariable>CurrentTrackDuration</relatedStateVariable></argument> 245 + <argument><name>TrackMetaData</name><direction>out</direction><relatedStateVariable>CurrentTrackMetaData</relatedStateVariable></argument> 246 + <argument><name>TrackURI</name><direction>out</direction><relatedStateVariable>CurrentTrackURI</relatedStateVariable></argument> 247 + <argument><name>RelTime</name><direction>out</direction><relatedStateVariable>RelativeTimePosition</relatedStateVariable></argument> 248 + <argument><name>AbsTime</name><direction>out</direction><relatedStateVariable>AbsoluteTimePosition</relatedStateVariable></argument> 249 + <argument><name>RelCount</name><direction>out</direction><relatedStateVariable>RelativeCounterPosition</relatedStateVariable></argument> 250 + <argument><name>AbsCount</name><direction>out</direction><relatedStateVariable>AbsoluteCounterPosition</relatedStateVariable></argument> 251 + </argumentList></action> 252 + <action><name>GetMediaInfo</name><argumentList> 253 + <argument><name>InstanceID</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable></argument> 254 + <argument><name>NrTracks</name><direction>out</direction><relatedStateVariable>NumberOfTracks</relatedStateVariable></argument> 255 + <argument><name>MediaDuration</name><direction>out</direction><relatedStateVariable>CurrentMediaDuration</relatedStateVariable></argument> 256 + <argument><name>CurrentURI</name><direction>out</direction><relatedStateVariable>AVTransportURI</relatedStateVariable></argument> 257 + <argument><name>CurrentURIMetaData</name><direction>out</direction><relatedStateVariable>AVTransportURIMetaData</relatedStateVariable></argument> 258 + <argument><name>NextURI</name><direction>out</direction><relatedStateVariable>NextAVTransportURI</relatedStateVariable></argument> 259 + <argument><name>NextURIMetaData</name><direction>out</direction><relatedStateVariable>NextAVTransportURIMetaData</relatedStateVariable></argument> 260 + <argument><name>PlayMedium</name><direction>out</direction><relatedStateVariable>PlaybackStorageMedium</relatedStateVariable></argument> 261 + <argument><name>RecordMedium</name><direction>out</direction><relatedStateVariable>RecordStorageMedium</relatedStateVariable></argument> 262 + <argument><name>WriteStatus</name><direction>out</direction><relatedStateVariable>RecordMediumWriteStatus</relatedStateVariable></argument> 263 + </argumentList></action> 264 + </actionList> 265 + <serviceStateTable> 266 + <stateVariable sendEvents="yes"><name>TransportState</name><dataType>string</dataType></stateVariable> 267 + <stateVariable sendEvents="no"><name>TransportStatus</name><dataType>string</dataType></stateVariable> 268 + <stateVariable sendEvents="no"><name>TransportPlaySpeed</name><dataType>string</dataType></stateVariable> 269 + <stateVariable sendEvents="no"><name>AVTransportURI</name><dataType>string</dataType></stateVariable> 270 + <stateVariable sendEvents="no"><name>AVTransportURIMetaData</name><dataType>string</dataType></stateVariable> 271 + <stateVariable sendEvents="no"><name>NextAVTransportURI</name><dataType>string</dataType></stateVariable> 272 + <stateVariable sendEvents="no"><name>NextAVTransportURIMetaData</name><dataType>string</dataType></stateVariable> 273 + <stateVariable sendEvents="no"><name>CurrentTrack</name><dataType>ui4</dataType></stateVariable> 274 + <stateVariable sendEvents="no"><name>CurrentTrackDuration</name><dataType>string</dataType></stateVariable> 275 + <stateVariable sendEvents="no"><name>CurrentTrackMetaData</name><dataType>string</dataType></stateVariable> 276 + <stateVariable sendEvents="no"><name>CurrentTrackURI</name><dataType>string</dataType></stateVariable> 277 + <stateVariable sendEvents="no"><name>RelativeTimePosition</name><dataType>string</dataType></stateVariable> 278 + <stateVariable sendEvents="no"><name>AbsoluteTimePosition</name><dataType>string</dataType></stateVariable> 279 + <stateVariable sendEvents="no"><name>RelativeCounterPosition</name><dataType>i4</dataType></stateVariable> 280 + <stateVariable sendEvents="no"><name>AbsoluteCounterPosition</name><dataType>i4</dataType></stateVariable> 281 + <stateVariable sendEvents="no"><name>NumberOfTracks</name><dataType>ui4</dataType></stateVariable> 282 + <stateVariable sendEvents="no"><name>CurrentMediaDuration</name><dataType>string</dataType></stateVariable> 283 + <stateVariable sendEvents="no"><name>PlaybackStorageMedium</name><dataType>string</dataType></stateVariable> 284 + <stateVariable sendEvents="no"><name>RecordStorageMedium</name><dataType>string</dataType></stateVariable> 285 + <stateVariable sendEvents="no"><name>RecordMediumWriteStatus</name><dataType>string</dataType></stateVariable> 286 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_InstanceID</name><dataType>ui4</dataType></stateVariable> 287 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_SeekMode</name><dataType>string</dataType></stateVariable> 288 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_SeekTarget</name><dataType>string</dataType></stateVariable> 289 + </serviceStateTable> 290 + </scpd>"#.to_string()) 291 + } 292 + 293 + fn rendering_control_scpd() -> Response<Full<Bytes>> { 294 + xml_response(r#"<?xml version="1.0" encoding="UTF-8"?> 295 + <scpd xmlns="urn:schemas-upnp-org:service-1-0"> 296 + <specVersion><major>1</major><minor>0</minor></specVersion> 297 + <actionList> 298 + <action><name>GetVolume</name><argumentList> 299 + <argument><name>InstanceID</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable></argument> 300 + <argument><name>Channel</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable></argument> 301 + <argument><name>CurrentVolume</name><direction>out</direction><relatedStateVariable>Volume</relatedStateVariable></argument> 302 + </argumentList></action> 303 + <action><name>SetVolume</name><argumentList> 304 + <argument><name>InstanceID</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable></argument> 305 + <argument><name>Channel</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable></argument> 306 + <argument><name>DesiredVolume</name><direction>in</direction><relatedStateVariable>Volume</relatedStateVariable></argument> 307 + </argumentList></action> 308 + <action><name>GetMute</name><argumentList> 309 + <argument><name>InstanceID</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable></argument> 310 + <argument><name>Channel</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable></argument> 311 + <argument><name>CurrentMute</name><direction>out</direction><relatedStateVariable>Mute</relatedStateVariable></argument> 312 + </argumentList></action> 313 + <action><name>SetMute</name><argumentList> 314 + <argument><name>InstanceID</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable></argument> 315 + <argument><name>Channel</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable></argument> 316 + <argument><name>DesiredMute</name><direction>in</direction><relatedStateVariable>Mute</relatedStateVariable></argument> 317 + </argumentList></action> 318 + </actionList> 319 + <serviceStateTable> 320 + <stateVariable sendEvents="yes"><name>Volume</name><dataType>ui2</dataType><allowedValueRange><minimum>0</minimum><maximum>100</maximum><step>1</step></allowedValueRange></stateVariable> 321 + <stateVariable sendEvents="yes"><name>Mute</name><dataType>boolean</dataType></stateVariable> 322 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_InstanceID</name><dataType>ui4</dataType></stateVariable> 323 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_Channel</name><dataType>string</dataType></stateVariable> 324 + </serviceStateTable> 325 + </scpd>"#.to_string()) 326 + } 327 + 328 + fn renderer_connection_manager_scpd() -> Response<Full<Bytes>> { 329 + xml_response(r#"<?xml version="1.0" encoding="UTF-8"?> 330 + <scpd xmlns="urn:schemas-upnp-org:service-1-0"> 331 + <specVersion><major>1</major><minor>0</minor></specVersion> 332 + <actionList> 333 + <action><name>GetProtocolInfo</name><argumentList> 334 + <argument><name>Source</name><direction>out</direction><relatedStateVariable>SourceProtocolInfo</relatedStateVariable></argument> 335 + <argument><name>Sink</name><direction>out</direction><relatedStateVariable>SinkProtocolInfo</relatedStateVariable></argument> 336 + </argumentList></action> 337 + </actionList> 338 + <serviceStateTable> 339 + <stateVariable sendEvents="yes"><name>SourceProtocolInfo</name><dataType>string</dataType></stateVariable> 340 + <stateVariable sendEvents="yes"><name>SinkProtocolInfo</name><dataType>string</dataType></stateVariable> 341 + <stateVariable sendEvents="no"><name>CurrentConnectionIDs</name><dataType>string</dataType></stateVariable> 342 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_ConnectionStatus</name><dataType>string</dataType></stateVariable> 343 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_ConnectionID</name><dataType>i4</dataType></stateVariable> 344 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_AVTransportID</name><dataType>i4</dataType></stateVariable> 345 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_RcsID</name><dataType>i4</dataType></stateVariable> 346 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_Direction</name><dataType>string</dataType></stateVariable> 347 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_ProtocolInfo</name><dataType>string</dataType></stateVariable> 348 + </serviceStateTable> 349 + </scpd>"#.to_string()) 350 + } 351 + 352 + // --------------------------------------------------------------------------- 353 + // AVTransport SOAP 354 + // --------------------------------------------------------------------------- 355 + 356 + async fn avtransport_control(body: String, header_action: Option<String>) -> Response<Full<Bytes>> { 357 + let action = resolve_action(header_action, &body); 358 + match action.as_deref() { 359 + Some("SetAVTransportURI") => { 360 + let uri = extract_tag(&body, "CurrentURI").unwrap_or_default(); 361 + let uri = trim_url(&uri); 362 + if uri.is_empty() { 363 + return soap_error(402, "Invalid Args"); 364 + } 365 + // CurrentURIMetaData is XML-escaped DIDL-Lite inside the SOAP body. 366 + let metadata = extract_tag(&body, "CurrentURIMetaData") 367 + .map(|s| xml_unescape(&s)) 368 + .unwrap_or_default(); 369 + tracing::info!("UPnP renderer: SetAVTransportURI = {uri}"); 370 + { 371 + let mut st = RENDERER_STATE.lock().unwrap(); 372 + st.current_uri = Some(uri); 373 + st.current_metadata = metadata; 374 + st.transport_state = TransportState::Stopped; 375 + } 376 + soap_ok( 377 + "urn:schemas-upnp-org:service:AVTransport:1", 378 + "SetAVTransportURI", 379 + "", 380 + ) 381 + } 382 + 383 + Some("Play") => { 384 + let uri = { 385 + let st = RENDERER_STATE.lock().unwrap(); 386 + st.current_uri.clone() 387 + }; 388 + match uri { 389 + None => soap_error(701, "Transition not available"), 390 + Some(uri) => { 391 + tracing::info!("UPnP renderer: Play {uri}"); 392 + play_url(&uri).await; 393 + RENDERER_STATE.lock().unwrap().transport_state = TransportState::Playing; 394 + soap_ok("urn:schemas-upnp-org:service:AVTransport:1", "Play", "") 395 + } 396 + } 397 + } 398 + 399 + Some("Stop") => { 400 + tracing::info!("UPnP renderer: Stop"); 401 + let was_active = matches!( 402 + RENDERER_STATE.lock().unwrap().transport_state, 403 + TransportState::Playing | TransportState::PausedPlayback 404 + ); 405 + if was_active { 406 + tokio::task::spawn_blocking(stop_playback).await.ok(); 407 + } 408 + { 409 + let mut st = RENDERER_STATE.lock().unwrap(); 410 + st.transport_state = TransportState::Stopped; 411 + st.current_metadata = String::new(); 412 + } 413 + soap_ok("urn:schemas-upnp-org:service:AVTransport:1", "Stop", "") 414 + } 415 + 416 + Some("Pause") => { 417 + let state = RENDERER_STATE.lock().unwrap().transport_state.clone(); 418 + match state { 419 + TransportState::Playing => { 420 + tokio::task::spawn_blocking(|| { 421 + pause_playback(); 422 + }) 423 + .await 424 + .ok(); 425 + RENDERER_STATE.lock().unwrap().transport_state = TransportState::PausedPlayback; 426 + soap_ok("urn:schemas-upnp-org:service:AVTransport:1", "Pause", "") 427 + } 428 + TransportState::PausedPlayback => { 429 + tokio::task::spawn_blocking(|| { 430 + resume_playback(); 431 + }) 432 + .await 433 + .ok(); 434 + RENDERER_STATE.lock().unwrap().transport_state = TransportState::Playing; 435 + soap_ok("urn:schemas-upnp-org:service:AVTransport:1", "Pause", "") 436 + } 437 + _ => soap_error(701, "Transition not available"), 438 + } 439 + } 440 + 441 + Some("Seek") => { 442 + let ts = RENDERER_STATE.lock().unwrap().transport_state.clone(); 443 + if !matches!(ts, TransportState::Playing | TransportState::PausedPlayback) { 444 + return soap_error(701, "Transition not available"); 445 + } 446 + let unit = extract_tag(&body, "Unit").unwrap_or_default(); 447 + let target = extract_tag(&body, "Target").unwrap_or_default(); 448 + if unit == "REL_TIME" || unit == "ABS_TIME" { 449 + if let Some(ms) = parse_time_to_ms(&target) { 450 + tokio::task::spawn_blocking(move || { 451 + seek_to(ms); 452 + }) 453 + .await 454 + .ok(); 455 + return soap_ok("urn:schemas-upnp-org:service:AVTransport:1", "Seek", ""); 456 + } 457 + } 458 + soap_error(711, "Illegal seek target") 459 + } 460 + 461 + Some("GetTransportInfo") => { 462 + let state = RENDERER_STATE.lock().unwrap().transport_state.clone(); 463 + let inner = format!( 464 + "<CurrentTransportState>{}</CurrentTransportState>\ 465 + <CurrentTransportStatus>OK</CurrentTransportStatus>\ 466 + <CurrentSpeed>1</CurrentSpeed>", 467 + state.as_str() 468 + ); 469 + soap_ok( 470 + "urn:schemas-upnp-org:service:AVTransport:1", 471 + "GetTransportInfo", 472 + &inner, 473 + ) 474 + } 475 + 476 + Some("GetPositionInfo") => { 477 + let (uri, elapsed_ms, duration_ms) = get_position_info(); 478 + let metadata_raw = RENDERER_STATE.lock().unwrap().current_metadata.clone(); 479 + let rel_time = ms_to_time(elapsed_ms); 480 + let track_duration = ms_to_time(duration_ms); 481 + let track_metadata_escaped = xml_escape(&metadata_raw); 482 + let inner = format!( 483 + "<Track>1</Track>\ 484 + <TrackDuration>{track_duration}</TrackDuration>\ 485 + <TrackMetaData>{track_metadata_escaped}</TrackMetaData>\ 486 + <TrackURI>{uri}</TrackURI>\ 487 + <RelTime>{rel_time}</RelTime>\ 488 + <AbsTime>{rel_time}</AbsTime>\ 489 + <RelCount>0</RelCount>\ 490 + <AbsCount>0</AbsCount>", 491 + uri = xml_escape(&uri), 492 + ); 493 + soap_ok( 494 + "urn:schemas-upnp-org:service:AVTransport:1", 495 + "GetPositionInfo", 496 + &inner, 497 + ) 498 + } 499 + 500 + Some("GetMediaInfo") => { 501 + let (uri, _, duration_ms) = get_position_info(); 502 + let metadata_raw = RENDERER_STATE.lock().unwrap().current_metadata.clone(); 503 + let duration = ms_to_time(duration_ms); 504 + let metadata_escaped = xml_escape(&metadata_raw); 505 + let inner = format!( 506 + "<NrTracks>1</NrTracks>\ 507 + <MediaDuration>{duration}</MediaDuration>\ 508 + <CurrentURI>{uri}</CurrentURI>\ 509 + <CurrentURIMetaData>{metadata_escaped}</CurrentURIMetaData>\ 510 + <NextURI></NextURI>\ 511 + <NextURIMetaData></NextURIMetaData>\ 512 + <PlayMedium>NETWORK</PlayMedium>\ 513 + <RecordMedium>NOT_IMPLEMENTED</RecordMedium>\ 514 + <WriteStatus>NOT_IMPLEMENTED</WriteStatus>", 515 + uri = xml_escape(&uri), 516 + ); 517 + soap_ok( 518 + "urn:schemas-upnp-org:service:AVTransport:1", 519 + "GetMediaInfo", 520 + &inner, 521 + ) 522 + } 523 + 524 + _ => soap_error(401, "Invalid Action"), 525 + } 526 + } 527 + 528 + // --------------------------------------------------------------------------- 529 + // RenderingControl SOAP 530 + // --------------------------------------------------------------------------- 531 + 532 + fn rendering_control(body: String, header_action: Option<String>) -> Response<Full<Bytes>> { 533 + let action = resolve_action(header_action, &body); 534 + match action.as_deref() { 535 + Some("GetVolume") => { 536 + let vol = current_volume_pct(); 537 + let inner = format!("<CurrentVolume>{vol}</CurrentVolume>"); 538 + soap_ok( 539 + "urn:schemas-upnp-org:service:RenderingControl:1", 540 + "GetVolume", 541 + &inner, 542 + ) 543 + } 544 + 545 + Some("SetVolume") => { 546 + let desired: i32 = extract_tag(&body, "DesiredVolume") 547 + .and_then(|v| v.parse().ok()) 548 + .unwrap_or(50) 549 + .clamp(0, 100); 550 + set_volume_pct(desired); 551 + soap_ok( 552 + "urn:schemas-upnp-org:service:RenderingControl:1", 553 + "SetVolume", 554 + "", 555 + ) 556 + } 557 + 558 + Some("GetMute") => { 559 + let mute = RENDERER_STATE.lock().unwrap().mute; 560 + let inner = format!( 561 + "<CurrentMute>{}</CurrentMute>", 562 + if mute { "1" } else { "0" } 563 + ); 564 + soap_ok( 565 + "urn:schemas-upnp-org:service:RenderingControl:1", 566 + "GetMute", 567 + &inner, 568 + ) 569 + } 570 + 571 + Some("SetMute") => { 572 + let desired = extract_tag(&body, "DesiredMute") 573 + .map(|v| v == "1" || v.eq_ignore_ascii_case("true")) 574 + .unwrap_or(false); 575 + RENDERER_STATE.lock().unwrap().mute = desired; 576 + // Rockbox doesn't have a dedicated mute — drive volume to min when muting. 577 + if desired { 578 + let min = rockbox_sys::sound::min(0); 579 + rockbox_sys::sound::set(0, min); 580 + } else { 581 + let vol = current_volume_pct(); 582 + set_volume_pct(vol); 583 + } 584 + soap_ok( 585 + "urn:schemas-upnp-org:service:RenderingControl:1", 586 + "SetMute", 587 + "", 588 + ) 589 + } 590 + 591 + _ => soap_error(401, "Invalid Action"), 592 + } 593 + } 594 + 595 + fn renderer_connection_manager( 596 + body: String, 597 + header_action: Option<String>, 598 + ) -> Response<Full<Bytes>> { 599 + let action = resolve_action(header_action, &body); 600 + if matches!(action.as_deref(), Some("GetProtocolInfo")) { 601 + // Build sink protocol info from all supported Rockbox formats. 602 + let formats = [ 603 + "audio/mpeg", 604 + "audio/flac", 605 + "audio/ogg", 606 + "audio/opus", 607 + "audio/mp4", 608 + "audio/aac", 609 + "audio/wav", 610 + "audio/aiff", 611 + "audio/x-w64", 612 + "audio/x-wavpack", 613 + "audio/x-ape", 614 + "audio/x-musepack", 615 + "audio/ac3", 616 + "audio/x-ms-wma", 617 + "audio/x-pn-realaudio", 618 + "audio/x-tta", 619 + "audio/x-shorten", 620 + "audio/basic", 621 + "audio/x-sony-oma", 622 + "audio/vox", 623 + "audio/x-adx", 624 + "audio/mod", 625 + ]; 626 + let sink: Vec<String> = formats 627 + .iter() 628 + .map(|m| format!("http-get:*:{m}:*")) 629 + .collect(); 630 + let inner = format!("<Source></Source><Sink>{}</Sink>", sink.join(",")); 631 + return soap_ok( 632 + "urn:schemas-upnp-org:service:ConnectionManager:1", 633 + "GetProtocolInfo", 634 + &inner, 635 + ); 636 + } 637 + soap_error(401, "Invalid Action") 638 + } 639 + 640 + async fn play_url(uri: &str) { 641 + use crate::api::rockbox::v1alpha1::{ 642 + playback_service_client::PlaybackServiceClient, PlayTrackRequest, 643 + }; 644 + let port = std::env::var("ROCKBOX_PORT").unwrap_or_else(|_| "6061".to_string()); 645 + let url = format!("tcp://127.0.0.1:{}", port); 646 + match PlaybackServiceClient::connect(url).await { 647 + Ok(mut client) => { 648 + let req = PlayTrackRequest { 649 + path: uri.to_string(), 650 + }; 651 + if let Err(e) = client.play_track(req).await { 652 + tracing::error!("UPnP renderer: PlayTrack gRPC error: {e}"); 653 + } 654 + } 655 + Err(e) => tracing::error!("UPnP renderer: gRPC connect error: {e}"), 656 + } 657 + } 658 + 659 + fn stop_playback() { 660 + rockbox_sys::playback::hard_stop(); 661 + } 662 + 663 + fn pause_playback() { 664 + rockbox_sys::playback::pause(); 665 + } 666 + 667 + fn resume_playback() { 668 + rockbox_sys::playback::resume(); 669 + } 670 + 671 + fn seek_to(ms: i32) { 672 + rockbox_sys::playback::ff_rewind(ms); 673 + } 674 + 675 + fn get_position_info() -> (String, i32, i32) { 676 + let uri = RENDERER_STATE 677 + .lock() 678 + .unwrap() 679 + .current_uri 680 + .clone() 681 + .unwrap_or_default(); 682 + 683 + let (elapsed, duration) = match rockbox_sys::playback::current_track() { 684 + Some(track) => (track.elapsed as i32, track.length as i32), 685 + None => (0, 0), 686 + }; 687 + (uri, elapsed, duration) 688 + } 689 + 690 + // SOUND_VOLUME = 0 in Rockbox (first entry in the sound settings enum). 691 + const SOUND_VOLUME: i32 = 0; 692 + 693 + fn current_volume_pct() -> i32 { 694 + let cur = rockbox_sys::sound::current(SOUND_VOLUME); 695 + let min = rockbox_sys::sound::min(SOUND_VOLUME); 696 + let max = rockbox_sys::sound::max(SOUND_VOLUME); 697 + if max == min { 698 + return 50; 699 + } 700 + ((cur - min) * 100) / (max - min) 701 + } 702 + 703 + fn set_volume_pct(pct: i32) { 704 + let min = rockbox_sys::sound::min(SOUND_VOLUME); 705 + let max = rockbox_sys::sound::max(SOUND_VOLUME); 706 + let val = min + (pct.clamp(0, 100) * (max - min)) / 100; 707 + rockbox_sys::sound::set(SOUND_VOLUME, val); 708 + } 709 + 710 + // --------------------------------------------------------------------------- 711 + // SSDP for MediaRenderer:1 712 + // --------------------------------------------------------------------------- 713 + 714 + async fn run_ssdp(http_port: u16) { 715 + let std_sock = match create_ssdp_socket() { 716 + Ok(s) => s, 717 + Err(e) => { 718 + tracing::warn!("UPnP renderer SSDP: could not bind port 1900: {e}"); 719 + return; 720 + } 721 + }; 722 + let socket = match tokio::net::UdpSocket::from_std(std_sock) { 723 + Ok(s) => s, 724 + Err(e) => { 725 + tracing::error!("UPnP renderer SSDP: tokio socket error: {e}"); 726 + return; 727 + } 728 + }; 729 + 730 + send_notify_alive(&socket, http_port).await; 731 + tracing::info!("UPnP renderer SSDP advertising on 239.255.255.250:1900"); 732 + 733 + let mut interval = tokio::time::interval(Duration::from_secs(900)); 734 + interval.tick().await; 735 + let mut buf = vec![0u8; 2048]; 736 + 737 + loop { 738 + tokio::select! { 739 + result = socket.recv_from(&mut buf) => { 740 + match result { 741 + Ok((len, from)) => { 742 + if let Ok(msg) = std::str::from_utf8(&buf[..len]) { 743 + handle_msearch(msg, from, &socket, http_port).await; 744 + } 745 + } 746 + Err(e) => tracing::debug!("UPnP renderer SSDP recv error: {e}"), 747 + } 748 + } 749 + _ = interval.tick() => { 750 + send_notify_alive(&socket, http_port).await; 751 + } 752 + } 753 + } 754 + } 755 + 756 + async fn handle_msearch( 757 + msg: &str, 758 + from: std::net::SocketAddr, 759 + socket: &tokio::net::UdpSocket, 760 + http_port: u16, 761 + ) { 762 + if !msg.starts_with("M-SEARCH") { 763 + return; 764 + } 765 + let st = msg 766 + .lines() 767 + .find(|l| l.to_ascii_uppercase().starts_with("ST:")) 768 + .map(|l| l[3..].trim()) 769 + .unwrap_or(""); 770 + 771 + let want = matches!( 772 + st, 773 + "ssdp:all" 774 + | "upnp:rootdevice" 775 + | "urn:schemas-upnp-org:device:MediaRenderer:1" 776 + | "urn:schemas-upnp-org:service:AVTransport:1" 777 + | "urn:schemas-upnp-org:service:RenderingControl:1" 778 + ) || st.starts_with("uuid:"); 779 + 780 + if !want { 781 + return; 782 + } 783 + 784 + tokio::time::sleep(Duration::from_millis(100)).await; 785 + 786 + let ip = get_local_ip(); 787 + let uuid = renderer_uuid(); 788 + let location = format!("http://{}:{}/renderer/desc.xml", ip, http_port); 789 + let friendly = CONFIG 790 + .lock() 791 + .map(|c| c.friendly_name.clone()) 792 + .unwrap_or_default(); 793 + 794 + for (nt, usn) in renderer_nt_usn_pairs(uuid) { 795 + let response = format!( 796 + "HTTP/1.1 200 OK\r\n\ 797 + CACHE-CONTROL: max-age=1800\r\n\ 798 + EXT:\r\n\ 799 + LOCATION: {location}\r\n\ 800 + SERVER: Linux/1.0 UPnP/1.0 Rockbox/1.0\r\n\ 801 + ST: {nt}\r\n\ 802 + USN: {usn}\r\n\ 803 + X-Friendly-Name: {friendly} (Renderer)\r\n\ 804 + \r\n" 805 + ); 806 + socket.send_to(response.as_bytes(), from).await.ok(); 807 + } 808 + } 809 + 810 + async fn send_notify_alive(socket: &tokio::net::UdpSocket, http_port: u16) { 811 + let ip = get_local_ip(); 812 + let uuid = renderer_uuid(); 813 + let location = format!("http://{}:{}/renderer/desc.xml", ip, http_port); 814 + let dest: std::net::SocketAddr = 815 + std::net::SocketAddr::from((Ipv4Addr::new(239, 255, 255, 250), 1900u16)); 816 + 817 + for (nt, usn) in renderer_nt_usn_pairs(uuid) { 818 + let notify = format!( 819 + "NOTIFY * HTTP/1.1\r\n\ 820 + HOST: 239.255.255.250:1900\r\n\ 821 + CACHE-CONTROL: max-age=1800\r\n\ 822 + LOCATION: {location}\r\n\ 823 + NT: {nt}\r\n\ 824 + NTS: ssdp:alive\r\n\ 825 + SERVER: Linux/1.0 UPnP/1.0 Rockbox/1.0\r\n\ 826 + USN: {usn}\r\n\ 827 + \r\n" 828 + ); 829 + for _ in 0..3 { 830 + socket.send_to(notify.as_bytes(), dest).await.ok(); 831 + } 832 + } 833 + } 834 + 835 + fn renderer_nt_usn_pairs(uuid: &str) -> Vec<(String, String)> { 836 + let dev = "urn:schemas-upnp-org:device:MediaRenderer:1"; 837 + let avt = "urn:schemas-upnp-org:service:AVTransport:1"; 838 + let rc = "urn:schemas-upnp-org:service:RenderingControl:1"; 839 + let cm = "urn:schemas-upnp-org:service:ConnectionManager:1"; 840 + vec![ 841 + ( 842 + "upnp:rootdevice".into(), 843 + format!("uuid:{uuid}::upnp:rootdevice"), 844 + ), 845 + (format!("uuid:{uuid}"), format!("uuid:{uuid}")), 846 + (dev.into(), format!("uuid:{uuid}::{dev}")), 847 + (avt.into(), format!("uuid:{uuid}::{avt}")), 848 + (rc.into(), format!("uuid:{uuid}::{rc}")), 849 + (cm.into(), format!("uuid:{uuid}::{cm}")), 850 + ] 851 + } 852 + 853 + fn create_ssdp_socket() -> std::io::Result<std::net::UdpSocket> { 854 + let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?; 855 + socket.set_reuse_address(true)?; 856 + #[cfg(unix)] 857 + socket.set_reuse_port(true)?; 858 + socket.bind(&SocketAddr::from((Ipv4Addr::UNSPECIFIED, 1900u16)).into())?; 859 + socket.join_multicast_v4(&Ipv4Addr::new(239, 255, 255, 250), &Ipv4Addr::UNSPECIFIED)?; 860 + socket.set_multicast_loop_v4(true)?; 861 + socket.set_nonblocking(true)?; 862 + Ok(socket.into()) 863 + } 864 + 865 + // --------------------------------------------------------------------------- 866 + // Helpers 867 + // --------------------------------------------------------------------------- 868 + 869 + /// Extract the SOAP action from the `SOAPAction` HTTP header. 870 + fn soap_action_from_header(headers: &http::HeaderMap) -> Option<String> { 871 + headers 872 + .get("SOAPAction") 873 + .or_else(|| headers.get("soapaction")) 874 + .and_then(|v| v.to_str().ok()) 875 + .and_then(|s| { 876 + let s = s.trim().trim_matches('"'); 877 + s.rfind('#').map(|i| s[i + 1..].to_string()) 878 + }) 879 + } 880 + 881 + /// Extract the SOAP action by scanning the XML body character by character. 882 + /// Works for both compact (single-line) and pretty-printed envelopes. 883 + fn soap_action_from_body(body: &str) -> Option<String> { 884 + let mut pos = 0; 885 + let b = body.as_bytes(); 886 + while pos < b.len() { 887 + if b[pos] != b'<' { 888 + pos += 1; 889 + continue; 890 + } 891 + pos += 1; 892 + if pos >= b.len() { 893 + break; 894 + } 895 + match b[pos] { 896 + b'/' | b'?' | b'!' => { 897 + while pos < b.len() && b[pos] != b'>' { 898 + pos += 1; 899 + } 900 + continue; 901 + } 902 + _ => {} 903 + } 904 + let name_start = pos; 905 + while pos < b.len() { 906 + match b[pos] { 907 + b'>' | b' ' | b'\t' | b'\n' | b'\r' | b'/' => break, 908 + _ => pos += 1, 909 + } 910 + } 911 + if let Ok(full) = std::str::from_utf8(&b[name_start..pos]) { 912 + let bare = full.split(':').last().unwrap_or(full); 913 + if !bare.is_empty() && !matches!(bare, "Envelope" | "Body") { 914 + return Some(bare.to_string()); 915 + } 916 + } 917 + } 918 + None 919 + } 920 + 921 + fn resolve_action(header_action: Option<String>, body: &str) -> Option<String> { 922 + header_action.or_else(|| soap_action_from_body(body)) 923 + } 924 + 925 + /// Strip leading/trailing whitespace and any URL fragment from a URI string. 926 + fn trim_url(uri: &str) -> String { 927 + let s = uri.trim(); 928 + s.split('#').next().unwrap_or(s).to_string() 929 + } 930 + 931 + fn extract_tag(xml: &str, tag: &str) -> Option<String> { 932 + let open = format!("<{tag}>"); 933 + let close = format!("</{tag}>"); 934 + let start = xml.find(&open)? + open.len(); 935 + let end = xml[start..].find(&close)?; 936 + Some(xml[start..start + end].trim().to_string()) 937 + } 938 + 939 + /// Parse `h:mm:ss[.mmm]` into milliseconds. 940 + fn parse_time_to_ms(s: &str) -> Option<i32> { 941 + let parts: Vec<&str> = s.splitn(3, ':').collect(); 942 + if parts.len() < 3 { 943 + return None; 944 + } 945 + let h: i32 = parts[0].parse().ok()?; 946 + let m: i32 = parts[1].parse().ok()?; 947 + let s_parts: Vec<&str> = parts[2].splitn(2, '.').collect(); 948 + let s: i32 = s_parts[0].parse().ok()?; 949 + let ms: i32 = if s_parts.len() > 1 { 950 + let frac = format!("{:0<3}", s_parts[1]); 951 + frac[..3].parse().ok()? 952 + } else { 953 + 0 954 + }; 955 + Some((h * 3600 + m * 60 + s) * 1000 + ms) 956 + } 957 + 958 + /// Format milliseconds as `h:mm:ss.mmm`. 959 + fn ms_to_time(ms: i32) -> String { 960 + let ms = ms.max(0) as u64; 961 + let millis = ms % 1000; 962 + let secs = ms / 1000; 963 + let mins = secs / 60; 964 + let secs = secs % 60; 965 + let hours = mins / 60; 966 + let mins = mins % 60; 967 + format!("{hours}:{mins:02}:{secs:02}.{millis:03}") 968 + } 969 + 970 + fn soap_ok(service: &str, action: &str, inner: &str) -> Response<Full<Bytes>> { 971 + let xml = format!( 972 + r#"<?xml version="1.0" encoding="utf-8"?> 973 + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" 974 + s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 975 + <s:Body> 976 + <u:{action}Response xmlns:u="{service}">{inner}</u:{action}Response> 977 + </s:Body> 978 + </s:Envelope>"# 979 + ); 980 + xml_response(xml) 981 + } 982 + 983 + fn soap_error(code: u32, description: &str) -> Response<Full<Bytes>> { 984 + let xml = format!( 985 + r#"<?xml version="1.0" encoding="utf-8"?> 986 + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" 987 + s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 988 + <s:Body> 989 + <s:Fault> 990 + <faultcode>s:Client</faultcode> 991 + <faultstring>UPnPError</faultstring> 992 + <detail> 993 + <UPnPError xmlns="urn:schemas-upnp-org:control-1-0"> 994 + <errorCode>{code}</errorCode> 995 + <errorDescription>{description}</errorDescription> 996 + </UPnPError> 997 + </detail> 998 + </s:Fault> 999 + </s:Body> 1000 + </s:Envelope>"# 1001 + ); 1002 + Response::builder() 1003 + .status(500) 1004 + .header("Content-Type", "text/xml; charset=\"utf-8\"") 1005 + .body(Full::from(Bytes::from(xml))) 1006 + .unwrap() 1007 + } 1008 + 1009 + fn xml_response(xml: String) -> Response<Full<Bytes>> { 1010 + Response::builder() 1011 + .status(200) 1012 + .header("Content-Type", "text/xml; charset=\"utf-8\"") 1013 + .body(Full::from(Bytes::from(xml))) 1014 + .unwrap() 1015 + } 1016 + 1017 + fn not_found() -> Response<Full<Bytes>> { 1018 + Response::builder() 1019 + .status(StatusCode::NOT_FOUND) 1020 + .body(Full::from(Bytes::new())) 1021 + .unwrap() 1022 + } 1023 + 1024 + fn xml_escape(s: &str) -> String { 1025 + s.replace('&', "&amp;") 1026 + .replace('<', "&lt;") 1027 + .replace('>', "&gt;") 1028 + .replace('"', "&quot;") 1029 + } 1030 + 1031 + fn xml_unescape(s: &str) -> String { 1032 + // Order matters: &amp; must be last to avoid double-unescaping. 1033 + s.replace("&lt;", "<") 1034 + .replace("&gt;", ">") 1035 + .replace("&quot;", "\"") 1036 + .replace("&apos;", "'") 1037 + .replace("&amp;", "&") 1038 + }
+185
crates/upnp/src/scan.rs
··· 1 + use std::collections::HashSet; 2 + use std::net::{Ipv4Addr, SocketAddr, UdpSocket}; 3 + use std::time::Duration; 4 + 5 + const SSDP_ADDR: &str = "239.255.255.250:1900"; 6 + const MSEARCH: &str = "M-SEARCH * HTTP/1.1\r\n\ 7 + HOST: 239.255.255.250:1900\r\n\ 8 + MAN: \"ssdp:discover\"\r\n\ 9 + MX: 3\r\n\ 10 + ST: urn:schemas-upnp-org:device:MediaRenderer:1\r\n\ 11 + \r\n"; 12 + 13 + #[derive(Debug, Clone)] 14 + pub struct RendererInfo { 15 + pub friendly_name: String, 16 + pub location: String, 17 + pub av_transport_url: String, 18 + pub ip: String, 19 + pub port: u16, 20 + pub udn: String, 21 + } 22 + 23 + /// Send SSDP M-SEARCH and probe every discovered location. 24 + /// Blocks for up to `timeout_secs` while collecting SSDP replies. 25 + pub async fn scan_renderers(timeout_secs: u64) -> Vec<RendererInfo> { 26 + let locations = ssdp_discover(timeout_secs); 27 + tracing::debug!("upnp scan: {} location(s) found", locations.len()); 28 + 29 + let client = reqwest::Client::builder() 30 + .timeout(Duration::from_secs(5)) 31 + .build() 32 + .unwrap(); 33 + 34 + let mut renderers = vec![]; 35 + for location in locations { 36 + match probe_renderer(&client, &location).await { 37 + Some(r) => { 38 + tracing::info!( 39 + "upnp scan: found renderer \"{}\" av={} ({}:{})", 40 + r.friendly_name, 41 + r.av_transport_url, 42 + r.ip, 43 + r.port 44 + ); 45 + renderers.push(r); 46 + } 47 + None => tracing::debug!("upnp scan: {location} skipped (not a renderer)"), 48 + } 49 + } 50 + renderers 51 + } 52 + 53 + // --------------------------------------------------------------------------- 54 + // SSDP 55 + // --------------------------------------------------------------------------- 56 + 57 + fn ssdp_discover(timeout_secs: u64) -> HashSet<String> { 58 + let socket = match UdpSocket::bind(SocketAddr::from((Ipv4Addr::UNSPECIFIED, 0))) { 59 + Ok(s) => s, 60 + Err(e) => { 61 + tracing::warn!("upnp scan: bind failed: {e}"); 62 + return HashSet::new(); 63 + } 64 + }; 65 + socket 66 + .set_read_timeout(Some(Duration::from_secs(timeout_secs))) 67 + .ok(); 68 + 69 + let dest: SocketAddr = SSDP_ADDR.parse().unwrap(); 70 + if let Err(e) = socket.send_to(MSEARCH.as_bytes(), dest) { 71 + tracing::warn!("upnp scan: M-SEARCH send failed: {e}"); 72 + return HashSet::new(); 73 + } 74 + 75 + let mut locations = HashSet::new(); 76 + let mut buf = vec![0u8; 4096]; 77 + loop { 78 + match socket.recv_from(&mut buf) { 79 + Ok((len, _)) => { 80 + if let Ok(msg) = std::str::from_utf8(&buf[..len]) { 81 + if let Some(loc) = header_value(msg, "LOCATION") { 82 + locations.insert(loc.to_string()); 83 + } 84 + } 85 + } 86 + Err(_) => break, 87 + } 88 + } 89 + locations 90 + } 91 + 92 + // --------------------------------------------------------------------------- 93 + // Device description probe 94 + // --------------------------------------------------------------------------- 95 + 96 + async fn probe_renderer(client: &reqwest::Client, location: &str) -> Option<RendererInfo> { 97 + let xml = client.get(location).send().await.ok()?.text().await.ok()?; 98 + 99 + if !xml.contains("MediaRenderer") { 100 + return None; 101 + } 102 + 103 + let friendly_name = tag_value(&xml, "friendlyName")?.to_string(); 104 + let udn = tag_value(&xml, "UDN").unwrap_or("").to_string(); 105 + 106 + let av_control_path = find_service_control_url(&xml, "AVTransport")?; 107 + let base = base_url_of(location)?; 108 + let av_transport_url = if av_control_path.starts_with("http") { 109 + av_control_path.to_string() 110 + } else { 111 + format!( 112 + "{}/{}", 113 + base.trim_end_matches('/'), 114 + av_control_path.trim_start_matches('/') 115 + ) 116 + }; 117 + 118 + let (ip, port) = ip_port_of(location)?; 119 + 120 + Some(RendererInfo { 121 + friendly_name, 122 + location: location.to_string(), 123 + av_transport_url, 124 + ip, 125 + port, 126 + udn, 127 + }) 128 + } 129 + 130 + fn find_service_control_url<'a>(xml: &'a str, service_type: &str) -> Option<&'a str> { 131 + let mut search_from = 0; 132 + while let Some(rel) = xml[search_from..].find("<service>") { 133 + let start = search_from + rel; 134 + let end = xml[start..].find("</service>").map(|e| start + e)?; 135 + let block = &xml[start..end]; 136 + if block.contains(service_type) { 137 + if let Some(url) = tag_value(block, "controlURL") { 138 + return Some(url); 139 + } 140 + } 141 + search_from = end + "</service>".len(); 142 + } 143 + None 144 + } 145 + 146 + // --------------------------------------------------------------------------- 147 + // Helpers 148 + // --------------------------------------------------------------------------- 149 + 150 + fn tag_value<'a>(xml: &'a str, tag: &str) -> Option<&'a str> { 151 + let open = format!("<{tag}>"); 152 + let close = format!("</{tag}>"); 153 + let start = xml.find(&open)? + open.len(); 154 + let end = xml[start..].find(&close)?; 155 + Some(xml[start..start + end].trim()) 156 + } 157 + 158 + fn header_value<'a>(msg: &'a str, name: &str) -> Option<&'a str> { 159 + let prefix_upper = format!("{}:", name.to_ascii_uppercase()); 160 + msg.lines() 161 + .find(|l| l.to_ascii_uppercase().starts_with(&prefix_upper)) 162 + .map(|l| l[name.len() + 1..].trim()) 163 + } 164 + 165 + fn base_url_of(url: &str) -> Option<String> { 166 + let after_scheme = url.find("://")?; 167 + let authority_start = after_scheme + 3; 168 + let path_start = url[authority_start..] 169 + .find('/') 170 + .map(|p| authority_start + p) 171 + .unwrap_or(url.len()); 172 + Some(url[..path_start].to_string()) 173 + } 174 + 175 + fn ip_port_of(url: &str) -> Option<(String, u16)> { 176 + let after_scheme = url.find("://")?; 177 + let authority = url[after_scheme + 3..].split('/').next()?; 178 + if let Some(colon) = authority.rfind(':') { 179 + let ip = authority[..colon].to_string(); 180 + let port: u16 = authority[colon + 1..].parse().ok()?; 181 + Some((ip, port)) 182 + } else { 183 + Some((authority.to_string(), 80)) 184 + } 185 + }
+740
crates/upnp/src/server.rs
··· 1 + use crate::{db, device_uuid, didl, format, get_local_ip, CONFIG}; 2 + use bytes::Bytes; 3 + use http::{Method, Request, Response, StatusCode}; 4 + use http_body_util::{BodyExt, Full}; 5 + use hyper::body::Incoming; 6 + use hyper::service::service_fn; 7 + use hyper_util::rt::{TokioExecutor, TokioIo}; 8 + use hyper_util::server::conn::auto::Builder; 9 + use sqlx::Pool; 10 + use std::net::SocketAddr; 11 + use std::sync::Arc; 12 + use tokio::fs::File; 13 + use tokio::net::TcpListener; 14 + 15 + type BoxBody = Full<Bytes>; 16 + 17 + struct State { 18 + pool: Pool<sqlx::Sqlite>, 19 + server_port: u16, 20 + friendly_name: String, 21 + uuid: String, 22 + local_ip: std::net::Ipv4Addr, 23 + } 24 + 25 + pub async fn run(port: u16) -> anyhow::Result<()> { 26 + let pool = match db::open_pool().await { 27 + Ok(p) => p, 28 + Err(e) => { 29 + tracing::warn!("UPnP media server: library DB not available ({e}); retrying in 10s"); 30 + tokio::time::sleep(std::time::Duration::from_secs(10)).await; 31 + db::open_pool().await? 32 + } 33 + }; 34 + 35 + let (friendly_name, server_port) = { 36 + let cfg = CONFIG.lock().unwrap(); 37 + (cfg.friendly_name.clone(), cfg.server_port) 38 + }; 39 + 40 + let state = Arc::new(State { 41 + pool, 42 + server_port, 43 + friendly_name: if friendly_name.is_empty() { 44 + "Rockbox".to_string() 45 + } else { 46 + friendly_name 47 + }, 48 + uuid: device_uuid().to_string(), 49 + local_ip: get_local_ip(), 50 + }); 51 + 52 + let listener = TcpListener::bind(SocketAddr::from(([0, 0, 0, 0], port))).await?; 53 + tracing::info!("UPnP HTTP server listening on :{port}"); 54 + 55 + loop { 56 + let (stream, _) = listener.accept().await?; 57 + let io = TokioIo::new(stream); 58 + let state = state.clone(); 59 + tokio::spawn(async move { 60 + let svc = service_fn(move |req| { 61 + let state = state.clone(); 62 + async move { handle(req, state).await } 63 + }); 64 + if let Err(e) = Builder::new(TokioExecutor::new()) 65 + .serve_connection(io, svc) 66 + .await 67 + { 68 + tracing::debug!("UPnP HTTP: connection error: {e}"); 69 + } 70 + }); 71 + } 72 + } 73 + 74 + async fn handle( 75 + req: Request<Incoming>, 76 + state: Arc<State>, 77 + ) -> Result<Response<BoxBody>, hyper::Error> { 78 + let path = req.uri().path().to_string(); 79 + let method = req.method().clone(); 80 + // Extract headers before the body is consumed. 81 + let header_action = soap_action_from_header(req.headers()); 82 + let range_hdr = req 83 + .headers() 84 + .get("Range") 85 + .and_then(|v| v.to_str().ok()) 86 + .map(|s| s.to_string()); 87 + 88 + let resp = match (method, path.as_str()) { 89 + (Method::GET, "/desc.xml") => device_description(&state), 90 + (Method::GET, "/ContentDirectory/desc.xml") => content_directory_scpd(), 91 + (Method::GET, "/ConnectionManager/desc.xml") => connection_manager_scpd(), 92 + (Method::POST, "/ContentDirectory/control") => { 93 + let body = req.collect().await?.to_bytes(); 94 + let body_str = std::str::from_utf8(&body).unwrap_or("").to_string(); 95 + content_directory_control(body_str, header_action, &state).await 96 + } 97 + (Method::POST, "/ConnectionManager/control") => { 98 + let body = req.collect().await?.to_bytes(); 99 + let body_str = std::str::from_utf8(&body).unwrap_or("").to_string(); 100 + connection_manager_control(body_str, header_action) 101 + } 102 + (Method::GET, p) if p.starts_with("/audio/") => { 103 + let id = &p[7..]; 104 + serve_audio(id, range_hdr.as_deref(), &state).await 105 + } 106 + (Method::GET, p) if p.starts_with("/art/") => { 107 + let album_id = &p[5..]; 108 + serve_album_art(album_id, &state).await 109 + } 110 + (m, _) if m.as_str() == "SUBSCRIBE" || m.as_str() == "UNSUBSCRIBE" => Response::builder() 111 + .status(200) 112 + .header("SID", "uuid:rockbox-event-sid") 113 + .header("TIMEOUT", "Second-1800") 114 + .body(empty_body()) 115 + .unwrap(), 116 + _ => not_found(), 117 + }; 118 + Ok(resp) 119 + } 120 + 121 + // --------------------------------------------------------------------------- 122 + // Device + service descriptions 123 + // --------------------------------------------------------------------------- 124 + 125 + fn device_description(state: &State) -> Response<BoxBody> { 126 + let xml = format!( 127 + r#"<?xml version="1.0" encoding="UTF-8"?> 128 + <root xmlns="urn:schemas-upnp-org:device-1-0"> 129 + <specVersion><major>1</major><minor>0</minor></specVersion> 130 + <device> 131 + <deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType> 132 + <friendlyName>{name}</friendlyName> 133 + <manufacturer>Rockbox</manufacturer> 134 + <manufacturerURL>https://www.rockbox.org</manufacturerURL> 135 + <modelDescription>Rockbox UPnP/DLNA Media Server</modelDescription> 136 + <modelName>Rockbox</modelName> 137 + <modelNumber>1.0</modelNumber> 138 + <UDN>uuid:{uuid}</UDN> 139 + <dlna:X_DLNADOC xmlns:dlna="urn:schemas-dlna-org:device-1-0">DMS-1.50</dlna:X_DLNADOC> 140 + <serviceList> 141 + <service> 142 + <serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType> 143 + <serviceId>urn:upnp-org:serviceId:ContentDirectory</serviceId> 144 + <SCPDURL>/ContentDirectory/desc.xml</SCPDURL> 145 + <controlURL>/ContentDirectory/control</controlURL> 146 + <eventSubURL>/ContentDirectory/events</eventSubURL> 147 + </service> 148 + <service> 149 + <serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType> 150 + <serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId> 151 + <SCPDURL>/ConnectionManager/desc.xml</SCPDURL> 152 + <controlURL>/ConnectionManager/control</controlURL> 153 + <eventSubURL>/ConnectionManager/events</eventSubURL> 154 + </service> 155 + </serviceList> 156 + </device> 157 + </root>"#, 158 + name = xml_escape(&state.friendly_name), 159 + uuid = state.uuid, 160 + ); 161 + xml_response(xml) 162 + } 163 + 164 + fn content_directory_scpd() -> Response<BoxBody> { 165 + xml_response( 166 + r#"<?xml version="1.0" encoding="UTF-8"?> 167 + <scpd xmlns="urn:schemas-upnp-org:service-1-0"> 168 + <specVersion><major>1</major><minor>0</minor></specVersion> 169 + <actionList> 170 + <action> 171 + <name>Browse</name> 172 + <argumentList> 173 + <argument><name>ObjectID</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable></argument> 174 + <argument><name>BrowseFlag</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_BrowseFlag</relatedStateVariable></argument> 175 + <argument><name>Filter</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_Filter</relatedStateVariable></argument> 176 + <argument><name>StartingIndex</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_Index</relatedStateVariable></argument> 177 + <argument><name>RequestedCount</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable></argument> 178 + <argument><name>SortCriteria</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_SortCriteria</relatedStateVariable></argument> 179 + <argument><name>Result</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_Result</relatedStateVariable></argument> 180 + <argument><name>NumberReturned</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable></argument> 181 + <argument><name>TotalMatches</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable></argument> 182 + <argument><name>UpdateID</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_UpdateID</relatedStateVariable></argument> 183 + </argumentList> 184 + </action> 185 + </actionList> 186 + <serviceStateTable> 187 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_ObjectID</name><dataType>string</dataType></stateVariable> 188 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_Result</name><dataType>string</dataType></stateVariable> 189 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_BrowseFlag</name><dataType>string</dataType><allowedValueList><allowedValue>BrowseMetadata</allowedValue><allowedValue>BrowseDirectChildren</allowedValue></allowedValueList></stateVariable> 190 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_Filter</name><dataType>string</dataType></stateVariable> 191 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_SortCriteria</name><dataType>string</dataType></stateVariable> 192 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_Index</name><dataType>ui4</dataType></stateVariable> 193 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_Count</name><dataType>ui4</dataType></stateVariable> 194 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_UpdateID</name><dataType>ui4</dataType></stateVariable> 195 + <stateVariable sendEvents="yes"><name>SystemUpdateID</name><dataType>ui4</dataType></stateVariable> 196 + <stateVariable sendEvents="yes"><name>ContainerUpdateIDs</name><dataType>string</dataType></stateVariable> 197 + </serviceStateTable> 198 + </scpd>"# 199 + .to_string(), 200 + ) 201 + } 202 + 203 + fn connection_manager_scpd() -> Response<BoxBody> { 204 + xml_response( 205 + r#"<?xml version="1.0" encoding="UTF-8"?> 206 + <scpd xmlns="urn:schemas-upnp-org:service-1-0"> 207 + <specVersion><major>1</major><minor>0</minor></specVersion> 208 + <actionList> 209 + <action> 210 + <name>GetProtocolInfo</name> 211 + <argumentList> 212 + <argument><name>Source</name><direction>out</direction><relatedStateVariable>SourceProtocolInfo</relatedStateVariable></argument> 213 + <argument><name>Sink</name><direction>out</direction><relatedStateVariable>SinkProtocolInfo</relatedStateVariable></argument> 214 + </argumentList> 215 + </action> 216 + </actionList> 217 + <serviceStateTable> 218 + <stateVariable sendEvents="yes"><name>SourceProtocolInfo</name><dataType>string</dataType></stateVariable> 219 + <stateVariable sendEvents="yes"><name>SinkProtocolInfo</name><dataType>string</dataType></stateVariable> 220 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_ConnectionStatus</name><dataType>string</dataType></stateVariable> 221 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_ConnectionID</name><dataType>i4</dataType></stateVariable> 222 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_AVTransportID</name><dataType>i4</dataType></stateVariable> 223 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_RcsID</name><dataType>i4</dataType></stateVariable> 224 + <stateVariable sendEvents="no"><name>CurrentConnectionIDs</name><dataType>string</dataType></stateVariable> 225 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_Direction</name><dataType>string</dataType></stateVariable> 226 + <stateVariable sendEvents="no"><name>A_ARG_TYPE_ProtocolInfo</name><dataType>string</dataType></stateVariable> 227 + </serviceStateTable> 228 + </scpd>"# 229 + .to_string(), 230 + ) 231 + } 232 + 233 + // --------------------------------------------------------------------------- 234 + // ContentDirectory SOAP control 235 + // --------------------------------------------------------------------------- 236 + 237 + async fn content_directory_control( 238 + body: String, 239 + header_action: Option<String>, 240 + state: &State, 241 + ) -> Response<BoxBody> { 242 + let action = resolve_action(header_action, &body); 243 + match action.as_deref() { 244 + Some("Browse") => browse_action(body, state).await, 245 + Some("GetSystemUpdateID") => soap_ok_response( 246 + "urn:schemas-upnp-org:service:ContentDirectory:1", 247 + "GetSystemUpdateID", 248 + "<Id>1</Id>", 249 + ), 250 + Some("GetSearchCapabilities") => soap_ok_response( 251 + "urn:schemas-upnp-org:service:ContentDirectory:1", 252 + "GetSearchCapabilities", 253 + "<SearchCaps></SearchCaps>", 254 + ), 255 + Some("GetSortCapabilities") => soap_ok_response( 256 + "urn:schemas-upnp-org:service:ContentDirectory:1", 257 + "GetSortCapabilities", 258 + "<SortCaps></SortCaps>", 259 + ), 260 + _ => soap_error(401, "Invalid Action"), 261 + } 262 + } 263 + 264 + async fn browse_action(body: String, state: &State) -> Response<BoxBody> { 265 + let object_id = extract_tag(&body, "ObjectID").unwrap_or_else(|| "0".to_string()); 266 + let browse_flag = 267 + extract_tag(&body, "BrowseFlag").unwrap_or_else(|| "BrowseDirectChildren".to_string()); 268 + let starting_index: usize = extract_tag(&body, "StartingIndex") 269 + .and_then(|s| s.parse().ok()) 270 + .unwrap_or(0); 271 + let requested_count: usize = extract_tag(&body, "RequestedCount") 272 + .and_then(|s| s.parse().ok()) 273 + .unwrap_or(0); // 0 = all 274 + 275 + let base_url = format!("http://{}:{}", state.local_ip, state.server_port); 276 + let pool = &state.pool; 277 + 278 + let (items, total): (Vec<String>, usize) = match (browse_flag.as_str(), object_id.as_str()) { 279 + // ── Root container ──────────────────────────────────────────────── 280 + (_, "0") => { 281 + let n_tracks = db::count_tracks(pool).await; 282 + let n_albums = db::count_albums(pool).await; 283 + let n_artists = db::count_artists(pool).await; 284 + let items = vec![ 285 + didl::simple_container("1", "0", "Music", n_tracks), 286 + didl::simple_container("2", "0", "Albums", n_albums), 287 + didl::simple_container("3", "0", "Artists", n_artists), 288 + didl::simple_container("4", "0", "All Tracks", n_tracks), 289 + ]; 290 + let total = items.len(); 291 + (items, total) 292 + } 293 + 294 + // ── Music category ──────────────────────────────────────────────── 295 + (_, "1") => { 296 + let n_tracks = db::count_tracks(pool).await; 297 + let n_albums = db::count_albums(pool).await; 298 + let n_artists = db::count_artists(pool).await; 299 + let items = vec![ 300 + didl::simple_container("2", "1", "Albums", n_albums), 301 + didl::simple_container("3", "1", "Artists", n_artists), 302 + didl::simple_container("4", "1", "All Tracks", n_tracks), 303 + ]; 304 + let total = items.len(); 305 + (items, total) 306 + } 307 + 308 + // ── Albums container ────────────────────────────────────────────── 309 + (_, "2") => match db::all_albums(pool).await { 310 + Ok(albums) => { 311 + let total = albums.len(); 312 + let items: Vec<String> = albums 313 + .iter() 314 + .map(|a| didl::album_container(a, "2")) 315 + .collect(); 316 + (items, total) 317 + } 318 + Err(e) => { 319 + tracing::warn!("UPnP Browse albums: {e}"); 320 + (vec![], 0) 321 + } 322 + }, 323 + 324 + // ── Artists container ───────────────────────────────────────────── 325 + (_, "3") => match db::all_artists(pool).await { 326 + Ok(artists) => { 327 + let total = artists.len(); 328 + let items: Vec<String> = artists 329 + .iter() 330 + .map(|a| didl::artist_container(a, "3")) 331 + .collect(); 332 + (items, total) 333 + } 334 + Err(e) => { 335 + tracing::warn!("UPnP Browse artists: {e}"); 336 + (vec![], 0) 337 + } 338 + }, 339 + 340 + // ── All tracks ──────────────────────────────────────────────────── 341 + (_, "4") => match db::all_tracks(pool).await { 342 + Ok(tracks) => { 343 + let total = tracks.len(); 344 + let items: Vec<String> = tracks 345 + .iter() 346 + .map(|t| didl::track_item(t, "4", &base_url)) 347 + .collect(); 348 + (items, total) 349 + } 350 + Err(e) => { 351 + tracing::warn!("UPnP Browse all tracks: {e}"); 352 + (vec![], 0) 353 + } 354 + }, 355 + 356 + // ── Album children ──────────────────────────────────────────────── 357 + (_, id) if id.starts_with("album:") => { 358 + let album_id = &id[6..]; 359 + match db::tracks_by_album(pool, album_id).await { 360 + Ok(tracks) => { 361 + let total = tracks.len(); 362 + let items: Vec<String> = tracks 363 + .iter() 364 + .map(|t| didl::track_item(t, id, &base_url)) 365 + .collect(); 366 + (items, total) 367 + } 368 + Err(e) => { 369 + tracing::warn!("UPnP Browse album {album_id}: {e}"); 370 + (vec![], 0) 371 + } 372 + } 373 + } 374 + 375 + // ── Artist children ─────────────────────────────────────────────── 376 + (_, id) if id.starts_with("artist:") => { 377 + let artist_id = &id[7..]; 378 + match db::tracks_by_artist(pool, artist_id).await { 379 + Ok(tracks) => { 380 + let total = tracks.len(); 381 + let items: Vec<String> = tracks 382 + .iter() 383 + .map(|t| didl::track_item(t, id, &base_url)) 384 + .collect(); 385 + (items, total) 386 + } 387 + Err(e) => { 388 + tracing::warn!("UPnP Browse artist {artist_id}: {e}"); 389 + (vec![], 0) 390 + } 391 + } 392 + } 393 + 394 + // ── Single track metadata ───────────────────────────────────────── 395 + ("BrowseMetadata", id) if id.starts_with("track:") => { 396 + let track_id = &id[6..]; 397 + match db::track_by_id(pool, track_id).await { 398 + Ok(Some(track)) => { 399 + let item = didl::track_item(&track, "4", &base_url); 400 + (vec![item], 1) 401 + } 402 + _ => (vec![], 0), 403 + } 404 + } 405 + 406 + _ => (vec![], 0), 407 + }; 408 + 409 + let total = total; 410 + let (slice, start, count) = paginate(&items, starting_index, requested_count); 411 + let didl = didl::wrap_didl(slice); 412 + let escaped = didl::escape_for_result(&didl); 413 + let result_body = format!( 414 + "<Result>{escaped}</Result>\ 415 + <NumberReturned>{count}</NumberReturned>\ 416 + <TotalMatches>{total}</TotalMatches>\ 417 + <UpdateID>1</UpdateID>" 418 + ); 419 + let _ = (start,); // suppress unused warning 420 + 421 + soap_ok_response( 422 + "urn:schemas-upnp-org:service:ContentDirectory:1", 423 + "Browse", 424 + &result_body, 425 + ) 426 + } 427 + 428 + fn connection_manager_control(body: String, header_action: Option<String>) -> Response<BoxBody> { 429 + let action = resolve_action(header_action, &body); 430 + match action.as_deref() { 431 + Some("GetProtocolInfo") => soap_ok_response( 432 + "urn:schemas-upnp-org:service:ConnectionManager:1", 433 + "GetProtocolInfo", 434 + "<Source>\ 435 + http-get:*:audio/mpeg:*,\ 436 + http-get:*:audio/flac:*,\ 437 + http-get:*:audio/ogg:*,\ 438 + http-get:*:audio/opus:*,\ 439 + http-get:*:audio/mp4:*,\ 440 + http-get:*:audio/aac:*,\ 441 + http-get:*:audio/wav:*,\ 442 + http-get:*:audio/aiff:*,\ 443 + http-get:*:audio/x-w64:*,\ 444 + http-get:*:audio/x-wavpack:*,\ 445 + http-get:*:audio/x-ape:*,\ 446 + http-get:*:audio/x-musepack:*,\ 447 + http-get:*:audio/ac3:*,\ 448 + http-get:*:audio/x-ms-wma:*,\ 449 + http-get:*:audio/x-pn-realaudio:*,\ 450 + http-get:*:audio/x-tta:*,\ 451 + http-get:*:audio/x-shorten:*,\ 452 + http-get:*:audio/basic:*,\ 453 + http-get:*:audio/x-sony-oma:*,\ 454 + http-get:*:audio/vox:*,\ 455 + http-get:*:audio/x-adx:*,\ 456 + http-get:*:audio/mod:*,\ 457 + http-get:*:audio/prs.sid:*,\ 458 + http-get:*:audio/x-nsf:*,\ 459 + http-get:*:audio/x-spc:*,\ 460 + http-get:*:audio/x-asap:*,\ 461 + http-get:*:audio/x-ay:*,\ 462 + http-get:*:audio/x-vtx:*,\ 463 + http-get:*:audio/x-gbs:*,\ 464 + http-get:*:audio/x-hes:*,\ 465 + http-get:*:audio/x-sgc:*,\ 466 + http-get:*:audio/x-vgm:*,\ 467 + http-get:*:audio/x-kss:*\ 468 + </Source>\ 469 + <Sink></Sink>", 470 + ), 471 + _ => soap_error(401, "Invalid Action"), 472 + } 473 + } 474 + 475 + // --------------------------------------------------------------------------- 476 + // Audio file serving 477 + // --------------------------------------------------------------------------- 478 + 479 + async fn serve_audio(id: &str, range: Option<&str>, state: &State) -> Response<BoxBody> { 480 + let track = match db::track_by_id(&state.pool, id).await { 481 + Ok(Some(t)) => t, 482 + Ok(None) => return not_found(), 483 + Err(e) => { 484 + tracing::warn!("UPnP audio: db error for {id}: {e}"); 485 + return server_error(); 486 + } 487 + }; 488 + 489 + let mut file = match File::open(&track.path).await { 490 + Ok(f) => f, 491 + Err(e) => { 492 + tracing::warn!("UPnP audio: open {:?}: {e}", track.path); 493 + return not_found(); 494 + } 495 + }; 496 + 497 + let file_size = file 498 + .metadata() 499 + .await 500 + .map(|m| m.len()) 501 + .unwrap_or(track.filesize as u64); 502 + 503 + let ct = format::content_type_for_path(&track.path); 504 + 505 + // Parse a "bytes=start-[end]" Range header. 506 + let parsed_range = range.and_then(|r| { 507 + let r = r.strip_prefix("bytes=")?; 508 + let (s, e) = r.split_once('-')?; 509 + let start: u64 = s.parse().ok()?; 510 + let end: Option<u64> = if e.is_empty() { None } else { e.parse().ok() }; 511 + Some((start, end)) 512 + }); 513 + 514 + let (start, end) = match parsed_range { 515 + Some((s, e)) => (s, e.unwrap_or(file_size - 1).min(file_size - 1)), 516 + None => (0, file_size - 1), 517 + }; 518 + 519 + let length = end - start + 1; 520 + 521 + // Seek and read only the requested slice to avoid buffering the whole file. 522 + use tokio::io::{AsyncReadExt as _, AsyncSeekExt as _}; 523 + if start > 0 { 524 + if file.seek(std::io::SeekFrom::Start(start)).await.is_err() { 525 + return server_error(); 526 + } 527 + } 528 + let mut data = vec![0u8; length as usize]; 529 + if file.read_exact(&mut data).await.is_err() { 530 + return server_error(); 531 + } 532 + 533 + let (status, content_range) = if parsed_range.is_some() { 534 + (206u16, Some(format!("bytes {start}-{end}/{file_size}"))) 535 + } else { 536 + (200, None) 537 + }; 538 + 539 + let mut builder = Response::builder() 540 + .status(status) 541 + .header("Content-Type", ct) 542 + .header("Content-Length", data.len().to_string()) 543 + .header("Accept-Ranges", "bytes") 544 + .header("TransferMode.DLNA.ORG", "Interactive"); 545 + 546 + if let Some(cr) = content_range { 547 + builder = builder.header("Content-Range", cr); 548 + } 549 + 550 + builder.body(Full::from(Bytes::from(data))).unwrap() 551 + } 552 + 553 + async fn serve_album_art(album_id: &str, state: &State) -> Response<BoxBody> { 554 + // Find the album_art path from any track with this album_id. 555 + let art_path: Option<String> = sqlx::query_scalar( 556 + "SELECT album_art FROM track WHERE album_id = ? AND album_art IS NOT NULL LIMIT 1", 557 + ) 558 + .bind(album_id) 559 + .fetch_optional(&state.pool) 560 + .await 561 + .ok() 562 + .flatten(); 563 + 564 + let Some(path) = art_path else { 565 + return not_found(); 566 + }; 567 + 568 + let data = match tokio::fs::read(&path).await { 569 + Ok(d) => d, 570 + Err(_) => return not_found(), 571 + }; 572 + 573 + let ct = if path.ends_with(".png") { 574 + "image/png" 575 + } else { 576 + "image/jpeg" 577 + }; 578 + 579 + Response::builder() 580 + .status(200) 581 + .header("Content-Type", ct) 582 + .header("Content-Length", data.len().to_string()) 583 + .body(Full::from(Bytes::from(data))) 584 + .unwrap() 585 + } 586 + 587 + // --------------------------------------------------------------------------- 588 + // Helpers 589 + // --------------------------------------------------------------------------- 590 + 591 + /// Extract the SOAP action from the `SOAPAction` HTTP header. 592 + /// Value looks like `"urn:schemas-upnp-org:service:ContentDirectory:1#Browse"`. 593 + fn soap_action_from_header(headers: &http::HeaderMap) -> Option<String> { 594 + headers 595 + .get("SOAPAction") 596 + .or_else(|| headers.get("soapaction")) 597 + .and_then(|v| v.to_str().ok()) 598 + .and_then(|s| { 599 + let s = s.trim().trim_matches('"'); 600 + s.rfind('#').map(|i| s[i + 1..].to_string()) 601 + }) 602 + } 603 + 604 + /// Extract the SOAP action by scanning the XML body character by character. 605 + /// Works for both compact (single-line) and pretty-printed envelopes. 606 + fn soap_action_from_body(body: &str) -> Option<String> { 607 + let mut pos = 0; 608 + let b = body.as_bytes(); 609 + while pos < b.len() { 610 + if b[pos] != b'<' { 611 + pos += 1; 612 + continue; 613 + } 614 + pos += 1; 615 + if pos >= b.len() { 616 + break; 617 + } 618 + match b[pos] { 619 + b'/' | b'?' | b'!' => { 620 + while pos < b.len() && b[pos] != b'>' { 621 + pos += 1; 622 + } 623 + continue; 624 + } 625 + _ => {} 626 + } 627 + let name_start = pos; 628 + while pos < b.len() { 629 + match b[pos] { 630 + b'>' | b' ' | b'\t' | b'\n' | b'\r' | b'/' => break, 631 + _ => pos += 1, 632 + } 633 + } 634 + if let Ok(full) = std::str::from_utf8(&b[name_start..pos]) { 635 + let bare = full.split(':').last().unwrap_or(full); 636 + if !bare.is_empty() && !matches!(bare, "Envelope" | "Body") { 637 + return Some(bare.to_string()); 638 + } 639 + } 640 + } 641 + None 642 + } 643 + 644 + fn resolve_action(header_action: Option<String>, body: &str) -> Option<String> { 645 + header_action.or_else(|| soap_action_from_body(body)) 646 + } 647 + 648 + fn extract_tag(xml: &str, tag: &str) -> Option<String> { 649 + let open = format!("<{tag}>"); 650 + let close = format!("</{tag}>"); 651 + let start = xml.find(&open)? + open.len(); 652 + let end = xml[start..].find(&close)?; 653 + Some(xml[start..start + end].trim().to_string()) 654 + } 655 + 656 + fn paginate<'a>(items: &'a [String], start: usize, count: usize) -> (&'a [String], usize, usize) { 657 + let total = items.len(); 658 + let from = start.min(total); 659 + let slice = if count == 0 { 660 + &items[from..] 661 + } else { 662 + let to = (from + count).min(total); 663 + &items[from..to] 664 + }; 665 + (slice, from, slice.len()) 666 + } 667 + 668 + fn soap_ok_response(service_type: &str, action: &str, inner: &str) -> Response<BoxBody> { 669 + let xml = format!( 670 + r#"<?xml version="1.0" encoding="utf-8"?> 671 + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" 672 + s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 673 + <s:Body> 674 + <u:{action}Response xmlns:u="{service_type}"> 675 + {inner} 676 + </u:{action}Response> 677 + </s:Body> 678 + </s:Envelope>"# 679 + ); 680 + xml_response(xml) 681 + } 682 + 683 + fn soap_error(code: u32, description: &str) -> Response<BoxBody> { 684 + let xml = format!( 685 + r#"<?xml version="1.0" encoding="utf-8"?> 686 + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" 687 + s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 688 + <s:Body> 689 + <s:Fault> 690 + <faultcode>s:Client</faultcode> 691 + <faultstring>UPnPError</faultstring> 692 + <detail> 693 + <UPnPError xmlns="urn:schemas-upnp-org:control-1-0"> 694 + <errorCode>{code}</errorCode> 695 + <errorDescription>{description}</errorDescription> 696 + </UPnPError> 697 + </detail> 698 + </s:Fault> 699 + </s:Body> 700 + </s:Envelope>"# 701 + ); 702 + Response::builder() 703 + .status(500) 704 + .header("Content-Type", "text/xml; charset=\"utf-8\"") 705 + .body(Full::from(Bytes::from(xml))) 706 + .unwrap() 707 + } 708 + 709 + fn xml_response(xml: String) -> Response<BoxBody> { 710 + Response::builder() 711 + .status(200) 712 + .header("Content-Type", "text/xml; charset=\"utf-8\"") 713 + .body(Full::from(Bytes::from(xml))) 714 + .unwrap() 715 + } 716 + 717 + fn not_found() -> Response<BoxBody> { 718 + Response::builder() 719 + .status(StatusCode::NOT_FOUND) 720 + .body(empty_body()) 721 + .unwrap() 722 + } 723 + 724 + fn server_error() -> Response<BoxBody> { 725 + Response::builder() 726 + .status(StatusCode::INTERNAL_SERVER_ERROR) 727 + .body(empty_body()) 728 + .unwrap() 729 + } 730 + 731 + fn empty_body() -> BoxBody { 732 + Full::from(Bytes::new()) 733 + } 734 + 735 + fn xml_escape(s: &str) -> String { 736 + s.replace('&', "&amp;") 737 + .replace('<', "&lt;") 738 + .replace('>', "&gt;") 739 + .replace('"', "&quot;") 740 + }
+164
crates/upnp/src/ssdp.rs
··· 1 + use crate::{device_uuid, get_local_ip, CONFIG}; 2 + use socket2::{Domain, Protocol, Socket, Type}; 3 + use std::net::{Ipv4Addr, SocketAddr, UdpSocket as StdUdpSocket}; 4 + use std::time::Duration; 5 + use tokio::net::UdpSocket; 6 + use tokio::time; 7 + 8 + const SSDP_ADDR: Ipv4Addr = Ipv4Addr::new(239, 255, 255, 250); 9 + const SSDP_PORT: u16 = 1900; 10 + 11 + /// Run the SSDP server: send initial advertisements, respond to M-SEARCH, 12 + /// and re-advertise every 15 minutes. 13 + pub async fn run(http_port: u16) { 14 + let std_sock = match create_ssdp_socket() { 15 + Ok(s) => s, 16 + Err(e) => { 17 + tracing::warn!( 18 + "UPnP SSDP: could not bind port 1900 \ 19 + (another SSDP listener may already be running): {e}" 20 + ); 21 + return; 22 + } 23 + }; 24 + let socket = match UdpSocket::from_std(std_sock) { 25 + Ok(s) => s, 26 + Err(e) => { 27 + tracing::error!("UPnP SSDP: tokio socket error: {e}"); 28 + return; 29 + } 30 + }; 31 + 32 + // Send initial alive notifications. 33 + send_notify_alive(&socket, http_port).await; 34 + tracing::info!("UPnP SSDP: advertising on 239.255.255.250:1900"); 35 + 36 + let mut interval = time::interval(Duration::from_secs(900)); // re-advertise every 15 min 37 + interval.tick().await; // consume the immediate first tick 38 + let mut buf = vec![0u8; 2048]; 39 + 40 + loop { 41 + tokio::select! { 42 + result = socket.recv_from(&mut buf) => { 43 + match result { 44 + Ok((len, from)) => { 45 + if let Ok(msg) = std::str::from_utf8(&buf[..len]) { 46 + handle_message(msg, from, &socket, http_port).await; 47 + } 48 + } 49 + Err(e) => tracing::warn!("UPnP SSDP: recv error: {e}"), 50 + } 51 + } 52 + _ = interval.tick() => { 53 + send_notify_alive(&socket, http_port).await; 54 + } 55 + } 56 + } 57 + } 58 + 59 + async fn handle_message(msg: &str, from: SocketAddr, socket: &UdpSocket, http_port: u16) { 60 + if !msg.starts_with("M-SEARCH") { 61 + return; 62 + } 63 + let st = msg 64 + .lines() 65 + .find(|l| l.to_ascii_uppercase().starts_with("ST:")) 66 + .map(|l| l[3..].trim()) 67 + .unwrap_or(""); 68 + 69 + let want = matches!( 70 + st, 71 + "ssdp:all" 72 + | "upnp:rootdevice" 73 + | "urn:schemas-upnp-org:device:MediaServer:1" 74 + | "urn:schemas-upnp-org:service:ContentDirectory:1" 75 + ) || st.starts_with("uuid:"); 76 + 77 + if !want { 78 + return; 79 + } 80 + 81 + // Small delay to avoid overwhelming the control point. 82 + tokio::time::sleep(Duration::from_millis(100)).await; 83 + 84 + let ip = get_local_ip(); 85 + let uuid = device_uuid(); 86 + let friendly = CONFIG 87 + .lock() 88 + .map(|c| c.friendly_name.clone()) 89 + .unwrap_or_default(); 90 + let location = format!("http://{}:{}/desc.xml", ip, http_port); 91 + 92 + for (nt, usn) in nt_usn_pairs(uuid) { 93 + let response = format!( 94 + "HTTP/1.1 200 OK\r\n\ 95 + CACHE-CONTROL: max-age=1800\r\n\ 96 + EXT:\r\n\ 97 + LOCATION: {location}\r\n\ 98 + SERVER: Linux/1.0 UPnP/1.0 Rockbox/1.0\r\n\ 99 + ST: {nt}\r\n\ 100 + USN: {usn}\r\n\ 101 + X-Friendly-Name: {friendly}\r\n\ 102 + \r\n" 103 + ); 104 + if let Err(e) = socket.send_to(response.as_bytes(), from).await { 105 + tracing::debug!("UPnP SSDP: M-SEARCH response error: {e}"); 106 + } 107 + } 108 + } 109 + 110 + async fn send_notify_alive(socket: &UdpSocket, http_port: u16) { 111 + let ip = get_local_ip(); 112 + let uuid = device_uuid(); 113 + let location = format!("http://{}:{}/desc.xml", ip, http_port); 114 + let dest: SocketAddr = SocketAddr::from((SSDP_ADDR, SSDP_PORT)); 115 + 116 + for (nt, usn) in nt_usn_pairs(uuid) { 117 + let notify = format!( 118 + "NOTIFY * HTTP/1.1\r\n\ 119 + HOST: 239.255.255.250:1900\r\n\ 120 + CACHE-CONTROL: max-age=1800\r\n\ 121 + LOCATION: {location}\r\n\ 122 + NT: {nt}\r\n\ 123 + NTS: ssdp:alive\r\n\ 124 + SERVER: Linux/1.0 UPnP/1.0 Rockbox/1.0\r\n\ 125 + USN: {usn}\r\n\ 126 + \r\n" 127 + ); 128 + // Send three times for reliability (UPnP spec recommendation). 129 + for _ in 0..3 { 130 + if let Err(e) = socket.send_to(notify.as_bytes(), dest).await { 131 + tracing::debug!("UPnP SSDP: notify error: {e}"); 132 + } 133 + } 134 + } 135 + } 136 + 137 + /// Returns (NT, USN) pairs for all UPnP types we advertise. 138 + fn nt_usn_pairs(uuid: &str) -> Vec<(String, String)> { 139 + let device_nt = "urn:schemas-upnp-org:device:MediaServer:1"; 140 + let cd_nt = "urn:schemas-upnp-org:service:ContentDirectory:1"; 141 + let cm_nt = "urn:schemas-upnp-org:service:ConnectionManager:1"; 142 + vec![ 143 + ( 144 + "upnp:rootdevice".to_string(), 145 + format!("uuid:{uuid}::upnp:rootdevice"), 146 + ), 147 + (format!("uuid:{uuid}"), format!("uuid:{uuid}")), 148 + (device_nt.to_string(), format!("uuid:{uuid}::{device_nt}")), 149 + (cd_nt.to_string(), format!("uuid:{uuid}::{cd_nt}")), 150 + (cm_nt.to_string(), format!("uuid:{uuid}::{cm_nt}")), 151 + ] 152 + } 153 + 154 + fn create_ssdp_socket() -> std::io::Result<StdUdpSocket> { 155 + let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?; 156 + socket.set_reuse_address(true)?; 157 + #[cfg(unix)] 158 + socket.set_reuse_port(true)?; 159 + socket.bind(&SocketAddr::from((Ipv4Addr::UNSPECIFIED, SSDP_PORT)).into())?; 160 + socket.join_multicast_v4(&SSDP_ADDR, &Ipv4Addr::UNSPECIFIED)?; 161 + socket.set_multicast_loop_v4(true)?; 162 + socket.set_nonblocking(true)?; 163 + Ok(socket.into()) 164 + }
+1
firmware/SOURCES
··· 539 539 target/hosted/pcm-fifo.c 540 540 target/hosted/pcm-airplay.c 541 541 target/hosted/pcm-squeezelite.c 542 + target/hosted/pcm-upnp.c 542 543 #endif 543 544 #ifdef HAVE_SW_VOLUME_CONTROL 544 545 pcm_sw_volume.c
+6
firmware/export/pcm_sink.h
··· 56 56 PCM_SINK_FIFO, 57 57 PCM_SINK_AIRPLAY, 58 58 PCM_SINK_SQUEEZELITE, 59 + PCM_SINK_UPNP, 59 60 #endif 60 61 PCM_SINK_NUM 61 62 }; ··· 73 74 74 75 /* Squeezelite (Slim Protocol) sink — serves PCM via HTTP to squeezelite */ 75 76 extern struct pcm_sink squeezelite_pcm_sink; 77 + 78 + /* UPnP/DLNA sink — streams WAV over HTTP to UPnP renderers */ 79 + extern struct pcm_sink upnp_pcm_sink; 80 + void pcm_upnp_set_http_port(uint16_t port); 81 + void pcm_upnp_set_renderer_url(const char *url); 76 82 #endif
+1
firmware/pcm.c
··· 84 84 [PCM_SINK_FIFO] = &fifo_pcm_sink, 85 85 [PCM_SINK_AIRPLAY] = &airplay_pcm_sink, 86 86 [PCM_SINK_SQUEEZELITE] = &squeezelite_pcm_sink, 87 + [PCM_SINK_UPNP] = &upnp_pcm_sink, 87 88 #endif 88 89 }; 89 90 static enum pcm_sink_ids cur_sink = PCM_SINK_BUILTIN;
+209
firmware/target/hosted/pcm-upnp.c
··· 1 + /*************************************************************************** 2 + * PCM sink that streams raw S16LE stereo PCM to UPnP/DLNA renderers as a 3 + * continuous WAV stream over HTTP (port 7879 by default). 4 + * 5 + * Usage: 6 + * pcm_upnp_set_http_port(7879); // optional, this is the default 7 + * pcm_upnp_set_renderer_url("http://..."); // optional: auto-command a renderer 8 + * pcm_switch_sink(PCM_SINK_UPNP); 9 + * 10 + * Copyright (C) 2026 Rockbox contributors 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + 22 + #include "autoconf.h" 23 + #include "config.h" 24 + 25 + #include <pthread.h> 26 + #include <stdbool.h> 27 + #include <stddef.h> 28 + #include <stdint.h> 29 + #include <time.h> 30 + #include <unistd.h> 31 + 32 + #include "pcm.h" 33 + #include "pcm-internal.h" 34 + #include "pcm_mixer.h" 35 + #include "pcm_sampr.h" 36 + #include "pcm_sink.h" 37 + 38 + #define LOGF_ENABLE 39 + #include "logf.h" 40 + 41 + /* Rust C API — symbols provided by the rockbox-upnp crate via librockbox_cli.a */ 42 + extern void pcm_upnp_set_http_port(uint16_t port); 43 + extern void pcm_upnp_set_renderer_url(const char *url); 44 + extern void pcm_upnp_set_sample_rate(uint32_t rate); 45 + extern int pcm_upnp_start(void); 46 + extern int pcm_upnp_write(const uint8_t *data, size_t len); 47 + extern void pcm_upnp_stop(void); 48 + extern void pcm_upnp_close(void); 49 + 50 + static const void *pcm_data = NULL; 51 + static size_t pcm_size = 0; 52 + 53 + static pthread_mutex_t upnp_mtx; 54 + static pthread_t upnp_tid; 55 + static volatile bool upnp_running = false; 56 + static volatile bool upnp_stop = false; 57 + 58 + /* Real-time pacing — reset on every sink_dma_start(). */ 59 + static struct timespec play_start; 60 + static uint64_t play_bytes; 61 + 62 + /* Actual sample rate set by sink_set_freq(); defaults to 44100. 63 + * bytes_per_sec = sample_rate * 2 channels * 2 bytes/sample */ 64 + static unsigned long current_sample_rate = 44100; 65 + 66 + static void *upnp_thread(void *arg) 67 + { 68 + (void)arg; 69 + 70 + while (!upnp_stop) { 71 + pthread_mutex_lock(&upnp_mtx); 72 + const void *data = pcm_data; 73 + size_t size = pcm_size; 74 + pcm_data = NULL; 75 + pcm_size = 0; 76 + pthread_mutex_unlock(&upnp_mtx); 77 + 78 + if (data && size > 0) { 79 + if (pcm_upnp_write((const uint8_t *)data, size) < 0) { 80 + logf("pcm-upnp: write error"); 81 + upnp_stop = true; 82 + break; 83 + } 84 + 85 + /* Pace to real-time so the DMA loop does not drain the entire 86 + * track instantly. Same technique as pcm-squeezelite.c — use 87 + * signed int64_t for the nanosecond diff to avoid uint wrap. */ 88 + play_bytes += size; 89 + uint64_t bps = (uint64_t)current_sample_rate * 4; 90 + uint64_t expected_us = play_bytes * 1000000ULL / bps; 91 + 92 + struct timespec now; 93 + clock_gettime(CLOCK_MONOTONIC, &now); 94 + int64_t elapsed_us = 95 + (int64_t)(now.tv_sec - play_start.tv_sec) * 1000000LL + 96 + ((int64_t)now.tv_nsec - (int64_t)play_start.tv_nsec) / 1000LL; 97 + 98 + if (elapsed_us >= 0 && expected_us > (uint64_t)elapsed_us) { 99 + usleep((useconds_t)(expected_us - (uint64_t)elapsed_us)); 100 + } 101 + } 102 + 103 + if (upnp_stop) 104 + break; 105 + 106 + pthread_mutex_lock(&upnp_mtx); 107 + bool got_more = pcm_play_dma_complete_callback(PCM_DMAST_OK, 108 + &pcm_data, &pcm_size); 109 + pthread_mutex_unlock(&upnp_mtx); 110 + 111 + if (!got_more) { 112 + logf("pcm-upnp: no more PCM data"); 113 + break; 114 + } 115 + 116 + pcm_play_dma_status_callback(PCM_DMAST_STARTED); 117 + } 118 + 119 + upnp_running = false; 120 + return NULL; 121 + } 122 + 123 + static void sink_dma_init(void) 124 + { 125 + pthread_mutexattr_t attr; 126 + pthread_mutexattr_init(&attr); 127 + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); 128 + pthread_mutex_init(&upnp_mtx, &attr); 129 + pthread_mutexattr_destroy(&attr); 130 + } 131 + 132 + static void sink_dma_postinit(void) 133 + { 134 + } 135 + 136 + static void sink_set_freq(uint16_t freq) 137 + { 138 + current_sample_rate = hw_freq_sampr[freq]; 139 + pcm_upnp_set_sample_rate((uint32_t)current_sample_rate); 140 + logf("pcm-upnp: sample rate %lu Hz", current_sample_rate); 141 + } 142 + 143 + static void sink_lock(void) 144 + { 145 + pthread_mutex_lock(&upnp_mtx); 146 + } 147 + 148 + static void sink_unlock(void) 149 + { 150 + pthread_mutex_unlock(&upnp_mtx); 151 + } 152 + 153 + static void sink_dma_start(const void *addr, size_t size) 154 + { 155 + logf("pcm-upnp: start (%p, %zu)", addr, size); 156 + 157 + if (pcm_upnp_start() < 0) { 158 + logf("pcm-upnp: server start failed"); 159 + return; 160 + } 161 + 162 + clock_gettime(CLOCK_MONOTONIC, &play_start); 163 + play_bytes = 0; 164 + 165 + pthread_mutex_lock(&upnp_mtx); 166 + pcm_data = addr; 167 + pcm_size = size; 168 + pthread_mutex_unlock(&upnp_mtx); 169 + 170 + upnp_stop = false; 171 + upnp_running = true; 172 + pthread_create(&upnp_tid, NULL, upnp_thread, NULL); 173 + } 174 + 175 + static void sink_dma_stop(void) 176 + { 177 + logf("pcm-upnp: stop"); 178 + 179 + upnp_stop = true; 180 + 181 + if (upnp_running) { 182 + pthread_join(upnp_tid, NULL); 183 + upnp_running = false; 184 + } 185 + 186 + pthread_mutex_lock(&upnp_mtx); 187 + pcm_data = NULL; 188 + pcm_size = 0; 189 + pthread_mutex_unlock(&upnp_mtx); 190 + 191 + pcm_upnp_stop(); 192 + } 193 + 194 + struct pcm_sink upnp_pcm_sink = { 195 + .caps = { 196 + .samprs = hw_freq_sampr, 197 + .num_samprs = HW_NUM_FREQ, 198 + .default_freq = HW_FREQ_DEFAULT, 199 + }, 200 + .ops = { 201 + .init = sink_dma_init, 202 + .postinit = sink_dma_postinit, 203 + .set_freq = sink_set_freq, 204 + .lock = sink_lock, 205 + .unlock = sink_unlock, 206 + .play = sink_dma_start, 207 + .stop = sink_dma_stop, 208 + }, 209 + };