legacylisten 0.2.0

A simple CLI audio player with strange features.
Documentation
use std::{cmp, convert::TryFrom, io::Read, path::PathBuf, sync::Arc, thread, time::Duration};

use crossbeam_channel::{Receiver, Sender};
use diskit::Diskit;
use id3::Tag;
use legacytranslate::MessageHandler;
use nix::sys::sysinfo::sysinfo;
use signal_hook::consts::{SIGINT, SIGTERM, SIGUSR1, SIGUSR2};
use signal_hook::iterator::Signals;

use crate::{
    commands::Command,
    config::ArcConfig,
    dbus::handle_mpris,
    l10n::{
        messages::{LogLevel, Message},
        L10n,
    },
    unicode_reader::UnicodeReader,
};

fn input_handler<R>(
    tx: &Sender<(Command, u32)>,
    reader: fn() -> R,
    l10n: L10n,
    config: &Arc<ArcConfig>,
) where
    R: Read,
{
    l10n.write(Message::HelpNotice);

    let mut summed = 0;
    let mut current_num = 0;

    // Nursery lint (and this code *should* be correct).
    #[allow(clippy::significant_drop_in_scrutinee)]
    for c in UnicodeReader::new(reader())
    {
        let c = match c
        {
            Ok(c) => c,
            Err(err) =>
            {
                summed += current_num;
                current_num = 0;
                l10n.write(Message::UnknownCommandBytes(err));
                continue;
            }
        };

        if ('0'..='9').contains(&c)
        {
            current_num *= 10;
            current_num += (c as u8 - b'0') as u32;
            continue;
        }

        summed += current_num;
        current_num = 0;

        if c == '?'
        {
            Command::show_help(l10n, config);
        }
        else if c == '\n'
        { // Silently ignored.
        }
        else if let Some(Ok(command)) = config
            .conffile
            .command_characters
            .iter()
            .position(|&x| x == c)
            .map(Command::try_from)
        {
            let _ = tx.send((command, cmp::max(summed + current_num, 1)));
            summed = 0;
            current_num = 0;
        }
        else
        {
            l10n.write(Message::UnknownCommandChar(c));
        }
    }

    tx.send((Command::Quit, 1)).expect("Couldn't quit normally");
}

fn signal_handler(
    tx: &Sender<(Command, u32)>,
    mut signals: Signals,
    config: &Arc<ArcConfig>,
    l10n: L10n,
)
{
    for sig in signals.forever()
    {
        l10n.write(Message::InSignalHandler(sig));

        match sig
        {
            SIGINT | SIGTERM => tx.send((Command::Quit, 1)).unwrap(),
            SIGUSR1 => config
                .reading_paused
                .store(true, std::sync::atomic::Ordering::SeqCst),
            SIGUSR2 => config
                .reading_paused
                .store(false, std::sync::atomic::Ordering::SeqCst),
            _ => l10n.write(Message::CaughtUnknownSignal),
        }
    }
}

fn mpris_handler<D>(
    tx: &Sender<(Command, u32)>,
    tx_control: &Sender<()>,
    rx_paused: Receiver<bool>,
    rx_path: Receiver<(PathBuf, Option<Tag>)>,
    config: &Arc<ArcConfig>,
    l10n: L10n,
    diskit: D,
) where
    D: Diskit + Send + 'static,
{
    if config.conffile.enable_dbus
    {
        if let Err(e) = handle_mpris(tx, tx_control, rx_paused, rx_path, config, diskit)
        {
            l10n.write(Message::MprisHandlerError(e));
        }

        let _ = tx;
        let _ = tx_control;
    }
}

fn low_memory_handler(tx: &Sender<(Command, u32)>, config: &Arc<ArcConfig>, l10n: L10n)
{
    // Getting it early, so that it doesn't make problems later;
    // probably stupid.
    let s = l10n.get(Message::MemoryTight);

    loop
    {
        let not_enough_memory = sysinfo().map_or(true, |sysinfo| {
            sysinfo.ram_unused() < config.conffile.minimum_ram
        });
        if not_enough_memory && !config.conffile.ignore_ram
        {
            let _ = tx.send((Command::Quit, 1));
            l10n.write(Message::LiteralString(s, LogLevel::Error));
            thread::sleep(Duration::from_secs(10));
            break;
        }
        thread::sleep(Duration::from_millis(10));
    }
}

// I think that this function sadly needs so many arguments; TODO: Fix
// or get better reason.
#[allow(clippy::too_many_arguments)]
// TODO: Fix
#[allow(clippy::module_name_repetitions)]
pub fn start_threads<D, R>(
    tx: Sender<(Command, u32)>,
    tx_control: Sender<()>,
    rx_paused: Receiver<bool>,
    rx_path: Receiver<(PathBuf, Option<Tag>)>,
    signals: Signals,
    reader: fn() -> R,
    config: Arc<ArcConfig>,
    l10n: L10n,
    diskit: D,
) where
    D: Diskit + Send + 'static,
    R: Read + 'static,
{
    let tx1 = tx.clone();
    let tx2 = tx.clone();
    let tx3 = tx.clone();
    let tx4 = tx;
    let config1 = config.clone();
    let config2 = config.clone();
    let config3 = config.clone();
    let config4 = config;

    let _ = thread::spawn(move || input_handler(&tx1, reader, l10n, &config4));
    let _ = thread::spawn(move || signal_handler(&tx2, signals, &config1, l10n));
    let _ = thread::spawn(move || {
        mpris_handler(
            &tx3,
            &tx_control,
            rx_paused,
            rx_path,
            &config2,
            l10n,
            diskit,
        );
    });
    let _ = thread::spawn(move || low_memory_handler(&tx4, &config3, l10n));
}