mirror of
https://github.com/Dangoware/dmp-core.git
synced 2025-04-19 13:32:53 -05:00
Finished main query function, added MusicLibrary
to MusicController
This commit is contained in:
parent
d2d92149f8
commit
bde2d194dc
4 changed files with 85 additions and 26 deletions
|
@ -33,5 +33,5 @@ arrayvec = "0.7.4"
|
|||
discord-presence = "0.5.18"
|
||||
chrono = { version = "0.4.31", features = ["serde"] }
|
||||
bincode = "1.3.3"
|
||||
wana_kana = "3.0.0"
|
||||
unidecode = "0.3.0"
|
||||
rayon = "1.8.0"
|
||||
|
|
|
@ -3,21 +3,27 @@ use std::sync::{RwLock, Arc};
|
|||
|
||||
use crate::music_controller::config::Config;
|
||||
use crate::music_player::music_player::{MusicPlayer, PlayerStatus, DecoderMessage, DSPMessage};
|
||||
use crate::music_storage::music_db::Song;
|
||||
use crate::music_storage::music_db::{MusicLibrary, Song, Tag};
|
||||
|
||||
pub struct MusicController {
|
||||
pub config: Arc<RwLock<Config>>,
|
||||
pub library: MusicLibrary,
|
||||
music_player: MusicPlayer,
|
||||
}
|
||||
|
||||
impl MusicController {
|
||||
/// Creates new MusicController with config at given path
|
||||
pub fn new(config_path: &PathBuf) -> Result<MusicController, std::io::Error>{
|
||||
pub fn new(config_path: &PathBuf) -> Result<MusicController, Box<dyn std::error::Error>>{
|
||||
let config = Arc::new(RwLock::new(Config::new(config_path)?));
|
||||
let music_player = MusicPlayer::new(config.clone());
|
||||
let library = match MusicLibrary::init(config.clone()) {
|
||||
Ok(library) => library,
|
||||
Err(error) => return Err(error)
|
||||
};
|
||||
|
||||
let controller = MusicController {
|
||||
config,
|
||||
library,
|
||||
music_player,
|
||||
};
|
||||
|
||||
|
@ -25,12 +31,17 @@ impl MusicController {
|
|||
}
|
||||
|
||||
/// Creates new music controller from a config at given path
|
||||
pub fn from(config_path: &PathBuf) -> std::result::Result<MusicController, toml::de::Error> {
|
||||
pub fn from(config_path: &PathBuf) -> Result<MusicController, Box<dyn std::error::Error>> {
|
||||
let config = Arc::new(RwLock::new(Config::from(config_path)?));
|
||||
let music_player = MusicPlayer::new(config.clone());
|
||||
let library = match MusicLibrary::init(config.clone()) {
|
||||
Ok(library) => library,
|
||||
Err(error) => return Err(error)
|
||||
};
|
||||
|
||||
let controller = MusicController {
|
||||
config,
|
||||
library,
|
||||
music_player,
|
||||
};
|
||||
|
||||
|
@ -63,4 +74,13 @@ impl MusicController {
|
|||
self.song_control(DecoderMessage::DSP(DSPMessage::UpdateProcessor(Box::new(self.music_player.music_processor.clone()))));
|
||||
}
|
||||
|
||||
/// Queries the library for a `Vec<Song>`
|
||||
pub fn query_library(
|
||||
&self,
|
||||
query_string: &String,
|
||||
target_tags: Vec<Tag>,
|
||||
sort_by: Vec<Tag>
|
||||
) -> Option<Vec<&Song>> {
|
||||
self.library.query(query_string, &target_tags, &sort_by)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,11 @@ use std::io::BufWriter;
|
|||
use std::path::{Path, PathBuf};
|
||||
use unidecode::unidecode;
|
||||
|
||||
// Fun parallel stuff
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use rayon::iter;
|
||||
use rayon::prelude::*;
|
||||
|
||||
use crate::music_controller::config::Config;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
|
@ -29,6 +34,7 @@ pub enum Tag {
|
|||
Genre,
|
||||
Comment,
|
||||
Track,
|
||||
Disk,
|
||||
Key(String)
|
||||
}
|
||||
|
||||
|
@ -41,6 +47,7 @@ impl ToString for Tag {
|
|||
Self::Genre => "Genre".into(),
|
||||
Self::Comment => "Comment".into(),
|
||||
Self::Track => "TrackNumber".into(),
|
||||
Self::Disk => "DiscNumber".into(),
|
||||
Self::Key(key) => key.into()
|
||||
}
|
||||
}
|
||||
|
@ -68,6 +75,17 @@ pub struct Song {
|
|||
}
|
||||
|
||||
impl Song {
|
||||
/**
|
||||
* Get a tag's value
|
||||
*
|
||||
* ```
|
||||
* // Assuming an already created song:
|
||||
*
|
||||
* let tag = this_song.get_tag(Tag::Title);
|
||||
*
|
||||
* assert_eq!(tag, "Title");
|
||||
* ```
|
||||
**/
|
||||
pub fn get_tag(&self, target_key: &Tag) -> Option<&String> {
|
||||
let index = self.tags.iter().position(|r| r.0 == *target_key);
|
||||
|
||||
|
@ -124,24 +142,25 @@ pub struct MusicLibrary {
|
|||
}
|
||||
|
||||
pub fn normalize(input_string: &String) -> String {
|
||||
unidecode(input_string).to_ascii_lowercase()
|
||||
unidecode(input_string).to_ascii_lowercase().replace(|c: char| !c.is_alphanumeric() && !c.is_ascii_punctuation(), "")
|
||||
}
|
||||
|
||||
impl MusicLibrary {
|
||||
/// Initialize the database
|
||||
///
|
||||
/// If the database file already exists, return the Library, otherwise create
|
||||
/// If the database file already exists, return the [MusicLibrary], otherwise create
|
||||
/// the database first. This needs to be run before anything else to retrieve
|
||||
/// the library vec
|
||||
pub fn init(config: &Config) -> Result<Self, Box<dyn Error>> {
|
||||
/// the [MusicLibrary] Vec
|
||||
pub fn init(config: Arc<RwLock<Config>>) -> Result<Self, Box<dyn Error>> {
|
||||
let global_config = &*config.read().unwrap();
|
||||
let mut library: Vec<Song> = Vec::new();
|
||||
let mut backup_path = config.db_path.clone();
|
||||
let mut backup_path = global_config.db_path.clone();
|
||||
backup_path.set_extension("bkp");
|
||||
|
||||
match config.db_path.try_exists() {
|
||||
match global_config.db_path.try_exists() {
|
||||
Ok(true) => {
|
||||
// The database exists, so get it from the file
|
||||
let database = fs::File::open(config.db_path.to_path_buf())?;
|
||||
let database = fs::File::open(global_config.db_path.to_path_buf())?;
|
||||
let reader = BufReader::new(database);
|
||||
library = deserialize_from(reader)?;
|
||||
}
|
||||
|
@ -149,11 +168,11 @@ impl MusicLibrary {
|
|||
// Create the database if it does not exist
|
||||
// possibly from the backup file
|
||||
if backup_path.try_exists().is_ok_and(|x| x == true) {
|
||||
let database = fs::File::open(config.db_path.to_path_buf())?;
|
||||
let database = fs::File::open(global_config.db_path.to_path_buf())?;
|
||||
let reader = BufReader::new(database);
|
||||
library = deserialize_from(reader)?;
|
||||
} else {
|
||||
let mut writer = BufWriter::new(fs::File::create(config.db_path.to_path_buf())?);
|
||||
let mut writer = BufWriter::new(fs::File::create(global_config.db_path.to_path_buf())?);
|
||||
serialize_into(&mut writer, &library)?;
|
||||
}
|
||||
},
|
||||
|
@ -197,16 +216,21 @@ impl MusicLibrary {
|
|||
None
|
||||
}
|
||||
|
||||
pub fn find_all_music(&mut self, target_path: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub fn find_all_music(&mut self, target_path: &str, config: &Config) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut current_dir = PathBuf::new();
|
||||
for entry in WalkDir::new(target_path)
|
||||
for (i, entry) in WalkDir::new(target_path)
|
||||
.follow_links(true)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.filter_map(|e| e.ok()).enumerate()
|
||||
{
|
||||
let target_file = entry;
|
||||
let is_file = fs::metadata(target_file.path())?.is_file();
|
||||
|
||||
// Save periodically while scanning
|
||||
if i%250 == 0 {
|
||||
self.save(config).unwrap();
|
||||
}
|
||||
|
||||
// Ensure the target is a file and not a directory, if it isn't, skip this loop
|
||||
if !is_file {
|
||||
current_dir = target_file.into_path();
|
||||
|
@ -225,12 +249,16 @@ impl MusicLibrary {
|
|||
match self.add_file_to_db(target_file.path()) {
|
||||
Ok(_) => (),
|
||||
Err(_error) => () //println!("{}, {:?}: {}", format, target_file.file_name(), error)
|
||||
// TODO: Handle more of these errors
|
||||
};
|
||||
} else if extension.to_ascii_lowercase() == "cue" {
|
||||
// TODO: implement cuesheet support
|
||||
}
|
||||
}
|
||||
|
||||
// Save the database after scanning finishes
|
||||
self.save(&config).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -346,29 +374,32 @@ impl MusicLibrary {
|
|||
todo!()
|
||||
}
|
||||
|
||||
/// Query the database, returning a list of items
|
||||
/// Query the database, returning a list of [Song]s
|
||||
pub fn query(
|
||||
&self,
|
||||
query_string: &String, // The query itself
|
||||
target_tags: &Vec<Tag>, // The tags to search
|
||||
sort_by: &Vec<Tag>, // Tags to sort the resulting data by
|
||||
) -> Option<Vec<Song>> {
|
||||
let mut songs = Vec::new();
|
||||
) -> Option<Vec<&Song>> {
|
||||
let songs = Arc::new(Mutex::new(Vec::new()));
|
||||
|
||||
for track in &self.library {
|
||||
self.library.par_iter().for_each(|track| {
|
||||
for tag in &track.tags {
|
||||
if !target_tags.contains(&tag.0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if normalize(&tag.1).contains(&normalize(&query_string)) {
|
||||
songs.push(track.clone());
|
||||
songs.lock().unwrap().push(track);
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
songs.sort_by(|a, b| {
|
||||
let lock = Arc::try_unwrap(songs).expect("Lock still has multiple owners!");
|
||||
let mut new_songs = lock.into_inner().expect("Mutex cannot be locked!");
|
||||
|
||||
new_songs.par_sort_by(|a, b| {
|
||||
for opt in sort_by {
|
||||
let tag_a = match a.get_tag(&opt) {
|
||||
Some(tag) => tag,
|
||||
|
@ -402,8 +433,8 @@ impl MusicLibrary {
|
|||
a.get_tag(&Tag::Title).cmp(&b.get_tag(&Tag::Title))
|
||||
});
|
||||
|
||||
if songs.len() > 0 {
|
||||
Some(songs)
|
||||
if new_songs.len() > 0 {
|
||||
Some(new_songs)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -269,6 +269,13 @@ impl MusicTracker for DiscordRPC {
|
|||
&unknown
|
||||
};
|
||||
|
||||
// Sets album
|
||||
let album = if let Some(album) = song.get_tag(&Tag::Album) {
|
||||
album
|
||||
} else {
|
||||
&unknown
|
||||
};
|
||||
|
||||
let _client_thread = self.client.start();
|
||||
|
||||
// Blocks thread execution until it has connected to local discord client
|
||||
|
@ -281,7 +288,8 @@ impl MusicTracker for DiscordRPC {
|
|||
// Sets discord account activity to current playing song
|
||||
let send_activity = self.client.set_activity(|activity| {
|
||||
activity
|
||||
.state(format!("Listening to: {}", song_name))
|
||||
.state(format!("{}", album))
|
||||
.details(format!("{}", song_name))
|
||||
.assets(|assets| assets.large_image(&self.config.dango_icon))
|
||||
.timestamps(|time| time.start(start_time))
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue