Finished Controller Prototype

This commit is contained in:
MrDulfin 2024-02-12 13:43:30 -05:00
parent 3ad8b78e9d
commit 599ddc584c
6 changed files with 139 additions and 25 deletions

2
.gitignore vendored
View file

@ -13,3 +13,5 @@ music_database*
*.m3u *.m3u
*.m3u8 *.m3u8
*.json *.json
*.zip
*.xml

View file

@ -71,8 +71,10 @@ impl ConfigLibraries {
} }
pub fn get_library(&self, uuid: &Uuid) -> Result<ConfigLibrary, ConfigError> { pub fn get_library(&self, uuid: &Uuid) -> Result<ConfigLibrary, ConfigError> {
dbg!(&uuid);
for library in &self.libraries { for library in &self.libraries {
if &library.uuid == uuid { if &library.uuid == uuid {
dbg!(&library.uuid);
return Ok(library.to_owned()) return Ok(library.to_owned())
} }
} }
@ -128,8 +130,8 @@ impl Config {
let mut file: File = File::open(path)?; let mut file: File = File::open(path)?;
let mut bun: String = String::new(); let mut bun: String = String::new();
_ = file.read_to_string(&mut bun); _ = file.read_to_string(&mut bun);
let ny: Config = serde_json::from_str::<Config>(&bun)?; let config: Config = serde_json::from_str::<Config>(&bun)?;
Ok(ny) Ok(config)
} }
} }

View file

