pub mod playlist { use std::path::{Path,PathBuf}; use std::error::Error; use std::fmt; use std::fs; use serde::{Deserialize, Serialize}; use crate::songmeta::songmeta::*; use std::io::Write; use rand::thread_rng; use rand::prelude::SliceRandom; //----------------ERROR #[derive(Debug)] pub enum PlaylistError { IndexNotFound, FileNotFound, FileCantWrite, } impl fmt::Display for PlaylistError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { PlaylistError::IndexNotFound => write!(f, "Playlist does not have that index"), PlaylistError::FileNotFound => write!(f, "Given playlist file is not readable or does not exist"), PlaylistError::FileCantWrite => write!(f, "Can't write playlist file at the given path. Is it valid ?"), } } } impl Error for PlaylistError {} //----------------STRUCT ///Stores a list of SongMetas to be used with a player. ///Index starts at zero ! #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Playlist { pub songs: Vec, } impl Playlist { ///Returns a new empty playlist. pub fn new() -> Self { Self { songs: vec![], } } ///Returns a playlist from a file. ///File must be yaml pub fn from_file(path: &String) -> Result> { let p = Path::new(path); //Check existence if !p.exists() { return Err(Box::new(PlaylistError::FileNotFound)); } //Read the yaml let content = fs::read_to_string(p)?; let res: Playlist = serde_yaml::from_str(&content)?; return Ok(res); } ///Writes the playlist with its index to a file with the path p ///Path must be fully qualified pub fn to_file(&self, path: &String) -> Result<(), Box>{ let p = Path::new(&path); //Check parent let mut parent = PathBuf::from(path); let _ = &parent.pop(); if !&parent.exists() { return Err(Box::new(PlaylistError::FileCantWrite)); } //Serialize and write let serialized = serde_yaml::to_string(&self.clone())?; let mut file = fs::OpenOptions::new() .write(true) .append(false) .create(true) .open(&p)?; file.write_all(format!("---\n{}", serialized).as_bytes())?; return Ok(()); } ///shortcut to myplaylist.songs.push(SongMeta::frompath(&String::from("/a/file/path"))) pub fn add_song_from_path(&mut self, path: &String) -> Result<(), Box> { let song = SongMeta::frompath(path)?; self.songs.push(song); return Ok(()); } ///Checks if a number is a valid index in this playlist. pub fn check_index(&self, index: usize) -> bool { if self.songs.len() == 0 { return false; } if index > (self.songs.len() - 1) { return false; } return true; } ///Removes a song at index (if possible) pub fn remove(&mut self, index: usize) -> Result<(), Box> { if !self.check_index(index.clone()) { return Err(Box::new(PlaylistError::IndexNotFound)); } self.songs.remove(index); return Ok(()); } ///Shuffles the playlist pub fn shuffle(&mut self) { //Return if playlist is empty if self.songs.len() == 0 { return; } self.songs.shuffle(&mut thread_rng()); } ///Sorts the playlist by artist, album, trackorder, title pub fn sort(&mut self) { self.songs.sort_by(|a, b| { a.artist .cmp(&b.artist) .then_with(|| a.album.cmp(&b.album)) .then_with(|| a.track.cmp(&b.track)) .then_with(|| a.title.cmp(&b.title)) }); } } }