libfoksalaudio 1.2.7

foksal audio library
Documentation
use anyhow::{Result, anyhow, ensure};
use rand::seq::IteratorRandom;
use serde::Serialize;
use std::{
    collections::HashSet,
    path::{Path, PathBuf},
};
use thiserror::Error;

#[derive(Debug, Error)]
enum QueueError {
    #[error("index {index} out of bounds (length: {len})")]
    OutOfBounds { index: usize, len: usize },
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum QueueMode {
    #[default]
    Sequential,
    Random,
    Loop,
    Single,
}

#[derive(Debug, Default)]
struct IdGenerator {
    next: usize,
}

#[derive(Debug, Default)]
pub struct Queue {
    list: Vec<PathBuf>,
    ids: Vec<usize>,
    pos: Option<usize>,
    mode: QueueMode,
    available: HashSet<PathBuf>,
    id_gen: IdGenerator,
}

impl IdGenerator {
    fn next(&mut self) -> usize {
        let x = self.next;
        self.next += 1;

        x
    }
}

impl Queue {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn list(&self) -> &[PathBuf] {
        &self.list
    }

    pub fn list_cloned(&self) -> Vec<PathBuf> {
        self.list.clone()
    }

    pub fn list_ids(&self) -> &[usize] {
        &self.ids
    }

    pub fn cur(&self) -> Option<(&Path, usize)> {
        self.pos.map(|p| (self.list[p].as_path(), self.ids[p]))
    }

    pub fn pos(&self) -> Option<usize> {
        self.pos
    }

    pub fn mode(&self) -> QueueMode {
        self.mode
    }

    pub fn stop(&mut self) {
        self.available.clear();
        self.pos = None;
    }

    pub fn clear(&mut self) {
        self.list.clear();
        self.available.clear();
        self.pos = None;
    }

    pub fn get(&self, pos: usize) -> Result<&Path> {
        self.list
            .get(pos)
            .map(|uri| uri.as_path())
            .ok_or(anyhow!(QueueError::OutOfBounds {
                index: pos,
                len: self.list.len()
            }))
    }

    pub fn get_id(&self, pos: usize) -> Result<usize> {
        self.ids
            .get(pos)
            .copied()
            .ok_or(anyhow!(QueueError::OutOfBounds {
                index: pos,
                len: self.list.len()
            }))
    }

    pub fn insert(&mut self, uris: &[impl AsRef<Path> + Into<PathBuf>], pos: usize) -> Result<()> {
        let len = self.list.len();
        ensure!(pos <= len, QueueError::OutOfBounds { index: pos, len });
        if self.mode == QueueMode::Random {
            for uri in uris.iter() {
                self.available.insert(uri.as_ref().into());
            }
        }
        if pos == len {
            for uri in uris.iter() {
                self.list.push(uri.as_ref().into());
                self.ids.push(self.id_gen.next());
            }
        } else {
            let mut p = pos;
            for uri in uris.iter() {
                self.list.insert(p, uri.as_ref().into());
                self.ids.push(self.id_gen.next());
                p += 1;
            }
        }

        Ok(())
    }

    pub fn push(&mut self, uris: &[impl AsRef<Path> + Into<PathBuf>]) {
        if self.mode == QueueMode::Random {
            for uri in uris.iter() {
                self.available.insert(uri.as_ref().into());
            }
        }
        for uri in uris.iter() {
            self.list.push(uri.as_ref().into());
            self.ids.push(self.id_gen.next());
        }
    }

    pub fn push_and_move_to(&mut self, uris: &[impl AsRef<Path> + Into<PathBuf>]) {
        let _ = self.move_to(self.list.len().saturating_sub(1));
        self.push(uris);
    }

    pub fn remove(&mut self, pos: usize) -> Result<()> {
        let len = self.list.len();
        ensure!(pos < len, QueueError::OutOfBounds { index: pos, len });
        self.ids.remove(pos);
        let removed_uri = self.list.remove(pos);
        self.available.remove(&removed_uri);
        if self.pos.is_some_and(|p| p >= pos) {
            self.move_to_prev();
        }

        Ok(())
    }

    pub fn move_pos(&mut self, from: usize, to: usize) -> Result<()> {
        let len = self.list.len();
        ensure!(from < len, QueueError::OutOfBounds { index: from, len });
        ensure!(to <= len, QueueError::OutOfBounds { index: to, len });
        let (moved_id, moved_uri) = (self.ids.remove(from), self.list.remove(from));
        self.list.insert(to, moved_uri);
        self.ids.insert(to, moved_id);

        if let Some(p) = self.pos {
            if p == from {
                self.pos = Some(to);
            } else if from < p && p <= to {
                self.pos = Some(p - 1);
            } else if to <= p && p < from {
                self.pos = Some(p + 1);
            }
        }

        Ok(())
    }

