commands: implement all database selection responses

This commit is contained in:
2025-12-07 21:33:02 +09:00
parent 267fd0e2e9
commit c811094908
7 changed files with 375 additions and 45 deletions

View File

@@ -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<DbSongInfo>);
impl Command for Find {
type Request = FindRequest;
@@ -76,6 +77,18 @@ impl Command for Find {
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
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::<Result<Vec<DbSongInfo>, ResponseParserError<'_>>>()
.map(FindResponse)
}
}

View File

@@ -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<String>;
pub type ListAllResponse = Vec<DbSelectionPrintResponse>;
impl Command for ListAll {
type Request = Option<Uri>;
@@ -40,6 +41,133 @@ impl Command for ListAll {
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
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
})
])
)
}
}

View File

@@ -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<String>;
pub type ListAllInfoResponse = Vec<DbSelectionPrintResponse>;
impl Command for ListAllInfo {
type Request = Option<Uri>;
@@ -41,6 +40,90 @@ impl Command for ListAllInfo {
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
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())
})
]),
)
}
}

View File

@@ -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<String>;
pub type ListFilesResponse = Vec<DbDirectoryInfo>;
impl Command for ListFiles {
type Request = Option<Uri>;
@@ -40,6 +41,65 @@ impl Command for ListFiles {
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
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::<Result<Self::Response, ResponseParserError<'_>>>()
}
}
#[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()),
}
])
)
}
}

View File

@@ -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<LsInfoResponseEntry>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LsInfoResponseEntry {
playlist: String,
last_modified: Option<String>,
}
pub type LsInfoResponse = Vec<DbSelectionPrintResponse>;
impl Command for LsInfo {
type Request = Option<Uri>;
@@ -46,30 +40,70 @@ impl Command for LsInfo {
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
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())
})
])
);
}
}

View File

@@ -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<DbSongInfo>);
impl Command for Search {
type Request = SearchRequest;
@@ -76,6 +77,18 @@ impl Command for Search {
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
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::<Result<Vec<DbSongInfo>, ResponseParserError<'_>>>()
.map(SearchResponse)
}
}

View File

@@ -114,6 +114,5 @@ mod tests {
},
])
);
>>>>>>> a0f4105 (commands/listplaylists: implement)
}
}