davis 0.1.1

An CLI client for MPD.
use crate::error::Error;
use std::convert::TryFrom;
use std::str::FromStr;

pub fn seek(client: &mut mpdrs::Client, seek_arg: Arg) -> Result<(), Error> {
    let status = client.status()?;
    let currentsongid = if let Some(place) = client.currentsong()?.and_then(|s| s.place) {
        place.id
    } else {
        println!("Not plaing.");
        std::process::exit(1);
    };

    match (seek_arg.direction, status.elapsed) {
        (SeekDirection::Absolute, _) => client.seek_id(currentsongid, seek_arg.seconds)?,
        (SeekDirection::Forward, Some(e)) => {
            client.seek_id(
                currentsongid,
                u32::try_from(e.as_secs()).expect("Time does not fit in u32") + seek_arg.seconds,
            )?;
        }
        (SeekDirection::Back, Some(e)) => {
            client.seek_id(
                currentsongid,
                u32::try_from(e.as_secs()).expect("Time does not fit in u32") - seek_arg.seconds,
            )?;
        }
        _ => (),
    }

    Ok(())
}

#[derive(PartialEq, Eq, Debug)]
enum SeekDirection {
    Forward,
    Back,
    Absolute,
}

#[derive(PartialEq, Eq, Debug)]
pub struct Arg {
    direction: SeekDirection,
    seconds: u32,
}

impl FromStr for Arg {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.is_empty() {
            return Err(Error::ParseSeek("Empty string."));
        }

        let (direction, rest) = match s.chars().next() {
            Some('+') => (SeekDirection::Forward, &s[1..]),
            Some('-') => (SeekDirection::Back, &s[1..]),
            _ => (SeekDirection::Absolute, s),
        };

        let sections = rest
            .split(':')
            .map(u32::from_str)
            .collect::<Result<Vec<_>, _>>()
            .map_err(|_| Error::ParseSeek("Field is not integer."))?;

        let seconds = sections
            .iter()
            .rev()
            .enumerate()
            .map(|(i, v)| v * 60_u32.pow(u32::try_from(i).expect("Counter does not fit in u32")))
            .sum();

        Ok(Arg { direction, seconds })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_hms() {
        assert_eq!(
            Arg::from_str("+1:2:3").unwrap(),
            Arg {
                direction: SeekDirection::Forward,
                seconds: 1 * 60 * 60 + 2 * 60 + 3
            }
        );

        assert_eq!(
            Arg::from_str("0:2:3").unwrap(),
            Arg {
                direction: SeekDirection::Absolute,
                seconds: 2 * 60 + 3
            }
        );
    }

    #[test]
    fn test_ms() {
        assert_eq!(
            Arg::from_str("+2:3").unwrap(),
            Arg {
                direction: SeekDirection::Forward,
                seconds: 2 * 60 + 3
            }
        );
    }

    #[test]
    fn test_s() {
        assert_eq!(
            Arg::from_str("3").unwrap(),
            Arg {
                direction: SeekDirection::Absolute,
                seconds: 3
            }
        );
    }

    #[test]
    fn test_leading_zeroes() {
        assert_eq!(
            Arg::from_str("01:01:01").unwrap(),
            Arg {
                direction: SeekDirection::Absolute,
                seconds: 60 * 60 * 1 + 60 * 1 + 1
            }
        );
    }
}