legacylisten 0.2.0

A simple CLI audio player with strange features.
Documentation
// Look in lib.rs for justification.
#![allow(clippy::needless_pass_by_value)]

use std::{process, sync::atomic::Ordering, thread};

use diskit::Diskit;
use legacytranslate::MessageHandler;

use crate::{
    audio::print_info, config::Config, l10n::messages::Message, matcher::BigAction, songs::Repeat,
};

use super::Command;

fn increase_likelihood<D>(count: u32, config: &mut Config, _diskit: D) -> BigAction
where
    D: Diskit,
{
    if config.num == u32::MAX
    {
        config.l10n.write(Message::MaxLikelihoodReached);
    }
    else
    {
        config.num = config.num.saturating_add(count);
        config.l10n.write(Message::LikelihoodIncreased(config.num));
    }

    BigAction::Nothing
}

fn decrease_likelihood<D>(count: u32, config: &mut Config, _diskit: D) -> BigAction
where
    D: Diskit,
{
    if config.num == 0
    {
        config.l10n.write(Message::SongAlreadyNever);
    }
    else
    {
        config.num = config.num.saturating_sub(count);
        config.l10n.write(Message::LikelihoodDecreased(config.num));

        if config.num == 0
        {
            config.l10n.write(Message::SongNever);
        }
    }

    BigAction::Nothing
}

pub fn quit<D>(_: u32, config: &mut Config, _diskit: D) -> BigAction
where
    D: Diskit,
{
    config.l10n.write(Message::StoppingProgram);
    BigAction::Quit
}

pub fn pause<D>(_: u32, config: &mut Config, _diskit: D) -> BigAction
where
    D: Diskit,
{
    if config.paused
    {
        config.l10n.write(Message::AlreadyPaused);
    }
    else
    {
        config.l10n.write(Message::Pausing);
        config.paused = true;
        config.audio_handler.pause();
    }
    config.arc_config.update_dbus.store(true, Ordering::SeqCst);

    BigAction::Nothing
}

pub fn resume<D>(_: u32, config: &mut Config, _diskit: D) -> BigAction
where
    D: Diskit,
{
    if config.paused
    {
        config.l10n.write(Message::Resuming);
        config.paused = false;
        config.audio_handler.play();
    }
    else
    {
        config.l10n.write(Message::AlreadyRunning);
    }
    config.arc_config.update_dbus.store(true, Ordering::SeqCst);

    BigAction::Nothing
}

pub fn skip<D>(count: u32, config: &mut Config, _diskit: D) -> BigAction
where
    D: Diskit,
{
    config.l10n.write(Message::SkippingSong);
    config.paused = false;
    config.audio_handler.skip();
    config.arc_config.update_dbus.store(true, Ordering::SeqCst);
    // To skip multiple songs we have to send the message again,
    // because we must wait until a new song is chosen (because
    // legacylisten is mostly single-threaded this should cause no
    // race condition).  If count is 0 matcher::main_match will filter
    // the request out.  Errors are discarded.
    let _ = config.tx.send((Command::Skip, count - 1));

    BigAction::Nothing
}

fn increase_volume<D>(count: u32, config: &mut Config, _diskit: D) -> BigAction
where
    D: Diskit,
{
    config.loud += 0.01 * count as f32;
    config.l10n.write(Message::MakingLouder(config.loud as f64));
    config.audio_handler.set_volume(config.loud);

    BigAction::Nothing
}

fn decrease_volume<D>(count: u32, config: &mut Config, _diskit: D) -> BigAction
where
    D: Diskit,
{
    config.loud -= 0.01 * count as f32;
    config
        .l10n
        .write(Message::MakingQuieter(config.loud as f64));
    if config.loud < 0.0
    {
        config.l10n.write(Message::LoudZero);
        config.loud = 0.0;
    }
    config.audio_handler.set_volume(config.loud);

    BigAction::Nothing
}

fn show_duration<D>(_: u32, config: &mut Config, _diskit: D) -> BigAction
where
    D: Diskit,
{
    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;
        config.l10n.write(Message::DurationKnown(pos, len));
    }
    else
    {
        config.l10n.write(Message::DurationUnknown(pos));
    }

    BigAction::Nothing
}

fn switch_play_pause<D>(count: u32, config: &mut Config, _diskit: D) -> BigAction
where
    D: Diskit,
{
    if count % 2 == 1
    {
        if config.paused
        {
            let _ = config.tx.send((Command::Resume, 1));
        }
        else
        {
            let _ = config.tx.send((Command::Pause, 1));
        }
    }

    BigAction::Nothing
}

