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.

MPD: add albumart handlers and binary output

Add albumart and readpicture handler stubs and wire them into the MPD
command list. Switch MPD handler channels from Sender<String> to
Sender<Vec<u8>> so handlers can emit binary album art chunks. Add a
read_album_art_bytes helper and the multi-step album art lookup (DB
cache, embedded tags, directory files). Handlers are temporarily
disabled and return ACK/OK until fully enabled.

+728 -258
+16
crates/library/src/album_art.rs
··· 3 3 use anyhow::Error; 4 4 use lofty::{file::TaggedFileExt, probe::Probe, tag::Accessor}; 5 5 6 + /// Read the first embedded picture from an audio file's tags and return the raw bytes + MIME type. 7 + /// Returns None if the file has no embedded art or cannot be read. 8 + pub fn read_album_art_bytes(track_path: &str) -> Option<(Vec<u8>, &'static str)> { 9 + let tagged_file = Probe::open(track_path).ok()?.read().ok()?; 10 + let tag = tagged_file 11 + .primary_tag() 12 + .filter(|t| !t.pictures().is_empty()) 13 + .or_else(|| tagged_file.tags().iter().find(|t| !t.pictures().is_empty()))?; 14 + let picture = tag.pictures().first()?; 15 + let mime = match picture.mime_type() { 16 + Some(lofty::picture::MimeType::Png) => "image/png", 17 + _ => "image/jpeg", 18 + }; 19 + Some((picture.data().to_vec(), mime)) 20 + } 21 + 6 22 pub fn extract_and_save_album_cover(track_path: &str) -> Result<Option<String>, Error> { 7 23 extract_and_save_album_cover_with_key(track_path, None) 8 24 }
+3
crates/mpd/src/consts.rs
··· 316 316 317 317 pub const COMMANDS: &str = r#"command: add 318 318 command: addid 319 + command: albumart 319 320 command: clear 320 321 command: commands 321 322 command: consume ··· 349 350 command: playlistid 350 351 command: playlistinfo 351 352 command: playlistsearch 353 + command: listplaylistinfo 352 354 command: plchanges 353 355 command: previous 356 + command: readpicture 354 357 command: random 355 358 command: rename 356 359 command: repeat
+278
crates/mpd/src/handlers/albumart.rs
··· 1 + use anyhow::Error; 2 + use rockbox_library::{album_art, repo}; 3 + use rockbox_settings::get_music_dir; 4 + use tokio::sync::mpsc::Sender; 5 + use tracing::debug; 6 + 7 + use crate::Context; 8 + 9 + const CHUNK_SIZE: usize = 8192; 10 + 11 + pub async fn handle_albumart( 12 + ctx: &mut Context, 13 + request: &str, 14 + tx: Sender<Vec<u8>>, 15 + ) -> Result<String, Error> { 16 + // TEMPORARILY DISABLED 17 + if !ctx.batch { 18 + tx.send(b"ACK [50@0] {albumart} No file exists\n".to_vec()) 19 + .await?; 20 + } 21 + return Ok("ACK [50@0] {albumart} No file exists\n".to_string()); 22 + 23 + #[allow(unreachable_code)] 24 + let (uri, offset) = parse_args(request); 25 + debug!("albumart command: uri={:?} offset={}", uri, offset); 26 + 27 + if uri.is_empty() { 28 + if !ctx.batch { 29 + tx.send(b"ACK [2@0] {albumart} missing argument\n".to_vec()) 30 + .await?; 31 + } 32 + return Ok("ACK [2@0] {albumart} missing argument\n".to_string()); 33 + } 34 + 35 + let full_path = resolve_path(&uri)?; 36 + let data = get_art_bytes(ctx, &full_path).await; 37 + 38 + match data { 39 + None => { 40 + if !ctx.batch { 41 + tx.send(b"ACK [50@0] {albumart} No file exists\n".to_vec()) 42 + .await?; 43 + } 44 + Ok("ACK [50@0] {albumart} No file exists\n".to_string()) 45 + } 46 + Some(data) => send_binary_chunk("albumart", &data, offset, None, &tx, ctx.batch).await, 47 + } 48 + } 49 + 50 + pub async fn handle_readpicture( 51 + ctx: &mut Context, 52 + request: &str, 53 + tx: Sender<Vec<u8>>, 54 + ) -> Result<String, Error> { 55 + // TEMPORARILY DISABLED 56 + if !ctx.batch { 57 + tx.send(b"OK\n".to_vec()).await?; 58 + } 59 + return Ok("OK\n".to_string()); 60 + 61 + #[allow(unreachable_code)] 62 + let (uri, offset) = parse_args(request); 63 + debug!("readpicture command: uri={:?} offset={}", uri, offset); 64 + 65 + if uri.is_empty() { 66 + if !ctx.batch { 67 + tx.send(b"ACK [2@0] {readpicture} missing argument\n".to_vec()) 68 + .await?; 69 + } 70 + return Ok("ACK [2@0] {readpicture} missing argument\n".to_string()); 71 + } 72 + 73 + let full_path = resolve_path(&uri)?; 74 + let art = get_art_bytes_with_type(ctx, &full_path).await; 75 + 76 + match art { 77 + None => { 78 + // readpicture returns empty OK when no art found 79 + if !ctx.batch { 80 + tx.send(b"OK\n".to_vec()).await?; 81 + } 82 + Ok("OK\n".to_string()) 83 + } 84 + Some((data, mime_type)) => { 85 + send_binary_chunk( 86 + "readpicture", 87 + &data, 88 + offset, 89 + Some(mime_type), 90 + &tx, 91 + ctx.batch, 92 + ) 93 + .await 94 + } 95 + } 96 + } 97 + 98 + fn parse_args(request: &str) -> (String, usize) { 99 + // Skip the command name, then parse a possibly-quoted path that may contain spaces. 100 + let rest = request 101 + .trim_start() 102 + .splitn(2, char::is_whitespace) 103 + .nth(1) 104 + .unwrap_or("") 105 + .trim_start(); 106 + 107 + let (uri, remainder) = if rest.starts_with('"') { 108 + // Quoted path: find the closing quote 109 + if let Some(end) = rest[1..].find('"') { 110 + let path = &rest[1..end + 1]; 111 + let rem = rest[end + 2..].trim_start(); 112 + (path.to_string(), rem) 113 + } else { 114 + (rest.trim_matches('"').to_string(), "") 115 + } 116 + } else { 117 + // Unquoted: split on first whitespace 118 + let mut it = rest.splitn(2, char::is_whitespace); 119 + let p = it.next().unwrap_or("").to_string(); 120 + let rem = it.next().unwrap_or("").trim_start(); 121 + (p, rem) 122 + }; 123 + 124 + let offset = remainder.trim_matches('"').parse::<usize>().unwrap_or(0); 125 + 126 + (uri, offset) 127 + } 128 + 129 + fn resolve_path(uri: &str) -> Result<String, Error> { 130 + if uri.starts_with('/') { 131 + Ok(uri.to_string()) 132 + } else { 133 + let music_dir = get_music_dir()?; 134 + Ok(format!("{}/{}", music_dir, uri)) 135 + } 136 + } 137 + 138 + fn covers_dir() -> Option<String> { 139 + let home = std::env::var("HOME").ok()?; 140 + Some(format!("{}/.config/rockbox.org/covers", home)) 141 + } 142 + 143 + async fn get_art_bytes_with_type( 144 + ctx: &Context, 145 + track_path: &str, 146 + ) -> Option<(Vec<u8>, &'static str)> { 147 + debug!("albumart lookup for: {}", track_path); 148 + 149 + // 1. Try DB-cached cover file 150 + match repo::track::find_by_path(ctx.pool.clone(), track_path).await { 151 + Ok(Some(track)) => { 152 + debug!("albumart DB hit, album_art field = {:?}", track.album_art); 153 + if let Some(filename) = track.album_art { 154 + if let Some(dir) = covers_dir() { 155 + let full_path = format!("{}/{}", dir, filename); 156 + match tokio::fs::read(&full_path).await { 157 + Ok(data) => { 158 + debug!( 159 + "albumart loaded from cache: {} ({} bytes)", 160 + full_path, 161 + data.len() 162 + ); 163 + let mime = if filename.ends_with(".png") { 164 + "image/png" 165 + } else { 166 + "image/jpeg" 167 + }; 168 + return Some((data, mime)); 169 + } 170 + Err(e) => debug!("albumart cache file unreadable {}: {}", full_path, e), 171 + } 172 + } 173 + } 174 + } 175 + Ok(None) => debug!("albumart: track not found in DB for path: {}", track_path), 176 + Err(e) => debug!("albumart DB error: {}", e), 177 + } 178 + 179 + // 2. Read embedded art directly from the audio file tags 180 + let path = track_path.to_string(); 181 + match tokio::task::spawn_blocking(move || album_art::read_album_art_bytes(&path)).await { 182 + Ok(Some((data, mime))) => { 183 + debug!( 184 + "albumart loaded from embedded tags ({} bytes, {})", 185 + data.len(), 186 + mime 187 + ); 188 + return Some((data, mime)); 189 + } 190 + Ok(None) => debug!("albumart: no embedded art in file tags"), 191 + Err(e) => debug!("albumart spawn_blocking error: {}", e), 192 + } 193 + 194 + // 3. Look for cover image files in the same directory 195 + match find_cover_file_with_type(track_path) { 196 + Some(art) => { 197 + debug!( 198 + "albumart loaded from directory cover file ({} bytes)", 199 + art.0.len() 200 + ); 201 + Some(art) 202 + } 203 + None => { 204 + debug!("albumart: no art found for {}", track_path); 205 + None 206 + } 207 + } 208 + } 209 + 210 + async fn get_art_bytes(ctx: &Context, track_path: &str) -> Option<Vec<u8>> { 211 + get_art_bytes_with_type(ctx, track_path) 212 + .await 213 + .map(|(data, _)| data) 214 + } 215 + 216 + fn find_cover_file(track_path: &str) -> Option<Vec<u8>> { 217 + find_cover_file_with_type(track_path).map(|(data, _)| data) 218 + } 219 + 220 + fn find_cover_file_with_type(track_path: &str) -> Option<(Vec<u8>, &'static str)> { 221 + let dir = std::path::Path::new(track_path).parent()?; 222 + const CANDIDATES: &[(&str, &'static str)] = &[ 223 + ("cover.jpg", "image/jpeg"), 224 + ("cover.jpeg", "image/jpeg"), 225 + ("cover.png", "image/png"), 226 + ("cover.webp", "image/webp"), 227 + ("folder.jpg", "image/jpeg"), 228 + ("folder.jpeg", "image/jpeg"), 229 + ("folder.png", "image/png"), 230 + ("album.jpg", "image/jpeg"), 231 + ("album.png", "image/png"), 232 + ("front.jpg", "image/jpeg"), 233 + ("front.jpeg", "image/jpeg"), 234 + ("front.png", "image/png"), 235 + ("artwork.jpg", "image/jpeg"), 236 + ("artwork.png", "image/png"), 237 + ("AlbumArt.jpg", "image/jpeg"), 238 + ("AlbumArt.jpeg", "image/jpeg"), 239 + ("AlbumArt.png", "image/png"), 240 + ]; 241 + for (name, mime) in CANDIDATES { 242 + let p = dir.join(name); 243 + if let Ok(data) = std::fs::read(&p) { 244 + return Some((data, mime)); 245 + } 246 + } 247 + None 248 + } 249 + 250 + async fn send_binary_chunk( 251 + _cmd: &str, 252 + data: &[u8], 253 + offset: usize, 254 + mime_type: Option<&str>, 255 + tx: &Sender<Vec<u8>>, 256 + batch: bool, 257 + ) -> Result<String, Error> { 258 + let total = data.len(); 259 + let start = offset.min(total); 260 + let end = (start + CHUNK_SIZE).min(total); 261 + let chunk = &data[start..end]; 262 + let chunk_len = chunk.len(); 263 + 264 + let mut header = format!("size: {}\n", total); 265 + if let Some(mime) = mime_type { 266 + header.push_str(&format!("type: {}\n", mime)); 267 + } 268 + header.push_str(&format!("binary: {}\n", chunk_len)); 269 + 270 + let mut response = header.into_bytes(); 271 + response.extend_from_slice(chunk); 272 + // In a command list each binary response ends with list_OK; standalone ends with OK. 273 + response.extend_from_slice(if batch { b"list_OK\n" } else { b"OK\n" }); 274 + 275 + // Always send immediately — binary data cannot be accumulated in a String buffer. 276 + tx.send(response).await?; 277 + Ok("".to_string()) 278 + }
+27 -19
crates/mpd/src/handlers/batch.rs
··· 4 4 use crate::{parse_command, setup_context, Context}; 5 5 6 6 use super::{ 7 + albumart::{handle_albumart, handle_readpicture}, 7 8 browse::{handle_listall, handle_listallinfo, handle_listfiles, handle_lsinfo}, 8 9 library::{ 9 10 handle_config, handle_count, handle_find_album, handle_find_artist, handle_find_title, 10 11 handle_findadd, handle_list_album, handle_list_artist, handle_list_date, handle_list_genre, 11 - handle_list_title, handle_listplaylists, handle_load, handle_rename, handle_rescan, 12 - handle_rm, handle_save, handle_search, handle_searchadd, handle_stats, handle_tagtypes, 13 - handle_tagtypes_clear, handle_tagtypes_enable, 12 + handle_list_title, handle_listplaylistinfo, handle_listplaylists, handle_load, 13 + handle_rename, handle_rescan, handle_rm, handle_save, handle_search, handle_searchadd, 14 + handle_stats, handle_tagtypes, handle_tagtypes_clear, handle_tagtypes_enable, 14 15 }, 15 16 playback::{ 16 17 handle_consume, handle_currentsong, handle_disableoutput, handle_enableoutput, ··· 29 30 pub async fn handle_command_list_begin( 30 31 ctx: &mut Context, 31 32 request: &str, 32 - tx: Sender<String>, 33 + tx: Sender<Vec<u8>>, 33 34 ) -> Result<String, Error> { 34 35 let mut ctx = setup_context(true, Some(ctx.clone())).await?; 35 36 ··· 38 39 .filter(|x| !vec!["command_list_begin", "command_list_end", ""].contains(x)) 39 40 .collect(); 40 41 41 - let mut response = String::new(); 42 42 for request in commands { 43 43 let command = parse_command(&request)?; 44 - response.push_str(&match_command(&command, &mut ctx, request, tx.clone()).await?); 44 + let response = match_command(&command, &mut ctx, request, tx.clone()).await?; 45 + // Binary commands already sent via tx; send non-empty text responses now. 46 + if !response.is_empty() { 47 + tx.send(response.into_bytes()).await?; 48 + } 45 49 } 46 50 47 - tx.send(response.clone()).await?; 48 - 49 - Ok(response) 51 + tx.send(b"OK\n".to_vec()).await?; 52 + Ok("OK\n".to_string()) 50 53 } 51 54 52 55 pub async fn handle_command_list_ok_begin( 53 56 ctx: &mut Context, 54 57 request: &str, 55 - tx: Sender<String>, 58 + tx: Sender<Vec<u8>>, 56 59 ) -> Result<String, Error> { 57 60 let mut ctx = setup_context(true, Some(ctx.clone())).await?; 58 61 ··· 61 64 .filter(|x| !vec!["command_list_ok_begin", "command_list_end", ""].contains(x)) 62 65 .collect(); 63 66 64 - let mut response = String::new(); 65 - 66 67 for request in commands { 67 68 let command = parse_command(&request)?; 68 - response.push_str(&match_command(&command, &mut ctx, request, tx.clone()).await?); 69 + let response = match_command(&command, &mut ctx, request, tx.clone()).await?; 70 + // Binary commands (albumart/readpicture) already sent via tx with list_OK; 71 + // text commands return their response string here. 72 + if !response.is_empty() { 73 + let response = response.replace("OK\n", "list_OK\n"); 74 + tx.send(response.into_bytes()).await?; 75 + } 69 76 } 70 77 71 - let mut response = response.replace("OK\n", "list_OK\n"); 72 - response.push_str("OK\n"); 73 - tx.send(response.clone()).await?; 74 - Ok(response) 78 + tx.send(b"OK\n".to_vec()).await?; 79 + Ok("OK\n".to_string()) 75 80 } 76 81 77 82 pub async fn match_command( 78 83 command: &str, 79 84 ctx: &mut Context, 80 85 request: &str, 81 - tx: Sender<String>, 86 + tx: Sender<Vec<u8>>, 82 87 ) -> Result<String, Error> { 83 88 match command { 84 89 "play" => handle_play(ctx, request, tx.clone()).await, ··· 138 143 "listall" => handle_listall(ctx, request, tx.clone()).await, 139 144 "listallinfo" => handle_listallinfo(ctx, request, tx.clone()).await, 140 145 "listfiles" => handle_listfiles(ctx, request, tx.clone()).await, 146 + "albumart" => handle_albumart(ctx, request, tx.clone()).await, 147 + "readpicture" => handle_readpicture(ctx, request, tx.clone()).await, 141 148 "listplaylists" => handle_listplaylists(ctx, request, tx.clone()).await, 149 + "listplaylistinfo" => handle_listplaylistinfo(ctx, request, tx.clone()).await, 142 150 "load" => handle_load(ctx, request, tx.clone()).await, 143 151 "save" => handle_save(ctx, request, tx.clone()).await, 144 152 "rm" => handle_rm(ctx, request, tx.clone()).await, ··· 151 159 "urlhandlers" => handle_urlhandlers(ctx, request, tx.clone()).await, 152 160 _ => { 153 161 if !ctx.batch { 154 - tx.send("ACK [5@0] {unhandled} unknown command\n".to_string()) 162 + tx.send(b"ACK [5@0] {unhandled} unknown command\n".to_vec()) 155 163 .await?; 156 164 } 157 165 Ok("ACK [5@0] {unhandled} unknown command\n".to_string())
+11 -11
crates/mpd/src/handlers/browse.rs
··· 10 10 pub async fn handle_lsinfo( 11 11 ctx: &mut Context, 12 12 request: &str, 13 - tx: Sender<String>, 13 + tx: Sender<Vec<u8>>, 14 14 ) -> Result<String, Error> { 15 15 repo::track::all(ctx.pool.clone()).await?; 16 16 ··· 30 30 // verify if path is a file or directory or doesn't exist 31 31 if fs::metadata(path).is_err() { 32 32 if !ctx.batch { 33 - tx.send("ACK [50@0] {lsinfo} No such file or directory\n".to_string()) 33 + tx.send(b"ACK [50@0] {lsinfo} No such file or directory\n".to_vec()) 34 34 .await?; 35 35 } 36 36 return Ok("ACK [50@0] {lsinfo} No such file or directory\n".to_string()); ··· 49 49 } 50 50 51 51 if !ctx.batch { 52 - tx.send(response.clone()).await?; 52 + tx.send(response.clone().into_bytes()).await?; 53 53 } 54 54 55 55 Ok(response) ··· 58 58 pub async fn handle_listall( 59 59 ctx: &mut Context, 60 60 _request: &str, 61 - tx: Sender<String>, 61 + tx: Sender<Vec<u8>>, 62 62 ) -> Result<String, Error> { 63 63 let mut response: String = "".to_string(); 64 64 let music_dir = get_music_dir()?; ··· 70 70 } 71 71 72 72 if !ctx.batch { 73 - tx.send(response.clone()).await?; 73 + tx.send(response.clone().into_bytes()).await?; 74 74 } 75 75 76 76 Ok(response) ··· 79 79 pub async fn handle_listallinfo( 80 80 ctx: &mut Context, 81 81 _request: &str, 82 - tx: Sender<String>, 82 + tx: Sender<Vec<u8>>, 83 83 ) -> Result<String, Error> { 84 84 repo::track::all(ctx.pool.clone()).await?; 85 85 let music_dir = get_music_dir()?; ··· 88 88 // verify if path is a file or directory or doesn't exist 89 89 if fs::metadata(&path).is_err() { 90 90 if !ctx.batch { 91 - tx.send("ACK [50@0] {lsinfo} No such file or directory\n".to_string()) 91 + tx.send(b"ACK [50@0] {lsinfo} No such file or directory\n".to_vec()) 92 92 .await?; 93 93 } 94 94 return Ok("ACK [50@0] {lsinfo} No such file or directory\n".to_string()); ··· 101 101 response.push_str("OK\n"); 102 102 103 103 if !ctx.batch { 104 - tx.send(response.clone()).await?; 104 + tx.send(response.clone().into_bytes()).await?; 105 105 } 106 106 107 107 Ok(response) ··· 110 110 pub async fn handle_listfiles( 111 111 ctx: &mut Context, 112 112 request: &str, 113 - tx: Sender<String>, 113 + tx: Sender<Vec<u8>>, 114 114 ) -> Result<String, Error> { 115 115 let request = request.trim(); 116 116 let re = Regex::new(r#"^([\w-]+)(?:\s+"([^"]*)")?$"#).unwrap(); ··· 128 128 // verify if path is a file or directory or doesn't exist 129 129 if fs::metadata(&path).is_err() { 130 130 if !ctx.batch { 131 - tx.send("ACK [50@0] {lsinfo} No such file or directory\n".to_string()) 131 + tx.send(b"ACK [50@0] {lsinfo} No such file or directory\n".to_vec()) 132 132 .await?; 133 133 } 134 134 return Ok("ACK [50@0] {lsinfo} No such file or directory\n".to_string()); ··· 147 147 } 148 148 149 149 if !ctx.batch { 150 - tx.send(response.clone()).await?; 150 + tx.send(response.clone().into_bytes()).await?; 151 151 } 152 152 153 153 Ok(response)
+217 -91
crates/mpd/src/handlers/library.rs
··· 6 6 use rockbox_library::{entity::track::Track, repo}; 7 7 use rockbox_rpc::api::rockbox::v1alpha1::{ 8 8 CreateSavedPlaylistRequest, DeleteSavedPlaylistRequest, GetAlbumsRequest, GetArtistsRequest, 9 - GetGlobalSettingsRequest, GetSavedPlaylistsRequest, GetTracksRequest, InsertTracksRequest, 10 - PlaySavedPlaylistRequest, ScanLibraryRequest, SearchRequest, UpdateSavedPlaylistRequest, 9 + GetGlobalSettingsRequest, GetSavedPlaylistTracksRequest, GetSavedPlaylistsRequest, 10 + GetSmartPlaylistTracksRequest, GetSmartPlaylistsRequest, GetTracksRequest, InsertTracksRequest, 11 + PlaySavedPlaylistRequest, PlaySmartPlaylistRequest, ScanLibraryRequest, SearchRequest, 12 + UpdateSavedPlaylistRequest, 11 13 }; 12 14 use rockbox_settings::get_music_dir; 13 15 use tokio::sync::mpsc::Sender; ··· 19 21 pub async fn handle_list_album( 20 22 ctx: &mut Context, 21 23 request: &str, 22 - tx: Sender<String>, 24 + tx: Sender<Vec<u8>>, 23 25 ) -> Result<String, Error> { 24 26 let query = request.replace("list album", "").replace("list Album", ""); 25 27 let query = query.trim(); ··· 46 48 let response = format!("{}OK\n", response); 47 49 48 50 if !ctx.batch { 49 - tx.send(response.clone()).await?; 51 + tx.send(response.clone().into_bytes()).await?; 50 52 } 51 53 52 54 Ok(response) ··· 55 57 pub async fn handle_list_artist( 56 58 ctx: &mut Context, 57 59 request: &str, 58 - tx: Sender<String>, 60 + tx: Sender<Vec<u8>>, 59 61 ) -> Result<String, Error> { 60 62 let tag = if request.contains("albumartist") || request.contains("AlbumArtist") { 61 63 "AlbumArtist" ··· 71 73 .collect::<String>(); 72 74 let response = format!("{}OK\n", response); 73 75 if !ctx.batch { 74 - tx.send(response.clone()).await?; 76 + tx.send(response.clone().into_bytes()).await?; 75 77 } 76 78 Ok(response) 77 79 } ··· 79 81 pub async fn handle_list_genre( 80 82 ctx: &mut Context, 81 83 _request: &str, 82 - tx: Sender<String>, 84 + tx: Sender<Vec<u8>>, 83 85 ) -> Result<String, Error> { 84 86 let tracks = repo::track::all(ctx.pool.clone()).await?; 85 87 let mut genres: Vec<String> = tracks ··· 96 98 .collect::<String>(); 97 99 let response = format!("{}OK\n", response); 98 100 if !ctx.batch { 99 - tx.send(response.clone()).await?; 101 + tx.send(response.clone().into_bytes()).await?; 100 102 } 101 103 Ok(response) 102 104 } ··· 104 106 pub async fn handle_list_date( 105 107 ctx: &mut Context, 106 108 _request: &str, 107 - tx: Sender<String>, 109 + tx: Sender<Vec<u8>>, 108 110 ) -> Result<String, Error> { 109 111 let tracks = repo::track::all(ctx.pool.clone()).await?; 110 112 let mut dates: Vec<String> = tracks ··· 121 123 .collect::<String>(); 122 124 let response = format!("{}OK\n", response); 123 125 if !ctx.batch { 124 - tx.send(response.clone()).await?; 126 + tx.send(response.clone().into_bytes()).await?; 125 127 } 126 128 Ok(response) 127 129 } ··· 129 131 pub async fn handle_list_title( 130 132 ctx: &mut Context, 131 133 _request: &str, 132 - tx: Sender<String>, 134 + tx: Sender<Vec<u8>>, 133 135 ) -> Result<String, Error> { 134 136 let response = ctx.library.get_tracks(GetTracksRequest {}).await?; 135 137 let response = response.into_inner(); ··· 140 142 .collect::<String>(); 141 143 let response = format!("{}OK\n", response); 142 144 if !ctx.batch { 143 - tx.send(response.clone()).await?; 145 + tx.send(response.clone().into_bytes()).await?; 144 146 } 145 147 Ok(response) 146 148 } ··· 148 150 pub async fn handle_search( 149 151 ctx: &mut Context, 150 152 request: &str, 151 - tx: Sender<String>, 153 + tx: Sender<Vec<u8>>, 152 154 ) -> Result<String, Error> { 153 155 let mut term = request 154 156 .trim_matches('"') ··· 188 190 .collect::<String>(); 189 191 let response = format!("{}OK\n", response); 190 192 if !ctx.batch { 191 - tx.send(response.clone()).await?; 193 + tx.send(response.clone().into_bytes()).await?; 192 194 } 193 195 return Ok(response); 194 196 } ··· 214 216 .collect::<String>(); 215 217 let response = format!("{}OK\n", response); 216 218 if !ctx.batch { 217 - tx.send(response.clone()).await?; 219 + tx.send(response.clone().into_bytes()).await?; 218 220 } 219 221 Ok(response) 220 222 } ··· 222 224 pub async fn handle_rescan( 223 225 ctx: &mut Context, 224 226 request: &str, 225 - tx: Sender<String>, 227 + tx: Sender<Vec<u8>>, 226 228 ) -> Result<String, Error> { 227 229 let response = ctx 228 230 .settings ··· 246 248 .await?; 247 249 248 250 if !ctx.batch { 249 - tx.send("OK\n".to_string()).await?; 251 + tx.send(b"OK\n".to_vec()).await?; 250 252 } 251 253 Ok("OK\n".to_string()) 252 254 } ··· 254 256 pub async fn handle_config( 255 257 ctx: &mut Context, 256 258 _request: &str, 257 - tx: Sender<String>, 259 + tx: Sender<Vec<u8>>, 258 260 ) -> Result<String, Error> { 259 261 let response = "ACK [4@0] {config} Command only permitted to local clients"; 260 262 if !ctx.batch { 261 - tx.send(response.to_string()).await?; 263 + tx.send(response.as_bytes().to_vec()).await?; 262 264 } 263 265 264 266 Ok(response.to_string()) ··· 267 269 pub async fn handle_tagtypes( 268 270 ctx: &mut Context, 269 271 _request: &str, 270 - tx: Sender<String>, 272 + tx: Sender<Vec<u8>>, 271 273 ) -> Result<String, Error> { 272 274 let response = format!( 273 275 "Tagtype: Artist\nTagtype: Album\nTagtype: Title\nTagtype: Track\nTagtype: Date\nOK\n" 274 276 ); 275 277 276 278 if !ctx.batch { 277 - tx.send(response.clone()).await?; 279 + tx.send(response.clone().into_bytes()).await?; 278 280 } 279 281 280 282 Ok(response) ··· 283 285 pub async fn handle_tagtypes_clear( 284 286 ctx: &mut Context, 285 287 _request: &str, 286 - tx: Sender<String>, 288 + tx: Sender<Vec<u8>>, 287 289 ) -> Result<String, Error> { 288 290 let response = format!("OK\n"); 289 291 290 292 if !ctx.batch { 291 - tx.send(response.clone()).await?; 293 + tx.send(response.clone().into_bytes()).await?; 292 294 } 293 295 294 296 Ok(response) ··· 297 299 pub async fn handle_tagtypes_enable( 298 300 ctx: &mut Context, 299 301 _request: &str, 300 - tx: Sender<String>, 302 + tx: Sender<Vec<u8>>, 301 303 ) -> Result<String, Error> { 302 304 let response = format!("OK\n"); 303 305 304 306 if !ctx.batch { 305 - tx.send(response.to_string()).await?; 307 + tx.send(response.into_bytes()).await?; 306 308 } 307 309 308 310 Ok("".to_string()) ··· 311 313 pub async fn handle_stats( 312 314 ctx: &mut Context, 313 315 _request: &str, 314 - tx: Sender<String>, 316 + tx: Sender<Vec<u8>>, 315 317 ) -> Result<String, Error> { 316 318 let response = ctx.library.get_albums(GetAlbumsRequest {}).await?; 317 319 let response = response.into_inner(); ··· 328 330 ); 329 331 330 332 if !ctx.batch { 331 - tx.send(response.clone()).await?; 333 + tx.send(response.clone().into_bytes()).await?; 332 334 } 333 335 334 336 Ok(response) ··· 337 339 pub async fn handle_find_artist( 338 340 ctx: &mut Context, 339 341 request: &str, 340 - tx: Sender<String>, 342 + tx: Sender<Vec<u8>>, 341 343 ) -> Result<String, Error> { 342 344 let re = Regex::new(r#"(?i)(artist|album|date)\s+\"([^\"]+)\""#).unwrap(); 343 345 let mut fields = HashMap::new(); ··· 367 369 build_file_metadata(tracks, &mut response).await?; 368 370 369 371 if !ctx.batch { 370 - tx.send(response.clone()).await?; 372 + tx.send(response.clone().into_bytes()).await?; 371 373 } 372 374 373 375 Ok(response) ··· 376 378 pub async fn handle_find_album( 377 379 ctx: &mut Context, 378 380 request: &str, 379 - tx: Sender<String>, 381 + tx: Sender<Vec<u8>>, 380 382 ) -> Result<String, Error> { 381 383 let arg = request.replace("find album ", "").replace("find Album", ""); 382 384 let arg = arg.trim(); ··· 388 390 build_file_metadata(tracks, &mut response).await?; 389 391 390 392 if !ctx.batch { 391 - tx.send(response.clone()).await?; 393 + tx.send(response.clone().into_bytes()).await?; 392 394 } 393 395 394 396 Ok(response) ··· 397 399 pub async fn handle_find_title( 398 400 ctx: &mut Context, 399 401 request: &str, 400 - tx: Sender<String>, 402 + tx: Sender<Vec<u8>>, 401 403 ) -> Result<String, Error> { 402 404 let arg = request 403 405 .replace("find title ", "") ··· 411 413 build_file_metadata(tracks, &mut response).await?; 412 414 413 415 if !ctx.batch { 414 - tx.send(response.clone()).await?; 416 + tx.send(response.clone().into_bytes()).await?; 415 417 } 416 418 417 419 Ok(response) ··· 420 422 pub async fn handle_find( 421 423 ctx: &mut Context, 422 424 request: &str, 423 - tx: Sender<String>, 425 + tx: Sender<Vec<u8>>, 424 426 ) -> Result<String, Error> { 425 427 let arg = request.replace("find ", ""); 426 428 let arg = arg.trim(); ··· 436 438 Ok("".to_string()) 437 439 } 438 440 439 - async fn execute(ctx: &mut Context, expr: &Expression, tx: Sender<String>) -> Result<(), Error> { 441 + async fn execute(ctx: &mut Context, expr: &Expression, tx: Sender<Vec<u8>>) -> Result<(), Error> { 440 442 let mut columns = HashMap::new(); 441 443 columns.insert("Title".to_string(), "title".to_string()); 442 444 columns.insert("Artist".to_string(), "artist".to_string()); ··· 462 464 let mut response: String = "".to_string(); 463 465 464 466 build_file_metadata(tracks, &mut response).await?; 465 - tx.send(response).await?; 467 + tx.send(response.into_bytes()).await?; 466 468 Ok(()) 467 469 } 468 470 ··· 505 507 pub async fn handle_count( 506 508 ctx: &mut Context, 507 509 request: &str, 508 - tx: Sender<String>, 510 + tx: Sender<Vec<u8>>, 509 511 ) -> Result<String, Error> { 510 512 let arg = request 511 513 .splitn(2, ' ') ··· 528 530 let response = format!("songs: {}\nplaytime: {}\nOK\n", tracks.len(), playtime); 529 531 530 532 if !ctx.batch { 531 - tx.send(response.clone()).await?; 533 + tx.send(response.clone().into_bytes()).await?; 532 534 } 533 535 Ok(response) 534 536 } ··· 536 538 pub async fn handle_findadd( 537 539 ctx: &mut Context, 538 540 request: &str, 539 - tx: Sender<String>, 541 + tx: Sender<Vec<u8>>, 540 542 ) -> Result<String, Error> { 541 543 let arg = request 542 544 .splitn(2, ' ') ··· 569 571 } 570 572 571 573 if !ctx.batch { 572 - tx.send("OK\n".to_string()).await?; 574 + tx.send(b"OK\n".to_vec()).await?; 573 575 } 574 576 Ok("OK\n".to_string()) 575 577 } ··· 577 579 pub async fn handle_searchadd( 578 580 ctx: &mut Context, 579 581 request: &str, 580 - tx: Sender<String>, 582 + tx: Sender<Vec<u8>>, 581 583 ) -> Result<String, Error> { 582 584 let arg = request 583 585 .splitn(2, ' ') ··· 610 612 } 611 613 612 614 if !ctx.batch { 613 - tx.send("OK\n".to_string()).await?; 615 + tx.send(b"OK\n".to_vec()).await?; 614 616 } 615 617 Ok("OK\n".to_string()) 616 618 } ··· 618 620 pub async fn handle_listplaylists( 619 621 ctx: &mut Context, 620 622 _request: &str, 621 - tx: Sender<String>, 623 + tx: Sender<Vec<u8>>, 622 624 ) -> Result<String, Error> { 623 - let response = ctx 625 + let saved = ctx 624 626 .saved_playlist 625 627 .get_saved_playlists(GetSavedPlaylistsRequest { folder_id: None }) 626 628 .await? 627 629 .into_inner(); 628 630 629 - let response = response 630 - .playlists 631 - .iter() 632 - .map(|p| { 633 - let last_modified = chrono::DateTime::from_timestamp(p.updated_at, 0) 634 - .unwrap_or_default() 635 - .format("%Y-%m-%dT%H:%M:%SZ") 636 - .to_string(); 637 - format!("playlist: {}\nLast-Modified: {}\n", p.name, last_modified) 638 - }) 639 - .collect::<String>(); 640 - let response = format!("{}OK\n", response); 631 + let smart = ctx 632 + .smart_playlist 633 + .get_smart_playlists(GetSmartPlaylistsRequest {}) 634 + .await? 635 + .into_inner(); 636 + 637 + let mut response = String::new(); 638 + 639 + for p in &saved.playlists { 640 + let last_modified = chrono::DateTime::from_timestamp(p.updated_at, 0) 641 + .unwrap_or_default() 642 + .format("%Y-%m-%dT%H:%M:%SZ") 643 + .to_string(); 644 + response.push_str(&format!( 645 + "playlist: {}\nLast-Modified: {}\n", 646 + p.name, last_modified 647 + )); 648 + } 649 + for p in &smart.playlists { 650 + let last_modified = chrono::DateTime::from_timestamp(p.updated_at, 0) 651 + .unwrap_or_default() 652 + .format("%Y-%m-%dT%H:%M:%SZ") 653 + .to_string(); 654 + response.push_str(&format!( 655 + "playlist: {}\nLast-Modified: {}\n", 656 + p.name, last_modified 657 + )); 658 + } 659 + response.push_str("OK\n"); 641 660 642 661 if !ctx.batch { 643 - tx.send(response.clone()).await?; 662 + tx.send(response.clone().into_bytes()).await?; 644 663 } 645 664 Ok(response) 646 665 } ··· 648 667 pub async fn handle_load( 649 668 ctx: &mut Context, 650 669 request: &str, 651 - tx: Sender<String>, 670 + tx: Sender<Vec<u8>>, 652 671 ) -> Result<String, Error> { 653 672 let name = request 654 673 .splitn(2, ' ') ··· 660 679 661 680 if name.is_empty() { 662 681 if !ctx.batch { 663 - tx.send("ACK [2@0] {load} missing argument\n".to_string()) 682 + tx.send(b"ACK [2@0] {load} missing argument\n".to_vec()) 664 683 .await?; 665 684 } 666 685 return Ok("ACK [2@0] {load} missing argument\n".to_string()); 667 686 } 668 687 669 - let playlists = ctx 688 + let saved = ctx 670 689 .saved_playlist 671 690 .get_saved_playlists(GetSavedPlaylistsRequest { folder_id: None }) 672 691 .await? 673 692 .into_inner(); 674 693 675 - let playlist = playlists.playlists.into_iter().find(|p| p.name == name); 694 + if let Some(p) = saved.playlists.into_iter().find(|p| p.name == name) { 695 + ctx.saved_playlist 696 + .play_saved_playlist(PlaySavedPlaylistRequest { 697 + playlist_id: p.id.clone(), 698 + }) 699 + .await?; 700 + match ctx.event_sender.send(Subsystem::Playlist) { 701 + Ok(_) => {} 702 + Err(_) => {} 703 + } 704 + if !ctx.batch { 705 + tx.send(b"OK\n".to_vec()).await?; 706 + } 707 + return Ok("OK\n".to_string()); 708 + } 676 709 677 - match playlist { 678 - Some(p) => { 679 - ctx.saved_playlist 680 - .play_saved_playlist(PlaySavedPlaylistRequest { 681 - playlist_id: p.id.clone(), 682 - }) 683 - .await?; 684 - match ctx.event_sender.send(Subsystem::Playlist) { 685 - Ok(_) => {} 686 - Err(_) => {} 687 - } 710 + // Try smart playlists 711 + let smart = ctx 712 + .smart_playlist 713 + .get_smart_playlists(GetSmartPlaylistsRequest {}) 714 + .await? 715 + .into_inner(); 716 + 717 + if let Some(p) = smart.playlists.into_iter().find(|p| p.name == name) { 718 + ctx.smart_playlist 719 + .play_smart_playlist(PlaySmartPlaylistRequest { id: p.id.clone() }) 720 + .await?; 721 + match ctx.event_sender.send(Subsystem::Playlist) { 722 + Ok(_) => {} 723 + Err(_) => {} 688 724 } 689 - None => { 690 - let msg = format!("ACK [50@0] {{load}} No such playlist\n"); 691 - if !ctx.batch { 692 - tx.send(msg.clone()).await?; 693 - } 694 - return Ok(msg); 725 + if !ctx.batch { 726 + tx.send(b"OK\n".to_vec()).await?; 695 727 } 728 + return Ok("OK\n".to_string()); 696 729 } 697 730 731 + let msg = "ACK [50@0] {load} No such playlist\n".to_string(); 698 732 if !ctx.batch { 699 - tx.send("OK\n".to_string()).await?; 733 + tx.send(msg.clone().into_bytes()).await?; 700 734 } 701 - Ok("OK\n".to_string()) 735 + Ok(msg) 702 736 } 703 737 704 738 pub async fn handle_save( 705 739 ctx: &mut Context, 706 740 request: &str, 707 - tx: Sender<String>, 741 + tx: Sender<Vec<u8>>, 708 742 ) -> Result<String, Error> { 709 743 let name = request 710 744 .splitn(2, ' ') ··· 716 750 717 751 if name.is_empty() { 718 752 if !ctx.batch { 719 - tx.send("ACK [2@0] {save} missing argument\n".to_string()) 753 + tx.send(b"ACK [2@0] {save} missing argument\n".to_vec()) 720 754 .await?; 721 755 } 722 756 return Ok("ACK [2@0] {save} missing argument\n".to_string()); ··· 754 788 } 755 789 756 790 if !ctx.batch { 757 - tx.send("OK\n".to_string()).await?; 791 + tx.send(b"OK\n".to_vec()).await?; 758 792 } 759 793 Ok("OK\n".to_string()) 760 794 } ··· 762 796 pub async fn handle_rm( 763 797 ctx: &mut Context, 764 798 request: &str, 765 - tx: Sender<String>, 799 + tx: Sender<Vec<u8>>, 766 800 ) -> Result<String, Error> { 767 801 let name = request 768 802 .splitn(2, ' ') ··· 774 808 775 809 if name.is_empty() { 776 810 if !ctx.batch { 777 - tx.send("ACK [2@0] {rm} missing argument\n".to_string()) 811 + tx.send(b"ACK [2@0] {rm} missing argument\n".to_vec()) 778 812 .await?; 779 813 } 780 814 return Ok("ACK [2@0] {rm} missing argument\n".to_string()); ··· 801 835 None => { 802 836 let msg = format!("ACK [50@0] {{rm}} No such playlist\n"); 803 837 if !ctx.batch { 804 - tx.send(msg.clone()).await?; 838 + tx.send(msg.clone().into_bytes()).await?; 805 839 } 806 840 return Ok(msg); 807 841 } 808 842 } 809 843 810 844 if !ctx.batch { 811 - tx.send("OK\n".to_string()).await?; 845 + tx.send(b"OK\n".to_vec()).await?; 812 846 } 813 847 Ok("OK\n".to_string()) 814 848 } ··· 816 850 pub async fn handle_rename( 817 851 ctx: &mut Context, 818 852 request: &str, 819 - tx: Sender<String>, 853 + tx: Sender<Vec<u8>>, 820 854 ) -> Result<String, Error> { 821 855 let args: Vec<&str> = request.splitn(3, '"').collect(); 822 856 let old_name = args.get(1).map(|s| s.trim()).unwrap_or("").to_string(); ··· 827 861 let parts: Vec<&str> = request.split_whitespace().collect(); 828 862 if parts.len() < 3 { 829 863 if !ctx.batch { 830 - tx.send("ACK [2@0] {rename} missing arguments\n".to_string()) 864 + tx.send(b"ACK [2@0] {rename} missing arguments\n".to_vec()) 831 865 .await?; 832 866 } 833 867 return Ok("ACK [2@0] {rename} missing arguments\n".to_string()); ··· 861 895 None => { 862 896 let msg = format!("ACK [50@0] {{rename}} No such playlist\n"); 863 897 if !ctx.batch { 864 - tx.send(msg.clone()).await?; 898 + tx.send(msg.clone().into_bytes()).await?; 865 899 } 866 900 return Ok(msg); 867 901 } 868 902 } 869 903 870 904 if !ctx.batch { 871 - tx.send("OK\n".to_string()).await?; 905 + tx.send(b"OK\n".to_vec()).await?; 872 906 } 873 907 Ok("OK\n".to_string()) 908 + } 909 + 910 + pub async fn handle_listplaylistinfo( 911 + ctx: &mut Context, 912 + request: &str, 913 + tx: Sender<Vec<u8>>, 914 + ) -> Result<String, Error> { 915 + let name = request 916 + .splitn(2, ' ') 917 + .nth(1) 918 + .unwrap_or("") 919 + .trim() 920 + .trim_matches('"') 921 + .to_string(); 922 + 923 + if name.is_empty() { 924 + let msg = "ACK [2@0] {listplaylistinfo} missing argument\n".to_string(); 925 + if !ctx.batch { 926 + tx.send(msg.clone().into_bytes()).await?; 927 + } 928 + return Ok(msg); 929 + } 930 + 931 + // Check saved playlists first 932 + let saved = ctx 933 + .saved_playlist 934 + .get_saved_playlists(GetSavedPlaylistsRequest { folder_id: None }) 935 + .await? 936 + .into_inner(); 937 + 938 + if let Some(p) = saved.playlists.into_iter().find(|p| p.name == name) { 939 + let track_ids = ctx 940 + .saved_playlist 941 + .get_saved_playlist_tracks(GetSavedPlaylistTracksRequest { 942 + playlist_id: p.id.clone(), 943 + }) 944 + .await? 945 + .into_inner() 946 + .track_ids; 947 + 948 + let mut tracks = Vec::new(); 949 + for id in &track_ids { 950 + if let Ok(Some(t)) = repo::track::find(ctx.pool.clone(), id).await { 951 + tracks.push(t); 952 + } 953 + } 954 + 955 + let mut response = String::new(); 956 + build_file_metadata(tracks, &mut response).await?; 957 + 958 + if !ctx.batch { 959 + tx.send(response.clone().into_bytes()).await?; 960 + } 961 + return Ok(response); 962 + } 963 + 964 + // Check smart playlists 965 + let smart = ctx 966 + .smart_playlist 967 + .get_smart_playlists(GetSmartPlaylistsRequest {}) 968 + .await? 969 + .into_inner(); 970 + 971 + if let Some(p) = smart.playlists.into_iter().find(|p| p.name == name) { 972 + let track_ids = ctx 973 + .smart_playlist 974 + .get_smart_playlist_tracks(GetSmartPlaylistTracksRequest { id: p.id.clone() }) 975 + .await? 976 + .into_inner() 977 + .track_ids; 978 + 979 + let mut tracks = Vec::new(); 980 + for id in &track_ids { 981 + if let Ok(Some(t)) = repo::track::find(ctx.pool.clone(), id).await { 982 + tracks.push(t); 983 + } 984 + } 985 + 986 + let mut response = String::new(); 987 + build_file_metadata(tracks, &mut response).await?; 988 + 989 + if !ctx.batch { 990 + tx.send(response.clone().into_bytes()).await?; 991 + } 992 + return Ok(response); 993 + } 994 + 995 + let msg = "ACK [50@0] {listplaylistinfo} No such playlist\n".to_string(); 996 + if !ctx.batch { 997 + tx.send(msg.clone().into_bytes()).await?; 998 + } 999 + Ok(msg) 874 1000 } 875 1001 876 1002 async fn build_file_metadata(tracks: Vec<Track>, response: &mut String) -> Result<(), Error> {
+1
crates/mpd/src/handlers/mod.rs
··· 1 + pub mod albumart; 1 2 pub mod batch; 2 3 pub mod browse; 3 4 pub mod library;
+61 -61
crates/mpd/src/handlers/playback.rs
··· 12 12 pub async fn handle_play( 13 13 ctx: &mut Context, 14 14 request: &str, 15 - tx: Sender<String>, 15 + tx: Sender<Vec<u8>>, 16 16 ) -> Result<String, Error> { 17 17 let arg = request.split_whitespace().nth(1); 18 18 ··· 37 37 } 38 38 39 39 if !ctx.batch { 40 - tx.send("OK\n".to_string()).await?; 40 + tx.send(b"OK\n".to_vec()).await?; 41 41 } 42 42 43 43 Ok("OK\n".to_string()) ··· 46 46 pub async fn handle_stop( 47 47 ctx: &mut Context, 48 48 _request: &str, 49 - tx: Sender<String>, 49 + tx: Sender<Vec<u8>>, 50 50 ) -> Result<String, Error> { 51 51 ctx.playback.hard_stop(HardStopRequest {}).await?; 52 52 match ctx.event_sender.send(Subsystem::Player) { ··· 54 54 Err(_) => {} 55 55 } 56 56 if !ctx.batch { 57 - tx.send("OK\n".to_string()).await?; 57 + tx.send(b"OK\n".to_vec()).await?; 58 58 } 59 59 Ok("OK\n".to_string()) 60 60 } ··· 62 62 pub async fn handle_pause( 63 63 ctx: &mut Context, 64 64 request: &str, 65 - tx: Sender<String>, 65 + tx: Sender<Vec<u8>>, 66 66 ) -> Result<String, Error> { 67 67 let arg = request.split_whitespace().nth(1); 68 68 let playback_status = ctx.playback_status.lock().await; ··· 89 89 } 90 90 _ => { 91 91 if !ctx.batch { 92 - tx.send("ACK [2@0] {pause} no song is playing\n".to_string()) 92 + tx.send(b"ACK [2@0] {pause} no song is playing\n".to_vec()) 93 93 .await?; 94 94 } 95 95 return Ok("ACK [2@0] {pause} no song is playing\n".to_string()); ··· 103 103 } 104 104 105 105 if !ctx.batch { 106 - tx.send("OK\n".to_string()).await?; 106 + tx.send(b"OK\n".to_vec()).await?; 107 107 } 108 108 109 109 Ok("OK\n".to_string()) ··· 112 112 pub async fn handle_toggle( 113 113 ctx: &mut Context, 114 114 _request: &str, 115 - tx: Sender<String>, 115 + tx: Sender<Vec<u8>>, 116 116 ) -> Result<String, Error> { 117 117 let playback_status = { 118 118 let guard = ctx.playback_status.lock().await; ··· 128 128 } 129 129 _ => { 130 130 if !ctx.batch { 131 - tx.send("ACK [2@0] {toggle} no song is playing\n".to_string()) 131 + tx.send(b"ACK [2@0] {toggle} no song is playing\n".to_vec()) 132 132 .await?; 133 133 } 134 134 return Ok("ACK [2@0] {toggle} no song is playing\n".to_string()); 135 135 } 136 136 } 137 137 if !ctx.batch { 138 - tx.send("OK\n".to_string()).await?; 138 + tx.send(b"OK\n".to_vec()).await?; 139 139 } 140 140 Ok("OK\n".to_string()) 141 141 } ··· 143 143 pub async fn handle_status( 144 144 ctx: &mut Context, 145 145 _request: &str, 146 - tx: Sender<String>, 146 + tx: Sender<Vec<u8>>, 147 147 ) -> Result<String, Error> { 148 148 let playback_status = ctx.playback_status.lock().await; 149 149 let playback_status = playback_status.as_ref().map(|x| x.status); ··· 183 183 volume, repeat, random, consume_val, status, 184 184 ); 185 185 if !ctx.batch { 186 - tx.send(response.clone()).await?; 186 + tx.send(response.clone().into_bytes()).await?; 187 187 } 188 188 return Ok(response); 189 189 } ··· 210 210 volume, repeat, random, single, consume_val, status, elapsed, time, duration, audio, bitrate, 211 211 ); 212 212 if !ctx.batch { 213 - tx.send(response.clone()).await?; 213 + tx.send(response.clone().into_bytes()).await?; 214 214 } 215 215 return Ok(response); 216 216 } ··· 227 227 ); 228 228 229 229 if !ctx.batch { 230 - tx.send(response.clone()).await?; 230 + tx.send(response.clone().into_bytes()).await?; 231 231 } 232 232 Ok(response) 233 233 } ··· 235 235 pub async fn handle_next( 236 236 ctx: &mut Context, 237 237 _request: &str, 238 - tx: Sender<String>, 238 + tx: Sender<Vec<u8>>, 239 239 ) -> Result<String, Error> { 240 240 ctx.playback.next(NextRequest {}).await?; 241 241 match ctx.event_sender.send(Subsystem::Player) { ··· 243 243 Err(_) => {} 244 244 } 245 245 if !ctx.batch { 246 - tx.send("OK\n".to_string()).await?; 246 + tx.send(b"OK\n".to_vec()).await?; 247 247 } 248 248 Ok("OK\n".to_string()) 249 249 } ··· 251 251 pub async fn handle_previous( 252 252 ctx: &mut Context, 253 253 _request: &str, 254 - tx: Sender<String>, 254 + tx: Sender<Vec<u8>>, 255 255 ) -> Result<String, Error> { 256 256 ctx.playback.previous(PreviousRequest {}).await?; 257 257 match ctx.event_sender.send(Subsystem::Player) { ··· 260 260 } 261 261 262 262 if !ctx.batch { 263 - tx.send("OK\n".to_string()).await?; 263 + tx.send(b"OK\n".to_vec()).await?; 264 264 } 265 265 266 266 Ok("OK\n".to_string()) ··· 269 269 pub async fn handle_playid( 270 270 ctx: &mut Context, 271 271 request: &str, 272 - tx: Sender<String>, 272 + tx: Sender<Vec<u8>>, 273 273 ) -> Result<String, Error> { 274 274 let arg = request.split_whitespace().nth(1); 275 275 276 276 if arg.is_none() { 277 277 if !ctx.batch { 278 - tx.send("ACK [2@0] {playid} incorrect arguments\n".to_string()) 278 + tx.send(b"ACK [2@0] {playid} incorrect arguments\n".to_vec()) 279 279 .await?; 280 280 } 281 281 return Ok("ACK [2@0] {playid} incorrect arguments\n".to_string()); ··· 288 288 289 289 if arg.is_err() { 290 290 if !ctx.batch { 291 - tx.send("ACK [2@0] {playid} incorrect arguments\n".to_string()) 291 + tx.send(b"ACK [2@0] {playid} incorrect arguments\n".to_vec()) 292 292 .await?; 293 293 } 294 294 return Ok("ACK [2@0] {playid} incorrect arguments\n".to_string()); ··· 304 304 .await?; 305 305 306 306 if !ctx.batch { 307 - tx.send("OK\n".to_string()).await?; 307 + tx.send(b"OK\n".to_vec()).await?; 308 308 } 309 309 310 310 Ok("OK\n".to_string()) ··· 313 313 pub async fn handle_seek( 314 314 ctx: &mut Context, 315 315 request: &str, 316 - tx: Sender<String>, 316 + tx: Sender<Vec<u8>>, 317 317 ) -> Result<String, Error> { 318 318 let mut parts = request.split_whitespace().skip(1); 319 319 let songpos = parts.next(); ··· 337 337 } 338 338 _ => { 339 339 if !ctx.batch { 340 - tx.send("ACK [2@0] {seek} incorrect arguments\n".to_string()) 340 + tx.send(b"ACK [2@0] {seek} incorrect arguments\n".to_vec()) 341 341 .await?; 342 342 } 343 343 return Ok("ACK [2@0] {seek} incorrect arguments\n".to_string()); ··· 345 345 } 346 346 347 347 if !ctx.batch { 348 - tx.send("OK\n".to_string()).await?; 348 + tx.send(b"OK\n".to_vec()).await?; 349 349 } 350 350 Ok("OK\n".to_string()) 351 351 } ··· 353 353 pub async fn handle_seekid( 354 354 ctx: &mut Context, 355 355 request: &str, 356 - tx: Sender<String>, 356 + tx: Sender<Vec<u8>>, 357 357 ) -> Result<String, Error> { 358 358 let mut parts = request.split_whitespace().skip(1); 359 359 let songid = parts.next(); ··· 377 377 } 378 378 _ => { 379 379 if !ctx.batch { 380 - tx.send("ACK [2@0] {seekid} incorrect arguments\n".to_string()) 380 + tx.send(b"ACK [2@0] {seekid} incorrect arguments\n".to_vec()) 381 381 .await?; 382 382 } 383 383 return Ok("ACK [2@0] {seekid} incorrect arguments\n".to_string()); ··· 385 385 } 386 386 387 387 if !ctx.batch { 388 - tx.send("OK\n".to_string()).await?; 388 + tx.send(b"OK\n".to_vec()).await?; 389 389 } 390 390 Ok("OK\n".to_string()) 391 391 } ··· 393 393 pub async fn handle_seekcur( 394 394 ctx: &mut Context, 395 395 request: &str, 396 - tx: Sender<String>, 396 + tx: Sender<Vec<u8>>, 397 397 ) -> Result<String, Error> { 398 398 let arg = request.split_whitespace().nth(1); 399 399 if arg.is_none() { 400 400 if !ctx.batch { 401 - tx.send("ACK [2@0] {seekcur} incorrect arguments\n".to_string()) 401 + tx.send(b"ACK [2@0] {seekcur} incorrect arguments\n".to_vec()) 402 402 .await?; 403 403 } 404 404 return Ok("ACK [2@0] {seekcur} incorrect arguments\n".to_string()); ··· 420 420 } 421 421 422 422 if !ctx.batch { 423 - tx.send("OK\n".to_string()).await?; 423 + tx.send(b"OK\n".to_vec()).await?; 424 424 } 425 425 Ok("OK\n".to_string()) 426 426 } ··· 428 428 pub async fn handle_random( 429 429 ctx: &mut Context, 430 430 request: &str, 431 - tx: Sender<String>, 431 + tx: Sender<Vec<u8>>, 432 432 ) -> Result<String, Error> { 433 433 let arg = request.split_whitespace().nth(1); 434 434 if arg.is_none() { 435 435 if !ctx.batch { 436 - tx.send("ACK [2@0] {random} incorrect arguments\n".to_string()) 436 + tx.send(b"ACK [2@0] {random} incorrect arguments\n".to_vec()) 437 437 .await?; 438 438 } 439 439 return Ok("ACK [2@0] {random} incorrect arguments\n".to_string()); ··· 446 446 }) 447 447 .await?; 448 448 if !ctx.batch { 449 - tx.send("OK\n".to_string()).await?; 449 + tx.send(b"OK\n".to_vec()).await?; 450 450 } 451 451 Ok("OK\n".to_string()) 452 452 } ··· 454 454 pub async fn handle_repeat( 455 455 ctx: &mut Context, 456 456 request: &str, 457 - tx: Sender<String>, 457 + tx: Sender<Vec<u8>>, 458 458 ) -> Result<String, Error> { 459 459 let arg = request.split_whitespace().nth(1); 460 460 if arg.is_none() { 461 461 if !ctx.batch { 462 - tx.send("ACK [2@0] {repeat} incorrect arguments\n".to_string()) 462 + tx.send(b"ACK [2@0] {repeat} incorrect arguments\n".to_vec()) 463 463 .await?; 464 464 } 465 465 return Ok("ACK [2@0] {repeat} incorrect arguments\n".to_string()); ··· 475 475 }, 476 476 _ => { 477 477 if !ctx.batch { 478 - tx.send("ACK [2@0] {repeat} incorrect arguments\n".to_string()) 478 + tx.send(b"ACK [2@0] {repeat} incorrect arguments\n".to_vec()) 479 479 .await?; 480 480 } 481 481 return Ok("ACK [2@0] {repeat} incorrect arguments\n".to_string()); ··· 489 489 }) 490 490 .await?; 491 491 if !ctx.batch { 492 - tx.send("OK\n".to_string()).await?; 492 + tx.send(b"OK\n".to_vec()).await?; 493 493 } 494 494 Ok("OK\n".to_string()) 495 495 } ··· 497 497 pub async fn handle_consume( 498 498 ctx: &mut Context, 499 499 request: &str, 500 - tx: Sender<String>, 500 + tx: Sender<Vec<u8>>, 501 501 ) -> Result<String, Error> { 502 502 let arg = request.split_whitespace().nth(1); 503 503 if arg.is_none() { 504 504 if !ctx.batch { 505 - tx.send("ACK [2@0] {consume} incorrect arguments\n".to_string()) 505 + tx.send(b"ACK [2@0] {consume} incorrect arguments\n".to_vec()) 506 506 .await?; 507 507 } 508 508 return Ok("ACK [2@0] {consume} incorrect arguments\n".to_string()); ··· 511 511 *consume = arg.unwrap().trim_matches('"') == "1"; 512 512 drop(consume); 513 513 if !ctx.batch { 514 - tx.send("OK\n".to_string()).await?; 514 + tx.send(b"OK\n".to_vec()).await?; 515 515 } 516 516 Ok("OK\n".to_string()) 517 517 } ··· 519 519 pub async fn handle_getvol( 520 520 ctx: &mut Context, 521 521 _request: &str, 522 - tx: Sender<String>, 522 + tx: Sender<Vec<u8>>, 523 523 ) -> Result<String, Error> { 524 524 let status = rockbox_sys::system::get_global_status(); 525 525 let volume = status.volume; ··· 527 527 let response = format!("volume: {}\nOK\n", volume); 528 528 529 529 if !ctx.batch { 530 - tx.send(response.clone()).await?; 530 + tx.send(response.clone().into_bytes()).await?; 531 531 } 532 532 533 533 Ok(response) ··· 536 536 pub async fn handle_setvol( 537 537 ctx: &mut Context, 538 538 request: &str, 539 - tx: Sender<String>, 539 + tx: Sender<Vec<u8>>, 540 540 ) -> Result<String, Error> { 541 541 let status = rockbox_sys::system::get_global_status(); 542 542 let volume = status.volume; 543 543 let arg = request.split_whitespace().nth(1); 544 544 if arg.is_none() { 545 545 if !ctx.batch { 546 - tx.send("ACK [2@0] {setvol} incorrect arguments\n".to_string()) 546 + tx.send(b"ACK [2@0] {setvol} incorrect arguments\n".to_vec()) 547 547 .await?; 548 548 } 549 549 return Ok("ACK [2@0] {setvol} incorrect arguments\n".to_string()); ··· 556 556 .adjust_volume(AdjustVolumeRequest { steps }) 557 557 .await?; 558 558 if !ctx.batch { 559 - tx.send("OK\n".to_string()).await?; 559 + tx.send(b"OK\n".to_vec()).await?; 560 560 } 561 561 Ok("OK\n".to_string()) 562 562 } ··· 564 564 pub async fn handle_single( 565 565 ctx: &mut Context, 566 566 request: &str, 567 - tx: Sender<String>, 567 + tx: Sender<Vec<u8>>, 568 568 ) -> Result<String, Error> { 569 569 let arg = request.split_whitespace().nth(1); 570 570 if arg.is_none() { 571 571 if !ctx.batch { 572 - tx.send("ACK [2@0] {single} incorrect arguments\n".to_string()) 572 + tx.send(b"ACK [2@0] {single} incorrect arguments\n".to_vec()) 573 573 .await?; 574 574 } 575 575 return Ok("ACK [2@0] {single} incorrect arguments\n".to_string()); ··· 578 578 let mut single = ctx.single.lock().await; 579 579 *single = arg.unwrap().to_string(); 580 580 if !ctx.batch { 581 - tx.send("OK\n".to_string()).await?; 581 + tx.send(b"OK\n".to_vec()).await?; 582 582 } 583 583 Ok("OK\n".to_string()) 584 584 } ··· 586 586 pub async fn handle_currentsong( 587 587 ctx: &mut Context, 588 588 _request: &str, 589 - tx: Sender<String>, 589 + tx: Sender<Vec<u8>>, 590 590 ) -> Result<String, Error> { 591 591 let current = ctx.current_track.lock().await; 592 592 if current.is_none() { 593 593 let response = "OK\n".to_string(); 594 594 if !ctx.batch { 595 - tx.send(response.clone()).await?; 595 + tx.send(response.clone().into_bytes()).await?; 596 596 } 597 597 return Ok(response); 598 598 } ··· 612 612 (current.length / 1000) as i64, 613 613 ); 614 614 if !ctx.batch { 615 - tx.send(response.clone()).await?; 615 + tx.send(response.clone().into_bytes()).await?; 616 616 } 617 617 return Ok(response); 618 618 } ··· 633 633 pos + 1, 634 634 ); 635 635 if !ctx.batch { 636 - tx.send(response.clone()).await?; 636 + tx.send(response.clone().into_bytes()).await?; 637 637 } 638 638 Ok(response) 639 639 } ··· 641 641 pub async fn handle_outputs( 642 642 ctx: &mut Context, 643 643 _request: &str, 644 - tx: Sender<String>, 644 + tx: Sender<Vec<u8>>, 645 645 ) -> Result<String, Error> { 646 646 let response = 647 647 "outputid: 0\noutputname: default detected output\nplugin: pulse\noutputenabled: 1\nOK\n" 648 648 .to_string(); 649 649 if !ctx.batch { 650 - tx.send(response.clone()).await?; 650 + tx.send(response.clone().into_bytes()).await?; 651 651 } 652 652 Ok(response) 653 653 } ··· 655 655 pub async fn handle_enableoutput( 656 656 ctx: &mut Context, 657 657 _request: &str, 658 - tx: Sender<String>, 658 + tx: Sender<Vec<u8>>, 659 659 ) -> Result<String, Error> { 660 660 if !ctx.batch { 661 - tx.send("OK\n".to_string()).await?; 661 + tx.send(b"OK\n".to_vec()).await?; 662 662 } 663 663 Ok("OK\n".to_string()) 664 664 } ··· 666 666 pub async fn handle_disableoutput( 667 667 ctx: &mut Context, 668 668 _request: &str, 669 - tx: Sender<String>, 669 + tx: Sender<Vec<u8>>, 670 670 ) -> Result<String, Error> { 671 671 if !ctx.batch { 672 - tx.send("OK\n".to_string()).await?; 672 + tx.send(b"OK\n".to_vec()).await?; 673 673 } 674 674 Ok("OK\n".to_string()) 675 675 } ··· 677 677 pub async fn handle_toggleoutput( 678 678 ctx: &mut Context, 679 679 _request: &str, 680 - tx: Sender<String>, 680 + tx: Sender<Vec<u8>>, 681 681 ) -> Result<String, Error> { 682 682 if !ctx.batch { 683 - tx.send("OK\n".to_string()).await?; 683 + tx.send(b"OK\n".to_vec()).await?; 684 684 } 685 685 Ok("OK\n".to_string()) 686 686 }
+44 -44
crates/mpd/src/handlers/queue.rs
··· 12 12 pub async fn handle_shuffle( 13 13 ctx: &mut Context, 14 14 _request: &str, 15 - tx: Sender<String>, 15 + tx: Sender<Vec<u8>>, 16 16 ) -> Result<String, Error> { 17 17 ctx.playlist 18 18 .shuffle_playlist(ShufflePlaylistRequest { start_index: 0 }) 19 19 .await?; 20 20 if !ctx.batch { 21 - tx.send("OK\n".to_string()).await?; 21 + tx.send(b"OK\n".to_vec()).await?; 22 22 } 23 23 24 24 match ctx.event_sender.send(Subsystem::Playlist) { ··· 32 32 pub async fn handle_add( 33 33 ctx: &mut Context, 34 34 request: &str, 35 - tx: Sender<String>, 35 + tx: Sender<Vec<u8>>, 36 36 ) -> Result<String, Error> { 37 37 let response = ctx 38 38 .settings ··· 46 46 let captures = re.captures(request); 47 47 if captures.is_none() { 48 48 if !ctx.batch { 49 - tx.send("ACK [2@0] {add} missing argument\n".to_string()) 49 + tx.send(b"ACK [2@0] {add} missing argument\n".to_vec()) 50 50 .await?; 51 51 } 52 52 return Ok("ACK [2@0] {add} missing argument\n".to_string()); ··· 61 61 62 62 if path.is_empty() { 63 63 if !ctx.batch { 64 - tx.send("ACK [2@0] {add} missing argument\n".to_string()) 64 + tx.send(b"ACK [2@0] {add} missing argument\n".to_vec()) 65 65 .await?; 66 66 } 67 67 return Ok("ACK [2@0] {add} missing argument\n".to_string()); ··· 74 74 75 75 if fs::metadata(&path).is_err() { 76 76 if !ctx.batch { 77 - tx.send("ACK [50@0] {add} No such file or directory\n".to_string()) 77 + tx.send(b"ACK [50@0] {add} No such file or directory\n".to_vec()) 78 78 .await?; 79 79 } 80 80 return Ok("ACK [50@0] {add} No such file or directory\n".to_string()); ··· 108 108 } 109 109 110 110 if !ctx.batch { 111 - tx.send("OK\n".to_string()).await?; 111 + tx.send(b"OK\n".to_vec()).await?; 112 112 } 113 113 114 114 match ctx.event_sender.send(Subsystem::Playlist) { ··· 122 122 pub async fn handle_addid( 123 123 ctx: &mut Context, 124 124 request: &str, 125 - tx: Sender<String>, 125 + tx: Sender<Vec<u8>>, 126 126 ) -> Result<String, Error> { 127 127 let response = ctx 128 128 .settings ··· 137 137 138 138 if captures.is_none() { 139 139 if !ctx.batch { 140 - tx.send("ACK [2@0] {addid} missing argument\n".to_string()) 140 + tx.send(b"ACK [2@0] {addid} missing argument\n".to_vec()) 141 141 .await?; 142 142 } 143 143 return Ok("ACK [2@0] {addid} missing argument\n".to_string()); ··· 152 152 153 153 if path.is_empty() { 154 154 if !ctx.batch { 155 - tx.send("ACK [2@0] {addid} missing argument\n".to_string()) 155 + tx.send(b"ACK [2@0] {addid} missing argument\n".to_vec()) 156 156 .await?; 157 157 } 158 158 return Ok("ACK [2@0] {addid} missing argument\n".to_string()); ··· 165 165 166 166 if fs::metadata(&path).is_err() { 167 167 if !ctx.batch { 168 - tx.send("ACK [50@0] {addid} No such file or directory\n".to_string()) 168 + tx.send(b"ACK [50@0] {addid} No such file or directory\n".to_vec()) 169 169 .await?; 170 170 } 171 171 return Ok("ACK [50@0] {addid} No such file or directory\n".to_string()); ··· 173 173 174 174 if fs::metadata(&path)?.is_dir() { 175 175 if !ctx.batch { 176 - tx.send("ACK [2@0] {addid} cannot add directory; use add instead\n".to_string()) 176 + tx.send(b"ACK [2@0] {addid} cannot add directory; use add instead\n".to_vec()) 177 177 .await?; 178 178 } 179 179 return Ok("ACK [2@0] {addid} cannot add directory; use add instead\n".to_string()); ··· 210 210 let response = format!("Id: {}\nOK\n", new_id); 211 211 212 212 if !ctx.batch { 213 - tx.send(response.clone()).await?; 213 + tx.send(response.clone().into_bytes()).await?; 214 214 } 215 215 216 216 match ctx.event_sender.send(Subsystem::Playlist) { ··· 224 224 pub async fn handle_playlistinfo( 225 225 ctx: &mut Context, 226 226 _request: &str, 227 - tx: Sender<String>, 227 + tx: Sender<Vec<u8>>, 228 228 ) -> Result<String, Error> { 229 229 let current_playlist = ctx.current_playlist.lock().await; 230 230 231 231 if current_playlist.is_none() { 232 232 if !ctx.batch { 233 - tx.send("OK\n".to_string()).await?; 233 + tx.send(b"OK\n".to_vec()).await?; 234 234 } 235 235 return Ok("OK\n".to_string()); 236 236 } ··· 262 262 let response = format!("{}OK\n", response); 263 263 264 264 if !ctx.batch { 265 - tx.send(response.clone()).await?; 265 + tx.send(response.clone().into_bytes()).await?; 266 266 } 267 267 268 268 Ok(response) ··· 271 271 pub async fn handle_playlistid( 272 272 ctx: &mut Context, 273 273 request: &str, 274 - tx: Sender<String>, 274 + tx: Sender<Vec<u8>>, 275 275 ) -> Result<String, Error> { 276 276 let arg = request.split_whitespace().nth(1); 277 277 let id = arg.and_then(|x| x.trim_matches('"').parse::<usize>().ok()); ··· 280 280 281 281 if current_playlist.is_none() { 282 282 if !ctx.batch { 283 - tx.send("OK\n".to_string()).await?; 283 + tx.send(b"OK\n".to_vec()).await?; 284 284 } 285 285 return Ok("OK\n".to_string()); 286 286 } ··· 301 301 return { 302 302 let msg = format!("ACK [50@0] {{playlistid}} No such song\n"); 303 303 if !ctx.batch { 304 - tx.send(msg.clone()).await?; 304 + tx.send(msg.clone().into_bytes()).await?; 305 305 } 306 306 Ok(msg) 307 307 }; ··· 325 325 let response = format!("{}OK\n", response); 326 326 327 327 if !ctx.batch { 328 - tx.send(response.clone()).await?; 328 + tx.send(response.clone().into_bytes()).await?; 329 329 } 330 330 331 331 Ok(response) ··· 334 334 pub async fn handle_deleteid( 335 335 ctx: &mut Context, 336 336 request: &str, 337 - tx: Sender<String>, 337 + tx: Sender<Vec<u8>>, 338 338 ) -> Result<String, Error> { 339 339 let arg = request.split_whitespace().last(); 340 340 if arg.is_none() { 341 341 if !ctx.batch { 342 - tx.send("ACK [2@0] {deleteid} missing argument\n".to_string()) 342 + tx.send(b"ACK [2@0] {deleteid} missing argument\n".to_vec()) 343 343 .await?; 344 344 } 345 345 return Ok("ACK [2@0] {deleteid} missing argument\n".to_string()); ··· 351 351 Ok(x) => vec![x - 1], 352 352 Err(_) => { 353 353 if !ctx.batch { 354 - tx.send("ACK [2@0] {deleteid} invalid argument\n".to_string()) 354 + tx.send(b"ACK [2@0] {deleteid} invalid argument\n".to_vec()) 355 355 .await?; 356 356 } 357 357 return Ok("ACK [2@0] {deleteid} invalid argument\n".to_string()); ··· 361 361 .remove_tracks(RemoveTracksRequest { positions }) 362 362 .await?; 363 363 if !ctx.batch { 364 - tx.send("OK\n".to_string()).await?; 364 + tx.send(b"OK\n".to_vec()).await?; 365 365 } 366 366 367 367 match ctx.event_sender.send(Subsystem::Playlist) { ··· 374 374 pub async fn handle_delete( 375 375 ctx: &mut Context, 376 376 request: &str, 377 - tx: Sender<String>, 377 + tx: Sender<Vec<u8>>, 378 378 ) -> Result<String, Error> { 379 379 let arg = request.split_whitespace().last(); 380 380 if arg.is_none() { 381 381 if !ctx.batch { 382 - tx.send("ACK [2@0] {delete} missing argument\n".to_string()) 382 + tx.send(b"ACK [2@0] {delete} missing argument\n".to_vec()) 383 383 .await?; 384 384 } 385 385 return Ok("ACK [2@0] {delete} missing argument\n".to_string()); ··· 394 394 .remove_tracks(RemoveTracksRequest { positions }) 395 395 .await?; 396 396 if !ctx.batch { 397 - tx.send("OK\n".to_string()).await?; 397 + tx.send(b"OK\n".to_vec()).await?; 398 398 } 399 399 match ctx.event_sender.send(Subsystem::Playlist) { 400 400 Ok(_) => {} ··· 406 406 Ok(x) => vec![x], 407 407 Err(_) => { 408 408 if !ctx.batch { 409 - tx.send("ACK [2@0] {delete} invalid argument\n".to_string()) 409 + tx.send(b"ACK [2@0] {delete} invalid argument\n".to_vec()) 410 410 .await?; 411 411 } 412 412 return Ok("ACK [2@0] {delete} invalid argument\n".to_string()); ··· 416 416 .remove_tracks(RemoveTracksRequest { positions }) 417 417 .await?; 418 418 if !ctx.batch { 419 - tx.send("OK\n".to_string()).await?; 419 + tx.send(b"OK\n".to_vec()).await?; 420 420 } 421 421 422 422 match ctx.event_sender.send(Subsystem::Playlist) { ··· 430 430 pub async fn handle_clear( 431 431 ctx: &mut Context, 432 432 _request: &str, 433 - tx: Sender<String>, 433 + tx: Sender<Vec<u8>>, 434 434 ) -> Result<String, Error> { 435 435 ctx.playlist 436 436 .remove_all_tracks(RemoveAllTracksRequest {}) 437 437 .await?; 438 438 if !ctx.batch { 439 - tx.send("OK\n".to_string()).await?; 439 + tx.send(b"OK\n".to_vec()).await?; 440 440 } 441 441 442 442 match ctx.event_sender.send(Subsystem::Playlist) { ··· 450 450 pub async fn handle_move( 451 451 ctx: &mut Context, 452 452 request: &str, 453 - tx: Sender<String>, 453 + tx: Sender<Vec<u8>>, 454 454 ) -> Result<String, Error> { 455 455 let mut parts = request.split_whitespace().skip(1); 456 456 let from_str = parts.next(); ··· 458 458 459 459 if from_str.is_none() || to_str.is_none() { 460 460 if !ctx.batch { 461 - tx.send("ACK [2@0] {move} incorrect arguments\n".to_string()) 461 + tx.send(b"ACK [2@0] {move} incorrect arguments\n".to_vec()) 462 462 .await?; 463 463 } 464 464 return Ok("ACK [2@0] {move} incorrect arguments\n".to_string()); ··· 469 469 470 470 if from.is_err() || to.is_err() { 471 471 if !ctx.batch { 472 - tx.send("ACK [2@0] {move} invalid argument\n".to_string()) 472 + tx.send(b"ACK [2@0] {move} invalid argument\n".to_vec()) 473 473 .await?; 474 474 } 475 475 return Ok("ACK [2@0] {move} invalid argument\n".to_string()); ··· 508 508 } 509 509 510 510 if !ctx.batch { 511 - tx.send("OK\n".to_string()).await?; 511 + tx.send(b"OK\n".to_vec()).await?; 512 512 } 513 513 Ok("OK\n".to_string()) 514 514 } ··· 516 516 pub async fn handle_moveid( 517 517 ctx: &mut Context, 518 518 request: &str, 519 - tx: Sender<String>, 519 + tx: Sender<Vec<u8>>, 520 520 ) -> Result<String, Error> { 521 521 let mut parts = request.split_whitespace().skip(1); 522 522 let id_str = parts.next(); ··· 524 524 525 525 if id_str.is_none() || to_str.is_none() { 526 526 if !ctx.batch { 527 - tx.send("ACK [2@0] {moveid} incorrect arguments\n".to_string()) 527 + tx.send(b"ACK [2@0] {moveid} incorrect arguments\n".to_vec()) 528 528 .await?; 529 529 } 530 530 return Ok("ACK [2@0] {moveid} incorrect arguments\n".to_string()); ··· 535 535 536 536 if id.is_err() || to.is_err() { 537 537 if !ctx.batch { 538 - tx.send("ACK [2@0] {moveid} invalid argument\n".to_string()) 538 + tx.send(b"ACK [2@0] {moveid} invalid argument\n".to_vec()) 539 539 .await?; 540 540 } 541 541 return Ok("ACK [2@0] {moveid} invalid argument\n".to_string()); ··· 574 574 } 575 575 576 576 if !ctx.batch { 577 - tx.send("OK\n".to_string()).await?; 577 + tx.send(b"OK\n".to_vec()).await?; 578 578 } 579 579 Ok("OK\n".to_string()) 580 580 } ··· 582 582 pub async fn handle_swap( 583 583 ctx: &mut Context, 584 584 request: &str, 585 - tx: Sender<String>, 585 + tx: Sender<Vec<u8>>, 586 586 ) -> Result<String, Error> { 587 587 let mut parts = request.split_whitespace().skip(1); 588 588 let pos1 = parts ··· 594 594 595 595 if pos1.is_none() || pos2.is_none() { 596 596 if !ctx.batch { 597 - tx.send("ACK [2@0] {swap} incorrect arguments\n".to_string()) 597 + tx.send(b"ACK [2@0] {swap} incorrect arguments\n".to_vec()) 598 598 .await?; 599 599 } 600 600 return Ok("ACK [2@0] {swap} incorrect arguments\n".to_string()); ··· 655 655 } 656 656 657 657 if !ctx.batch { 658 - tx.send("OK\n".to_string()).await?; 658 + tx.send(b"OK\n".to_vec()).await?; 659 659 } 660 660 Ok("OK\n".to_string()) 661 661 } ··· 663 663 pub async fn handle_swapid( 664 664 ctx: &mut Context, 665 665 request: &str, 666 - tx: Sender<String>, 666 + tx: Sender<Vec<u8>>, 667 667 ) -> Result<String, Error> { 668 668 let mut parts = request.split_whitespace().skip(1); 669 669 let id1 = parts ··· 680 680 } 681 681 _ => { 682 682 if !ctx.batch { 683 - tx.send("ACK [2@0] {swapid} incorrect arguments\n".to_string()) 683 + tx.send(b"ACK [2@0] {swapid} incorrect arguments\n".to_vec()) 684 684 .await?; 685 685 } 686 686 Ok("ACK [2@0] {swapid} incorrect arguments\n".to_string())
+38 -19
crates/mpd/src/handlers/system.rs
··· 11 11 pub async fn handle_idle( 12 12 ctx: &mut Context, 13 13 _request: &str, 14 - tx: Sender<String>, 14 + tx: Sender<Vec<u8>>, 15 15 ) -> Result<String, Error> { 16 16 let receiver = ctx.event_receiver.clone(); 17 17 18 18 tokio::spawn(async move { 19 19 let mut rx = receiver.lock().await; 20 - while let Ok(event) = rx.recv().await { 21 - if event == Subsystem::NoIdle { 22 - break; 20 + loop { 21 + match rx.recv().await { 22 + Ok(Subsystem::NoIdle) => break, // handle_noidle already sent OK 23 + Ok(event) => { 24 + let mut changed = vec![event.to_string()]; 25 + // Drain any other pending events without blocking 26 + while let Ok(next) = rx.try_recv() { 27 + if next != Subsystem::NoIdle { 28 + let name = next.to_string(); 29 + if !changed.contains(&name) { 30 + changed.push(name); 31 + } 32 + } 33 + } 34 + let mut response = changed 35 + .iter() 36 + .map(|s| format!("changed: {}\n", s)) 37 + .collect::<String>(); 38 + response.push_str("OK\n"); 39 + tx.send(response.into_bytes()).await.ok(); 40 + break; 41 + } 42 + Err(_) => break, 23 43 } 24 - tx.send(format!("changed: {}\n", event.to_string())).await?; 25 44 } 26 45 Ok::<(), Error>(()) 27 46 }); ··· 32 51 pub async fn handle_noidle( 33 52 ctx: &mut Context, 34 53 _request: &str, 35 - tx: Sender<String>, 54 + tx: Sender<Vec<u8>>, 36 55 ) -> Result<String, Error> { 37 56 ctx.event_sender.send(Subsystem::NoIdle)?; 38 57 39 58 let response = "OK\n".to_string(); 40 59 if !ctx.batch { 41 - tx.send(response.clone()).await?; 60 + tx.send(response.clone().into_bytes()).await?; 42 61 } 43 62 Ok(response) 44 63 } ··· 46 65 pub async fn handle_decoders( 47 66 ctx: &mut Context, 48 67 _request: &str, 49 - tx: Sender<String>, 68 + tx: Sender<Vec<u8>>, 50 69 ) -> Result<String, Error> { 51 70 if !ctx.batch { 52 - tx.send(DECODERS.to_string()).await?; 71 + tx.send(DECODERS.as_bytes().to_vec()).await?; 53 72 } 54 73 Ok(DECODERS.to_string()) 55 74 } ··· 57 76 pub async fn handle_commands( 58 77 ctx: &mut Context, 59 78 _request: &str, 60 - tx: Sender<String>, 79 + tx: Sender<Vec<u8>>, 61 80 ) -> Result<String, Error> { 62 81 if !ctx.batch { 63 - tx.send(COMMANDS.to_string()).await?; 82 + tx.send(COMMANDS.as_bytes().to_vec()).await?; 64 83 } 65 84 Ok(COMMANDS.to_string()) 66 85 } ··· 68 87 pub async fn handle_binarylimit( 69 88 ctx: &mut Context, 70 89 _request: &str, 71 - tx: Sender<String>, 90 + tx: Sender<Vec<u8>>, 72 91 ) -> Result<String, Error> { 73 92 if !ctx.batch { 74 - tx.send("OK\n".to_string()).await?; 93 + tx.send(b"OK\n".to_vec()).await?; 75 94 } 76 95 Ok("OK\n".to_string()) 77 96 } ··· 79 98 pub async fn handle_ping( 80 99 ctx: &mut Context, 81 100 _request: &str, 82 - tx: Sender<String>, 101 + tx: Sender<Vec<u8>>, 83 102 ) -> Result<String, Error> { 84 103 if !ctx.batch { 85 - tx.send("OK\n".to_string()).await?; 104 + tx.send(b"OK\n".to_vec()).await?; 86 105 } 87 106 Ok("OK\n".to_string()) 88 107 } ··· 90 109 pub async fn handle_notcommands( 91 110 ctx: &mut Context, 92 111 _request: &str, 93 - tx: Sender<String>, 112 + tx: Sender<Vec<u8>>, 94 113 ) -> Result<String, Error> { 95 114 let response = "OK\n".to_string(); 96 115 if !ctx.batch { 97 - tx.send(response.clone()).await?; 116 + tx.send(response.clone().into_bytes()).await?; 98 117 } 99 118 Ok(response) 100 119 } ··· 102 121 pub async fn handle_urlhandlers( 103 122 ctx: &mut Context, 104 123 _request: &str, 105 - tx: Sender<String>, 124 + tx: Sender<Vec<u8>>, 106 125 ) -> Result<String, Error> { 107 126 let response = "handler: file://\nOK\n".to_string(); 108 127 if !ctx.batch { 109 - tx.send(response.clone()).await?; 128 + tx.send(response.clone().into_bytes()).await?; 110 129 } 111 130 Ok(response) 112 131 }
+32 -13
crates/mpd/src/lib.rs
··· 1 1 use anyhow::Error; 2 2 use handlers::{ 3 + albumart::{handle_albumart, handle_readpicture}, 3 4 batch::{handle_command_list_begin, handle_command_list_ok_begin}, 4 5 browse::{handle_listall, handle_listallinfo, handle_listfiles, handle_lsinfo}, 5 6 library::{ 6 7 handle_config, handle_count, handle_find, handle_find_album, handle_find_artist, 7 8 handle_find_title, handle_findadd, handle_list_album, handle_list_artist, handle_list_date, 8 - handle_list_genre, handle_list_title, handle_listplaylists, handle_load, handle_rename, 9 - handle_rescan, handle_rm, handle_save, handle_search, handle_searchadd, handle_stats, 10 - handle_tagtypes, handle_tagtypes_clear, handle_tagtypes_enable, 9 + handle_list_genre, handle_list_title, handle_listplaylistinfo, handle_listplaylists, 10 + handle_load, handle_rename, handle_rescan, handle_rm, handle_save, handle_search, 11 + handle_searchadd, handle_stats, handle_tagtypes, handle_tagtypes_clear, 12 + handle_tagtypes_enable, 11 13 }, 12 14 playback::{ 13 15 handle_consume, handle_currentsong, handle_disableoutput, handle_enableoutput, ··· 37 39 library_service_client::LibraryServiceClient, playback_service_client::PlaybackServiceClient, 38 40 playlist_service_client::PlaylistServiceClient, 39 41 saved_playlist_service_client::SavedPlaylistServiceClient, 40 - settings_service_client::SettingsServiceClient, sound_service_client::SoundServiceClient, 41 - system_service_client::SystemServiceClient, GetCurrentRequest, GetGlobalStatusRequest, 42 - PlaylistResumeRequest, 42 + settings_service_client::SettingsServiceClient, 43 + smart_playlist_service_client::SmartPlaylistServiceClient, 44 + sound_service_client::SoundServiceClient, system_service_client::SystemServiceClient, 45 + GetCurrentRequest, GetGlobalStatusRequest, PlaylistResumeRequest, 43 46 }; 44 47 use rockbox_sys::types::user_settings::UserSettings; 45 48 use sqlx::{Pool, Sqlite}; ··· 67 70 pub playlist: PlaylistServiceClient<Channel>, 68 71 pub system: SystemServiceClient<Channel>, 69 72 pub saved_playlist: SavedPlaylistServiceClient<Channel>, 73 + pub smart_playlist: SmartPlaylistServiceClient<Channel>, 70 74 pub single: Arc<Mutex<String>>, 71 75 pub consume: Arc<Mutex<bool>>, 72 76 pub batch: bool, ··· 117 121 let mut reader = tokio::io::BufReader::new(reader_stream); 118 122 let mut writer = tokio::io::BufWriter::new(writer_stream); 119 123 120 - let (tx, mut rx) = tokio::sync::mpsc::channel::<String>(32); 124 + let (tx, mut rx) = tokio::sync::mpsc::channel::<Vec<u8>>(32); 121 125 122 126 tokio::spawn(async move { 123 127 while let Some(msg) = rx.recv().await { 124 - writer.write_all(msg.as_bytes()).await?; 128 + writer.write_all(&msg).await?; 125 129 writer.flush().await?; 126 130 } 127 131 Ok::<(), Error>(()) 128 132 }); 129 133 130 - tx.send("OK MPD 0.23.15\n".to_string()).await?; 134 + tx.send(b"OK MPD 0.23.15\n".to_vec()).await?; 131 135 132 136 while let Ok(n) = reader.read(&mut buf).await { 133 137 if n == 0 { ··· 198 202 "listall" => handle_listall(&mut ctx, &request, tx.clone()).await?, 199 203 "listallinfo" => handle_listallinfo(&mut ctx, &request, tx.clone()).await?, 200 204 "listfiles" => handle_listfiles(&mut ctx, &request, tx.clone()).await?, 205 + "albumart" => handle_albumart(&mut ctx, &request, tx.clone()).await?, 206 + "readpicture" => handle_readpicture(&mut ctx, &request, tx.clone()).await?, 201 207 "listplaylists" => handle_listplaylists(&mut ctx, &request, tx.clone()).await?, 208 + "listplaylistinfo" => handle_listplaylistinfo(&mut ctx, &request, tx.clone()).await?, 202 209 "load" => handle_load(&mut ctx, &request, tx.clone()).await?, 203 210 "save" => handle_save(&mut ctx, &request, tx.clone()).await?, 204 211 "rm" => handle_rm(&mut ctx, &request, tx.clone()).await?, ··· 224 231 continue; 225 232 } 226 233 debug!("unhandled MPD command: {}", command); 227 - tx.send(format!( 228 - "ACK [5@0] {{{}}} unknown command \"{}\"\n", 229 - command, command 230 - )) 234 + tx.send( 235 + format!( 236 + "ACK [5@0] {{{}}} unknown command \"{}\"\n", 237 + command, command 238 + ) 239 + .into_bytes(), 240 + ) 231 241 .await?; 232 242 format!("ACK [5@0] {{{}}} unknown command\n", command) 233 243 } ··· 272 282 let playlist = PlaylistServiceClient::connect(url.clone()).await?; 273 283 let system = SystemServiceClient::connect(url.clone()).await?; 274 284 let saved_playlist = SavedPlaylistServiceClient::connect(url.clone()).await?; 285 + let smart_playlist = SmartPlaylistServiceClient::connect(url.clone()).await?; 275 286 276 287 let (event_sender, event_receiver) = broadcast::channel(16); 277 288 ··· 283 294 playlist, 284 295 system, 285 296 saved_playlist, 297 + smart_playlist, 286 298 single: Arc::new(Mutex::new("0".to_string())), 287 299 consume: match ctx { 288 300 Some(ref ctx) => ctx.consume.clone(), ··· 337 349 while let Some(track) = rt.block_on(subscription.next()) { 338 350 let mut current_track = rt.block_on(ctx.current_track.lock()); 339 351 *current_track = Some(track); 352 + drop(current_track); 353 + match ctx.event_sender.send(Subsystem::Player) { 354 + Ok(_) => {} 355 + Err(e) => { 356 + warn!("player event send error on track change: {}", e); 357 + } 358 + } 340 359 } 341 360 }); 342 361