@ -2,7 +2,7 @@
//! player. It manages queues, playback, library access, and //! player. It manages queues, playback, library access, and
//! other functions //! other functions
use std::path::PathBuf; use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use std::time::Duration; use std::time::Duration;
use crossbeam_channel::{Sender, Receiver}; use crossbeam_channel::{Sender, Receiver};
@ -18,7 +18,7 @@ use rayon::iter::Rev;
use uuid::Uuid; use uuid::Uuid;
use crate::config; use crate::config;
use crate::music_storage::library::Tag; use crate::music_storage::library::{Tag, URI};
use crate::music_storage::playlist::Playlist; use crate::music_storage::playlist::Playlist;
use crate::{ use crate::{
music_player::Player, music_player::Player,
@ -31,7 +31,7 @@ use crate::{
struct Queue { struct Queue {
player: Player, player: Player,
name: String, name: String,
songs: Playlist, songs: Vec<Song>,
} }
impl Queue { impl Queue {
fn new() -> Result<Self, Box<dyn Error>> { fn new() -> Result<Self, Box<dyn Error>> {
@ -39,10 +39,15 @@ impl Queue {
Queue { Queue {
player: Player::new()?, player: Player::new()?,
name: String::new(), name: String::new(),
songs: Playlist::new() songs: Vec::new()
} }
) )
} }
fn set_tracks(&mut self, tracks: Vec<Song>) {
let mut tracks = tracks;
self.songs.clear();
self.songs.append(&mut tracks);
}
} }
pub struct Controller { pub struct Controller {
@ -54,39 +59,47 @@ pub struct Controller {
queue_mail: Vec<MailMan<QueueCommand, QueueResponse>>, queue_mail: Vec<MailMan<QueueCommand, QueueResponse>>,
} }
#[derive(Debug)] #[derive(Debug)]
pub enum ControllerCommand { pub enum ControllerCommand {
Default, Default,
Test Test
} }
#[derive(Debug)]
#[derive(Debug)]
enum ControllerResponse { enum ControllerResponse {
Empty, Empty,
QueueMailMan(MailMan<QueueCommand, QueueResponse>), QueueMailMan(MailMan<QueueCommand, QueueResponse>),
} }
#[derive(Debug)]
#[derive(Debug)]
pub enum DatabaseCommand { pub enum DatabaseCommand {
Default, Default,
Test, Test,
GetSongs, GetSongs,
QueryUuid(Uuid),
QueryUuids(Vec<Uuid>),
ReadFolder(String),
} }
#[derive(Debug)]
#[derive(Debug)]
enum DatabaseResponse { enum DatabaseResponse {
Empty, Empty,
Song(Song),
Songs(Vec<Song>), Songs(Vec<Song>),
} }
#[derive(Debug)] #[derive(Debug)]
enum QueueCommand { enum QueueCommand {
Default, Default,
Test, Test,
Play, Play,
Pause, Pause,
SetSongs(Vec<Song>),
// SetLocation(URI),
Enqueue(URI),
} }
#[derive(Debug)] #[derive(Debug)]
enum QueueResponse { enum QueueResponse {
Default, Default,
@ -94,11 +107,11 @@ enum QueueResponse {
} }
#[derive(Debug)] #[derive(Debug)]
struct MailMan<T, U> { struct MailMan<T, U> {
pub tx: Sender<T>, pub tx: Sender<T>,
rx: Receiver<U> rx: Receiver<U>
} }
impl<T> MailMan<T, T> { impl<T> MailMan<T, T> {
pub fn new() -> Self { pub fn new() -> Self {
let (tx, rx) = unbounded::<T>(); let (tx, rx) = unbounded::<T>();
@ -129,12 +142,13 @@ impl<T, U> MailMan<T, U> {
#[allow(unused_variables)] #[allow(unused_variables)]
impl Controller { impl Controller {
pub fn start(config: PathBuf) -> Result<Self, Box<dyn Error>> { pub fn start(config_path: String) -> Result<Self, Box<dyn Error>> {
let config = Config::read_file(config)?; let config_path = PathBuf::from(config_path);
let config = Config::read_file(config_path)?;
let uuid = config.libraries.get_default()?.uuid; let uuid = config.libraries.get_default()?.uuid;
let config = Arc::new(RwLock::from(config)); let config = Arc::new(RwLock::from(config));
let lib = MusicLibrary::init(config.clone(), uuid)?; let mut lib = MusicLibrary::init(config.clone(), uuid)?;
let (out_thread_controller, in_thread) = MailMan::double(); let (out_thread_controller, in_thread) = MailMan::double();
let monitor_thread = spawn(move || { let monitor_thread = spawn(move || {
@ -167,6 +181,26 @@ impl Controller {
let songs = lib.query_tracks(&String::from(""), &(vec![Tag::Title]), &(vec![Tag::Title])).unwrap().iter().cloned().cloned().collect(); let songs = lib.query_tracks(&String::from(""), &(vec![Tag::Title]), &(vec![Tag::Title])).unwrap().iter().cloned().cloned().collect();
in_thread.send(DatabaseResponse::Songs(songs)).unwrap(); in_thread.send(DatabaseResponse::Songs(songs)).unwrap();
}, },
QueryUuid(uuid) => {
match lib.query_uuid(&uuid) {
Some(song) => in_thread.send(DatabaseResponse::Song(song.0.clone())).unwrap(),
None => in_thread.send(DatabaseResponse::Empty).unwrap(),
}
},
QueryUuids(uuids) => {
let mut vec = Vec::new();
for uuid in uuids {
match lib.query_uuid(&uuid) {
Some(song) => vec.push(song.0.clone()),
None => unimplemented!()
}
}
in_thread.send(DatabaseResponse::Songs(vec)).unwrap();
},
ReadFolder(folder) => {
lib.scan_folder(&folder).unwrap();
in_thread.send(DatabaseResponse::Empty).unwrap();
}
} }
} }
@ -184,6 +218,7 @@ impl Controller {
} }
) )
} }
fn get_db_songs(&self) -> Vec<Song> { fn get_db_songs(&self) -> Vec<Song> {
self.db_mail.send(DatabaseCommand::GetSongs); self.db_mail.send(DatabaseCommand::GetSongs);
match self.db_mail.recv().unwrap() { match self.db_mail.recv().unwrap() {
@ -192,29 +227,76 @@ impl Controller {
} }
} }
pub fn new_queue(&mut self) { pub fn new_queue(&mut self) {
let (out_thread_queue, in_thread) = MailMan::<QueueCommand, QueueResponse>::double(); let (out_thread_queue, in_thread) = MailMan::<QueueCommand, QueueResponse>::double();
let queues_monitor = spawn(move || { let queues_monitor = spawn(move || {
use QueueCommand::*; use QueueCommand::*;
let mut queue = Queue::new().unwrap();
loop { loop {
let command = in_thread.recv().unwrap(); let command = in_thread.recv().unwrap();
match command { match command {
Default => {}, Default => {},
Test => {}, Test => { in_thread.send(QueueResponse::Test).unwrap() },
Play => {}, Play => {
queue.player.play().unwrap();
in_thread.send(QueueResponse::Default).unwrap();
},
Pause => {}, Pause => {},
SetSongs(songs) => {
queue.set_tracks(songs);
in_thread.send(QueueResponse::Default).unwrap();
},
Enqueue(uri) => {
queue.player.enqueue_next(&uri);
}
} }
} }
}); });
self.queue_mail.push(out_thread_queue); self.queue_mail.push(out_thread_queue);
} }
fn play(&self, index: usize) -> Result<(), Box<dyn Error>> {
let mail = &self.queue_mail[index];
mail.send(QueueCommand::Play)?;
dbg!(mail.recv()?);
Ok(())
}
fn set_songs(&self, index: usize, songs: Vec<Song>) -> Result<(), Box<dyn Error>> {
let mail = &self.queue_mail[index];
mail.send(QueueCommand::SetSongs(songs))?;
dbg!(mail.recv()?);
Ok(())
}
fn enqueue(&self, index: usize, uri: URI) -> Result<(), Box<dyn Error>> {
let mail = &self.queue_mail[index];
mail.send(QueueCommand::Enqueue(uri))?;
dbg!(mail.recv()?);
Ok(())
}
fn scan_folder(&self, folder: String) -> Result<(), Box<dyn Error>> {
let mail = &self.db_mail;
mail.send(DatabaseCommand::ReadFolder(folder))?;
dbg!(mail.recv()?);
Ok(())
}
} }
#[test] #[test]
fn name() { fn name() {
let a = Controller::start(PathBuf::from("test-config/config_test.json")).unwrap(); let mut a = match Controller::start("test-config/config_test.json".to_string()) {
// sleep(Duration::from_millis(5000)); Ok(c) => c,
_ = a.controller_mail.send(ControllerCommand::Test); Err(e) => panic!("{e}")
// dbg!(a.get_db_songs()); };
// sleep(Duration::from_secs(6)); sleep(Duration::from_millis(500));
a.scan_folder("test-config/music/".to_string());
a.new_queue();
let songs = a.get_db_songs();
a.enqueue(0, songs[0].location.clone());
a.play(0).unwrap();
sleep(Duration::from_secs(10));
} }

View file

@ -1,6 +1,8 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::{fs::File, io::Read, path::Path, time::Duration}; use std::{fs::File, io::Read, path::Path, time::Duration};
use uuid::Uuid;
use super::utils::meta_offset; use super::utils::meta_offset;
use crate::music_storage::db_reader::common::{get_bytes, get_bytes_vec}; use crate::music_storage::db_reader::common::{get_bytes, get_bytes_vec};
use crate::music_storage::db_reader::extern_library::ExternalLibrary; use crate::music_storage::db_reader::extern_library::ExternalLibrary;
@ -177,6 +179,7 @@ impl FoobarPlaylistTrack {
Song { Song {
location, location,
uuid: Uuid::new_v4(),
plays: 0, plays: 0,
skips: 0, skips: 0,
favorited: false, favorited: false,

View file

@ -170,6 +170,7 @@ impl ExternalLibrary for ITunesLibrary {
let ny: Song = Song { let ny: Song = Song {
location: sug, location: sug,
uuid: Uuid::new_v4(),
plays: track.plays, plays: track.plays,
skips: 0, skips: 0,
favorited: track.favorited, favorited: track.favorited,

View file

@ -119,6 +119,7 @@ impl ToString for Field {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct Song { pub struct Song {
pub location: URI, pub location: URI,
pub uuid: Uuid,
pub plays: i32, pub plays: i32,
pub skips: i32, pub skips: i32,
pub favorited: bool, pub favorited: bool,
@ -256,6 +257,7 @@ impl Song {
let new_song = Song { let new_song = Song {
location: URI::Local(binding), location: URI::Local(binding),
uuid: Uuid::new_v4(),
plays: 0, plays: 0,
skips: 0, skips: 0,
favorited: false, favorited: false,
@ -378,6 +380,7 @@ impl Song {
start, start,
end, end,
}, },
uuid: Uuid::new_v4(),
plays: 0, plays: 0,
skips: 0, skips: 0,
favorited: false, favorited: false,
@ -552,7 +555,7 @@ pub struct MusicLibrary {
#[test] #[test]
fn library_init() { fn library_init() {
let config = Config::read_file(PathBuf::from("config_test.json")).unwrap(); let config = Config::read_file(PathBuf::from("test_config/config_test.json")).unwrap();
let target_uuid = config.libraries.libraries[0].uuid; let target_uuid = config.libraries.libraries[0].uuid;
let a = MusicLibrary::init(Arc::new(RwLock::from(config)), target_uuid).unwrap(); let a = MusicLibrary::init(Arc::new(RwLock::from(config)), target_uuid).unwrap();
dbg!(a); dbg!(a);
@ -575,13 +578,14 @@ impl MusicLibrary {
/// the [MusicLibrary] Vec /// the [MusicLibrary] Vec
pub fn init(config: Arc<RwLock<Config>>, uuid: Uuid) -> Result<Self, Box<dyn Error>> { pub fn init(config: Arc<RwLock<Config>>, uuid: Uuid) -> Result<Self, Box<dyn Error>> {
let global_config = &*config.read().unwrap(); let global_config = &*config.read().unwrap();
let path = global_config.libraries.get_library(&uuid)?.path;
let library: MusicLibrary = match global_config.libraries.get_library(&uuid)?.path.exists() { let library: MusicLibrary = match path.exists() {
true => read_file(global_config.libraries.get_library(&uuid)?.path)?, true => read_file(path)?,
false => { false => {
// If the library does not exist, re-create it // If the library does not exist, re-create it
let lib = MusicLibrary::new(String::new(), uuid); let lib = MusicLibrary::new(String::new(), uuid);
write_file(&lib, global_config.libraries.get_library(&uuid)?.path)?; write_file(&lib, path)?;
lib lib
} }
}; };
@ -643,6 +647,26 @@ impl MusicLibrary {
} }
} }
/// Queries for a [Song] by its [Uuid], returning a single `Song`
/// with the `Uuid` that matches along with its position in the library
pub fn query_uuid(&self, uuid: &Uuid) -> Option<(&Song, usize)> {
let result = self
.library
.par_iter()
.enumerate()
.try_for_each(|(i, track)| {
if uuid == &track.uuid {
return std::ops::ControlFlow::Break((track, i));
}
Continue(())
});
match result {
Break(song) => Some(song),
Continue(_) => None,
}
}
/// Queries for a [Song] by its [PathBuf], returning a `Vec<&Song>` /// Queries for a [Song] by its [PathBuf], returning a `Vec<&Song>`
/// with matching `PathBuf`s /// with matching `PathBuf`s
fn query_path(&self, path: PathBuf) -> Option<Vec<&Song>> { fn query_path(&self, path: PathBuf) -> Option<Vec<&Song>> {