common/types: add db selection print types
This commit is contained in:
@@ -2,6 +2,10 @@ mod absolute_relative_song_position;
|
||||
mod audio;
|
||||
mod bool_or_oneshot;
|
||||
mod channel_name;
|
||||
mod db_directory_info;
|
||||
mod db_playlist_info;
|
||||
mod db_selection_print;
|
||||
mod db_song_info;
|
||||
mod group_type;
|
||||
mod one_or_range;
|
||||
mod replay_gain_mode_mode;
|
||||
@@ -20,6 +24,10 @@ pub use absolute_relative_song_position::AbsouluteRelativeSongPosition;
|
||||
pub use audio::Audio;
|
||||
pub use bool_or_oneshot::BoolOrOneshot;
|
||||
pub use channel_name::ChannelName;
|
||||
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 one_or_range::OneOrRange;
|
||||
pub use replay_gain_mode_mode::ReplayGainModeMode;
|
||||
|
||||
44
src/common/types/db_directory_info.rs
Normal file
44
src/common/types/db_directory_info.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
commands::ResponseParserError,
|
||||
response_tokenizer::{
|
||||
GenericResponseValue, ResponseAttributes, get_optional_property, get_property,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct DbDirectoryInfo {
|
||||
pub directory: PathBuf,
|
||||
// TODO: parse this
|
||||
pub last_modified: Option<String>,
|
||||
}
|
||||
|
||||
impl DbDirectoryInfo {
|
||||
pub fn is_full(&self) -> bool {
|
||||
self.last_modified.is_some()
|
||||
}
|
||||
|
||||
pub fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
|
||||
let parts: HashMap<_, _> = parts.into_map()?;
|
||||
Self::parse_map(parts)
|
||||
}
|
||||
|
||||
pub(crate) fn parse_map<'a>(
|
||||
parts: HashMap<&str, GenericResponseValue<'a>>,
|
||||
) -> Result<Self, ResponseParserError<'a>> {
|
||||
let directory = get_property!(parts, "directory", Text);
|
||||
let directory = directory
|
||||
.parse()
|
||||
.map_err(|_| ResponseParserError::InvalidProperty("directory", directory))?;
|
||||
|
||||
let last_modified =
|
||||
get_optional_property!(parts, "Last-Modified", Text).map(|s| s.to_owned());
|
||||
Ok(DbDirectoryInfo {
|
||||
directory,
|
||||
last_modified,
|
||||
})
|
||||
}
|
||||
}
|
||||
44
src/common/types/db_playlist_info.rs
Normal file
44
src/common/types/db_playlist_info.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
commands::ResponseParserError,
|
||||
response_tokenizer::{
|
||||
GenericResponseValue, ResponseAttributes, get_optional_property, get_property,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct DbPlaylistInfo {
|
||||
pub playlist: PathBuf,
|
||||
// TODO: parse this
|
||||
pub last_modified: Option<String>,
|
||||
}
|
||||
|
||||
impl DbPlaylistInfo {
|
||||
pub fn is_full(&self) -> bool {
|
||||
self.last_modified.is_some()
|
||||
}
|
||||
|
||||
pub fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
|
||||
let parts: HashMap<_, _> = parts.into_map()?;
|
||||
Self::parse_map(parts)
|
||||
}
|
||||
|
||||
pub(crate) fn parse_map<'a>(
|
||||
parts: HashMap<&str, GenericResponseValue<'a>>,
|
||||
) -> Result<Self, ResponseParserError<'a>> {
|
||||
let playlist = get_property!(parts, "playlist", Text);
|
||||
let playlist = playlist
|
||||
.parse()
|
||||
.map_err(|_| ResponseParserError::InvalidProperty("playlist", playlist))?;
|
||||
let last_modified =
|
||||
get_optional_property!(parts, "Last-Modified", Text).map(|s| s.to_owned());
|
||||
|
||||
Ok(DbPlaylistInfo {
|
||||
playlist,
|
||||
last_modified,
|
||||
})
|
||||
}
|
||||
}
|
||||
93
src/common/types/db_selection_print.rs
Normal file
93
src/common/types/db_selection_print.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{commands::ResponseParserError, response_tokenizer::ResponseAttributes};
|
||||
|
||||
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 is_full(&self) -> bool {
|
||||
match self {
|
||||
DbSelectionPrintResponse::Directory(info) => info.is_full(),
|
||||
DbSelectionPrintResponse::Song(info) => info.is_full(),
|
||||
DbSelectionPrintResponse::Playlist(info) => info.is_full(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(
|
||||
parts: ResponseAttributes<'_>,
|
||||
) -> Result<Vec<DbSelectionPrintResponse>, ResponseParserError<'_>> {
|
||||
debug_assert!(!parts.is_empty());
|
||||
let vec: Vec<_> = parts.into_vec()?;
|
||||
|
||||
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_map(attrs).map(DbSelectionPrintResponse::Directory)
|
||||
}
|
||||
"file" => DbSongInfo::parse_map(attrs).map(DbSelectionPrintResponse::Song),
|
||||
"playlist" => {
|
||||
DbPlaylistInfo::parse_map(attrs).map(DbSelectionPrintResponse::Playlist)
|
||||
}
|
||||
p => Err(ResponseParserError::UnexpectedProperty(p)),
|
||||
})
|
||||
.collect::<Result<Vec<DbSelectionPrintResponse>, ResponseParserError<'_>>>()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const INPUT: &str = indoc::indoc! {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());
|
||||
let result = DbSelectionPrintResponse::parse(parts);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
106
src/common/types/db_song_info.rs
Normal file
106
src/common/types/db_song_info.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
commands::ResponseParserError,
|
||||
response_tokenizer::{GenericResponseValue, ResponseAttributes, expect_property_type},
|
||||
};
|
||||
|
||||
use super::{PlaylistName, Seconds, Tag, TimeInterval};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct DbSongInfo {
|
||||
pub file: PathBuf,
|
||||
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::response_tokenizer::_expect_property_type!(
|
||||
{ $parts.remove($name) },
|
||||
$name,
|
||||
$variant
|
||||
);
|
||||
crate::response_tokenizer::_parse_optional_property_type!($name, prop)
|
||||
}};
|
||||
}
|
||||
|
||||
impl DbSongInfo {
|
||||
pub fn is_full(&self) -> bool {
|
||||
self.range.is_some()
|
||||
|| self.last_modified.is_some()
|
||||
|| self.added.is_some()
|
||||
|| self.format.is_some()
|
||||
|| !self.tags.is_empty()
|
||||
|| self.time.is_some()
|
||||
|| self.duration.is_some()
|
||||
|| self.playlist.is_some()
|
||||
}
|
||||
|
||||
pub fn parse(parts: ResponseAttributes<'_>) -> Result<Self, ResponseParserError<'_>> {
|
||||
let parts: HashMap<_, _> = parts.into_map()?;
|
||||
Self::parse_map(parts)
|
||||
}
|
||||
|
||||
pub(crate) fn parse_map<'a>(
|
||||
mut parts: HashMap<&'a str, GenericResponseValue<'a>>,
|
||||
) -> Result<Self, ResponseParserError<'a>> {
|
||||
let file: PathBuf = match remove_and_parse_next_optional_property!(parts, "file", Text) {
|
||||
Some(f) => f,
|
||||
None => return Err(ResponseParserError::MissingProperty("file")),
|
||||
};
|
||||
|
||||
if parts.is_empty() {
|
||||
return Ok(DbSongInfo {
|
||||
file,
|
||||
range: None,
|
||||
last_modified: None,
|
||||
added: None,
|
||||
format: None,
|
||||
tags: Vec::new(),
|
||||
time: None,
|
||||
duration: None,
|
||||
playlist: None,
|
||||
});
|
||||
}
|
||||
|
||||
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 mut 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<'_>>>()?;
|
||||
tags.sort_unstable();
|
||||
|
||||
Ok(DbSongInfo {
|
||||
file,
|
||||
range,
|
||||
last_modified,
|
||||
added,
|
||||
format,
|
||||
tags,
|
||||
time,
|
||||
duration,
|
||||
playlist,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user