| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) 2021, Richard Weinberger <richard@nod.at> |
| */ |
| |
| use std::ffi::OsStr; |
| use std::fs; |
| use std::io; |
| use std::path::PathBuf; |
| use std::time::SystemTime; |
| |
| extern crate rand; |
| use rand::seq::SliceRandom; |
| use rand::thread_rng; |
| |
| use crate::config; |
| use crate::playerbackend::PlayerBackend; |
| |
| pub trait PlayProgram { |
| fn start(&mut self, backend: &mut dyn PlayerBackend) -> io::Result<()>; |
| fn next(&mut self, backend: &mut dyn PlayerBackend) -> io::Result<()>; |
| fn prev(&mut self, backend: &mut dyn PlayerBackend) -> io::Result<()>; |
| } |
| |
| pub struct Linear { |
| base_dir: String, |
| base_dir_mtime: SystemTime, |
| items: Vec<PathBuf>, |
| pos: usize, |
| num_play: usize, |
| } |
| |
| pub struct Shuffle { |
| base_dir: String, |
| base_dir_mtime: SystemTime, |
| items: Vec<PathBuf>, |
| pos: usize, |
| num_play: usize, |
| } |
| |
| pub struct Random { |
| base_dir: String, |
| base_dir_mtime: SystemTime, |
| items: Vec<PathBuf>, |
| num_play: usize, |
| } |
| |
| fn generic_start_play( |
| backend: &mut dyn PlayerBackend, |
| items: &Vec<PathBuf>, |
| cur_pos: usize, |
| num_play: usize, |
| ) -> io::Result<usize> { |
| let mut it = items.iter().cycle(); |
| let num_want_play = std::cmp::min(num_play, items.len()); |
| let mut list = Vec::new(); |
| |
| //TODO: Use advance_by() as soon available |
| for _ in 0..cur_pos { |
| it.next(); |
| } |
| |
| for _ in 0..num_want_play { |
| list.push(it.next().unwrap()); |
| } |
| |
| backend.play_multiple_files(list)?; |
| |
| Ok((cur_pos + num_want_play) % items.len()) |
| } |
| |
| fn generic_next( |
| backend: &mut dyn PlayerBackend, |
| items: &Vec<PathBuf>, |
| cur_pos: usize, |
| num_play: usize, |
| ) -> io::Result<usize> { |
| let mut it = items.iter().cycle(); |
| let num_want_play = std::cmp::min(num_play, items.len()); |
| let mut list = Vec::new(); |
| let new_pos = (cur_pos + num_want_play) % items.len(); |
| |
| //TODO: Use advance_by() as soon available |
| for _ in 0..new_pos { |
| it.next(); |
| } |
| |
| for _ in 0..num_want_play { |
| list.push(it.next().unwrap()); |
| } |
| |
| backend.play_multiple_files(list)?; |
| |
| Ok(new_pos) |
| } |
| |
| fn generic_prev( |
| backend: &mut dyn PlayerBackend, |
| items: &Vec<PathBuf>, |
| cur_pos: usize, |
| num_play: usize, |
| ) -> io::Result<usize> { |
| let mut it = items.iter().cycle(); |
| let num_want_play = std::cmp::min(num_play, items.len()); |
| let mut list = Vec::new(); |
| let new_pos; |
| |
| if cur_pos < num_want_play { |
| new_pos = 0; |
| } else { |
| new_pos = cur_pos - num_want_play; |
| } |
| |
| //TODO: Use advance_by() as soon available |
| for _ in 0..new_pos { |
| it.next(); |
| } |
| |
| for _ in 0..num_want_play { |
| list.push(it.next().unwrap()); |
| } |
| |
| backend.play_multiple_files(list)?; |
| |
| Ok(new_pos) |
| } |
| |
| fn load_files_nonrec(from: &String, items: &mut Vec<PathBuf>) -> io::Result<()> { |
| for entry in fs::read_dir(from)? { |
| let entry = entry.unwrap().path(); |
| |
| if entry.is_file() { |
| let extension = entry.extension().unwrap_or(OsStr::new("")); |
| |
| if extension != OsStr::new("") { |
| items.push(entry); |
| } |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| fn source_has_changed(dir: &String, dir_mtime: SystemTime) -> io::Result<bool> { |
| let metadata = fs::metadata(&dir)?; |
| let mtime = metadata.modified().unwrap(); |
| |
| Ok(mtime != dir_mtime) |
| } |
| |
| impl Linear { |
| pub fn new(cfg: &config::PlayListConfig) -> io::Result<Linear> { |
| let num_play = match cfg.extra.get("num_play") { |
| None => 1, |
| Some(v) => v.parse::<usize>().unwrap_or(1), |
| }; |
| |
| let metadata = fs::metadata(&cfg.source)?; |
| |
| let mut pl = Linear { |
| base_dir: String::from(&cfg.source), |
| base_dir_mtime: metadata.modified().unwrap(), |
| num_play: num_play, |
| items: Vec::new(), |
| pos: 0, |
| }; |
| |
| load_files_nonrec(&pl.base_dir, &mut pl.items)?; |
| |
| pl.items.sort(); |
| |
| Ok(pl) |
| } |
| } |
| |
| impl PlayProgram for Linear { |
| fn start(&mut self, backend: &mut dyn PlayerBackend) -> io::Result<()> { |
| if source_has_changed(&self.base_dir, self.base_dir_mtime)? { |
| self.items = Vec::new(); |
| self.pos = 0; |
| load_files_nonrec(&self.base_dir, &mut self.items)?; |
| } |
| |
| generic_start_play(backend, &self.items, self.pos, self.num_play)?; |
| |
| Ok(()) |
| } |
| |
| fn next(&mut self, backend: &mut dyn PlayerBackend) -> io::Result<()> { |
| self.pos = generic_next(backend, &self.items, self.pos, self.num_play)?; |
| |
| Ok(()) |
| } |
| |
| fn prev(&mut self, backend: &mut dyn PlayerBackend) -> io::Result<()> { |
| self.pos = generic_prev(backend, &self.items, self.pos, self.num_play)?; |
| |
| Ok(()) |
| } |
| } |
| |
| impl Shuffle { |
| pub fn new(cfg: &config::PlayListConfig) -> io::Result<Shuffle> { |
| let num_play = match cfg.extra.get("num_play") { |
| None => 1, |
| Some(v) => v.parse::<usize>().unwrap_or(1), |
| }; |
| |
| let metadata = fs::metadata(&cfg.source)?; |
| |
| let mut pl = Shuffle { |
| base_dir: String::from(&cfg.source), |
| base_dir_mtime: metadata.modified().unwrap(), |
| num_play: num_play, |
| items: Vec::new(), |
| pos: 0, |
| }; |
| |
| load_files_nonrec(&pl.base_dir, &mut pl.items)?; |
| |
| pl.items.shuffle(&mut thread_rng()); |
| |
| Ok(pl) |
| } |
| } |
| |
| impl PlayProgram for Shuffle { |
| fn start(&mut self, backend: &mut dyn PlayerBackend) -> io::Result<()> { |
| if source_has_changed(&self.base_dir, self.base_dir_mtime)? { |
| self.items = Vec::new(); |
| self.pos = 0; |
| load_files_nonrec(&self.base_dir, &mut self.items)?; |
| } |
| |
| self.pos = generic_start_play(backend, &self.items, self.pos, self.num_play)?; |
| |
| Ok(()) |
| } |
| |
| fn next(&mut self, backend: &mut dyn PlayerBackend) -> io::Result<()> { |
| let new_pos = generic_next(backend, &self.items, self.pos, self.num_play)?; |
| |
| if new_pos < self.pos { |
| self.items.shuffle(&mut thread_rng()); |
| self.pos = 0; |
| } else { |
| self.pos = new_pos; |
| } |
| |
| Ok(()) |
| } |
| |
| fn prev(&mut self, backend: &mut dyn PlayerBackend) -> io::Result<()> { |
| self.pos = generic_prev(backend, &self.items, self.pos, self.num_play)?; |
| |
| Ok(()) |
| } |
| } |
| |
| impl Random { |
| pub fn new(cfg: &config::PlayListConfig) -> io::Result<Random> { |
| let num_play = match cfg.extra.get("num_play") { |
| None => 1, |
| Some(v) => v.parse::<usize>().unwrap_or(1), |
| }; |
| |
| let metadata = fs::metadata(&cfg.source)?; |
| |
| let mut pl = Random { |
| base_dir: String::from(&cfg.source), |
| base_dir_mtime: metadata.modified().unwrap(), |
| num_play: num_play, |
| items: Vec::new(), |
| }; |
| |
| load_files_nonrec(&pl.base_dir, &mut pl.items)?; |
| |
| Ok(pl) |
| } |
| } |
| |
| impl PlayProgram for Random { |
| fn start(&mut self, backend: &mut dyn PlayerBackend) -> io::Result<()> { |
| if source_has_changed(&self.base_dir, self.base_dir_mtime)? { |
| self.items = Vec::new(); |
| load_files_nonrec(&self.base_dir, &mut self.items)?; |
| } |
| |
| self.items.shuffle(&mut thread_rng()); |
| generic_start_play(backend, &self.items, 0, self.num_play)?; |
| |
| Ok(()) |
| } |
| |
| fn next(&mut self, backend: &mut dyn PlayerBackend) -> io::Result<()> { |
| self.start(backend) |
| } |
| |
| fn prev(&mut self, backend: &mut dyn PlayerBackend) -> io::Result<()> { |
| self.start(backend) |
| } |
| } |