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.

Fix MPD handlers

Register new MPD commands and wire handlers across library, playback,
queue and system modules.

Notable behavior changes:
- play accepts an index argument to start playback at a given position
- list artist uses AlbumArtist when requested; added listdate and
listgenre
- queue add starts playback when queue was empty and improves path
handling
- added stop, ping, notcommands, urlhandlers and several queue ops
(moveid,
swap, playlistid, etc.)

+1155 -133
+21 -1
crates/mpd/src/consts.rs
··· 318 318 command: addid 319 319 command: clear 320 320 command: commands 321 + command: consume 322 + command: count 321 323 command: currentsong 322 324 command: decoders 323 325 command: delete 324 326 command: deleteid 327 + command: disableoutput 328 + command: enableoutput 325 329 command: find 330 + command: findadd 326 331 command: getvol 327 332 command: list 328 333 command: listall 329 334 command: listallinfo 330 335 command: listfiles 331 336 command: listmounts 337 + command: listplaylists 338 + command: load 332 339 command: lsinfo 340 + command: move 341 + command: moveid 333 342 command: next 343 + command: notcommands 334 344 command: outputs 335 345 command: pause 346 + command: ping 336 347 command: play 337 348 command: playid 349 + command: playlistid 338 350 command: playlistinfo 339 351 command: playlistsearch 340 352 command: plchanges 341 353 command: previous 342 354 command: random 355 + command: rename 343 356 command: repeat 344 357 command: rescan 358 + command: rm 359 + command: save 345 360 command: search 361 + command: searchadd 362 + command: seek 346 363 command: seekcur 347 364 command: seekid 348 - command: sendmessage 349 365 command: setvol 350 366 command: shuffle 351 367 command: single 352 368 command: stats 353 369 command: status 354 370 command: stop 371 + command: swap 372 + command: swapid 355 373 command: tagtypes 374 + command: toggleoutput 356 375 command: update 376 + command: urlhandlers 357 377 command: volume 358 378 OK 359 379 "#;
+37 -11
crates/mpd/src/handlers/batch.rs
··· 6 6 use super::{ 7 7 browse::{handle_listall, handle_listallinfo, handle_listfiles, handle_lsinfo}, 8 8 library::{ 9 - handle_config, handle_find_album, handle_find_artist, handle_find_title, handle_list_album, 10 - handle_list_artist, handle_list_title, handle_rescan, handle_search, handle_stats, 11 - handle_tagtypes, handle_tagtypes_clear, handle_tagtypes_enable, 9 + handle_config, handle_count, handle_find_album, handle_find_artist, handle_find_title, 10 + 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 14 }, 13 15 playback::{ 14 - handle_currentsong, handle_getvol, handle_next, handle_outputs, handle_pause, handle_play, 15 - handle_playid, handle_previous, handle_random, handle_repeat, handle_seek, handle_seekcur, 16 - handle_seekid, handle_setvol, handle_single, handle_status, handle_toggle, 16 + handle_consume, handle_currentsong, handle_disableoutput, handle_enableoutput, 17 + handle_getvol, handle_next, handle_outputs, handle_pause, handle_play, handle_playid, 18 + handle_previous, handle_random, handle_repeat, handle_seek, handle_seekcur, handle_seekid, 19 + handle_setvol, handle_single, handle_status, handle_stop, handle_toggle, 20 + handle_toggleoutput, 17 21 }, 18 22 queue::{ 19 - handle_add, handle_addid, handle_clear, handle_delete, handle_move, handle_playlistinfo, 20 - handle_shuffle, 23 + handle_add, handle_addid, handle_clear, handle_delete, handle_move, handle_moveid, 24 + handle_playlistid, handle_playlistinfo, handle_shuffle, handle_swap, handle_swapid, 21 25 }, 22 - system::handle_decoders, 26 + system::{handle_decoders, handle_notcommands, handle_ping, handle_urlhandlers}, 23 27 }; 24 28 25 29 pub async fn handle_command_list_begin( ··· 78 82 ) -> Result<String, Error> { 79 83 match command { 80 84 "play" => handle_play(ctx, request, tx.clone()).await, 85 + "stop" => handle_stop(ctx, request, tx.clone()).await, 81 86 "pause" => handle_pause(ctx, request, tx.clone()).await, 82 87 "toggle" => handle_toggle(ctx, request, tx.clone()).await, 83 88 "next" => handle_next(ctx, request, tx.clone()).await, ··· 88 93 "seekcur" => handle_seekcur(ctx, request, tx.clone()).await, 89 94 "random" => handle_random(ctx, request, tx.clone()).await, 90 95 "repeat" => handle_repeat(ctx, request, tx.clone()).await, 96 + "consume" => handle_consume(ctx, request, tx.clone()).await, 91 97 "getvol" => handle_getvol(ctx, request, tx.clone()).await, 92 98 "setvol" => handle_setvol(ctx, request, tx.clone()).await, 93 99 "volume" => handle_setvol(ctx, request, tx.clone()).await, ··· 96 102 "add" => handle_add(ctx, request, tx.clone()).await, 97 103 "addid" => handle_addid(ctx, request, tx.clone()).await, 98 104 "playlistinfo" => handle_playlistinfo(ctx, request, tx.clone()).await, 105 + "playlistid" => handle_playlistid(ctx, request, tx.clone()).await, 106 + "plchanges" => handle_playlistinfo(ctx, request, tx.clone()).await, 99 107 "delete" => handle_delete(ctx, request, tx.clone()).await, 100 108 "clear" => handle_clear(ctx, request, tx.clone()).await, 101 109 "move" => handle_move(ctx, request, tx.clone()).await, 110 + "moveid" => handle_moveid(ctx, request, tx.clone()).await, 111 + "swap" => handle_swap(ctx, request, tx.clone()).await, 112 + "swapid" => handle_swapid(ctx, request, tx.clone()).await, 102 113 "list album" => handle_list_album(ctx, request, tx.clone()).await, 114 + "list albumartist" => handle_list_artist(ctx, request, tx.clone()).await, 103 115 "list artist" => handle_list_artist(ctx, request, tx.clone()).await, 104 116 "list title" => handle_list_title(ctx, request, tx.clone()).await, 117 + "list genre" => handle_list_genre(ctx, request, tx.clone()).await, 118 + "list date" => handle_list_date(ctx, request, tx.clone()).await, 105 119 "update" => handle_rescan(ctx, request, tx.clone()).await, 106 120 "search" => handle_search(ctx, request, tx.clone()).await, 121 + "searchadd" => handle_searchadd(ctx, request, tx.clone()).await, 107 122 "rescan" => handle_rescan(ctx, request, tx.clone()).await, 123 + "count" => handle_count(ctx, request, tx.clone()).await, 124 + "findadd" => handle_findadd(ctx, request, tx.clone()).await, 108 125 "status" => handle_status(ctx, request, tx.clone()).await, 109 126 "currentsong" => handle_currentsong(ctx, request, tx.clone()).await, 110 127 "config" => handle_config(ctx, request, tx.clone()).await, ··· 112 129 "tagtypes clear" => handle_tagtypes_clear(ctx, request, tx.clone()).await, 113 130 "tagtypes enable" => handle_tagtypes_enable(ctx, request, tx.clone()).await, 114 131 "stats" => handle_stats(ctx, request, tx.clone()).await, 115 - "plchanges" => handle_playlistinfo(ctx, request, tx.clone()).await, 116 132 "outputs" => handle_outputs(ctx, request, tx.clone()).await, 133 + "enableoutput" => handle_enableoutput(ctx, request, tx.clone()).await, 134 + "disableoutput" => handle_disableoutput(ctx, request, tx.clone()).await, 135 + "toggleoutput" => handle_toggleoutput(ctx, request, tx.clone()).await, 117 136 "decoders" => handle_decoders(ctx, request, tx.clone()).await, 118 137 "lsinfo" => handle_lsinfo(ctx, request, tx.clone()).await, 119 138 "listall" => handle_listall(ctx, request, tx.clone()).await, 120 139 "listallinfo" => handle_listallinfo(ctx, request, tx.clone()).await, 121 140 "listfiles" => handle_listfiles(ctx, request, tx.clone()).await, 141 + "listplaylists" => handle_listplaylists(ctx, request, tx.clone()).await, 142 + "load" => handle_load(ctx, request, tx.clone()).await, 143 + "save" => handle_save(ctx, request, tx.clone()).await, 144 + "rm" => handle_rm(ctx, request, tx.clone()).await, 145 + "rename" => handle_rename(ctx, request, tx.clone()).await, 122 146 "find artist" => handle_find_artist(ctx, request, tx.clone()).await, 123 147 "find album" => handle_find_album(ctx, request, tx.clone()).await, 124 148 "find title" => handle_find_title(ctx, request, tx.clone()).await, 149 + "ping" => handle_ping(ctx, request, tx.clone()).await, 150 + "notcommands" => handle_notcommands(ctx, request, tx.clone()).await, 151 + "urlhandlers" => handle_urlhandlers(ctx, request, tx.clone()).await, 125 152 _ => { 126 - println!("Unhandled command: {}", request); 127 153 if !ctx.batch { 128 154 tx.send("ACK [5@0] {unhandled} unknown command\n".to_string()) 129 155 .await?;
+434 -5
crates/mpd/src/handlers/library.rs
··· 5 5 use regex::Regex; 6 6 use rockbox_library::{entity::track::Track, repo}; 7 7 use rockbox_rpc::api::rockbox::v1alpha1::{ 8 - GetAlbumsRequest, GetArtistsRequest, GetGlobalSettingsRequest, GetTracksRequest, 9 - ScanLibraryRequest, SearchRequest, 8 + CreateSavedPlaylistRequest, DeleteSavedPlaylistRequest, GetAlbumsRequest, GetArtistsRequest, 9 + GetGlobalSettingsRequest, GetSavedPlaylistsRequest, GetTracksRequest, InsertTracksRequest, 10 + PlaySavedPlaylistRequest, ScanLibraryRequest, SearchRequest, UpdateSavedPlaylistRequest, 10 11 }; 11 12 use rockbox_settings::get_music_dir; 12 13 use tokio::sync::mpsc::Sender; 13 14 14 - use crate::Context; 15 + use crate::{consts::PLAYLIST_INSERT_LAST, Context}; 16 + 17 + use super::Subsystem; 15 18 16 19 pub async fn handle_list_album( 17 20 ctx: &mut Context, ··· 51 54 52 55 pub async fn handle_list_artist( 53 56 ctx: &mut Context, 54 - _request: &str, 57 + request: &str, 55 58 tx: Sender<String>, 56 59 ) -> Result<String, Error> { 60 + let tag = if request.contains("albumartist") || request.contains("AlbumArtist") { 61 + "AlbumArtist" 62 + } else { 63 + "Artist" 64 + }; 57 65 let response = ctx.library.get_artists(GetArtistsRequest {}).await?; 58 66 let response = response.into_inner(); 59 67 let response = response 60 68 .artists 61 69 .iter() 62 - .map(|x| format!("Artist: {}\n", x.name)) 70 + .map(|x| format!("{}: {}\n", tag, x.name)) 71 + .collect::<String>(); 72 + let response = format!("{}OK\n", response); 73 + if !ctx.batch { 74 + tx.send(response.clone()).await?; 75 + } 76 + Ok(response) 77 + } 78 + 79 + pub async fn handle_list_genre( 80 + ctx: &mut Context, 81 + _request: &str, 82 + tx: Sender<String>, 83 + ) -> Result<String, Error> { 84 + let tracks = repo::track::all(ctx.pool.clone()).await?; 85 + let mut genres: Vec<String> = tracks 86 + .iter() 87 + .filter_map(|t| t.genre.clone()) 88 + .filter(|g| !g.is_empty()) 89 + .collect::<std::collections::HashSet<_>>() 90 + .into_iter() 91 + .collect(); 92 + genres.sort(); 93 + let response = genres 94 + .iter() 95 + .map(|g| format!("Genre: {}\n", g)) 96 + .collect::<String>(); 97 + let response = format!("{}OK\n", response); 98 + if !ctx.batch { 99 + tx.send(response.clone()).await?; 100 + } 101 + Ok(response) 102 + } 103 + 104 + pub async fn handle_list_date( 105 + ctx: &mut Context, 106 + _request: &str, 107 + tx: Sender<String>, 108 + ) -> Result<String, Error> { 109 + let tracks = repo::track::all(ctx.pool.clone()).await?; 110 + let mut dates: Vec<String> = tracks 111 + .iter() 112 + .filter_map(|t| t.year_string.clone()) 113 + .filter(|y| !y.is_empty()) 114 + .collect::<std::collections::HashSet<_>>() 115 + .into_iter() 116 + .collect(); 117 + dates.sort(); 118 + let response = dates 119 + .iter() 120 + .map(|d| format!("Date: {}\n", d)) 63 121 .collect::<String>(); 64 122 let response = format!("{}OK\n", response); 65 123 if !ctx.batch { ··· 442 500 }) 443 501 .collect(); 444 502 Ok(tracks) 503 + } 504 + 505 + pub async fn handle_count( 506 + ctx: &mut Context, 507 + request: &str, 508 + tx: Sender<String>, 509 + ) -> Result<String, Error> { 510 + let arg = request 511 + .splitn(2, ' ') 512 + .nth(1) 513 + .unwrap_or("") 514 + .trim() 515 + .to_string(); 516 + 517 + let tracks = if arg.is_empty() { 518 + repo::track::all(ctx.pool.clone()).await? 519 + } else { 520 + let mut parser = Parser::new(&arg); 521 + match parser.parse() { 522 + Ok(expr) => evaluate_search_expression(ctx, &expr, true).await?, 523 + Err(_) => repo::track::all(ctx.pool.clone()).await?, 524 + } 525 + }; 526 + 527 + let playtime: u64 = tracks.iter().map(|t| (t.length / 1000) as u64).sum(); 528 + let response = format!("songs: {}\nplaytime: {}\nOK\n", tracks.len(), playtime); 529 + 530 + if !ctx.batch { 531 + tx.send(response.clone()).await?; 532 + } 533 + Ok(response) 534 + } 535 + 536 + pub async fn handle_findadd( 537 + ctx: &mut Context, 538 + request: &str, 539 + tx: Sender<String>, 540 + ) -> Result<String, Error> { 541 + let arg = request 542 + .splitn(2, ' ') 543 + .nth(1) 544 + .unwrap_or("") 545 + .trim() 546 + .to_string(); 547 + 548 + let mut parser = Parser::new(&arg); 549 + let tracks = match parser.parse() { 550 + Ok(expr) => evaluate_search_expression(ctx, &expr, true).await?, 551 + Err(_) => vec![], 552 + }; 553 + 554 + for track in &tracks { 555 + ctx.playlist 556 + .insert_tracks(InsertTracksRequest { 557 + tracks: vec![track.path.clone()], 558 + position: PLAYLIST_INSERT_LAST, 559 + ..Default::default() 560 + }) 561 + .await?; 562 + } 563 + 564 + if !tracks.is_empty() { 565 + match ctx.event_sender.send(Subsystem::Playlist) { 566 + Ok(_) => {} 567 + Err(_) => {} 568 + } 569 + } 570 + 571 + if !ctx.batch { 572 + tx.send("OK\n".to_string()).await?; 573 + } 574 + Ok("OK\n".to_string()) 575 + } 576 + 577 + pub async fn handle_searchadd( 578 + ctx: &mut Context, 579 + request: &str, 580 + tx: Sender<String>, 581 + ) -> Result<String, Error> { 582 + let arg = request 583 + .splitn(2, ' ') 584 + .nth(1) 585 + .unwrap_or("") 586 + .trim() 587 + .to_string(); 588 + 589 + let mut parser = Parser::new(&arg); 590 + let tracks = match parser.parse() { 591 + Ok(expr) => evaluate_search_expression(ctx, &expr, false).await?, 592 + Err(_) => vec![], 593 + }; 594 + 595 + for track in &tracks { 596 + ctx.playlist 597 + .insert_tracks(InsertTracksRequest { 598 + tracks: vec![track.path.clone()], 599 + position: PLAYLIST_INSERT_LAST, 600 + ..Default::default() 601 + }) 602 + .await?; 603 + } 604 + 605 + if !tracks.is_empty() { 606 + match ctx.event_sender.send(Subsystem::Playlist) { 607 + Ok(_) => {} 608 + Err(_) => {} 609 + } 610 + } 611 + 612 + if !ctx.batch { 613 + tx.send("OK\n".to_string()).await?; 614 + } 615 + Ok("OK\n".to_string()) 616 + } 617 + 618 + pub async fn handle_listplaylists( 619 + ctx: &mut Context, 620 + _request: &str, 621 + tx: Sender<String>, 622 + ) -> Result<String, Error> { 623 + let response = ctx 624 + .saved_playlist 625 + .get_saved_playlists(GetSavedPlaylistsRequest { folder_id: None }) 626 + .await? 627 + .into_inner(); 628 + 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); 641 + 642 + if !ctx.batch { 643 + tx.send(response.clone()).await?; 644 + } 645 + Ok(response) 646 + } 647 + 648 + pub async fn handle_load( 649 + ctx: &mut Context, 650 + request: &str, 651 + tx: Sender<String>, 652 + ) -> Result<String, Error> { 653 + let name = request 654 + .splitn(2, ' ') 655 + .nth(1) 656 + .unwrap_or("") 657 + .trim() 658 + .trim_matches('"') 659 + .to_string(); 660 + 661 + if name.is_empty() { 662 + if !ctx.batch { 663 + tx.send("ACK [2@0] {load} missing argument\n".to_string()) 664 + .await?; 665 + } 666 + return Ok("ACK [2@0] {load} missing argument\n".to_string()); 667 + } 668 + 669 + let playlists = ctx 670 + .saved_playlist 671 + .get_saved_playlists(GetSavedPlaylistsRequest { folder_id: None }) 672 + .await? 673 + .into_inner(); 674 + 675 + let playlist = playlists.playlists.into_iter().find(|p| p.name == name); 676 + 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 + } 688 + } 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); 695 + } 696 + } 697 + 698 + if !ctx.batch { 699 + tx.send("OK\n".to_string()).await?; 700 + } 701 + Ok("OK\n".to_string()) 702 + } 703 + 704 + pub async fn handle_save( 705 + ctx: &mut Context, 706 + request: &str, 707 + tx: Sender<String>, 708 + ) -> Result<String, Error> { 709 + let name = request 710 + .splitn(2, ' ') 711 + .nth(1) 712 + .unwrap_or("") 713 + .trim() 714 + .trim_matches('"') 715 + .to_string(); 716 + 717 + if name.is_empty() { 718 + if !ctx.batch { 719 + tx.send("ACK [2@0] {save} missing argument\n".to_string()) 720 + .await?; 721 + } 722 + return Ok("ACK [2@0] {save} missing argument\n".to_string()); 723 + } 724 + 725 + let track_paths: Vec<String> = { 726 + let current_playlist = ctx.current_playlist.lock().await; 727 + current_playlist 728 + .as_ref() 729 + .map(|p| p.tracks.iter().map(|t| t.path.clone()).collect()) 730 + .unwrap_or_default() 731 + }; 732 + 733 + let track_ids: Vec<String> = { 734 + let kv = ctx.kv.lock().await; 735 + track_paths 736 + .iter() 737 + .filter_map(|path| kv.get(path).map(|t| t.id.clone())) 738 + .collect() 739 + }; 740 + 741 + ctx.saved_playlist 742 + .create_saved_playlist(CreateSavedPlaylistRequest { 743 + name, 744 + description: None, 745 + image: None, 746 + folder_id: None, 747 + track_ids, 748 + }) 749 + .await?; 750 + 751 + match ctx.event_sender.send(Subsystem::StoredPlaylist) { 752 + Ok(_) => {} 753 + Err(_) => {} 754 + } 755 + 756 + if !ctx.batch { 757 + tx.send("OK\n".to_string()).await?; 758 + } 759 + Ok("OK\n".to_string()) 760 + } 761 + 762 + pub async fn handle_rm( 763 + ctx: &mut Context, 764 + request: &str, 765 + tx: Sender<String>, 766 + ) -> Result<String, Error> { 767 + let name = request 768 + .splitn(2, ' ') 769 + .nth(1) 770 + .unwrap_or("") 771 + .trim() 772 + .trim_matches('"') 773 + .to_string(); 774 + 775 + if name.is_empty() { 776 + if !ctx.batch { 777 + tx.send("ACK [2@0] {rm} missing argument\n".to_string()) 778 + .await?; 779 + } 780 + return Ok("ACK [2@0] {rm} missing argument\n".to_string()); 781 + } 782 + 783 + let playlists = ctx 784 + .saved_playlist 785 + .get_saved_playlists(GetSavedPlaylistsRequest { folder_id: None }) 786 + .await? 787 + .into_inner(); 788 + 789 + let playlist = playlists.playlists.into_iter().find(|p| p.name == name); 790 + 791 + match playlist { 792 + Some(p) => { 793 + ctx.saved_playlist 794 + .delete_saved_playlist(DeleteSavedPlaylistRequest { id: p.id }) 795 + .await?; 796 + match ctx.event_sender.send(Subsystem::StoredPlaylist) { 797 + Ok(_) => {} 798 + Err(_) => {} 799 + } 800 + } 801 + None => { 802 + let msg = format!("ACK [50@0] {{rm}} No such playlist\n"); 803 + if !ctx.batch { 804 + tx.send(msg.clone()).await?; 805 + } 806 + return Ok(msg); 807 + } 808 + } 809 + 810 + if !ctx.batch { 811 + tx.send("OK\n".to_string()).await?; 812 + } 813 + Ok("OK\n".to_string()) 814 + } 815 + 816 + pub async fn handle_rename( 817 + ctx: &mut Context, 818 + request: &str, 819 + tx: Sender<String>, 820 + ) -> Result<String, Error> { 821 + let args: Vec<&str> = request.splitn(3, '"').collect(); 822 + let old_name = args.get(1).map(|s| s.trim()).unwrap_or("").to_string(); 823 + let new_name = args.get(3).map(|s| s.trim()).unwrap_or("").to_string(); 824 + 825 + if old_name.is_empty() || new_name.is_empty() { 826 + // fallback: space-separated 827 + let parts: Vec<&str> = request.split_whitespace().collect(); 828 + if parts.len() < 3 { 829 + if !ctx.batch { 830 + tx.send("ACK [2@0] {rename} missing arguments\n".to_string()) 831 + .await?; 832 + } 833 + return Ok("ACK [2@0] {rename} missing arguments\n".to_string()); 834 + } 835 + } 836 + 837 + let playlists = ctx 838 + .saved_playlist 839 + .get_saved_playlists(GetSavedPlaylistsRequest { folder_id: None }) 840 + .await? 841 + .into_inner(); 842 + 843 + let playlist = playlists.playlists.into_iter().find(|p| p.name == old_name); 844 + 845 + match playlist { 846 + Some(p) => { 847 + ctx.saved_playlist 848 + .update_saved_playlist(UpdateSavedPlaylistRequest { 849 + id: p.id, 850 + name: new_name, 851 + description: p.description, 852 + image: p.image, 853 + folder_id: p.folder_id, 854 + }) 855 + .await?; 856 + match ctx.event_sender.send(Subsystem::StoredPlaylist) { 857 + Ok(_) => {} 858 + Err(_) => {} 859 + } 860 + } 861 + None => { 862 + let msg = format!("ACK [50@0] {{rename}} No such playlist\n"); 863 + if !ctx.batch { 864 + tx.send(msg.clone()).await?; 865 + } 866 + return Ok(msg); 867 + } 868 + } 869 + 870 + if !ctx.batch { 871 + tx.send("OK\n".to_string()).await?; 872 + } 873 + Ok("OK\n".to_string()) 445 874 } 446 875 447 876 async fn build_file_metadata(tracks: Vec<Track>, response: &mut String) -> Result<(), Error> {
+224 -60
crates/mpd/src/handlers/playback.rs
··· 1 1 use anyhow::Error; 2 2 use rockbox_rpc::api::rockbox::v1alpha1::{ 3 - AdjustVolumeRequest, NextRequest, PauseRequest, PlayRequest, PreviousRequest, ResumeRequest, 4 - SaveSettingsRequest, StartRequest, 3 + AdjustVolumeRequest, HardStopRequest, NextRequest, PauseRequest, PlayRequest, PreviousRequest, 4 + ResumeRequest, SaveSettingsRequest, StartRequest, 5 5 }; 6 6 use tokio::sync::mpsc::Sender; 7 - use tracing::warn; 8 7 9 8 use crate::Context; 10 9 ··· 12 11 13 12 pub async fn handle_play( 14 13 ctx: &mut Context, 15 - _request: &str, 14 + request: &str, 16 15 tx: Sender<String>, 17 16 ) -> Result<String, Error> { 18 - ctx.playback.resume(ResumeRequest {}).await?; 17 + let arg = request.split_whitespace().nth(1); 18 + 19 + if let Some(pos) = arg { 20 + if let Ok(pos) = pos.trim_matches('"').parse::<i32>() { 21 + ctx.playlist 22 + .start(StartRequest { 23 + start_index: Some(pos), 24 + ..Default::default() 25 + }) 26 + .await?; 27 + } else { 28 + ctx.playback.resume(ResumeRequest {}).await?; 29 + } 30 + } else { 31 + ctx.playback.resume(ResumeRequest {}).await?; 32 + } 33 + 19 34 match ctx.event_sender.send(Subsystem::Player) { 20 35 Ok(_) => {} 21 36 Err(_) => {} ··· 28 43 Ok("OK\n".to_string()) 29 44 } 30 45 46 + pub async fn handle_stop( 47 + ctx: &mut Context, 48 + _request: &str, 49 + tx: Sender<String>, 50 + ) -> Result<String, Error> { 51 + ctx.playback.hard_stop(HardStopRequest {}).await?; 52 + match ctx.event_sender.send(Subsystem::Player) { 53 + Ok(_) => {} 54 + Err(_) => {} 55 + } 56 + if !ctx.batch { 57 + tx.send("OK\n".to_string()).await?; 58 + } 59 + Ok("OK\n".to_string()) 60 + } 61 + 31 62 pub async fn handle_pause( 32 63 ctx: &mut Context, 33 - _request: &str, 64 + request: &str, 34 65 tx: Sender<String>, 35 66 ) -> Result<String, Error> { 67 + let arg = request.split_whitespace().nth(1); 36 68 let playback_status = ctx.playback_status.lock().await; 37 69 let status = playback_status.as_ref().map(|x| x.status); 70 + drop(playback_status); 38 71 39 - match status { 40 - Some(1) => { 41 - ctx.playback.pause(PauseRequest {}).await?; 72 + match arg { 73 + Some(a) if a.trim_matches('"') == "1" => { 74 + if status == Some(1) { 75 + ctx.playback.pause(PauseRequest {}).await?; 76 + } 42 77 } 43 - Some(3) => { 44 - ctx.playback.resume(ResumeRequest {}).await?; 78 + Some(a) if a.trim_matches('"') == "0" => { 79 + if status == Some(3) { 80 + ctx.playback.resume(ResumeRequest {}).await?; 81 + } 45 82 } 46 - _ => { 47 - tx.send("ACK [2@0] {pause} no song is playing\n".to_string()) 48 - .await?; 49 - } 83 + _ => match status { 84 + Some(1) => { 85 + ctx.playback.pause(PauseRequest {}).await?; 86 + } 87 + Some(3) => { 88 + ctx.playback.resume(ResumeRequest {}).await?; 89 + } 90 + _ => { 91 + if !ctx.batch { 92 + tx.send("ACK [2@0] {pause} no song is playing\n".to_string()) 93 + .await?; 94 + } 95 + return Ok("ACK [2@0] {pause} no song is playing\n".to_string()); 96 + } 97 + }, 50 98 } 51 99 52 100 match ctx.event_sender.send(Subsystem::Player) { ··· 54 102 Err(_) => {} 55 103 } 56 104 105 + if !ctx.batch { 106 + tx.send("OK\n".to_string()).await?; 107 + } 108 + 57 109 Ok("OK\n".to_string()) 58 110 } 59 111 ··· 62 114 _request: &str, 63 115 tx: Sender<String>, 64 116 ) -> Result<String, Error> { 65 - let playback_status = ctx.playback_status.lock().await; 66 - let playback_status = playback_status.as_ref().map(|x| x.status); 117 + let playback_status = { 118 + let guard = ctx.playback_status.lock().await; 119 + guard.as_ref().map(|x| x.status) 120 + }; 67 121 68 122 match playback_status { 69 123 Some(1) => { ··· 73 127 ctx.playback.resume(ResumeRequest {}).await?; 74 128 } 75 129 _ => { 76 - tx.send("ACK [2@0] {toggle} no song is playing\n".to_string()) 77 - .await?; 130 + if !ctx.batch { 131 + tx.send("ACK [2@0] {toggle} no song is playing\n".to_string()) 132 + .await?; 133 + } 134 + return Ok("ACK [2@0] {toggle} no song is playing\n".to_string()); 78 135 } 79 136 } 80 137 if !ctx.batch { ··· 109 166 true => 1, 110 167 false => 0, 111 168 }; 169 + drop(settings); 112 170 113 171 let system_status = rockbox_sys::system::get_global_status(); 114 172 let volume = system_status.volume; 115 - // volume is between -80 db and 0 db 116 - // we need to convert it to 0-100 117 - // -80 db is 0 118 - // 0 db is 100 119 173 let volume = ((volume + 80) * 100 / 80).max(0).min(100); 120 174 121 175 let current_track = ctx.current_track.lock().await; 176 + let consume = ctx.consume.lock().await; 177 + let consume_val = if *consume { 1 } else { 0 }; 178 + drop(consume); 122 179 123 180 if current_track.is_none() { 124 181 let response = format!( 125 - "state: {}\nrepeat: {}\nsingle: 0\nrandom: {}\ntime: 0:0\nelapsed: 0\nplaylistlength: 0\nvolume: {}\naudio: 0:16:2\nbitrate: 0\nOK\n", 126 - status, repeat, random, volume, 182 + "volume: {}\nrepeat: {}\nrandom: {}\nsingle: 0\nconsume: {}\nplaylist: 0\nplaylistlength: 0\nstate: {}\nOK\n", 183 + volume, repeat, random, consume_val, status, 127 184 ); 128 185 if !ctx.batch { 129 186 tx.send(response.clone()).await?; ··· 149 206 let current_playlist = ctx.current_playlist.lock().await; 150 207 if current_playlist.is_none() { 151 208 let response = format!( 152 - "state: {}\nrepeat: {}\nsingle: {}\nrandom: {}\ntime: {}\nelapsed: {}\nduration: {}\nplaylistlength: 0\nsong: 0\nvolume: {}\naudio: {}\nbitrate: {}\nOK\n", 153 - status, repeat, single, random, time, elapsed, duration, volume, audio, bitrate, 209 + "volume: {}\nrepeat: {}\nrandom: {}\nsingle: {}\nconsume: {}\nplaylist: 0\nplaylistlength: 0\nstate: {}\nsong: 0\nelapsed: {}\ntime: {}\nduration: {}\naudio: {}\nbitrate: {}\nOK\n", 210 + volume, repeat, random, single, consume_val, status, elapsed, time, duration, audio, bitrate, 154 211 ); 155 212 if !ctx.batch { 156 213 tx.send(response.clone()).await?; ··· 163 220 let song = current_playlist.index; 164 221 165 222 let response = format!( 166 - "state: {}\nrepeat: {}\nsingle: {}\nrandom: {}\ntime: {}\nelapsed: {}\nduration: {}\nplaylist: {}\nplaylistlength: {}\nsong: {}\nsongid: {}\nvolume: {}\naudio: {}\nbitrate: {}\nnextsong: {}\nnextsongid: {}\nOK\n", 167 - status, repeat, single, random, time, elapsed, duration, playlistlength + 1, playlistlength, song, song + 1, volume, audio, bitrate, 168 - song + 1, song + 2, 223 + "volume: {}\nrepeat: {}\nrandom: {}\nsingle: {}\nconsume: {}\nplaylist: {}\nplaylistlength: {}\nstate: {}\nsong: {}\nsongid: {}\nnextsong: {}\nnextsongid: {}\ntime: {}\nelapsed: {}\nduration: {}\naudio: {}\nbitrate: {}\nOK\n", 224 + volume, repeat, random, single, consume_val, playlistlength, playlistlength, status, 225 + song, song + 1, song + 1, song + 2, 226 + time, elapsed, duration, audio, bitrate, 169 227 ); 170 228 171 229 if !ctx.batch { ··· 216 274 let arg = request.split_whitespace().nth(1); 217 275 218 276 if arg.is_none() { 219 - tx.send("ACK [2@0] {playid} incorrect arguments\n".to_string()) 220 - .await?; 277 + if !ctx.batch { 278 + tx.send("ACK [2@0] {playid} incorrect arguments\n".to_string()) 279 + .await?; 280 + } 221 281 return Ok("ACK [2@0] {playid} incorrect arguments\n".to_string()); 222 282 } 223 283 ··· 227 287 let arg = arg.parse::<i32>(); 228 288 229 289 if arg.is_err() { 230 - tx.send("ACK [2@0] {playid} incorrect arguments\n".to_string()) 231 - .await?; 290 + if !ctx.batch { 291 + tx.send("ACK [2@0] {playid} incorrect arguments\n".to_string()) 292 + .await?; 293 + } 232 294 return Ok("ACK [2@0] {playid} incorrect arguments\n".to_string()); 233 295 } 234 296 ··· 253 315 request: &str, 254 316 tx: Sender<String>, 255 317 ) -> Result<String, Error> { 256 - // TODO: Implement seek 257 - warn!("handle_seek not implemented: {}", request); 318 + let mut parts = request.split_whitespace().skip(1); 319 + let songpos = parts.next(); 320 + let time = parts.next(); 321 + 322 + match (songpos, time) { 323 + (Some(pos), Some(t)) => { 324 + let pos = pos.trim_matches('"').parse::<i32>().unwrap_or(0); 325 + let t_secs = t.trim_matches('"').parse::<i32>().unwrap_or(0); 326 + ctx.playlist 327 + .start(StartRequest { 328 + start_index: Some(pos), 329 + elapsed: Some(t_secs * 1000), 330 + ..Default::default() 331 + }) 332 + .await?; 333 + match ctx.event_sender.send(Subsystem::Player) { 334 + Ok(_) => {} 335 + Err(_) => {} 336 + } 337 + } 338 + _ => { 339 + if !ctx.batch { 340 + tx.send("ACK [2@0] {seek} incorrect arguments\n".to_string()) 341 + .await?; 342 + } 343 + return Ok("ACK [2@0] {seek} incorrect arguments\n".to_string()); 344 + } 345 + } 258 346 259 347 if !ctx.batch { 260 348 tx.send("OK\n".to_string()).await?; 261 349 } 262 - 263 350 Ok("OK\n".to_string()) 264 351 } 265 352 ··· 268 355 request: &str, 269 356 tx: Sender<String>, 270 357 ) -> Result<String, Error> { 271 - // TODO: Implement seekid 272 - warn!("handle_seekid not implemented: {}", request); 358 + let mut parts = request.split_whitespace().skip(1); 359 + let songid = parts.next(); 360 + let time = parts.next(); 361 + 362 + match (songid, time) { 363 + (Some(id), Some(t)) => { 364 + let id = id.trim_matches('"').parse::<i32>().unwrap_or(1); 365 + let t_secs = t.trim_matches('"').parse::<i32>().unwrap_or(0); 366 + ctx.playlist 367 + .start(StartRequest { 368 + start_index: Some(id - 1), 369 + elapsed: Some(t_secs * 1000), 370 + ..Default::default() 371 + }) 372 + .await?; 373 + match ctx.event_sender.send(Subsystem::Player) { 374 + Ok(_) => {} 375 + Err(_) => {} 376 + } 377 + } 378 + _ => { 379 + if !ctx.batch { 380 + tx.send("ACK [2@0] {seekid} incorrect arguments\n".to_string()) 381 + .await?; 382 + } 383 + return Ok("ACK [2@0] {seekid} incorrect arguments\n".to_string()); 384 + } 385 + } 273 386 274 387 if !ctx.batch { 275 388 tx.send("OK\n".to_string()).await?; 276 389 } 277 - 278 390 Ok("OK\n".to_string()) 279 391 } 280 392 ··· 285 397 ) -> Result<String, Error> { 286 398 let arg = request.split_whitespace().nth(1); 287 399 if arg.is_none() { 288 - tx.send("ACK [2@0] {seekcur} incorrect arguments\n".to_string()) 289 - .await?; 400 + if !ctx.batch { 401 + tx.send("ACK [2@0] {seekcur} incorrect arguments\n".to_string()) 402 + .await?; 403 + } 290 404 return Ok("ACK [2@0] {seekcur} incorrect arguments\n".to_string()); 291 405 } 292 406 ··· 327 441 328 442 ctx.settings 329 443 .save_settings(SaveSettingsRequest { 330 - playlist_shuffle: Some(arg.unwrap() == r#""1""#), 444 + playlist_shuffle: Some(arg.unwrap().trim_matches('"') == "1"), 331 445 ..Default::default() 332 446 }) 333 447 .await?; ··· 353 467 354 468 let single = ctx.single.lock().await; 355 469 356 - let repeat_mode = match arg.unwrap() { 357 - r#""0""# => Some(0), 358 - r#""1""# => match single.as_str() { 359 - r#""1""# => Some(2), 470 + let repeat_mode = match arg.unwrap().trim_matches('"') { 471 + "0" => Some(0), 472 + "1" => match single.as_str().trim_matches('"') { 473 + "1" => Some(2), 360 474 _ => Some(1), 361 475 }, 362 476 _ => { ··· 367 481 return Ok("ACK [2@0] {repeat} incorrect arguments\n".to_string()); 368 482 } 369 483 }; 484 + drop(single); 370 485 ctx.settings 371 486 .save_settings(SaveSettingsRequest { 372 487 repeat_mode, ··· 379 494 Ok("OK\n".to_string()) 380 495 } 381 496 497 + pub async fn handle_consume( 498 + ctx: &mut Context, 499 + request: &str, 500 + tx: Sender<String>, 501 + ) -> Result<String, Error> { 502 + let arg = request.split_whitespace().nth(1); 503 + if arg.is_none() { 504 + if !ctx.batch { 505 + tx.send("ACK [2@0] {consume} incorrect arguments\n".to_string()) 506 + .await?; 507 + } 508 + return Ok("ACK [2@0] {consume} incorrect arguments\n".to_string()); 509 + } 510 + let mut consume = ctx.consume.lock().await; 511 + *consume = arg.unwrap().trim_matches('"') == "1"; 512 + drop(consume); 513 + if !ctx.batch { 514 + tx.send("OK\n".to_string()).await?; 515 + } 516 + Ok("OK\n".to_string()) 517 + } 518 + 382 519 pub async fn handle_getvol( 383 520 ctx: &mut Context, 384 521 _request: &str, ··· 386 523 ) -> Result<String, Error> { 387 524 let status = rockbox_sys::system::get_global_status(); 388 525 let volume = status.volume; 389 - // volume is between -80 db and 0 db 390 - // we need to convert it to 0-100 391 - // -80 db is 0 392 - // 0 db is 100 393 526 let volume = ((volume + 80) * 100 / 80).max(0).min(100); 394 527 let response = format!("volume: {}\nOK\n", volume); 395 528 ··· 417 550 } 418 551 419 552 let new_volume = arg.unwrap().replace("\"", "").parse::<i64>().unwrap(); 420 - // volume is between 0 and 100 421 - // we need to convert it to -80 db to 0 db 422 - // 0 is -80 db 423 - // 100 is 0 db 424 553 let new_volume = ((new_volume * 80 / 100) - 80) as i32; 425 554 let steps = new_volume - volume; 426 555 ctx.sound ··· 472 601 473 602 if current_playlist.is_none() { 474 603 let response = format!( 475 - "file: {}\nTitle: {}\nArtist: {}\nAlbum: {}\nTrack: {}\nDate: {}\nTime: {}\nPos: 0\nDuration: {}\nOK\n", 604 + "file: {}\nTitle: {}\nArtist: {}\nAlbum: {}\nTrack: {}\nDate: {}\nTime: {}\nDuration: {}\nPos: 0\nId: 1\nOK\n", 476 605 current.path, 477 606 current.title, 478 607 current.artist, 479 608 current.album, 480 609 current.tracknum, 481 610 current.year, 482 - (current.elapsed / 1000) as i64, 611 + (current.length / 1000) as i64, 483 612 (current.length / 1000) as i64, 484 613 ); 485 614 if !ctx.batch { ··· 489 618 } 490 619 491 620 let current_playlist = current_playlist.as_ref().unwrap(); 621 + let pos = current_playlist.index; 492 622 let response = format!( 493 - "file: {}\nTitle: {}\nArtist: {}\nAlbum: {}\nTrack: {}\nDate: {}\nTime: {}\nPos: {}\nduration: {}\nOK\n", 623 + "file: {}\nTitle: {}\nArtist: {}\nAlbum: {}\nTrack: {}\nDate: {}\nTime: {}\nDuration: {}\nPos: {}\nId: {}\nOK\n", 494 624 current.path, 495 625 current.title, 496 626 current.artist, 497 627 current.album, 498 628 current.tracknum, 499 629 current.year, 500 - (current.elapsed / 1000) as i64, 501 - current_playlist.index, 630 + (current.length / 1000) as i64, 502 631 (current.length / 1000) as i64, 632 + pos, 633 + pos + 1, 503 634 ); 504 635 if !ctx.batch { 505 636 tx.send(response.clone()).await?; ··· 520 651 } 521 652 Ok(response) 522 653 } 654 + 655 + pub async fn handle_enableoutput( 656 + ctx: &mut Context, 657 + _request: &str, 658 + tx: Sender<String>, 659 + ) -> Result<String, Error> { 660 + if !ctx.batch { 661 + tx.send("OK\n".to_string()).await?; 662 + } 663 + Ok("OK\n".to_string()) 664 + } 665 + 666 + pub async fn handle_disableoutput( 667 + ctx: &mut Context, 668 + _request: &str, 669 + tx: Sender<String>, 670 + ) -> Result<String, Error> { 671 + if !ctx.batch { 672 + tx.send("OK\n".to_string()).await?; 673 + } 674 + Ok("OK\n".to_string()) 675 + } 676 + 677 + pub async fn handle_toggleoutput( 678 + ctx: &mut Context, 679 + _request: &str, 680 + tx: Sender<String>, 681 + ) -> Result<String, Error> { 682 + if !ctx.batch { 683 + tx.send("OK\n".to_string()).await?; 684 + } 685 + Ok("OK\n".to_string()) 686 + }
+334 -28
crates/mpd/src/handlers/queue.rs
··· 72 72 false => format!("{}/{}", music_dir, path), 73 73 }; 74 74 75 - // verify if path is a file or directory or doesn't exist 76 75 if fs::metadata(&path).is_err() { 77 76 if !ctx.batch { 78 77 tx.send("ACK [50@0] {add} No such file or directory\n".to_string()) ··· 104 103 let current_track = ctx.current_track.lock().await; 105 104 106 105 if current_track.is_none() { 106 + drop(current_track); 107 107 ctx.playlist.start(StartRequest::default()).await?; 108 108 } 109 109 ··· 135 135 let re = Regex::new(r#"^(\w+)\s+"([^"]+)"(?:\s+"?(-?\d+)"?)?$"#).unwrap(); 136 136 let captures = re.captures(request); 137 137 138 - println!("captures: {:?}", captures); 139 - 140 138 if captures.is_none() { 141 139 if !ctx.batch { 142 - tx.send("ACK [2@0] {add} missing argument\n".to_string()) 140 + tx.send("ACK [2@0] {addid} missing argument\n".to_string()) 143 141 .await?; 144 142 } 145 - return Ok("ACK [2@0] {add} missing argument\n".to_string()); 143 + return Ok("ACK [2@0] {addid} missing argument\n".to_string()); 146 144 } 147 145 let captures = captures.unwrap(); 148 146 ··· 154 152 155 153 if path.is_empty() { 156 154 if !ctx.batch { 157 - tx.send("ACK [2@0] {add} missing argument\n".to_string()) 155 + tx.send("ACK [2@0] {addid} missing argument\n".to_string()) 158 156 .await?; 159 157 } 160 - return Ok("ACK [2@0] {add} missing argument\n".to_string()); 158 + return Ok("ACK [2@0] {addid} missing argument\n".to_string()); 161 159 } 162 160 163 161 let path = match path.starts_with('/') { ··· 165 163 false => format!("{}/{}", music_dir, path), 166 164 }; 167 165 168 - // verify if path is a file or directory or doesn't exist 169 166 if fs::metadata(&path).is_err() { 170 167 if !ctx.batch { 171 - tx.send("ACK [50@0] {add} No such file or directory\n".to_string()) 168 + tx.send("ACK [50@0] {addid} No such file or directory\n".to_string()) 172 169 .await?; 173 170 } 174 - return Ok("ACK [50@0] {add} No such file or directory\n".to_string()); 175 - } 176 - 177 - if fs::metadata(&path)?.is_file() { 178 - ctx.playlist 179 - .insert_tracks(InsertTracksRequest { 180 - tracks: vec![path.clone()], 181 - position, 182 - ..Default::default() 183 - }) 184 - .await?; 171 + return Ok("ACK [50@0] {addid} No such file or directory\n".to_string()); 185 172 } 186 173 187 174 if fs::metadata(&path)?.is_dir() { 188 - // return error if directory, invalid for addid 189 175 if !ctx.batch { 190 - tx.send("ACK [2@0] {addid} invalid argument\n".to_string()) 176 + tx.send("ACK [2@0] {addid} cannot add directory; use add instead\n".to_string()) 191 177 .await?; 192 178 } 193 - return Ok("ACK [2@0] {addid} invalid argument\n".to_string()); 179 + return Ok("ACK [2@0] {addid} cannot add directory; use add instead\n".to_string()); 194 180 } 195 181 182 + let current_len = { 183 + let current_playlist = ctx.current_playlist.lock().await; 184 + current_playlist 185 + .as_ref() 186 + .map(|p| p.tracks.len()) 187 + .unwrap_or(0) 188 + }; 189 + 190 + ctx.playlist 191 + .insert_tracks(InsertTracksRequest { 192 + tracks: vec![path.clone()], 193 + position, 194 + ..Default::default() 195 + }) 196 + .await?; 197 + 196 198 let current_track = ctx.current_track.lock().await; 197 199 if current_track.is_none() { 200 + drop(current_track); 198 201 ctx.playlist.start(StartRequest::default()).await?; 199 202 } 200 203 204 + let new_id = if position == PLAYLIST_INSERT_LAST { 205 + (current_len + 1) as i32 206 + } else { 207 + position + 1 208 + }; 209 + 210 + let response = format!("Id: {}\nOK\n", new_id); 211 + 201 212 if !ctx.batch { 202 - tx.send("OK\n".to_string()).await?; 213 + tx.send(response.clone()).await?; 203 214 } 204 215 205 216 match ctx.event_sender.send(Subsystem::Playlist) { ··· 207 218 Err(_) => {} 208 219 } 209 220 210 - Ok("OK\n".to_string()) 221 + Ok(response) 211 222 } 212 223 213 224 pub async fn handle_playlistinfo( ··· 257 268 Ok(response) 258 269 } 259 270 271 + pub async fn handle_playlistid( 272 + ctx: &mut Context, 273 + request: &str, 274 + tx: Sender<String>, 275 + ) -> Result<String, Error> { 276 + let arg = request.split_whitespace().nth(1); 277 + let id = arg.and_then(|x| x.trim_matches('"').parse::<usize>().ok()); 278 + 279 + let current_playlist = ctx.current_playlist.lock().await; 280 + 281 + if current_playlist.is_none() { 282 + if !ctx.batch { 283 + tx.send("OK\n".to_string()).await?; 284 + } 285 + return Ok("OK\n".to_string()); 286 + } 287 + 288 + let current_playlist = current_playlist.as_ref().unwrap(); 289 + 290 + let response = match id { 291 + Some(id) => { 292 + let idx = id.saturating_sub(1); 293 + if let Some(x) = current_playlist.tracks.get(idx) { 294 + format!( 295 + "file: {}\nTitle: {}\nArtist: {}\nAlbum: {}\nTime: {}\nDuration: {}\nPos: {}\nDisc: {}\nDate: {}\nAlbumArtist: {}\nTrack: {}\nId: {}\n", 296 + x.path, x.title, x.artist, x.album, 297 + (x.length / 1000) as u32, (x.length / 1000) as u32, 298 + idx, x.discnum, x.year_string, x.album_artist, x.tracknum, id 299 + ) 300 + } else { 301 + return { 302 + let msg = format!("ACK [50@0] {{playlistid}} No such song\n"); 303 + if !ctx.batch { 304 + tx.send(msg.clone()).await?; 305 + } 306 + Ok(msg) 307 + }; 308 + } 309 + } 310 + None => current_playlist 311 + .tracks 312 + .iter() 313 + .enumerate() 314 + .map(|(idx, x)| { 315 + format!( 316 + "file: {}\nTitle: {}\nArtist: {}\nAlbum: {}\nTime: {}\nDuration: {}\nPos: {}\nDisc: {}\nDate: {}\nAlbumArtist: {}\nTrack: {}\nId: {}\n", 317 + x.path, x.title, x.artist, x.album, 318 + (x.length / 1000) as u32, (x.length / 1000) as u32, 319 + idx, x.discnum, x.year_string, x.album_artist, x.tracknum, idx + 1 320 + ) 321 + }) 322 + .collect::<String>(), 323 + }; 324 + 325 + let response = format!("{}OK\n", response); 326 + 327 + if !ctx.batch { 328 + tx.send(response.clone()).await?; 329 + } 330 + 331 + Ok(response) 332 + } 333 + 260 334 pub async fn handle_deleteid( 261 335 ctx: &mut Context, 262 336 request: &str, ··· 314 388 let arg = arg.trim(); 315 389 let arg = arg.trim_matches('"'); 316 390 if arg.contains(':') { 317 - // get the range 318 391 let range: Vec<i32> = arg.split(':').map(|x| x.parse::<i32>().unwrap()).collect(); 319 - let positions: Vec<i32> = (range[0]..=range[1]).collect(); 392 + let positions: Vec<i32> = (range[0]..range[1]).collect(); 320 393 ctx.playlist 321 394 .remove_tracks(RemoveTracksRequest { positions }) 322 395 .await?; 323 396 if !ctx.batch { 324 397 tx.send("OK\n".to_string()).await?; 398 + } 399 + match ctx.event_sender.send(Subsystem::Playlist) { 400 + Ok(_) => {} 401 + Err(_) => {} 325 402 } 326 403 return Ok("OK\n".to_string()); 327 404 } ··· 375 452 request: &str, 376 453 tx: Sender<String>, 377 454 ) -> Result<String, Error> { 378 - println!("{}", request); 455 + let mut parts = request.split_whitespace().skip(1); 456 + let from_str = parts.next(); 457 + let to_str = parts.next(); 458 + 459 + if from_str.is_none() || to_str.is_none() { 460 + if !ctx.batch { 461 + tx.send("ACK [2@0] {move} incorrect arguments\n".to_string()) 462 + .await?; 463 + } 464 + return Ok("ACK [2@0] {move} incorrect arguments\n".to_string()); 465 + } 466 + 467 + let from = from_str.unwrap().trim_matches('"').parse::<i32>(); 468 + let to = to_str.unwrap().trim_matches('"').parse::<i32>(); 469 + 470 + if from.is_err() || to.is_err() { 471 + if !ctx.batch { 472 + tx.send("ACK [2@0] {move} invalid argument\n".to_string()) 473 + .await?; 474 + } 475 + return Ok("ACK [2@0] {move} invalid argument\n".to_string()); 476 + } 477 + 478 + let from = from.unwrap(); 479 + let to = to.unwrap(); 480 + 481 + let track_path = { 482 + let current_playlist = ctx.current_playlist.lock().await; 483 + current_playlist 484 + .as_ref() 485 + .and_then(|p| p.tracks.get(from as usize)) 486 + .map(|t| t.path.clone()) 487 + }; 488 + 489 + if let Some(path) = track_path { 490 + ctx.playlist 491 + .remove_tracks(RemoveTracksRequest { 492 + positions: vec![from], 493 + }) 494 + .await?; 495 + let insert_pos = if to > from { to - 1 } else { to }; 496 + ctx.playlist 497 + .insert_tracks(InsertTracksRequest { 498 + tracks: vec![path], 499 + position: insert_pos, 500 + ..Default::default() 501 + }) 502 + .await?; 503 + 504 + match ctx.event_sender.send(Subsystem::Playlist) { 505 + Ok(_) => {} 506 + Err(_) => {} 507 + } 508 + } 509 + 379 510 if !ctx.batch { 380 511 tx.send("OK\n".to_string()).await?; 381 512 } 382 513 Ok("OK\n".to_string()) 383 514 } 515 + 516 + pub async fn handle_moveid( 517 + ctx: &mut Context, 518 + request: &str, 519 + tx: Sender<String>, 520 + ) -> Result<String, Error> { 521 + let mut parts = request.split_whitespace().skip(1); 522 + let id_str = parts.next(); 523 + let to_str = parts.next(); 524 + 525 + if id_str.is_none() || to_str.is_none() { 526 + if !ctx.batch { 527 + tx.send("ACK [2@0] {moveid} incorrect arguments\n".to_string()) 528 + .await?; 529 + } 530 + return Ok("ACK [2@0] {moveid} incorrect arguments\n".to_string()); 531 + } 532 + 533 + let id = id_str.unwrap().trim_matches('"').parse::<i32>(); 534 + let to = to_str.unwrap().trim_matches('"').parse::<i32>(); 535 + 536 + if id.is_err() || to.is_err() { 537 + if !ctx.batch { 538 + tx.send("ACK [2@0] {moveid} invalid argument\n".to_string()) 539 + .await?; 540 + } 541 + return Ok("ACK [2@0] {moveid} invalid argument\n".to_string()); 542 + } 543 + 544 + let from = id.unwrap() - 1; 545 + let to = to.unwrap(); 546 + 547 + let track_path = { 548 + let current_playlist = ctx.current_playlist.lock().await; 549 + current_playlist 550 + .as_ref() 551 + .and_then(|p| p.tracks.get(from as usize)) 552 + .map(|t| t.path.clone()) 553 + }; 554 + 555 + if let Some(path) = track_path { 556 + ctx.playlist 557 + .remove_tracks(RemoveTracksRequest { 558 + positions: vec![from], 559 + }) 560 + .await?; 561 + let insert_pos = if to > from { to - 1 } else { to }; 562 + ctx.playlist 563 + .insert_tracks(InsertTracksRequest { 564 + tracks: vec![path], 565 + position: insert_pos, 566 + ..Default::default() 567 + }) 568 + .await?; 569 + 570 + match ctx.event_sender.send(Subsystem::Playlist) { 571 + Ok(_) => {} 572 + Err(_) => {} 573 + } 574 + } 575 + 576 + if !ctx.batch { 577 + tx.send("OK\n".to_string()).await?; 578 + } 579 + Ok("OK\n".to_string()) 580 + } 581 + 582 + pub async fn handle_swap( 583 + ctx: &mut Context, 584 + request: &str, 585 + tx: Sender<String>, 586 + ) -> Result<String, Error> { 587 + let mut parts = request.split_whitespace().skip(1); 588 + let pos1 = parts 589 + .next() 590 + .and_then(|x| x.trim_matches('"').parse::<i32>().ok()); 591 + let pos2 = parts 592 + .next() 593 + .and_then(|x| x.trim_matches('"').parse::<i32>().ok()); 594 + 595 + if pos1.is_none() || pos2.is_none() { 596 + if !ctx.batch { 597 + tx.send("ACK [2@0] {swap} incorrect arguments\n".to_string()) 598 + .await?; 599 + } 600 + return Ok("ACK [2@0] {swap} incorrect arguments\n".to_string()); 601 + } 602 + 603 + let pos1 = pos1.unwrap(); 604 + let pos2 = pos2.unwrap(); 605 + 606 + let (path1, path2) = { 607 + let current_playlist = ctx.current_playlist.lock().await; 608 + let p1 = current_playlist 609 + .as_ref() 610 + .and_then(|p| p.tracks.get(pos1 as usize)) 611 + .map(|t| t.path.clone()); 612 + let p2 = current_playlist 613 + .as_ref() 614 + .and_then(|p| p.tracks.get(pos2 as usize)) 615 + .map(|t| t.path.clone()); 616 + (p1, p2) 617 + }; 618 + 619 + if let (Some(path1), Some(path2)) = (path1, path2) { 620 + let (lo, hi, lo_path, hi_path) = if pos1 < pos2 { 621 + (pos1, pos2, path1, path2) 622 + } else { 623 + (pos2, pos1, path2, path1) 624 + }; 625 + // Remove higher index first to avoid position shifts 626 + ctx.playlist 627 + .remove_tracks(RemoveTracksRequest { 628 + positions: vec![hi], 629 + }) 630 + .await?; 631 + ctx.playlist 632 + .insert_tracks(InsertTracksRequest { 633 + tracks: vec![lo_path], 634 + position: hi, 635 + ..Default::default() 636 + }) 637 + .await?; 638 + ctx.playlist 639 + .remove_tracks(RemoveTracksRequest { 640 + positions: vec![lo], 641 + }) 642 + .await?; 643 + ctx.playlist 644 + .insert_tracks(InsertTracksRequest { 645 + tracks: vec![hi_path], 646 + position: lo, 647 + ..Default::default() 648 + }) 649 + .await?; 650 + 651 + match ctx.event_sender.send(Subsystem::Playlist) { 652 + Ok(_) => {} 653 + Err(_) => {} 654 + } 655 + } 656 + 657 + if !ctx.batch { 658 + tx.send("OK\n".to_string()).await?; 659 + } 660 + Ok("OK\n".to_string()) 661 + } 662 + 663 + pub async fn handle_swapid( 664 + ctx: &mut Context, 665 + request: &str, 666 + tx: Sender<String>, 667 + ) -> Result<String, Error> { 668 + let mut parts = request.split_whitespace().skip(1); 669 + let id1 = parts 670 + .next() 671 + .and_then(|x| x.trim_matches('"').parse::<i32>().ok()); 672 + let id2 = parts 673 + .next() 674 + .and_then(|x| x.trim_matches('"').parse::<i32>().ok()); 675 + 676 + match (id1, id2) { 677 + (Some(id1), Some(id2)) => { 678 + let modified = format!("swap {} {}", id1 - 1, id2 - 1); 679 + handle_swap(ctx, &modified, tx).await 680 + } 681 + _ => { 682 + if !ctx.batch { 683 + tx.send("ACK [2@0] {swapid} incorrect arguments\n".to_string()) 684 + .await?; 685 + } 686 + Ok("ACK [2@0] {swapid} incorrect arguments\n".to_string()) 687 + } 688 + } 689 + }
+35
crates/mpd/src/handlers/system.rs
··· 75 75 } 76 76 Ok("OK\n".to_string()) 77 77 } 78 + 79 + pub async fn handle_ping( 80 + ctx: &mut Context, 81 + _request: &str, 82 + tx: Sender<String>, 83 + ) -> Result<String, Error> { 84 + if !ctx.batch { 85 + tx.send("OK\n".to_string()).await?; 86 + } 87 + Ok("OK\n".to_string()) 88 + } 89 + 90 + pub async fn handle_notcommands( 91 + ctx: &mut Context, 92 + _request: &str, 93 + tx: Sender<String>, 94 + ) -> Result<String, Error> { 95 + let response = "OK\n".to_string(); 96 + if !ctx.batch { 97 + tx.send(response.clone()).await?; 98 + } 99 + Ok(response) 100 + } 101 + 102 + pub async fn handle_urlhandlers( 103 + ctx: &mut Context, 104 + _request: &str, 105 + tx: Sender<String>, 106 + ) -> Result<String, Error> { 107 + let response = "handler: file://\nOK\n".to_string(); 108 + if !ctx.batch { 109 + tx.send(response.clone()).await?; 110 + } 111 + Ok(response) 112 + }
+70 -28
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, 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, 6 + handle_config, handle_count, handle_find, handle_find_album, handle_find_artist, 7 + 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 11 }, 10 12 playback::{ 11 - handle_currentsong, handle_getvol, handle_next, handle_outputs, handle_pause, handle_play, 12 - handle_playid, handle_previous, handle_random, handle_repeat, handle_seek, handle_seekcur, 13 - handle_seekid, handle_setvol, handle_single, handle_status, handle_toggle, 13 + handle_consume, handle_currentsong, handle_disableoutput, handle_enableoutput, 14 + handle_getvol, handle_next, handle_outputs, handle_pause, handle_play, handle_playid, 15 + handle_previous, handle_random, handle_repeat, handle_seek, handle_seekcur, handle_seekid, 16 + handle_setvol, handle_single, handle_status, handle_stop, handle_toggle, 17 + handle_toggleoutput, 14 18 }, 15 19 queue::{ 16 20 handle_add, handle_addid, handle_clear, handle_delete, handle_deleteid, handle_move, 17 - handle_playlistinfo, handle_shuffle, 21 + handle_moveid, handle_playlistid, handle_playlistinfo, handle_shuffle, handle_swap, 22 + handle_swapid, 18 23 }, 19 - system::{handle_binarylimit, handle_commands, handle_decoders, handle_idle, handle_noidle}, 24 + system::{ 25 + handle_binarylimit, handle_commands, handle_decoders, handle_idle, handle_noidle, 26 + handle_notcommands, handle_ping, handle_urlhandlers, 27 + }, 20 28 Subsystem, 21 29 }; 22 30 use kv::{build_tracks_kv, KV}; ··· 27 35 use rockbox_library::{create_connection_pool, entity, repo}; 28 36 use rockbox_rpc::api::rockbox::v1alpha1::{ 29 37 library_service_client::LibraryServiceClient, playback_service_client::PlaybackServiceClient, 30 - playlist_service_client::PlaylistServiceClient, settings_service_client::SettingsServiceClient, 31 - sound_service_client::SoundServiceClient, system_service_client::SystemServiceClient, 32 - GetCurrentRequest, GetGlobalStatusRequest, PlaylistResumeRequest, 38 + playlist_service_client::PlaylistServiceClient, 39 + saved_playlist_service_client::SavedPlaylistServiceClient, 40 + settings_service_client::SettingsServiceClient, sound_service_client::SoundServiceClient, 41 + system_service_client::SystemServiceClient, GetCurrentRequest, GetGlobalStatusRequest, 42 + PlaylistResumeRequest, 33 43 }; 34 44 use rockbox_sys::types::user_settings::UserSettings; 35 45 use sqlx::{Pool, Sqlite}; ··· 41 51 }; 42 52 use tokio_stream::StreamExt; 43 53 use tonic::transport::Channel; 54 + use tracing::{debug, warn}; 44 55 45 56 pub mod consts; 46 57 pub mod dir; ··· 55 66 pub sound: SoundServiceClient<Channel>, 56 67 pub playlist: PlaylistServiceClient<Channel>, 57 68 pub system: SystemServiceClient<Channel>, 69 + pub saved_playlist: SavedPlaylistServiceClient<Channel>, 58 70 pub single: Arc<Mutex<String>>, 71 + pub consume: Arc<Mutex<bool>>, 59 72 pub batch: bool, 60 73 pub event_sender: broadcast::Sender<Subsystem>, 61 74 pub event_receiver: Arc<Mutex<broadcast::Receiver<Subsystem>>>, ··· 90 103 match handle_client(context, stream).await { 91 104 Ok(_) => {} 92 105 Err(e) => { 93 - eprintln!("Error: {}", e); 106 + warn!("MPD client error: {}", e); 94 107 } 95 108 } 96 109 }); ··· 122 135 } 123 136 let request = String::from_utf8_lossy(&buf[..n]); 124 137 let command = parse_command(&request)?; 125 - println!("request: {}", request); 138 + debug!("mpd request: {}", request.trim()); 126 139 127 140 match command.as_str() { 128 141 "play" => handle_play(&mut ctx, &request, tx.clone()).await?, 142 + "stop" => handle_stop(&mut ctx, &request, tx.clone()).await?, 129 143 "pause" => handle_pause(&mut ctx, &request, tx.clone()).await?, 130 144 "toggle" => handle_toggle(&mut ctx, &request, tx.clone()).await?, 131 145 "next" => handle_next(&mut ctx, &request, tx.clone()).await?, ··· 136 150 "seekcur" => handle_seekcur(&mut ctx, &request, tx.clone()).await?, 137 151 "random" => handle_random(&mut ctx, &request, tx.clone()).await?, 138 152 "repeat" => handle_repeat(&mut ctx, &request, tx.clone()).await?, 153 + "consume" => handle_consume(&mut ctx, &request, tx.clone()).await?, 139 154 "getvol" => handle_getvol(&mut ctx, &request, tx.clone()).await?, 140 155 "setvol" => handle_setvol(&mut ctx, &request, tx.clone()).await?, 141 156 "volume" => handle_setvol(&mut ctx, &request, tx.clone()).await?, ··· 145 160 "addid" => handle_addid(&mut ctx, &request, tx.clone()).await?, 146 161 "deleteid" => handle_deleteid(&mut ctx, &request, tx.clone()).await?, 147 162 "playlistinfo" => handle_playlistinfo(&mut ctx, &request, tx.clone()).await?, 163 + "playlistid" => handle_playlistid(&mut ctx, &request, tx.clone()).await?, 164 + "plchanges" => handle_playlistinfo(&mut ctx, &request, tx.clone()).await?, 148 165 "delete" => handle_delete(&mut ctx, &request, tx.clone()).await?, 149 166 "clear" => handle_clear(&mut ctx, &request, tx.clone()).await?, 150 167 "move" => handle_move(&mut ctx, &request, tx.clone()).await?, 168 + "moveid" => handle_moveid(&mut ctx, &request, tx.clone()).await?, 169 + "swap" => handle_swap(&mut ctx, &request, tx.clone()).await?, 170 + "swapid" => handle_swapid(&mut ctx, &request, tx.clone()).await?, 151 171 "list album" => handle_list_album(&mut ctx, &request, tx.clone()).await?, 152 172 "list albumartist" => handle_list_artist(&mut ctx, &request, tx.clone()).await?, 153 173 "list artist" => handle_list_artist(&mut ctx, &request, tx.clone()).await?, 154 174 "list title" => handle_list_title(&mut ctx, &request, tx.clone()).await?, 175 + "list genre" => handle_list_genre(&mut ctx, &request, tx.clone()).await?, 176 + "list date" => handle_list_date(&mut ctx, &request, tx.clone()).await?, 155 177 "update" => handle_rescan(&mut ctx, &request, tx.clone()).await?, 156 178 "search" => handle_search(&mut ctx, &request, tx.clone()).await?, 179 + "searchadd" => handle_searchadd(&mut ctx, &request, tx.clone()).await?, 157 180 "rescan" => handle_rescan(&mut ctx, &request, tx.clone()).await?, 181 + "count" => handle_count(&mut ctx, &request, tx.clone()).await?, 182 + "findadd" => handle_findadd(&mut ctx, &request, tx.clone()).await?, 158 183 "status" => handle_status(&mut ctx, &request, tx.clone()).await?, 159 184 "currentsong" => handle_currentsong(&mut ctx, &request, tx.clone()).await?, 160 185 "config" => handle_config(&mut ctx, &request, tx.clone()).await?, 161 186 "tagtypes " => handle_tagtypes(&mut ctx, &request, tx.clone()).await?, 162 - "tagtypes clear" => handle_clear(&mut ctx, &request, tx.clone()).await?, 187 + "tagtypes clear" => handle_tagtypes_clear(&mut ctx, &request, tx.clone()).await?, 163 188 "tagtypes enable" => handle_tagtypes_enable(&mut ctx, &request, tx.clone()).await?, 164 189 "stats" => handle_stats(&mut ctx, &request, tx.clone()).await?, 165 - "plchanges" => handle_playlistinfo(&mut ctx, &request, tx.clone()).await?, 166 190 "outputs" => handle_outputs(&mut ctx, &request, tx.clone()).await?, 191 + "enableoutput" => handle_enableoutput(&mut ctx, &request, tx.clone()).await?, 192 + "disableoutput" => handle_disableoutput(&mut ctx, &request, tx.clone()).await?, 193 + "toggleoutput" => handle_toggleoutput(&mut ctx, &request, tx.clone()).await?, 167 194 "idle" => handle_idle(&mut ctx, &request, tx.clone()).await?, 168 195 "noidle" => handle_noidle(&mut ctx, &request, tx.clone()).await?, 169 196 "decoders" => handle_decoders(&mut ctx, &request, tx.clone()).await?, ··· 171 198 "listall" => handle_listall(&mut ctx, &request, tx.clone()).await?, 172 199 "listallinfo" => handle_listallinfo(&mut ctx, &request, tx.clone()).await?, 173 200 "listfiles" => handle_listfiles(&mut ctx, &request, tx.clone()).await?, 201 + "listplaylists" => handle_listplaylists(&mut ctx, &request, tx.clone()).await?, 202 + "load" => handle_load(&mut ctx, &request, tx.clone()).await?, 203 + "save" => handle_save(&mut ctx, &request, tx.clone()).await?, 204 + "rm" => handle_rm(&mut ctx, &request, tx.clone()).await?, 205 + "rename" => handle_rename(&mut ctx, &request, tx.clone()).await?, 174 206 "find artist" => handle_find_artist(&mut ctx, &request, tx.clone()).await?, 175 207 "find album" => handle_find_album(&mut ctx, &request, tx.clone()).await?, 176 208 "find title" => handle_find_title(&mut ctx, &request, tx.clone()).await?, 177 209 "binarylimit" => handle_binarylimit(&mut ctx, &request, tx.clone()).await?, 210 + "ping" => handle_ping(&mut ctx, &request, tx.clone()).await?, 211 + "notcommands" => handle_notcommands(&mut ctx, &request, tx.clone()).await?, 212 + "urlhandlers" => handle_urlhandlers(&mut ctx, &request, tx.clone()).await?, 178 213 "commands" => handle_commands(&mut ctx, &request, tx.clone()).await?, 179 214 "command_list_begin" => { 180 215 handle_command_list_begin(&mut ctx, &request, tx.clone()).await? ··· 182 217 "command_list_ok_begin" => { 183 218 handle_command_list_ok_begin(&mut ctx, &request, tx.clone()).await? 184 219 } 220 + "close" => break, 185 221 _ => { 186 222 if command.starts_with("find ") { 187 223 handle_find(&mut ctx, &request, tx.clone()).await?; 188 - return Ok(()); 224 + continue; 189 225 } 190 - println!("Unhandled command: {}", command); 191 - println!("Unhandled request: {}", request); 192 - tx.send("ACK [5@0] {unhandled} unknown command\n".to_string()) 193 - .await?; 194 - "ACK [5@0] {unhandled} unknown command\n".to_string() 226 + debug!("unhandled MPD command: {}", command); 227 + tx.send(format!( 228 + "ACK [5@0] {{{}}} unknown command \"{}\"\n", 229 + command, command 230 + )) 231 + .await?; 232 + format!("ACK [5@0] {{{}}} unknown command\n", command) 195 233 } 196 234 }; 197 235 } ··· 202 240 let command = request.split_whitespace().next().unwrap_or_default(); 203 241 204 242 if command == "list" { 205 - // should parse the next word, and return "list album" or "list artist" or "list title" 206 243 let r#type = request.split_whitespace().nth(1).unwrap_or_default(); 207 244 return Ok(format!("list {}", r#type.to_lowercase())); 208 245 } ··· 234 271 let sound = SoundServiceClient::connect(url.clone()).await?; 235 272 let playlist = PlaylistServiceClient::connect(url.clone()).await?; 236 273 let system = SystemServiceClient::connect(url.clone()).await?; 274 + let saved_playlist = SavedPlaylistServiceClient::connect(url.clone()).await?; 237 275 238 276 let (event_sender, event_receiver) = broadcast::channel(16); 239 277 ··· 244 282 sound, 245 283 playlist, 246 284 system, 247 - single: Arc::new(Mutex::new("\"0\"".to_string())), 285 + saved_playlist, 286 + single: Arc::new(Mutex::new("0".to_string())), 287 + consume: match ctx { 288 + Some(ref ctx) => ctx.consume.clone(), 289 + None => Arc::new(Mutex::new(false)), 290 + }, 248 291 batch, 249 292 event_sender: match ctx { 250 293 Some(ref ctx) => ctx.clone().event_sender, ··· 304 347 while let Some(playlist) = rt.block_on(subscription.next()) { 305 348 let mut current_playlist = rt.block_on(ctx_clone.current_playlist.lock()); 306 349 307 - // verify if current_playlist index is different from playlist index 308 350 if (current_playlist.is_some() 309 351 && current_playlist.as_ref().unwrap().index != playlist.index) 310 352 || current_playlist.is_none() ··· 313 355 match ctx.event_sender.send(Subsystem::Playlist) { 314 356 Ok(_) => {} 315 357 Err(e) => { 316 - eprintln!("Error: {}", e) 358 + warn!("playlist event send error: {}", e) 317 359 } 318 360 } 319 361 } ··· 328 370 329 371 while let Some(status) = rt.block_on(subscription.next()) { 330 372 let mut playback_status = rt.block_on(another_ctx.playback_status.lock()); 331 - // verify if playback_status status is different from status status 332 373 if playback_status.is_some() 333 374 && playback_status.as_ref().unwrap().status != status.status 334 375 { ··· 336 377 match ctx.event_sender.send(Subsystem::Player) { 337 378 Ok(_) => {} 338 379 Err(e) => { 339 - eprintln!("Error: {}", e) 380 + warn!("player event send error: {}", e) 340 381 } 341 382 } 342 383 } ··· 363 404 if playback_status.is_some() { 364 405 status = playback_status.as_ref().unwrap().status; 365 406 } 407 + drop(playback_status); 366 408 367 409 if response.resume_index > -1 && status != 1 { 368 410 ctx.playlist ··· 399 441 track.album_art = metadata.album_art; 400 442 track.album_id = Some(metadata.album_id); 401 443 track.artist_id = Some(metadata.artist_id); 402 - SimpleBroker::publish(track); 444 + rockbox_graphql::simplebroker::SimpleBroker::publish(track); 403 445 } 404 446 } 405 447