legacylisten 0.2.0

A simple CLI audio player with strange features.
Documentation
//! Module for API of legacylisten
//!
//! If legacylisten is used as a library, this module provides an
//! interface to most data.

use std::sync::atomic::Ordering;

use crossbeam_channel::{bounded, Receiver, Sender};
use diskit::VoidDiskit;
use id3::frame::Picture;
use legacytranslate::MessageBuffer;

use crate::{
    audio::print_info,
    commands::impls::{
        self, disable_repeat, pause, pause_after_song, quit_after_song, repeat_forever,
        repeat_once, resume, skip, skip_to_previous,
    },
    config::Config,
    l10n::messages::Message,
    matcher::BigAction,
    songs::Repeat,
};

/// A request
///
/// These are the different possible request one can make.
pub enum Request
{
    GetLikelihood,
    SetLikelihood(u32),
    Quit,
    Pause,
    Resume,
    Skip(u32),
    GetVolume,
    SetVolume(f32),
    GetDuration,
    QuitAfterSong,
    PauseAfterSong,
    IsPlaying,
    GetInfo,
    Repeat(Repeat),
    Previous(u32),
    GetCover,
    Localize(Message),
}

/// A response
///
/// These are the different possible responses one can get.  If
/// nothing else applies, `Finished` is returned.
pub enum Response
{
    Likelihood(u32),
    Volume(f32),
    Duration(f64, Option<f64>),
    IsPlaying(bool),
    Info(Vec<Message>),
    Cover(Vec<Picture>),
    Localized(String),
    Finished,
}

/// API request endpoint
///
/// With this struct and the associated methods api request can be
/// made.
// I think this is the better name.
#[allow(clippy::module_name_repetitions)]
#[derive(Debug)]
pub struct ApiRequester
{
    request: Sender<Request>,
    response: Receiver<Response>,
}

/// API request handler
///
/// This struct handles api requests.  You shouldn't interact with it,
/// but give it to legacylisten.
// I think this is the better name.
#[allow(clippy::module_name_repetitions)]
#[derive(Debug)]
pub struct ApiResponder
{
    pub(crate) request: Receiver<Request>,
    pub(crate) response: Sender<Response>,
}

// The specific functions should be self-explanatory.
#[allow(missing_docs)]
impl ApiRequester
{
    /// Creating a new api endpoint pair
    ///
    /// This function creates a api endpoint pair.  Give the
    /// [`ApiResponder`] to a legacylisten instance, then you can make
    /// requests to it using [`ApiRequester`].
    #[must_use]
    pub fn new() -> (Self, ApiResponder)
    {
        let (tx_request, rx_request) = bounded(1);
        let (tx_response, rx_response) = bounded(1);

        (
            Self {
                request: tx_request,
                response: rx_response,
            },
            ApiResponder {
                request: rx_request,
                response: tx_response,
            },
        )
    }

    /// Making a generic request
    ///
    /// A generic request can be answered using this function.  If you
    /// don't need generics (and the slightly more detailed errors)
    /// it's recommended to use the dedicated functions.
    #[must_use]
    pub fn request(&self, request: Request) -> Option<Response>
    {
        self.request.send(request).ok()?;
        self.response.recv().ok()
    }

    #[must_use]
    pub fn get_likelihood(&self) -> Option<u32>
    {
        if let Response::Likelihood(rv) = self.request(Request::GetLikelihood)?
        {
            Some(rv)
        }
        else
        {
            None
        }
    }

    #[must_use]
    pub fn set_likelihood(&self, likelihood: u32) -> Option<()>
    {
        if matches!(
            self.request(Request::SetLikelihood(likelihood))?,
            Response::Finished
        )
        {
            Some(())
        }
        else
        {
            None
        }
    }

    #[must_use]
    pub fn quit(&self) -> Option<()>
    {
        if matches!(self.request(Request::Quit)?, Response::Finished)
        {
            Some(())
        }
        else
        {
            None
        }
    }

    #[must_use]
    pub fn pause(&self) -> Option<()>
    {
        if matches!(self.request(Request::Pause)?, Response::Finished)
        {
            Some(())
        }
        else
        {
            None
        }
    }

    #[must_use]
    pub fn resume(&self) -> Option<()>
    {
        if matches!(self.request(Request::Resume)?, Response::Finished)
        {
            Some(())
        }
        else
        {
            None
        }
    }

    #[must_use]
    pub fn skip(&self, count: u32) -> Option<()>
    {
        if matches!(self.request(Request::Skip(count))?, Response::Finished)
        {
            Some(())
        }
        else
        {
            None
        }
    }

    #[must_use]
    pub fn get_volume(&self) -> Option<f32>
    {
        if let Response::Volume(rv) = self.request(Request::GetVolume)?
        {
            Some(rv)
        }
        else
        {
            None
        }
    }

    #[must_use]
    pub fn set_volume(&self, loud: f32) -> Option<()>
    {
        if matches!(self.request(Request::SetVolume(loud))?, Response::Finished)
        {
            Some(())
        }
        else
        {
            None
        }
    }

    #[must_use]
    pub fn get_duration(&self) -> Option<(f64, Option<f64>)>
    {
        if let Response::Duration(cur, end) = self.request(Request::GetDuration)?
        {
            Some((cur, end))
        }
        else
        {
            None
        }
    }

