separate structs from main logic
This commit is contained in:
parent
e5f6a0c1db
commit
000eb9945a
112
src/main.rs
112
src/main.rs
|
@ -1,92 +1,19 @@
|
||||||
use serde::de;
|
use serde::de;
|
||||||
use serde::de::value::MapDeserializer;
|
use serde::de::value::MapDeserializer;
|
||||||
use serde::forward_to_deserialize_any;
|
use serde::forward_to_deserialize_any;
|
||||||
use serde::Deserialize;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt;
|
|
||||||
use std::time::Duration;
|
|
||||||
mod error;
|
mod error;
|
||||||
use error::{MpdResult,Error};
|
use error::{Error, MpdResult};
|
||||||
|
mod structs;
|
||||||
|
pub use structs::Track;
|
||||||
|
|
||||||
|
/// some unprintable character to separate repeated keys
|
||||||
|
const SEPARATOR: char = '\x02';
|
||||||
|
|
||||||
struct MPDeserializer<'de, Iter: Iterator<Item = (&'de str, &'de str)>> {
|
struct MPDeserializer<'de, Iter: Iterator<Item = (&'de str, &'de str)>> {
|
||||||
inner: MapDeserializer<'de, Iter, Error>,
|
inner: MapDeserializer<'de, Iter, Error>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deserialize time from an integer that represents the seconds.
|
|
||||||
/// mpd uses int for the database stats (e.g. total time played).
|
|
||||||
fn de_time_int<'de, D>(deserializer: D) -> Result<Duration, D::Error>
|
|
||||||
where
|
|
||||||
D: de::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
u64::deserialize(deserializer).map(Duration::from_secs)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deserialize time from a float that represents the seconds.
|
|
||||||
/// mpd uses floats for the current status (e.g. time elapsed in song).
|
|
||||||
fn de_time_float<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
|
|
||||||
where
|
|
||||||
D: de::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
f64::deserialize(deserializer)
|
|
||||||
.map(Duration::from_secs_f64)
|
|
||||||
.map(Some)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// mpd uses bints (0 or 1) to represent booleans,
|
|
||||||
/// so we need a special parser for those.
|
|
||||||
fn de_bint<'de, D>(deserializer: D) -> Result<bool, D::Error>
|
|
||||||
where
|
|
||||||
D: de::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
match u8::deserialize(deserializer)? {
|
|
||||||
0 => Ok(false),
|
|
||||||
1 => Ok(true),
|
|
||||||
n => Err(de::Error::invalid_value(
|
|
||||||
de::Unexpected::Unsigned(n as u64),
|
|
||||||
&"zero or one",
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, Debug)]
|
|
||||||
pub struct Track {
|
|
||||||
pub file: String,
|
|
||||||
pub artist_sort: Option<String>,
|
|
||||||
pub album_artist: Option<String>,
|
|
||||||
pub album_sort: Option<String>,
|
|
||||||
pub album_artist_sort: Option<String>,
|
|
||||||
#[serde(deserialize_with = "string_or_vec")]
|
|
||||||
#[serde(default)]
|
|
||||||
#[serde(rename = "performer")]
|
|
||||||
pub performers: Vec<String>,
|
|
||||||
pub genre: Option<String>,
|
|
||||||
pub title: Option<String>,
|
|
||||||
pub track: Option<u32>,
|
|
||||||
pub album: Option<String>,
|
|
||||||
pub artist: Option<String>,
|
|
||||||
pub pos: Option<u32>,
|
|
||||||
pub id: Option<u32>,
|
|
||||||
// TODO: use proper time here
|
|
||||||
pub last_modified: Option<String>,
|
|
||||||
pub original_date: Option<String>,
|
|
||||||
pub time: Option<String>,
|
|
||||||
pub format: Option<String>,
|
|
||||||
#[serde(deserialize_with = "de_time_float")]
|
|
||||||
pub duration: Option<Duration>,
|
|
||||||
pub label: Option<String>,
|
|
||||||
pub date: Option<String>,
|
|
||||||
pub disc: Option<u32>,
|
|
||||||
pub musicbraiz_trackid: Option<String>,
|
|
||||||
pub musicbrainz_albumid: Option<String>,
|
|
||||||
pub musicbrainz_albumartistid: Option<String>,
|
|
||||||
pub musicbrainz_artistid: Option<String>,
|
|
||||||
pub musicbraiz_releasetrackid: Option<String>,
|
|
||||||
pub composer: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// some unprintable character
|
|
||||||
const SEPARATOR: char = '\x02';
|
|
||||||
|
|
||||||
impl<'de, Iter: Iterator<Item = (&'de str, &'de str)>> de::Deserializer<'de>
|
impl<'de, Iter: Iterator<Item = (&'de str, &'de str)>> de::Deserializer<'de>
|
||||||
for MPDeserializer<'de, Iter>
|
for MPDeserializer<'de, Iter>
|
||||||
{
|
{
|
||||||
|
@ -113,30 +40,6 @@ impl<'de, Iter: Iterator<Item = (&'de str, &'de str)>> de::Deserializer<'de>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn string_or_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
|
|
||||||
where
|
|
||||||
D: de::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
struct StringOrVec();
|
|
||||||
|
|
||||||
impl<'de> de::Visitor<'de> for StringOrVec {
|
|
||||||
type Value = Vec<String>;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
formatter.write_str("string or list of strings")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
|
|
||||||
Ok(value
|
|
||||||
.split(SEPARATOR)
|
|
||||||
.map(std::string::String::from)
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deserializer.deserialize_any(StringOrVec())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_response<'a, I: Iterator<Item = &'a str>>(input: I) -> Result<Track, Error> {
|
fn deserialize_response<'a, I: Iterator<Item = &'a str>>(input: I) -> Result<Track, Error> {
|
||||||
let mut map: HashMap<String, String> = HashMap::new();
|
let mut map: HashMap<String, String> = HashMap::new();
|
||||||
for line in input {
|
for line in input {
|
||||||
|
@ -159,6 +62,7 @@ fn deserialize_response<'a, I: Iterator<Item = &'a str>>(input: I) -> Result<Tra
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Ok(envy::from_iter(map).unwrap());
|
return Ok(envy::from_iter(map).unwrap());
|
||||||
|
// Eventually, I’d like to use my own deserializer instead of envy
|
||||||
// let deser = MPDeserializer {
|
// let deser = MPDeserializer {
|
||||||
// inner: MapDeserializer::new(map.into_iter()),
|
// inner: MapDeserializer::new(map.into_iter()),
|
||||||
// };
|
// };
|
||||||
|
@ -179,5 +83,5 @@ duration: 432.727
|
||||||
Pos: 1548
|
Pos: 1548
|
||||||
Id: 1549";
|
Id: 1549";
|
||||||
let s = deserialize_response(input_str.lines());
|
let s = deserialize_response(input_str.lines());
|
||||||
dbg!(s);
|
println!("{:?}", s);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
use helpers::*;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
|
pub struct Track {
|
||||||
|
pub file: String,
|
||||||
|
pub artist_sort: Option<String>,
|
||||||
|
pub album_artist: Option<String>,
|
||||||
|
pub album_sort: Option<String>,
|
||||||
|
pub album_artist_sort: Option<String>,
|
||||||
|
#[serde(deserialize_with = "string_or_vec")]
|
||||||
|
#[serde(default)]
|
||||||
|
#[serde(rename = "performer")]
|
||||||
|
pub performers: Vec<String>,
|
||||||
|
pub genre: Option<String>,
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub track: Option<u32>,
|
||||||
|
pub album: Option<String>,
|
||||||
|
pub artist: Option<String>,
|
||||||
|
pub pos: Option<u32>,
|
||||||
|
pub id: Option<u32>,
|
||||||
|
// TODO: use proper time here
|
||||||
|
pub last_modified: Option<String>,
|
||||||
|
pub original_date: Option<String>,
|
||||||
|
pub time: Option<String>,
|
||||||
|
pub format: Option<String>,
|
||||||
|
#[serde(deserialize_with = "de_time_float")]
|
||||||
|
pub duration: Duration,
|
||||||
|
pub label: Option<String>,
|
||||||
|
pub date: Option<String>,
|
||||||
|
pub disc: Option<u32>,
|
||||||
|
pub musicbraiz_trackid: Option<String>,
|
||||||
|
pub musicbrainz_albumid: Option<String>,
|
||||||
|
pub musicbrainz_albumartistid: Option<String>,
|
||||||
|
pub musicbrainz_artistid: Option<String>,
|
||||||
|
pub musicbraiz_releasetrackid: Option<String>,
|
||||||
|
pub composer: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialization helpers to handle the quirks of mpd’s output.
|
||||||
|
mod helpers {
|
||||||
|
use crate::SEPARATOR;
|
||||||
|
use serde::de;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
/// Deserialize time from an integer that represents the seconds.
|
||||||
|
/// mpd uses int for the database stats (e.g. total time played).
|
||||||
|
pub fn de_time_int<'de, D>(deserializer: D) -> Result<Duration, D::Error>
|
||||||
|
where
|
||||||
|
D: de::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
u64::deserialize(deserializer).map(Duration::from_secs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize time from a float that represents the seconds.
|
||||||
|
/// mpd uses floats for the current status (e.g. time elapsed in song).
|
||||||
|
pub fn de_time_float<'de, D>(deserializer: D) -> Result<Duration, D::Error>
|
||||||
|
where
|
||||||
|
D: de::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
f64::deserialize(deserializer).map(Duration::from_secs_f64)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn string_or_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
|
||||||
|
where
|
||||||
|
D: de::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
String::deserialize(deserializer)
|
||||||
|
.map(|s| s.split(SEPARATOR).map(std::string::String::from).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// mpd uses bints (0 or 1) to represent booleans,
|
||||||
|
/// so we need a special parser for those.
|
||||||
|
pub fn de_bint<'de, D>(deserializer: D) -> Result<bool, D::Error>
|
||||||
|
where
|
||||||
|
D: de::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
match u8::deserialize(deserializer)? {
|
||||||
|
0 => Ok(false),
|
||||||
|
1 => Ok(true),
|
||||||
|
n => Err(de::Error::invalid_value(
|
||||||
|
de::Unexpected::Unsigned(n as u64),
|
||||||
|
&"zero or one",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user