    pub fn set_mode_seq(&mut self) {
        self.mode = QueueMode::Sequential;
    }

    pub fn set_mode_loop(&mut self) {
        self.mode = QueueMode::Loop;
    }

    pub fn set_mode_random(&mut self) {
        self.mode = QueueMode::Random;
        self.reinit_available(false);
    }

    pub fn set_mode_single(&mut self) {
        self.mode = QueueMode::Single;
    }

    pub fn move_to(&mut self, pos: usize) -> Result<()> {
        ensure!(
            pos < self.list.len(),
            QueueError::OutOfBounds {
                index: pos,
                len: self.list.len()
            }
        );
        self.pos = Some(pos);

        Ok(())
    }

    pub fn move_to_next(&mut self) {
        match self.mode {
            QueueMode::Sequential => self.move_to_next_seq(),
            QueueMode::Loop => self.move_to_next_loop(),
            QueueMode::Random => self.move_to_next_random(),
            QueueMode::Single => self.move_to_next_single(),
        }
    }

    pub fn move_to_prev(&mut self) {
        match self.pos {
            Some(pos) => {
                if pos > 0 {
                    self.pos = Some(pos - 1);
                } else {
                    self.pos = None;
                }
            }
            None => {
                self.pos = if self.list.is_empty() {
                    None
                } else {
                    Some(self.list.len() - 1)
                }
            }
        }
    }

    fn move_to_next_seq(&mut self) {
        match self.pos {
            Some(pos) => {
                if pos < self.list.len() - 1 {
                    self.pos = Some(pos + 1);
                } else {
                    self.pos = None;
                }
            }
            None => self.pos = if self.list.is_empty() { None } else { Some(0) },
        }
    }

    fn move_to_next_loop(&mut self) {
        if self.pos.is_none() {
            self.pos = if self.list.is_empty() { None } else { Some(0) };
        }
    }

    fn move_to_next_random(&mut self) {
        if self.list.is_empty() {
            self.pos = None;
            return;
        }
        if self.available.is_empty() {
            self.reinit_available(true);
        }
        let mut rng = rand::rng();
        let Some(random_uri) = self.available.iter().choose(&mut rng).cloned() else {
            self.pos = None;
            return;
        };
        self.available.remove(&random_uri);
        self.pos = self.list.iter().position(|uri| uri == &random_uri);
    }

    fn move_to_next_single(&mut self) {
        match self.pos {
            Some(_) => self.pos = None,
            None => self.pos = if self.list.is_empty() { None } else { Some(0) },
        }
    }

    fn reinit_available(&mut self, all: bool) {
        self.available.clear();
        let p = if all {
            0
        } else {
            self.pos.map(|p| p + 1).unwrap_or_default()
        };
        for uri in self.list[p..].iter() {
            self.available.insert(uri.to_path_buf());
        }
    }
}

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

    fn init_queue(n: usize) -> Queue {
        let mut q = Queue::new();
        for i in 0..n {
            q.push(&[format!("{}", i)]);
        }

        q
    }

    #[test]
    fn test_sequential_next() {
        let mut q = init_queue(3);
        q.move_to_next();
        assert_eq!(q.pos(), Some(0));
        q.move_to_next();
        assert_eq!(q.pos(), Some(1));
        q.move_to_next();
        assert_eq!(q.pos(), Some(2));
        q.move_to_next();
        assert_eq!(q.pos(), None);
    }

    #[test]
    fn test_random_next_plays_all() {
        let n = 10;
        let mut q = init_queue(n);
        q.set_mode_random();
        for _ in 0..2 {
            let mut vis = HashSet::new();
            for _ in 0..n {
                q.move_to_next();
                let pos = q.pos().expect("should still have unplayed songs");
                assert!(
                    vis.insert(pos),
                    "position {} visited twice in random mode",
                    pos
                );
            }
            assert_eq!(vis.len(), n);
        }
    }

    #[test]
    fn test_remove() {
        let mut q = init_queue(5);
        q.move_to_next();
        q.move_to_next();
        q.remove(0).unwrap();
        assert_eq!(q.pos(), Some(0));
        q.remove(3).unwrap();
        assert_eq!(q.pos(), Some(0));
        assert_eq!(q.list().len(), 3);
    }
}