pub fn quit_after_song<D>(_: u32, config: &mut Config, _diskit: D) -> BigAction
where
    D: Diskit,
{
    if config.quit_after_song
    {
        config.l10n.write(Message::AlreadyQuitting);
    }
    else
    {
        config.l10n.write(Message::QuittingAfter);
        config.quit_after_song = true;
    };

    BigAction::Nothing
}

pub fn pause_after_song<D>(_: u32, config: &mut Config, _diskit: D) -> BigAction
where
    D: Diskit,
{
    if config.pause_after_song
    {
        config.l10n.write(Message::AlreadyPausing);
    }
    else
    {
        config.l10n.write(Message::PausingAfter);
        config.pause_after_song = true;
    };

    BigAction::Nothing
}

fn show_info<D>(count: u32, config: &mut Config, _diskit: D) -> BigAction
where
    D: Diskit,
{
    print_info(&config.tag, &config.l10n);
    // Errors are discarded.
    let _ = config.tx.send((Command::ShowInfo, count - 1));

    BigAction::Nothing
}

fn open_cover<D>(count: u32, config: &mut Config, _diskit: D) -> BigAction
where
    D: Diskit,
{
    if let Ok(pic_path) = config.arc_config.pic_path.lock()
    {
        if let Some(pic_path) = pic_path.clone()
        {
            config.l10n.write(Message::OpeningPicture);
            thread::spawn(|| {
                let _ = process::Command::new("mimeopen")
                    .arg("-")
                    .arg(pic_path)
                    .spawn()
                    .and_then(|mut handle| handle.wait());
            });
        }
        else
        {
            config.l10n.write(Message::NothingPlayingYet);
        }
    }
    else
    {
        config.l10n.write(Message::CantOpenPicture);
    }

    // Errors are discarded.
    let _ = config.tx.send((Command::ShowInfo, count - 1));

    BigAction::Nothing
}

pub fn disable_repeat<D>(_: u32, config: &mut Config, _diskit: D) -> BigAction
where
    D: Diskit,
{
    if config.repeat == Repeat::Not
    {
        config.l10n.write(Message::NotRepeatingAlready);
    }
    else
    {
        config.song_index += 1;
        config.l10n.write(Message::StoppingRepeating);
        config.repeat = Repeat::Not;
    }

    BigAction::Nothing
}

pub fn repeat_once<D>(_: u32, config: &mut Config, _diskit: D) -> BigAction
where
    D: Diskit,
{
    if config.repeat == Repeat::Once
    {
        config.l10n.write(Message::AlreadyRepeatingOnce);
    }
    else
    {
        if config.repeat == Repeat::Not
        {
            config.song_index -= 1;
        }
        config.l10n.write(Message::RepeatingOnce);
        config.repeat = Repeat::Once;
    }

    BigAction::Nothing
}

pub fn repeat_forever<D>(_: u32, config: &mut Config, _diskit: D) -> BigAction
where
    D: Diskit,
{
    if config.repeat == Repeat::Always
    {
        config.l10n.write(Message::AlreadyRepeatingForever);
    }
    else
    {
        if config.repeat == Repeat::Not
        {
            config.song_index -= 1;
        }
        config.l10n.write(Message::RepeatingForever);
        config.repeat = Repeat::Always;
    }

    BigAction::Nothing
}

pub fn skip_to_previous<D>(count: u32, config: &mut Config, _diskit: D) -> BigAction
where
    D: Diskit,
{
    if config.song_index > 0
    {
        config.l10n.write(Message::Previous);
    }
    if count as usize > config.song_index
    {
        config.l10n.write(Message::AlreadyPlayingFirst);
    }
    config.song_index = config.song_index.saturating_sub(count as usize);

    BigAction::Nothing
}

impl Command
{
    pub fn get_handler<D>(self) -> fn(u32, &mut Config, D) -> BigAction
    where
        D: Diskit,
    {
        match self
        {
            Self::IncreaseLikelihood => increase_likelihood,
            Self::DecreaseLikelihood => decrease_likelihood,
            Self::Quit => quit,
            Self::Pause => pause,
            Self::Resume => resume,
            Self::Skip => skip,
            Self::IncreaseVolume => increase_volume,
            Self::DecreaseVolume => decrease_volume,
            Self::ShowDuration => show_duration,
            Self::SwitchPlayPause => switch_play_pause,
            Self::QuitAfterSong => quit_after_song,
            Self::PauseAfterSong => pause_after_song,
            Self::ShowInfo => show_info,
            Self::OpenCover => open_cover,
            Self::DisableRepeat => disable_repeat,
            Self::RepeatOnce => repeat_once,
            Self::RepeatForever => repeat_forever,
            Self::SkipToPrevious => skip_to_previous,
        }
    }
}