refactor(player): change to dependency injection
Use this pattern to decouple the dependencies and allow to test te code and introduce new dependencies in the future
This commit is contained in:
parent
462c480a45
commit
d98b645377
4 changed files with 71 additions and 70 deletions
|
|
@ -1,45 +0,0 @@
|
||||||
use ignore::types::TypesBuilder;
|
|
||||||
use ignore::WalkBuilder;
|
|
||||||
use std::error::Error;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
pub fn walk_dir(path: &PathBuf) -> Result<Vec<PathBuf>, Box<dyn Error>> {
|
|
||||||
let mut types_builder = TypesBuilder::new();
|
|
||||||
types_builder.add_defaults();
|
|
||||||
|
|
||||||
let accepted_filetypes = ["mp3", "flac", "wav"];
|
|
||||||
|
|
||||||
for filetype in accepted_filetypes {
|
|
||||||
let _ = types_builder.add("sound", format!("*.{}", filetype).as_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
types_builder.select("sound");
|
|
||||||
|
|
||||||
// let mut base_path = env::current_dir().expect("Error accesing the enviroment");
|
|
||||||
//
|
|
||||||
// match path {
|
|
||||||
// Some(dir) => {
|
|
||||||
// search_path = base_path
|
|
||||||
// .join(dir)
|
|
||||||
// .canonicalize()
|
|
||||||
// .expect("Couldn't canonicalizice the path")
|
|
||||||
// }
|
|
||||||
// None => search_path = base_path.to_owned(),
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // PathBuf.join() can override the hole path, this ensure we're not accessing files outside
|
|
||||||
// // base_dir
|
|
||||||
// if !search_path.starts_with(base_path) {
|
|
||||||
// return Err("Tried to access file or directory outside of server `base_path` config.");
|
|
||||||
// }
|
|
||||||
|
|
||||||
let entries: Vec<PathBuf> = WalkBuilder::new(path)
|
|
||||||
.types(types_builder.build().unwrap())
|
|
||||||
.build()
|
|
||||||
.filter_map(|entry| entry.ok())
|
|
||||||
.filter(|entry| !entry.path().is_dir())
|
|
||||||
.map(|entry| entry.path().to_path_buf())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(entries)
|
|
||||||
}
|
|
||||||
34
src/file_handler.rs
Normal file
34
src/file_handler.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
use ignore::types::TypesBuilder;
|
||||||
|
use ignore::WalkBuilder;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub trait FileExplorer {
|
||||||
|
fn get_files(path: &PathBuf) -> Vec<PathBuf>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LocalFileSystem;
|
||||||
|
|
||||||
|
impl FileExplorer for LocalFileSystem {
|
||||||
|
fn get_files(path: &PathBuf) -> Vec<PathBuf> {
|
||||||
|
let mut types_builder = TypesBuilder::new();
|
||||||
|
types_builder.add_defaults();
|
||||||
|
|
||||||
|
let accepted_filetypes = ["mp3", "flac", "wav"];
|
||||||
|
|
||||||
|
for filetype in accepted_filetypes {
|
||||||
|
let _ = types_builder.add("sound", format!("*.{}", filetype).as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
types_builder.select("sound");
|
||||||
|
|
||||||
|
let entries: Vec<PathBuf> = WalkBuilder::new(path)
|
||||||
|
.types(types_builder.build().unwrap())
|
||||||
|
.build()
|
||||||
|
.filter_map(|entry| entry.ok())
|
||||||
|
.filter(|entry| !entry.path().is_dir())
|
||||||
|
.map(|entry| entry.path().to_path_buf())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
entries
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,14 +8,15 @@ use tokio::sync::mpsc;
|
||||||
use crate::player::Player;
|
use crate::player::Player;
|
||||||
|
|
||||||
use self::configuration::{Commands, Config, ConfigMode};
|
use self::configuration::{Commands, Config, ConfigMode};
|
||||||
|
use self::file_handler::{FileExplorer, LocalFileSystem};
|
||||||
use self::grpc::server::GrpcServerMessage;
|
use self::grpc::server::GrpcServerMessage;
|
||||||
|
|
||||||
mod configuration;
|
mod configuration;
|
||||||
mod file_explorer;
|
mod file_handler;
|
||||||
mod grpc;
|
mod grpc;
|
||||||
mod player;
|
mod player;
|
||||||
|
|
||||||
async fn handle_message(player: &mut Player, message: GrpcServerMessage) {
|
async fn handle_message<T: FileExplorer>(player: &mut Player<T>, message: GrpcServerMessage) {
|
||||||
match message {
|
match message {
|
||||||
GrpcServerMessage::Play { resp } => {
|
GrpcServerMessage::Play { resp } => {
|
||||||
player.play();
|
player.play();
|
||||||
|
|
@ -64,7 +65,9 @@ async fn init_server(config: Config) -> Result<(), Box<dyn Error>> {
|
||||||
volume = config_volume;
|
volume = config_volume;
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut player = Player::new(base_path, volume).expect("Error creating player");
|
let mut player = Player::new(LocalFileSystem, base_path).expect("Error creating player");
|
||||||
|
|
||||||
|
player.set_volume(volume);
|
||||||
|
|
||||||
println!("Listening for incomming messages...");
|
println!("Listening for incomming messages...");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,18 @@ use std::path::PathBuf;
|
||||||
|
|
||||||
use rodio::{OutputStream, Sink};
|
use rodio::{OutputStream, Sink};
|
||||||
|
|
||||||
use crate::file_explorer::walk_dir;
|
use crate::file_handler::FileExplorer;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct Player {
|
pub struct Player<T: FileExplorer> {
|
||||||
queue: VecDeque<PathBuf>,
|
queue: VecDeque<PathBuf>,
|
||||||
sink: Sink,
|
sink: Sink,
|
||||||
stream: OutputStream,
|
stream: OutputStream,
|
||||||
base_dir: PathBuf,
|
base_dir: PathBuf,
|
||||||
|
explorer: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Deref for Player {
|
impl<T: FileExplorer> std::ops::Deref for Player<T> {
|
||||||
type Target = Sink;
|
type Target = Sink;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
|
|
@ -24,19 +25,19 @@ impl std::ops::Deref for Player {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Player {
|
impl<T: FileExplorer> Player<T> {
|
||||||
pub fn new(base_dir: PathBuf, volume: f32) -> Result<Self, Box<dyn Error>> {
|
pub fn new(explorer: T, base_dir: PathBuf) -> Result<Self, Box<dyn Error>> {
|
||||||
let queue = walk_dir(&base_dir)?;
|
let queue = T::get_files(&base_dir);
|
||||||
// stream needs to exist as long as sink to work
|
// stream needs to exist as long as sink to work
|
||||||
let (stream, stream_handle) = OutputStream::try_default()?;
|
let (stream, stream_handle) = OutputStream::try_default()?;
|
||||||
let sink = Sink::try_new(&stream_handle)?;
|
let sink = Sink::try_new(&stream_handle)?;
|
||||||
sink.set_volume(volume);
|
|
||||||
|
|
||||||
Ok(Player {
|
Ok(Player {
|
||||||
queue: VecDeque::from(queue),
|
queue: VecDeque::from(queue),
|
||||||
sink,
|
sink,
|
||||||
stream,
|
stream,
|
||||||
base_dir,
|
base_dir,
|
||||||
|
explorer,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,20 +65,6 @@ impl Player {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_files(&mut self, path: &PathBuf) -> Result<Vec<PathBuf>, Box<dyn Error>> {
|
pub fn get_files(&mut self, path: &PathBuf) -> Result<Vec<PathBuf>, Box<dyn Error>> {
|
||||||
// let mut base_path = env::current_dir().expect("Error accesing the enviroment");
|
|
||||||
//
|
|
||||||
// match path {
|
|
||||||
// Some(dir) => {
|
|
||||||
// }
|
|
||||||
// None => search_path = base_path.to_owned(),
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // PathBuf.join() can override the hole path, this ensure we're not accessing files outside
|
|
||||||
// // base_dir
|
|
||||||
// if !search_path.starts_with(base_path) {
|
|
||||||
// return Err("Tried to access file or directory outside of server `base_path` config.");
|
|
||||||
// }
|
|
||||||
|
|
||||||
let search_path = self
|
let search_path = self
|
||||||
.base_dir
|
.base_dir
|
||||||
.join(path)
|
.join(path)
|
||||||
|
|
@ -89,7 +76,7 @@ impl Player {
|
||||||
panic!("Tried to access file or directory outside of server `base_path` config.")
|
panic!("Tried to access file or directory outside of server `base_path` config.")
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(walk_dir(&search_path)?)
|
Ok(T::get_files(&search_path))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn play(&mut self) {
|
pub fn play(&mut self) {
|
||||||
|
|
@ -124,4 +111,26 @@ impl Player {
|
||||||
self.sink.append(rodio::Decoder::new(BufReader::new(file))?);
|
self.sink.append(rodio::Decoder::new(BufReader::new(file))?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_volume(&self, volume: f32) {
|
||||||
|
self.sink.set_volume(volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
struct MockFileExplorer;
|
||||||
|
|
||||||
|
impl FileExplorer for MockFileExplorer {
|
||||||
|
fn get_files(_: &PathBuf) -> Vec<PathBuf> {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn player_works() {
|
||||||
|
let _ = Player::new(MockFileExplorer, PathBuf::from(".")).expect("Error creating player");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue