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 #78 from tsirysndr/feat/mpd-filters

mpd: add support for MPD filters

authored by

Tsiry Sandratraina and committed by
GitHub
044fee09 eeafb71d

+140 -8
+8
Cargo.lock
··· 6461 6461 checksum = "b52c1b33ff98142aecea13138bd399b68aa7ab5d9546c300988c345004001eea" 6462 6462 6463 6463 [[package]] 6464 + name = "mpd-filters" 6465 + version = "0.4.1" 6466 + source = "registry+https://github.com/rust-lang/crates.io-index" 6467 + checksum = "029e541ef2a61b89028cde291b2497891cf093f97979293ffb16fb6b8814f6d8" 6468 + 6469 + [[package]] 6464 6470 name = "mpris-server" 6465 6471 version = "0.8.1" 6466 6472 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 8280 8286 dependencies = [ 8281 8287 "anyhow", 8282 8288 "chrono", 8289 + "futures", 8290 + "mpd-filters", 8283 8291 "regex", 8284 8292 "rockbox-graphql", 8285 8293 "rockbox-library",
+15
crates/library/src/repo/album.rs
··· 37 37 } 38 38 } 39 39 40 + pub async fn filter( 41 + pool: Pool<Sqlite>, 42 + r#where: (String, Vec<String>), 43 + ) -> Result<Vec<Album>, sqlx::Error> { 44 + let sql = format!("SELECT * FROM album WHERE {}", r#where.0); 45 + let mut query = sqlx::query_as(&sql); 46 + 47 + for value in r#where.1 { 48 + query = query.bind(value.clone()); 49 + } 50 + 51 + let result = query.fetch_all(&pool).await?; 52 + Ok(result) 53 + } 54 + 40 55 pub async fn find_by_md5(pool: Pool<Sqlite>, md5: &str) -> Result<Option<Album>, sqlx::Error> { 41 56 match sqlx::query_as::<_, Album>( 42 57 r#"
+15
crates/library/src/repo/artist.rs
··· 66 66 } 67 67 } 68 68 69 + pub async fn filter( 70 + pool: Pool<Sqlite>, 71 + r#where: (String, Vec<String>), 72 + ) -> Result<Vec<Artist>, Error> { 73 + let sql = format!("SELECT * FROM artist WHERE {}", r#where.0); 74 + let mut query = sqlx::query_as(&sql); 75 + 76 + for value in r#where.1 { 77 + query = query.bind(value.clone()); 78 + } 79 + 80 + let result = query.fetch_all(&pool).await?; 81 + Ok(result) 82 + } 83 + 69 84 pub async fn all(pool: Pool<Sqlite>) -> Result<Vec<Artist>, Error> { 70 85 match sqlx::query_as::<_, Artist>( 71 86 r#"
+23
crates/library/src/repo/track.rs
··· 72 72 Ok(result) 73 73 } 74 74 75 + pub async fn filter( 76 + pool: Pool<Sqlite>, 77 + r#where: (String, Vec<String>), 78 + ) -> Result<Vec<Track>, Error> { 79 + let sql = format!("SELECT * FROM track WHERE {}", r#where.0); 80 + let mut query = sqlx::query_as(&sql); 81 + 82 + for value in r#where.1 { 83 + query = query.bind(value.clone()); 84 + } 85 + 86 + let result = query.fetch_all(&pool).await?; 87 + Ok(result) 88 + } 89 + 75 90 pub async fn find_by_md5(pool: Pool<Sqlite>, md5: &str) -> Result<Option<Track>, Error> { 76 91 let result: Option<Track> = sqlx::query_as("SELECT * FROM track WHERE md5 = $1") 77 92 .bind(md5) 93 + .fetch_optional(&pool) 94 + .await?; 95 + Ok(result) 96 + } 97 + 98 + pub async fn find_by_path(pool: Pool<Sqlite>, path: &str) -> Result<Option<Track>, Error> { 99 + let result: Option<Track> = sqlx::query_as("SELECT * FROM track WHERE path = $1") 100 + .bind(path) 78 101 .fetch_optional(&pool) 79 102 .await?; 80 103 Ok(result)
+2
crates/mpd/Cargo.toml
··· 6 6 [dependencies] 7 7 anyhow = "1.0.93" 8 8 chrono = "0.4.38" 9 + futures.workspace = true 10 + mpd-filters = "0.4.1" 9 11 regex = "1.11.1" 10 12 rockbox-graphql = {path = "../graphql"} 11 13 rockbox-library = {path = "../library"}
+69 -5
crates/mpd/src/handlers/library.rs
··· 1 1 use std::{collections::HashMap, fs}; 2 2 3 3 use anyhow::Error; 4 + use mpd_filters::{Expression, Parser, SqlOptions, ToSql}; 4 5 use regex::Regex; 5 6 use rockbox_library::{entity::track::Track, repo}; 6 7 use rockbox_rpc::api::rockbox::v1alpha1::{ ··· 17 18 18 19 pub async fn handle_list_album( 19 20 ctx: &mut Context, 20 - _request: &str, 21 + request: &str, 21 22 stream: &mut BufReader<TcpStream>, 22 23 ) -> Result<String, Error> { 23 - let response = ctx.library.get_albums(GetAlbumsRequest {}).await?; 24 - let response = response.into_inner(); 25 - let response = response 26 - .albums 24 + let query = request.replace("list album", "").replace("list Album", ""); 25 + let query = query.trim(); 26 + let query = query.trim_matches('"'); 27 + let query = query.replace(r#"\\"#, r#"\"#); 28 + let mut albums = repo::album::all(ctx.pool.clone()).await?; 29 + 30 + if !query.is_empty() { 31 + let mut columns = HashMap::new(); 32 + columns.insert("AlbumArtist".to_string(), "artist".to_string()); 33 + let opts = SqlOptions { 34 + columns, 35 + ..Default::default() 36 + }; 37 + let mut parser = Parser::new(&query); 38 + let expr = parser.parse().map_err(|e| Error::msg(e))?; 39 + albums = repo::album::filter(ctx.pool.clone(), expr.to_sql(opts)).await?; 40 + } 41 + 42 + let response = albums 27 43 .iter() 28 44 .map(|x| format!("Album: {}\n", x.title)) 29 45 .collect::<String>(); ··· 308 324 } 309 325 310 326 Ok(response) 327 + } 328 + 329 + pub async fn handle_find( 330 + ctx: &mut Context, 331 + request: &str, 332 + stream: &mut BufReader<TcpStream>, 333 + ) -> Result<String, Error> { 334 + let arg = request.replace("find ", ""); 335 + let arg = arg.trim(); 336 + let arg = arg.trim_matches('"'); 337 + let arg = arg.replace(r#"\\"#, r#"\"#); 338 + let mut parser = Parser::new(&arg); 339 + match parser.parse() { 340 + Ok(expr) => { 341 + execute(ctx, &expr, stream).await?; 342 + } 343 + Err(e) => return Err(Error::msg(e)), 344 + } 345 + Ok("".to_string()) 346 + } 347 + 348 + async fn execute( 349 + ctx: &mut Context, 350 + expr: &Expression, 351 + stream: &mut BufReader<TcpStream>, 352 + ) -> Result<(), Error> { 353 + let mut columns = HashMap::new(); 354 + columns.insert("Title".to_string(), "title".to_string()); 355 + columns.insert("Artist".to_string(), "artist".to_string()); 356 + columns.insert("Album".to_string(), "album".to_string()); 357 + columns.insert("AlbumArtist".to_string(), "album_artist".to_string()); 358 + columns.insert("File".to_string(), "path".to_string()); 359 + 360 + let opts = SqlOptions { 361 + columns, 362 + file_prefix: Some(match get_music_dir()?.ends_with("/") { 363 + true => get_music_dir()?, 364 + false => format!("{}/", get_music_dir()?), 365 + }), 366 + }; 367 + 368 + let tracks = repo::track::filter(ctx.pool.clone(), expr.to_sql(opts)).await?; 369 + let mut response: String = "".to_string(); 370 + 371 + build_file_metadata(tracks, &mut response).await?; 372 + 373 + stream.write_all(response.as_bytes()).await?; 374 + Ok(()) 311 375 } 312 376 313 377 async fn build_file_metadata(tracks: Vec<Track>, response: &mut String) -> Result<(), Error> {
+8 -3
crates/mpd/src/lib.rs
··· 3 3 batch::{handle_command_list_begin, handle_command_list_ok_begin}, 4 4 browse::{handle_listall, handle_listallinfo, handle_listfiles, handle_lsinfo}, 5 5 library::{ 6 - handle_config, handle_find_album, handle_find_artist, handle_find_title, handle_list_album, 7 - handle_list_artist, handle_list_title, handle_rescan, handle_search, handle_stats, 8 - handle_tagtypes, handle_tagtypes_enable, 6 + handle_config, handle_find, handle_find_album, handle_find_artist, handle_find_title, 7 + handle_list_album, handle_list_artist, handle_list_title, handle_rescan, handle_search, 8 + handle_stats, handle_tagtypes, handle_tagtypes_enable, 9 9 }, 10 10 playback::{ 11 11 handle_currentsong, handle_getvol, handle_next, handle_outputs, handle_pause, handle_play, ··· 135 135 "clear" => handle_clear(&mut ctx, &request, &mut stream).await?, 136 136 "move" => handle_move(&mut ctx, &request, &mut stream).await?, 137 137 "list album" => handle_list_album(&mut ctx, &request, &mut stream).await?, 138 + "list albumartist" => handle_list_artist(&mut ctx, &request, &mut stream).await?, 138 139 "list artist" => handle_list_artist(&mut ctx, &request, &mut stream).await?, 139 140 "list title" => handle_list_title(&mut ctx, &request, &mut stream).await?, 140 141 "update" => handle_rescan(&mut ctx, &request, &mut stream).await?, ··· 167 168 handle_command_list_ok_begin(&mut ctx, &request, &mut stream).await? 168 169 } 169 170 _ => { 171 + if command.starts_with("find ") { 172 + handle_find(&mut ctx, &request, &mut stream).await?; 173 + return Ok(()); 174 + } 170 175 println!("Unhandled command: {}", command); 171 176 println!("Unhandled request: {}", request); 172 177 stream