tf_playlist 0.1.4

The playlist feature used in Tubefeeder
Documentation
use std::{
    borrow::Borrow,
    collections::HashMap,
    hash::Hash,
    sync::{Arc, Mutex},
};

use tf_observer::{Observable, Observer};

use crate::{Playlist, PlaylistEvent};

#[derive(Clone)]
pub struct PlaylistManager<I, T> {
    playlists: Arc<Mutex<PlaylistManagerInternal<I, T>>>,
}

struct PlaylistManagerInternal<I, T> {
    playlists: HashMap<I, Playlist<T>>,
}

impl<I, T> PlaylistManagerInternal<I, T>
where
    I: Hash + Eq + Clone,
    T: Hash + Eq + Clone,
{
    fn new() -> Self {
        PlaylistManagerInternal {
            playlists: HashMap::new(),
        }
    }

    fn toggle(&mut self, ident: &I, item: &T) {
        if let Some(playlist) = self.playlists.get_mut(ident.borrow()) {
            log::debug!("Adding item to existing playlist");
            playlist.toggle(item.borrow());
        } else {
            log::debug!("Creating new playlist for item");
            let mut playlist = Playlist::new();
            playlist.toggle(item.borrow());
            self.playlists.insert(ident.borrow().clone(), playlist);
        }
    }

    fn items(&self, ident: &I) -> Vec<&T> {
        if let Some(playlist) = self.playlists.get(ident.borrow()) {
            playlist.iter().collect()
        } else {
            vec![]
        }
    }

    fn attach_at(
        &mut self,
        observer: std::sync::Weak<Mutex<Box<(dyn Observer<PlaylistEvent<T>> + Send + 'static)>>>,
        ident: &I,
    ) {
        if let Some(playlist) = self.playlists.get_mut(ident.borrow()) {
            log::debug!("Adding observer to existing playlist");
            playlist.attach(observer);
        } else {
            log::debug!("Creating new playlist for observer");
            let mut playlist = Playlist::new();
            playlist.attach(observer);
            self.playlists.insert(ident.borrow().clone(), playlist);
        }
    }

    fn detach_at(
        &mut self,
        observer: std::sync::Weak<Mutex<Box<(dyn Observer<PlaylistEvent<T>> + Send + 'static)>>>,
        ident: &I,
    ) {
        if let Some(playlist) = self.playlists.get_mut(ident.borrow()) {
            playlist.detach(observer);
        }
    }
}

impl<I, T> PlaylistManager<I, T>
where
    I: Hash + Eq + Clone,
    T: Hash + Eq + Clone,
{
    pub fn new() -> Self {
        PlaylistManager {
            playlists: Arc::new(Mutex::new(PlaylistManagerInternal::new())),
        }
    }

    pub fn toggle(&mut self, ident: &I, item: &T) {
        self.playlists.lock().unwrap().toggle(ident, item);
    }

    pub fn items(&self, ident: &I) -> Vec<T> {
        self.playlists
            .lock()
            .unwrap()
            .items(ident)
            .into_iter()
            .cloned()
            .collect()
    }

    pub fn attach_at(
        &mut self,
        observer: std::sync::Weak<Mutex<Box<(dyn Observer<PlaylistEvent<T>> + Send + 'static)>>>,
        ident: &I,
    ) {
        self.playlists.lock().unwrap().attach_at(observer, ident);
    }

    pub fn detach_at(
        &mut self,
        observer: std::sync::Weak<Mutex<Box<(dyn Observer<PlaylistEvent<T>> + Send + 'static)>>>,
        ident: &I,
    ) {
        self.playlists.lock().unwrap().detach_at(observer, ident);
    }
}

impl<I, T> Default for PlaylistManager<I, T>
where
    I: Hash + Eq + Clone,
    T: Hash + Eq + Clone,
{
    fn default() -> Self {
        Self::new()
    }
}

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

    #[test]
    fn playlistmanagerinternal_toggle_empty() {
        let mut manager: PlaylistManagerInternal<&str, &str> = PlaylistManagerInternal::new();
        assert!(manager.playlists.is_empty());

        manager.toggle(&"Playlist1", &"Item1_1");

        assert_eq!(manager.playlists.len(), 1);

        assert!(manager.playlists.get(&"Playlist1").is_some());
        assert_eq!(manager.playlists.get(&"Playlist1").unwrap().len(), 1);
        assert!(manager
            .playlists
            .get(&"Playlist1")
            .unwrap()
            .get(&"Item1_1")
            .is_some());
    }

    #[test]
    fn playlistmanagerinternal_toggle_complex() {
        let mut manager: PlaylistManagerInternal<&str, &str> = PlaylistManagerInternal::new();
        assert!(manager.playlists.is_empty());

        manager.toggle(&"Playlist1", &"Item1_1");
        manager.toggle(&"Playlist1", &"Item1_2");
        manager.toggle(&"Playlist2", &"Item1_2");

        assert_eq!(manager.playlists.len(), 2);

        assert!(manager.playlists.get(&"Playlist1").is_some());
        assert!(manager.playlists.get(&"Playlist2").is_some());
        assert_eq!(manager.playlists.get(&"Playlist1").unwrap().len(), 2);
        assert_eq!(manager.playlists.get(&"Playlist2").unwrap().len(), 1);
    }
}