    #[must_use]
    pub fn quit_after_song(&self) -> Option<()>
    {
        if matches!(self.request(Request::QuitAfterSong)?, Response::Finished)
        {
            Some(())
        }
        else
        {
            None
        }
    }

    #[must_use]
    pub fn pause_after_song(&self) -> Option<()>
    {
        if matches!(self.request(Request::PauseAfterSong)?, Response::Finished)
        {
            Some(())
        }
        else
        {
            None
        }
    }

    #[must_use]
    pub fn is_playing(&self) -> Option<bool>
    {
        if let Response::IsPlaying(rv) = self.request(Request::IsPlaying)?
        {
            Some(rv)
        }
        else
        {
            None
        }
    }

    #[must_use]
    pub fn get_info(&self) -> Option<Vec<Message>>
    {
        if let Response::Info(rv) = self.request(Request::GetInfo)?
        {
            Some(rv)
        }
        else
        {
            None
        }
    }

    #[must_use]
    pub fn get_cover(&self) -> Option<Vec<Picture>>
    {
        if let Response::Cover(rv) = self.request(Request::GetCover)?
        {
            Some(rv)
        }
        else
        {
            None
        }
    }

    #[must_use]
    pub fn repeat(&self, repeat: Repeat) -> Option<()>
    {
        if matches!(self.request(Request::Repeat(repeat))?, Response::Finished)
        {
            Some(())
        }
        else
        {
            None
        }
    }

    #[must_use]
    pub fn previous(&self, count: u32) -> Option<()>
    {
        if matches!(self.request(Request::Previous(count))?, Response::Finished)
        {
            Some(())
        }
        else
        {
            None
        }
    }

    #[must_use]
    pub fn localize(&self, mes: Message) -> Option<String>
    {
        if let Response::Localized(rv) = self.request(Request::Localize(mes))?
        {
            Some(rv)
        }
        else
        {
            None
        }
    }
}

impl ApiResponder
{
    // This can't sensibly shortend or refactored.
    #[allow(clippy::too_many_lines)]
    pub(crate) fn handle(&self, config: &mut Config) -> BigAction
    {
        if let Ok(request) = self.request.try_recv()
        {
            let mut quit = false;
            let diskit = VoidDiskit::default();

            let response = match request
            {
                Request::GetLikelihood => Response::Likelihood(config.num),
                Request::SetLikelihood(num) =>
                {
                    config.num = num;

                    Response::Finished
                }
                Request::Quit =>
                {
                    impls::quit(1, config, diskit);
                    quit = true;

                    Response::Finished
                }
                Request::Pause =>
                {
                    pause(1, config, diskit);

                    Response::Finished
                }
                Request::Resume =>
                {
                    resume(1, config, diskit);

                    Response::Finished
                }
                Request::Skip(count) =>
                {
                    skip(count, config, diskit);

                    Response::Finished
                }
                Request::SetVolume(loud) =>
                {
                    config.loud = loud;
                    config.audio_handler.set_volume(config.loud);

                    Response::Finished
                }
                Request::GetVolume => Response::Volume(config.loud),
                Request::GetDuration =>
                {
                    let pos = config.source.get_pos() as f64
                        / config.source.sample_rate as f64
                        / config.arc_config.channels.load(Ordering::SeqCst) as f64;

                    if let Some(len) = config.source.samples_len()
                    {
                        let len = len as f64
                            / config.source.sample_rate as f64
                            / config.arc_config.channels.load(Ordering::SeqCst) as f64;
                        Response::Duration(pos, Some(len))
                    }
                    else
                    {
                        Response::Duration(pos, None)
                    }
                }
                Request::QuitAfterSong =>
                {
                    quit_after_song(1, config, diskit);

                    Response::Finished
                }
                Request::PauseAfterSong =>
                {
                    pause_after_song(1, config, diskit);

                    Response::Finished
                }
                Request::IsPlaying => Response::IsPlaying(!config.paused),
                Request::GetInfo =>
                {
                    let mut buf = MessageBuffer::new();
                    print_info(&config.tag, &&mut buf);

                    Response::Info(buf.get_messages())
                }
                Request::GetCover => Response::Cover(
                    config
                        .tag
                        .as_ref()
                        .and_then(|x| x.as_ref().ok())
                        .map(|tag| tag.pictures().cloned().collect::<Vec<Picture>>())
                        .unwrap_or_default(),
                ),
                Request::Repeat(Repeat::Not) =>
                {
                    disable_repeat(1, config, diskit);

                    Response::Finished
                }
                Request::Repeat(Repeat::Once) =>
                {
                    repeat_once(1, config, diskit);

                    Response::Finished
                }
                Request::Repeat(Repeat::Always) =>
                {
                    repeat_forever(1, config, diskit);

                    Response::Finished
                }
                Request::Previous(count) =>
                {
                    skip_to_previous(count, config, diskit);

                    Response::Finished
                }
                Request::Localize(mes) => Response::Localized(config.l10n.get(mes)),
            };

            self.response
                .send(response)
                .expect("This send shouldn't have failed");
            if quit
            {
                return BigAction::Quit;
            }
        }
        BigAction::Nothing
    }
}