Compare commits
2 Commits
main
...
db-selecti
Author | SHA1 | Date | |
---|---|---|---|
f674b0b0b1
|
|||
493995c1f2
|
src
commands
common
@ -46,6 +46,7 @@ impl Command for Find {
|
|||||||
fn parse_response(
|
fn parse_response(
|
||||||
parts: ResponseAttributes<'_>,
|
parts: ResponseAttributes<'_>,
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
) -> Result<Self::Response, ResponseParserError> {
|
||||||
|
// db_selection_print
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,11 @@ use crate::commands::{
|
|||||||
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
Command, Request, RequestParserError, RequestParserResult, ResponseAttributes,
|
||||||
ResponseParserError,
|
ResponseParserError,
|
||||||
};
|
};
|
||||||
|
use crate::common::DbSelectionPrintResponse;
|
||||||
|
|
||||||
pub struct ListAllInfo;
|
pub struct ListAllInfo;
|
||||||
|
|
||||||
// TODO: This is supposed to be a tree-like structure, with directories containing files and playlists
|
pub type ListAllInfoResponse = Vec<DbSelectionPrintResponse>;
|
||||||
// in addition to the metadata of each entry
|
|
||||||
pub type ListAllInfoResponse = Vec<String>;
|
|
||||||
|
|
||||||
impl Command for ListAllInfo {
|
impl Command for ListAllInfo {
|
||||||
type Response = ListAllInfoResponse;
|
type Response = ListAllInfoResponse;
|
||||||
@ -30,6 +29,6 @@ impl Command for ListAllInfo {
|
|||||||
fn parse_response(
|
fn parse_response(
|
||||||
parts: ResponseAttributes<'_>,
|
parts: ResponseAttributes<'_>,
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
) -> Result<Self::Response, ResponseParserError> {
|
||||||
unimplemented!()
|
DbSelectionPrintResponse::parse(parts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ impl Command for ListFiles {
|
|||||||
fn parse_response(
|
fn parse_response(
|
||||||
parts: ResponseAttributes<'_>,
|
parts: ResponseAttributes<'_>,
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
) -> Result<Self::Response, ResponseParserError> {
|
||||||
|
// db_selection_print
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ impl Command for LsInfo {
|
|||||||
fn parse_response(
|
fn parse_response(
|
||||||
parts: ResponseAttributes<'_>,
|
parts: ResponseAttributes<'_>,
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
) -> Result<Self::Response, ResponseParserError> {
|
||||||
|
// db_selection_print
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,7 @@ impl Command for Search {
|
|||||||
fn parse_response(
|
fn parse_response(
|
||||||
parts: ResponseAttributes<'_>,
|
parts: ResponseAttributes<'_>,
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
) -> Result<Self::Response, ResponseParserError> {
|
||||||
|
// db_selection_print
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@ impl Command for ListPlaylists {
|
|||||||
fn parse_response(
|
fn parse_response(
|
||||||
parts: ResponseAttributes<'_>,
|
parts: ResponseAttributes<'_>,
|
||||||
) -> Result<Self::Response, ResponseParserError> {
|
) -> Result<Self::Response, ResponseParserError> {
|
||||||
|
// playlist: string
|
||||||
|
// Last-Modified: mtime?
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
mod absolute_relative_song_position;
|
mod absolute_relative_song_position;
|
||||||
mod audio;
|
mod audio;
|
||||||
mod bool_or_oneshot;
|
mod bool_or_oneshot;
|
||||||
|
mod db_directory_info;
|
||||||
|
mod db_playlist_info;
|
||||||
|
mod db_selection_print;
|
||||||
|
mod db_song_info;
|
||||||
mod group_type;
|
mod group_type;
|
||||||
mod one_or_range;
|
mod one_or_range;
|
||||||
mod replay_gain_mode_mode;
|
mod replay_gain_mode_mode;
|
||||||
@ -15,6 +19,10 @@ mod window_range;
|
|||||||
pub use absolute_relative_song_position::AbsouluteRelativeSongPosition;
|
pub use absolute_relative_song_position::AbsouluteRelativeSongPosition;
|
||||||
pub use audio::Audio;
|
pub use audio::Audio;
|
||||||
pub use bool_or_oneshot::BoolOrOneshot;
|
pub use bool_or_oneshot::BoolOrOneshot;
|
||||||
|
pub use db_directory_info::DbDirectoryInfo;
|
||||||
|
pub use db_playlist_info::DbPlaylistInfo;
|
||||||
|
pub use db_selection_print::DbSelectionPrintResponse;
|
||||||
|
pub use db_song_info::DbSongInfo;
|
||||||
pub use group_type::GroupType;
|
pub use group_type::GroupType;
|
||||||
pub use one_or_range::OneOrRange;
|
pub use one_or_range::OneOrRange;
|
||||||
pub use replay_gain_mode_mode::ReplayGainModeMode;
|
pub use replay_gain_mode_mode::ReplayGainModeMode;
|
||||||
|
30
src/common/types/db_directory_info.rs
Normal file
30
src/common/types/db_directory_info.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::commands::{
|
||||||
|
get_optional_property, get_property, ResponseAttributes, ResponseParserError,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::Path;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct DbDirectoryInfo {
|
||||||
|
pub directory: Path,
|
||||||
|
// TODO: parse this
|
||||||
|
pub last_modified: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DbDirectoryInfo {
|
||||||
|
pub fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
|
||||||
|
let parts: HashMap<_, _> = parts.into();
|
||||||
|
|
||||||
|
let directory = get_property!(parts, "directory", Text).to_owned();
|
||||||
|
let last_modified =
|
||||||
|
get_optional_property!(parts, "Last-Modified", Text).map(|s| s.to_owned());
|
||||||
|
Ok(DbDirectoryInfo {
|
||||||
|
directory,
|
||||||
|
last_modified,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
31
src/common/types/db_playlist_info.rs
Normal file
31
src/common/types/db_playlist_info.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::commands::{
|
||||||
|
get_optional_property, get_property, ResponseAttributes, ResponseParserError,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::PlaylistName;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct DbPlaylistInfo {
|
||||||
|
pub playlist: PlaylistName,
|
||||||
|
// TODO: parse this
|
||||||
|
pub last_modified: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DbPlaylistInfo {
|
||||||
|
pub fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
|
||||||
|
let parts: HashMap<_, _> = parts.into();
|
||||||
|
|
||||||
|
let playlist = get_property!(parts, "playlist", Text).to_owned();
|
||||||
|
let last_modified =
|
||||||
|
get_optional_property!(parts, "Last-Modified", Text).map(|s| s.to_owned());
|
||||||
|
|
||||||
|
Ok(DbPlaylistInfo {
|
||||||
|
playlist,
|
||||||
|
last_modified,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
86
src/common/types/db_selection_print.rs
Normal file
86
src/common/types/db_selection_print.rs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::commands::{ResponseAttributes, ResponseParserError};
|
||||||
|
|
||||||
|
use super::{DbDirectoryInfo, DbPlaylistInfo, DbSongInfo};
|
||||||
|
|
||||||
|
// TODO: transform to a tree structure?
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum DbSelectionPrintResponse {
|
||||||
|
Directory(DbDirectoryInfo),
|
||||||
|
Song(DbSongInfo),
|
||||||
|
Playlist(DbPlaylistInfo),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DbSelectionPrintResponse {
|
||||||
|
pub fn parse(
|
||||||
|
parts: ResponseAttributes<'_>,
|
||||||
|
) -> Result<Vec<DbSelectionPrintResponse>, ResponseParserError> {
|
||||||
|
debug_assert!(!parts.is_empty());
|
||||||
|
let vec: Vec<_> = parts.into();
|
||||||
|
|
||||||
|
vec.into_iter()
|
||||||
|
.fold(Vec::new(), |mut acc, (key, value)| {
|
||||||
|
if ["directory", "file", "playlist"].contains(&key) {
|
||||||
|
acc.push((key, HashMap::new()));
|
||||||
|
}
|
||||||
|
let result = acc.last_mut().unwrap().1.insert(key, value);
|
||||||
|
debug_assert_eq!(result, None);
|
||||||
|
acc
|
||||||
|
})
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, attrs)| match key {
|
||||||
|
"directory" => DbDirectoryInfo::parse(attrs.into())
|
||||||
|
.map(DbSelectionPrintResponse::Directory),
|
||||||
|
"file" => {
|
||||||
|
DbSongInfo::parse(attrs.into()).map(DbSelectionPrintResponse::Song)
|
||||||
|
}
|
||||||
|
"playlist" => DbPlaylistInfo::parse(attrs.into())
|
||||||
|
.map(DbSelectionPrintResponse::Playlist),
|
||||||
|
p => Err(ResponseParserError::UnexpectedProperty(p)),
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<DbSelectionPrintResponse>, ResponseParserError<'_>>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::commands::*;
|
||||||
|
|
||||||
|
const INPUT: &str = r#"
|
||||||
|
directory: Albums
|
||||||
|
Last-Modified: 2023-05-15T17:25:12Z
|
||||||
|
directory: Albums/Name - Album
|
||||||
|
Last-Modified: 2022-12-31T17:40:04Z
|
||||||
|
file: Albums/Artist - Title.mp3
|
||||||
|
Last-Modified: 2022-12-31T02:43:04Z
|
||||||
|
Added: 2025-01-18T18:44:26Z
|
||||||
|
Format: 44100:16:2
|
||||||
|
Title: Title
|
||||||
|
Artist: Artist
|
||||||
|
Album: Album
|
||||||
|
AlbumArtist: Artist
|
||||||
|
Composer: Composer
|
||||||
|
Genre: Pop, Rock
|
||||||
|
Track: 1
|
||||||
|
Disc: 1
|
||||||
|
Date: 2022-04-27
|
||||||
|
Time: 341
|
||||||
|
duration: 341.463
|
||||||
|
playlist: Playlists/Playlist
|
||||||
|
Last-Modified: 2021-10-31T13:57:44Z
|
||||||
|
playlist: Playlists/Playlist 2
|
||||||
|
OK
|
||||||
|
"#;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_db_selection_print_response() {
|
||||||
|
let parts = ResponseAttributes::new(INPUT.trim()).unwrap();
|
||||||
|
let result = DbSelectionPrintResponse::parse(parts);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
}
|
68
src/common/types/db_song_info.rs
Normal file
68
src/common/types/db_song_info.rs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::commands::{expect_property_type, ResponseAttributes, ResponseParserError};
|
||||||
|
|
||||||
|
use super::{Path, PlaylistName, Seconds, Tag, TimeInterval};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct DbSongInfo {
|
||||||
|
pub file: Path,
|
||||||
|
pub range: Option<TimeInterval>,
|
||||||
|
// TODO: parse this
|
||||||
|
pub last_modified: Option<String>,
|
||||||
|
// TODO: parse this
|
||||||
|
pub added: Option<String>,
|
||||||
|
pub format: Option<String>,
|
||||||
|
pub tags: Vec<Tag>,
|
||||||
|
pub time: Option<Seconds>,
|
||||||
|
pub duration: Option<f64>,
|
||||||
|
pub playlist: Option<PlaylistName>,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! remove_and_parse_next_optional_property {
|
||||||
|
($parts:ident, $name:literal, $variant:ident) => {{
|
||||||
|
let prop =
|
||||||
|
crate::commands::_expect_property_type!({ $parts.remove($name) }, $name, $variant);
|
||||||
|
crate::commands::_parse_optional_property_type!($name, prop)
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DbSongInfo {
|
||||||
|
pub fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
|
||||||
|
let mut parts: HashMap<_, _> = parts.into();
|
||||||
|
let file: Path = match remove_and_parse_next_optional_property!(parts, "file", Text) {
|
||||||
|
Some(f) => f,
|
||||||
|
None => return Err(ResponseParserError::MissingProperty("file")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let range = remove_and_parse_next_optional_property!(parts, "Range", Text);
|
||||||
|
let last_modified = remove_and_parse_next_optional_property!(parts, "Last-Modified", Text);
|
||||||
|
let added = remove_and_parse_next_optional_property!(parts, "Added", Text);
|
||||||
|
let format = remove_and_parse_next_optional_property!(parts, "Format", Text);
|
||||||
|
let time = remove_and_parse_next_optional_property!(parts, "Time", Text);
|
||||||
|
let duration = remove_and_parse_next_optional_property!(parts, "Duration", Text);
|
||||||
|
let playlist = remove_and_parse_next_optional_property!(parts, "Playlist", Text);
|
||||||
|
|
||||||
|
let tags = parts
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, value)| {
|
||||||
|
let value = expect_property_type!(Some(value), key, Text).to_string();
|
||||||
|
Ok(Tag::new(key.to_string(), value))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<Tag>, ResponseParserError<'_>>>()?;
|
||||||
|
|
||||||
|
Ok(DbSongInfo {
|
||||||
|
file,
|
||||||
|
range,
|
||||||
|
last_modified,
|
||||||
|
added,
|
||||||
|
format,
|
||||||
|
tags,
|
||||||
|
time,
|
||||||
|
duration,
|
||||||
|
playlist,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user