mirror of
https://github.com/Dangoware/dmp-core.git
synced 2025-04-19 13:22:54 -05:00
preliminary query support, still some bugs to iron out
This commit is contained in:
parent
db53bed111
commit
d2d92149f8
3 changed files with 114 additions and 20 deletions
|
@ -33,3 +33,5 @@ arrayvec = "0.7.4"
|
||||||
discord-presence = "0.5.18"
|
discord-presence = "0.5.18"
|
||||||
chrono = { version = "0.4.31", features = ["serde"] }
|
chrono = { version = "0.4.31", features = ["serde"] }
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
|
wana_kana = "3.0.0"
|
||||||
|
unidecode = "0.3.0"
|
||||||
|
|
|
@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::BufWriter;
|
use std::io::BufWriter;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use unidecode::unidecode;
|
||||||
|
|
||||||
use crate::music_controller::config::Config;
|
use crate::music_controller::config::Config;
|
||||||
|
|
||||||
|
@ -20,6 +21,31 @@ pub struct AlbumArt {
|
||||||
pub path: Option<URI>,
|
pub path: Option<URI>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
|
pub enum Tag {
|
||||||
|
Title,
|
||||||
|
Album,
|
||||||
|
Artist,
|
||||||
|
Genre,
|
||||||
|
Comment,
|
||||||
|
Track,
|
||||||
|
Key(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for Tag {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Title => "TrackTitle".into(),
|
||||||
|
Self::Album => "AlbumTitle".into(),
|
||||||
|
Self::Artist => "TrackArtist".into(),
|
||||||
|
Self::Genre => "Genre".into(),
|
||||||
|
Self::Comment => "Comment".into(),
|
||||||
|
Self::Track => "TrackNumber".into(),
|
||||||
|
Self::Key(key) => key.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Stores information about a single song
|
/// Stores information about a single song
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct Song {
|
pub struct Song {
|
||||||
|
@ -35,13 +61,15 @@ pub struct Song {
|
||||||
pub last_played: Option<DateTime<Utc>>,
|
pub last_played: Option<DateTime<Utc>>,
|
||||||
#[serde(with = "ts_seconds_option")]
|
#[serde(with = "ts_seconds_option")]
|
||||||
pub date_added: Option<DateTime<Utc>>,
|
pub date_added: Option<DateTime<Utc>>,
|
||||||
|
#[serde(with = "ts_seconds_option")]
|
||||||
|
pub date_modified: Option<DateTime<Utc>>,
|
||||||
pub album_art: Vec<AlbumArt>,
|
pub album_art: Vec<AlbumArt>,
|
||||||
pub tags: Vec<(String, String)>,
|
pub tags: Vec<(Tag, String)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Song {
|
impl Song {
|
||||||
pub fn get_tag(&self, target_key: &str) -> Option<&String> {
|
pub fn get_tag(&self, target_key: &Tag) -> Option<&String> {
|
||||||
let index = self.tags.iter().position(|r| r.0 == target_key);
|
let index = self.tags.iter().position(|r| r.0 == *target_key);
|
||||||
|
|
||||||
match index {
|
match index {
|
||||||
Some(i) => return Some(&self.tags[i].1),
|
Some(i) => return Some(&self.tags[i].1),
|
||||||
|
@ -53,7 +81,7 @@ impl Song {
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
for tag in &self.tags {
|
for tag in &self.tags {
|
||||||
for key in target_keys {
|
for key in target_keys {
|
||||||
if &tag.0 == key {
|
if &tag.0.to_string() == key {
|
||||||
results.push(Some(tag.1.to_owned()))
|
results.push(Some(tag.1.to_owned()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,6 +123,10 @@ pub struct MusicLibrary {
|
||||||
pub library: Vec<Song>,
|
pub library: Vec<Song>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn normalize(input_string: &String) -> String {
|
||||||
|
unidecode(input_string).to_ascii_lowercase()
|
||||||
|
}
|
||||||
|
|
||||||
impl MusicLibrary {
|
impl MusicLibrary {
|
||||||
/// Initialize the database
|
/// Initialize the database
|
||||||
///
|
///
|
||||||
|
@ -225,12 +257,17 @@ impl MusicLibrary {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut tags: Vec<(String, String)> = Vec::new();
|
let mut tags: Vec<(Tag, String)> = Vec::new();
|
||||||
for item in tag.items() {
|
for item in tag.items() {
|
||||||
let mut key = String::new();
|
let key = match item.key() {
|
||||||
match item.key() {
|
ItemKey::TrackTitle => Tag::Title,
|
||||||
ItemKey::Unknown(unknown) => key.push_str(&unknown),
|
ItemKey::TrackNumber => Tag::Track,
|
||||||
custom => key = format!("{:?}", custom),
|
ItemKey::TrackArtist => Tag::Artist,
|
||||||
|
ItemKey::Genre => Tag::Genre,
|
||||||
|
ItemKey::Comment => Tag::Comment,
|
||||||
|
ItemKey::AlbumTitle => Tag::Album,
|
||||||
|
ItemKey::Unknown(unknown) => Tag::Key(unknown.to_string()),
|
||||||
|
custom => Tag::Key(format!("{:?}", custom)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let value = match item.value() {
|
let value = match item.value() {
|
||||||
|
@ -276,6 +313,7 @@ impl MusicLibrary {
|
||||||
play_time: Duration::from_secs(0),
|
play_time: Duration::from_secs(0),
|
||||||
last_played: None,
|
last_played: None,
|
||||||
date_added: Some(chrono::offset::Utc::now()),
|
date_added: Some(chrono::offset::Utc::now()),
|
||||||
|
date_modified: Some(chrono::offset::Utc::now()),
|
||||||
tags,
|
tags,
|
||||||
album_art,
|
album_art,
|
||||||
};
|
};
|
||||||
|
@ -311,10 +349,63 @@ impl MusicLibrary {
|
||||||
/// Query the database, returning a list of items
|
/// Query the database, returning a list of items
|
||||||
pub fn query(
|
pub fn query(
|
||||||
&self,
|
&self,
|
||||||
query_string: &String, // The query itself
|
query_string: &String, // The query itself
|
||||||
target_tags: &Vec<String>, // The tags to search
|
target_tags: &Vec<Tag>, // The tags to search
|
||||||
sort_by: &Vec<String>, // Tags to sort the resulting data by
|
sort_by: &Vec<Tag>, // Tags to sort the resulting data by
|
||||||
) -> Option<Vec<MusicObject>> {
|
) -> Option<Vec<Song>> {
|
||||||
unimplemented!()
|
let mut songs = Vec::new();
|
||||||
|
|
||||||
|
for track in &self.library {
|
||||||
|
for tag in &track.tags {
|
||||||
|
if !target_tags.contains(&tag.0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if normalize(&tag.1).contains(&normalize(&query_string)) {
|
||||||
|
songs.push(track.clone());
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
songs.sort_by(|a, b| {
|
||||||
|
for opt in sort_by {
|
||||||
|
let tag_a = match a.get_tag(&opt) {
|
||||||
|
Some(tag) => tag,
|
||||||
|
None => continue
|
||||||
|
};
|
||||||
|
|
||||||
|
let tag_b = match b.get_tag(&opt) {
|
||||||
|
Some(tag) => tag,
|
||||||
|
None => continue
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try to parse the tags as f64 (floating-point numbers)
|
||||||
|
if let (Ok(num_a), Ok(num_b)) = (tag_a.parse::<f64>(), tag_b.parse::<f64>()) {
|
||||||
|
// If parsing succeeds, compare as numbers
|
||||||
|
if num_a < num_b {
|
||||||
|
return std::cmp::Ordering::Less;
|
||||||
|
} else if num_a > num_b {
|
||||||
|
return std::cmp::Ordering::Greater;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If parsing fails, compare as strings
|
||||||
|
if tag_a < tag_b {
|
||||||
|
return std::cmp::Ordering::Less;
|
||||||
|
} else if tag_a > tag_b {
|
||||||
|
return std::cmp::Ordering::Greater;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all tags are equal, sort by some default criteria (e.g., song title)
|
||||||
|
a.get_tag(&Tag::Title).cmp(&b.get_tag(&Tag::Title))
|
||||||
|
});
|
||||||
|
|
||||||
|
if songs.len() > 0 {
|
||||||
|
Some(songs)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ use md5::{Md5, Digest};
|
||||||
use discord_presence::{Event};
|
use discord_presence::{Event};
|
||||||
use surf::StatusCode;
|
use surf::StatusCode;
|
||||||
|
|
||||||
use crate::music_storage::music_db::Song;
|
use crate::music_storage::music_db::{Song, Tag};
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait MusicTracker {
|
pub trait MusicTracker {
|
||||||
|
@ -75,7 +75,7 @@ impl MusicTracker for LastFM {
|
||||||
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("Your time is off.").as_secs() - 30;
|
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("Your time is off.").as_secs() - 30;
|
||||||
let string_timestamp = timestamp.to_string();
|
let string_timestamp = timestamp.to_string();
|
||||||
|
|
||||||
let (artist, track) = match (song.get_tag("Artist"), song.get_tag("Title")) {
|
let (artist, track) = match (song.get_tag(&Tag::Artist), song.get_tag(&Tag::Title)) {
|
||||||
(Some(artist), Some(track)) => (artist, track),
|
(Some(artist), Some(track)) => (artist, track),
|
||||||
_ => return Err(TrackerError::InvalidSong)
|
_ => return Err(TrackerError::InvalidSong)
|
||||||
};
|
};
|
||||||
|
@ -94,7 +94,7 @@ impl MusicTracker for LastFM {
|
||||||
async fn track_now(&mut self, song: Song) -> Result<(), TrackerError> {
|
async fn track_now(&mut self, song: Song) -> Result<(), TrackerError> {
|
||||||
let mut params: BTreeMap<&str, &str> = BTreeMap::new();
|
let mut params: BTreeMap<&str, &str> = BTreeMap::new();
|
||||||
|
|
||||||
let (artist, track) = match (song.get_tag("Artist"), song.get_tag("Title")) {
|
let (artist, track) = match (song.get_tag(&Tag::Artist), song.get_tag(&Tag::Title)) {
|
||||||
(Some(artist), Some(track)) => (artist, track),
|
(Some(artist), Some(track)) => (artist, track),
|
||||||
_ => return Err(TrackerError::InvalidSong)
|
_ => return Err(TrackerError::InvalidSong)
|
||||||
};
|
};
|
||||||
|
@ -256,13 +256,14 @@ impl DiscordRPC {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl MusicTracker for DiscordRPC {
|
impl MusicTracker for DiscordRPC {
|
||||||
async fn track_now(&mut self, song: Song) -> Result<(), TrackerError> {
|
async fn track_now(&mut self, song: Song) -> Result<(), TrackerError> {
|
||||||
let unknown = String::from("Unknown");
|
let unknown = String::from("Unknown");
|
||||||
|
|
||||||
// Sets song title
|
// Sets song title
|
||||||
let song_name = if let Some(song_name) = song.get_tag("Title") {
|
let song_name = if let Some(song_name) = song.get_tag(&Tag::Title) {
|
||||||
song_name
|
song_name
|
||||||
} else {
|
} else {
|
||||||
&unknown
|
&unknown
|
||||||
|
@ -318,7 +319,7 @@ pub struct ListenBrainz {
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl MusicTracker for ListenBrainz {
|
impl MusicTracker for ListenBrainz {
|
||||||
async fn track_now(&mut self, song: Song) -> Result<(), TrackerError> {
|
async fn track_now(&mut self, song: Song) -> Result<(), TrackerError> {
|
||||||
let (artist, track) = match (song.get_tag("Artist"), song.get_tag("Title")) {
|
let (artist, track) = match (song.get_tag(&Tag::Artist), song.get_tag(&Tag::Title)) {
|
||||||
(Some(artist), Some(track)) => (artist, track),
|
(Some(artist), Some(track)) => (artist, track),
|
||||||
_ => return Err(TrackerError::InvalidSong)
|
_ => return Err(TrackerError::InvalidSong)
|
||||||
};
|
};
|
||||||
|
@ -344,7 +345,7 @@ impl MusicTracker for ListenBrainz {
|
||||||
async fn track_song(&mut self, song: Song) -> Result<(), TrackerError> {
|
async fn track_song(&mut self, song: Song) -> Result<(), TrackerError> {
|
||||||
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("Your time is off.").as_secs() - 30;
|
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("Your time is off.").as_secs() - 30;
|
||||||
|
|
||||||
let (artist, track) = match (song.get_tag("Artist"), song.get_tag("Title")) {
|
let (artist, track) = match (song.get_tag(&Tag::Artist), song.get_tag(&Tag::Title)) {
|
||||||
(Some(artist), Some(track)) => (artist, track),
|
(Some(artist), Some(track)) => (artist, track),
|
||||||
_ => return Err(TrackerError::InvalidSong)
|
_ => return Err(TrackerError::InvalidSong)
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue