From c81109490805cde60dd407718aad02814dff8a3f Mon Sep 17 00:00:00 2001 From: h7x4 Date: Sun, 7 Dec 2025 21:33:02 +0900 Subject: [PATCH] commands: implement all database selection responses --- src/commands/music_database/find.rs | 17 ++- src/commands/music_database/listall.rs | 132 +++++++++++++++++- src/commands/music_database/listallinfo.rs | 91 +++++++++++- src/commands/music_database/listfiles.rs | 64 ++++++++- src/commands/music_database/lsinfo.rs | 98 ++++++++----- src/commands/music_database/search.rs | 17 ++- .../stored_playlists/listplaylists.rs | 1 - 7 files changed, 375 insertions(+), 45 deletions(-) diff --git a/src/commands/music_database/find.rs b/src/commands/music_database/find.rs index 3fec6d73..75a85e98 100644 --- a/src/commands/music_database/find.rs +++ b/src/commands/music_database/find.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; use crate::{ + DbSelectionPrintResponse, DbSongInfo, commands::{Command, RequestParserError, ResponseParserError}, common::types::{Sort, WindowRange}, filter::Filter, @@ -18,7 +19,7 @@ pub struct FindRequest { } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct FindResponse {} +pub struct FindResponse(Vec); impl Command for Find { type Request = FindRequest; @@ -76,6 +77,18 @@ impl Command for Find { fn parse_response( parts: ResponseAttributes<'_>, ) -> Result> { - unimplemented!() + DbSelectionPrintResponse::parse(parts)? + .into_iter() + .map(|i| match i { + DbSelectionPrintResponse::Song(db_song_info) => Ok(db_song_info), + DbSelectionPrintResponse::Directory(_db_directory_info) => { + Err(ResponseParserError::UnexpectedProperty("directory")) + } + DbSelectionPrintResponse::Playlist(_db_playlist_info) => { + Err(ResponseParserError::UnexpectedProperty("playlist")) + } + }) + .collect::, ResponseParserError<'_>>>() + .map(FindResponse) } } diff --git a/src/commands/music_database/listall.rs b/src/commands/music_database/listall.rs index be708b61..a23519be 100644 --- a/src/commands/music_database/listall.rs +++ b/src/commands/music_database/listall.rs @@ -1,4 +1,5 @@ use crate::{ + DbSelectionPrintResponse, commands::{Command, RequestParserError, ResponseParserError}, common::types::Uri, request_tokenizer::RequestTokenizer, @@ -8,7 +9,7 @@ use crate::{ pub struct ListAll; // TODO: This is supposed to be a tree-like structure, with directories containing files and playlists -pub type ListAllResponse = Vec; +pub type ListAllResponse = Vec; impl Command for ListAll { type Request = Option; @@ -40,6 +41,133 @@ impl Command for ListAll { fn parse_response( parts: ResponseAttributes<'_>, ) -> Result> { - unimplemented!() + + + DbSelectionPrintResponse::parse(parts) + } +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use crate::{DbDirectoryInfo, DbPlaylistInfo, DbSongInfo}; + + use super::*; + + use indoc::indoc; + + use pretty_assertions::assert_eq; + + #[test] + fn test_parse_response() { + let response = indoc! {" + directory: albums + directory: albums/a + file: albums/a/song1.mp3 + file: albums/a/song2.mp3 + file: albums/a/song3.mp3 + playlist: albums/a/album a.m3u8 + directory: albums/b + file: albums/b/song1.mp3 + file: albums/b/song2.mp3 + file: albums/b/song3.mp3 + playlist: albums/b/album b.m3u8 + OK + "}; + + let result = ListAll::parse_raw_response(response); + + assert_eq!( + result, + Ok(vec![ + DbSelectionPrintResponse::Directory(DbDirectoryInfo { + directory: PathBuf::from("albums"), + last_modified: None + }), + DbSelectionPrintResponse::Directory(DbDirectoryInfo { + directory: PathBuf::from("albums/a"), + last_modified: None + }), + DbSelectionPrintResponse::Song(DbSongInfo { + file: PathBuf::from("albums/a/song1.mp3"), + range: None, + last_modified: None, + added: None, + format: None, + tags: vec![], + time: None, + duration: None, + playlist: None + }), + DbSelectionPrintResponse::Song(DbSongInfo { + file: PathBuf::from("albums/a/song2.mp3"), + range: None, + last_modified: None, + added: None, + format: None, + tags: vec![], + time: None, + duration: None, + playlist: None + }), + DbSelectionPrintResponse::Song(DbSongInfo { + file: PathBuf::from("albums/a/song3.mp3"), + range: None, + last_modified: None, + added: None, + format: None, + tags: vec![], + time: None, + duration: None, + playlist: None + }), + DbSelectionPrintResponse::Playlist(DbPlaylistInfo { + playlist: PathBuf::from("albums/a/album a.m3u8"), + last_modified: None + }), + DbSelectionPrintResponse::Directory(DbDirectoryInfo { + directory: PathBuf::from("albums/b"), + last_modified: None + }), + DbSelectionPrintResponse::Song(DbSongInfo { + file: PathBuf::from("albums/b/song1.mp3"), + range: None, + last_modified: None, + added: None, + format: None, + tags: vec![], + time: None, + duration: None, + playlist: None + }), + DbSelectionPrintResponse::Song(DbSongInfo { + file: PathBuf::from("albums/b/song2.mp3"), + range: None, + last_modified: None, + added: None, + format: None, + tags: vec![], + time: None, + duration: None, + playlist: None + }), + DbSelectionPrintResponse::Song(DbSongInfo { + file: PathBuf::from("albums/b/song3.mp3"), + range: None, + last_modified: None, + added: None, + format: None, + tags: vec![], + time: None, + duration: None, + playlist: None + }), + DbSelectionPrintResponse::Playlist(DbPlaylistInfo { + playlist: PathBuf::from("albums/b/album b.m3u8"), + last_modified: None + }) + ]) + ) } } diff --git a/src/commands/music_database/listallinfo.rs b/src/commands/music_database/listallinfo.rs index 1fa0de74..e0516943 100644 --- a/src/commands/music_database/listallinfo.rs +++ b/src/commands/music_database/listallinfo.rs @@ -1,4 +1,5 @@ use crate::{ + DbSelectionPrintResponse, commands::{Command, RequestParserError, ResponseParserError}, common::types::Uri, request_tokenizer::RequestTokenizer, @@ -7,9 +8,7 @@ use crate::{ pub struct ListAllInfo; -// TODO: This is supposed to be a tree-like structure, with directories containing files and playlists -// in addition to the metadata of each entry -pub type ListAllInfoResponse = Vec; +pub type ListAllInfoResponse = Vec; impl Command for ListAllInfo { type Request = Option; @@ -41,6 +40,90 @@ impl Command for ListAllInfo { fn parse_response( parts: ResponseAttributes<'_>, ) -> Result> { - unimplemented!() + + + DbSelectionPrintResponse::parse(parts) + } +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use crate::{DbDirectoryInfo, DbPlaylistInfo, DbSongInfo, Tag}; + + use super::*; + + use indoc::indoc; + + use pretty_assertions::assert_eq; + + #[test] + fn test_parse_response() { + let response = indoc! {" + directory: albums + Last-Modified: 2024-10-01T10:00:00Z + directory: albums/a + Last-Modified: 2024-10-01T10:00:00Z + file: albums/a/song1.mp3 + Last-Modified: 2022-12-31T09:00:00Z + Added: 2021-12-31T09:00:00Z + Format: 44100:16:2 + Title: Song A + Artist: Artist A + Album: Album A + AlbumArtist: Artist A + Composer: Artist A + Genre: Pop + Track: 1 + Disc: 1 + Date: 2020-01-01 + Time: 360 + duration: 360.123 + playlist: albums/a/album a.m3u8 + Last-Modified: 2022-12-31T09:00:00Z + OK + "}; + + let result = ListAllInfo::parse_raw_response(response); + + assert_eq!( + result, + Ok(vec![ + DbSelectionPrintResponse::Directory(DbDirectoryInfo { + directory: PathBuf::from("albums"), + last_modified: Some("2024-10-01T10:00:00Z".to_string()), + }), + DbSelectionPrintResponse::Directory(DbDirectoryInfo { + directory: PathBuf::from("albums/a"), + last_modified: Some("2024-10-01T10:00:00Z".to_string()), + }), + DbSelectionPrintResponse::Song(DbSongInfo { + file: PathBuf::from("albums/a/song1.mp3"), + range: None, + last_modified: Some("2022-12-31T09:00:00Z".to_string()), + added: Some("2021-12-31T09:00:00Z".to_string()), + format: Some("44100:16:2".to_string()), + tags: vec![ + Tag::Album("Album A".to_string()), + Tag::AlbumArtist("Artist A".to_string()), + Tag::Artist("Artist A".to_string()), + Tag::Composer("Artist A".to_string()), + Tag::Date("2020-01-01".to_string()), + Tag::Disc("1".to_string()), + Tag::Genre("Pop".to_string()), + Tag::Title("Song A".to_string()), + Tag::Track("1".to_string()), + ], + time: Some(360), + duration: Some(360.123), + playlist: None, + }), + DbSelectionPrintResponse::Playlist(DbPlaylistInfo { + playlist: PathBuf::from("albums/a/album a.m3u8"), + last_modified: Some("2022-12-31T09:00:00Z".to_string()) + }) + ]), + ) } } diff --git a/src/commands/music_database/listfiles.rs b/src/commands/music_database/listfiles.rs index a984b320..aa206da0 100644 --- a/src/commands/music_database/listfiles.rs +++ b/src/commands/music_database/listfiles.rs @@ -1,4 +1,5 @@ use crate::{ + DbDirectoryInfo, DbSelectionPrintResponse, commands::{Command, RequestParserError, ResponseParserError}, common::types::Uri, request_tokenizer::RequestTokenizer, @@ -8,7 +9,7 @@ use crate::{ pub struct ListFiles; // TODO: fix this type -pub type ListFilesResponse = Vec; +pub type ListFilesResponse = Vec; impl Command for ListFiles { type Request = Option; @@ -40,6 +41,65 @@ impl Command for ListFiles { fn parse_response( parts: ResponseAttributes<'_>, ) -> Result> { - unimplemented!() + + + // TODO: debug assert only toplevel dirs + + DbSelectionPrintResponse::parse(parts)? + .into_iter() + .map(|i| match i { + DbSelectionPrintResponse::Directory(db_directory_info) => Ok(db_directory_info), + DbSelectionPrintResponse::Song(_db_song_info) => { + Err(ResponseParserError::UnexpectedProperty("file")) + } + DbSelectionPrintResponse::Playlist(_db_playlist_info) => { + Err(ResponseParserError::UnexpectedProperty("playlist")) + } + }) + .collect::>>() + } +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use super::*; + + use indoc::indoc; + + use pretty_assertions::assert_eq; + + #[test] + fn test_parse_response() { + let response = indoc! {" + directory: albums + Last-Modified: 2024-10-01T10:00:00Z + directory: playlists + Last-Modified: 2023-10-01T10:00:00Z + directory: Some Other Music + Last-Modified: 2022-10-01T10:00:00Z + OK + "}; + + let result = ListFiles::parse_raw_response(response); + + assert_eq!( + result, + Ok(vec![ + DbDirectoryInfo { + directory: PathBuf::from("albums"), + last_modified: Some("2024-10-01T10:00:00Z".to_string()), + }, + DbDirectoryInfo { + directory: PathBuf::from("playlists"), + last_modified: Some("2023-10-01T10:00:00Z".to_string()), + }, + DbDirectoryInfo { + directory: PathBuf::from("Some Other Music"), + last_modified: Some("2022-10-01T10:00:00Z".to_string()), + } + ]) + ) } } diff --git a/src/commands/music_database/lsinfo.rs b/src/commands/music_database/lsinfo.rs index de9898ab..95db079c 100644 --- a/src/commands/music_database/lsinfo.rs +++ b/src/commands/music_database/lsinfo.rs @@ -1,21 +1,15 @@ -use serde::{Deserialize, Serialize}; use crate::{ + DbSelectionPrintResponse, commands::{Command, RequestParserError, ResponseParserError}, common::types::Uri, request_tokenizer::RequestTokenizer, - response_tokenizer::{ResponseAttributes, expect_property_type}, + response_tokenizer::ResponseAttributes, }; pub struct LsInfo; -pub type LsInfoResponse = Vec; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct LsInfoResponseEntry { - playlist: String, - last_modified: Option, -} +pub type LsInfoResponse = Vec; impl Command for LsInfo { type Request = Option; @@ -46,30 +40,70 @@ impl Command for LsInfo { fn parse_response( parts: ResponseAttributes<'_>, ) -> Result> { - let parts: Vec<_> = parts.into_vec()?; - let mut playlists = Vec::new(); + - for (key, value) in parts { - match key { - "playlist" => { - playlists.push(LsInfoResponseEntry { - playlist: expect_property_type!(Some(value), "playlist", Text).to_string(), - last_modified: None, - }); - } - "Last-Modified" => { - if let Some(last) = playlists.last_mut() { - last.last_modified = Some( - expect_property_type!(Some(value), "Last-Modified", Text).to_string(), - ); - } else { - return Err(ResponseParserError::UnexpectedProperty("Last-Modified")); - } - } - _ => return Err(ResponseParserError::UnexpectedProperty(key)), - } - } + // TODO: debug_assert no song variants + // debug_assert!( + // result.iter(). - Ok(playlists) + DbSelectionPrintResponse::parse(parts) + } +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use crate::{DbDirectoryInfo, DbPlaylistInfo}; + + use super::*; + + use indoc::indoc; + + use pretty_assertions::assert_eq; + + #[test] + fn test_parse_response() { + let response = indoc! {" + directory: albums + Last-Modified: 2024-10-01T10:00:00Z + directory: albums/a + Last-Modified: 2024-10-01T10:00:00Z + playlist: albums/a/album a.m3u8 + Last-Modified: 2022-12-31T09:00:00Z + directory: albums/b + Last-Modified: 2023-10-01T10:00:00Z + playlist: albums/b/album b.m3u8 + Last-Modified: 2021-12-31T09:00:00Z + OK + "}; + + let result = LsInfo::parse_raw_response(response); + + assert_eq!( + result, + Ok(vec![ + DbSelectionPrintResponse::Directory(DbDirectoryInfo { + directory: PathBuf::from("albums"), + last_modified: Some("2024-10-01T10:00:00Z".to_string()) + }), + DbSelectionPrintResponse::Directory(DbDirectoryInfo { + directory: PathBuf::from("albums/a"), + last_modified: Some("2024-10-01T10:00:00Z".to_string()) + }), + DbSelectionPrintResponse::Playlist(DbPlaylistInfo { + playlist: PathBuf::from("albums/a/album a.m3u8"), + last_modified: Some("2022-12-31T09:00:00Z".to_string()) + }), + DbSelectionPrintResponse::Directory(DbDirectoryInfo { + directory: PathBuf::from("albums/b"), + last_modified: Some("2023-10-01T10:00:00Z".to_string()) + }), + DbSelectionPrintResponse::Playlist(DbPlaylistInfo { + playlist: PathBuf::from("albums/b/album b.m3u8"), + last_modified: Some("2021-12-31T09:00:00Z".to_string()) + }) + ]) + ); } } diff --git a/src/commands/music_database/search.rs b/src/commands/music_database/search.rs index 7f4596ca..79a8f144 100644 --- a/src/commands/music_database/search.rs +++ b/src/commands/music_database/search.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; use crate::{ + DbSelectionPrintResponse, DbSongInfo, commands::{Command, RequestParserError, ResponseParserError}, common::types::{Sort, WindowRange}, filter::Filter, @@ -18,7 +19,7 @@ pub struct SearchRequest { } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct SearchResponse {} +pub struct SearchResponse(Vec); impl Command for Search { type Request = SearchRequest; @@ -76,6 +77,18 @@ impl Command for Search { fn parse_response( parts: ResponseAttributes<'_>, ) -> Result> { - unimplemented!() + DbSelectionPrintResponse::parse(parts)? + .into_iter() + .map(|i| match i { + DbSelectionPrintResponse::Song(db_song_info) => Ok(db_song_info), + DbSelectionPrintResponse::Directory(_db_directory_info) => { + Err(ResponseParserError::UnexpectedProperty("directory")) + } + DbSelectionPrintResponse::Playlist(_db_playlist_info) => { + Err(ResponseParserError::UnexpectedProperty("playlist")) + } + }) + .collect::, ResponseParserError<'_>>>() + .map(SearchResponse) } } diff --git a/src/commands/stored_playlists/listplaylists.rs b/src/commands/stored_playlists/listplaylists.rs index 1f984001..9a9ce75a 100644 --- a/src/commands/stored_playlists/listplaylists.rs +++ b/src/commands/stored_playlists/listplaylists.rs @@ -114,6 +114,5 @@ mod tests { }, ]) ); ->>>>>>> a0f4105 (commands/listplaylists: implement) } }