blob: 4cf00bf50ca02b7befce49037825a52ed17b36b4 [file] [log] [blame]
// 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)
}
}