preliminary query support, still some bugs to iron out

This commit is contained in:
G2-Games 2023-10-02 01:26:36 -05:00
parent db53bed111
commit d2d92149f8
3 changed files with 114 additions and 20 deletions

View file

@ -33,3 +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"

View file

@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize};
use std::fs;
use std::io::BufWriter;
use std::path::{Path, PathBuf};
use unidecode::unidecode;
use crate::music_controller::config::Config;
@ -20,6 +21,31 @@ pub struct AlbumArt {
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
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Song {
@ -35,13 +61,15 @@ pub struct Song {
pub last_played: Option<DateTime<Utc>>,
#[serde(with = "ts_seconds_option")]
pub date_added: Option<DateTime<Utc>>,
#[serde(with = "ts_seconds_option")]
pub date_modified: Option<DateTime<Utc>>,
pub album_art: Vec<AlbumArt>,
pub tags: Vec<(String, String)>,
pub tags: Vec<(Tag, String)>,
}
impl Song {
pub fn get_tag(&self, target_key: &str) -> Option<&String> {
let index = self.tags.iter().position(|r| r.0 == target_key);
pub fn get_tag(&self, target_key: &Tag) -> Option<&String> {
let index = self.tags.iter().position(|r| r.0 == *target_key);
match index {
Some(i) => return Some(&self.tags[i].1),
@ -53,7 +81,7 @@ impl Song {
let mut results = Vec::new();
for tag in &self.tags {
for key in target_keys {
if &tag.0 == key {
if &tag.0.to_string() == key {
results.push(Some(tag.1.to_owned()))
}
}
@ -95,6 +123,10 @@ pub struct MusicLibrary {
pub library: Vec<Song>,
}
pub fn normalize(input_string: &String) -> String {
unidecode(input_string).to_ascii_lowercase()
}
impl MusicLibrary {
/// 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() {
let mut key = String::new();
match item.key() {
ItemKey::Unknown(unknown) => key.push_str(&unknown),
custom => key = format!("{:?}", custom),
let key = match item.key() {
ItemKey::TrackTitle => Tag::Title,
ItemKey::TrackNumber => Tag::Track,
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() {
@ -276,6 +313,7 @@ impl MusicLibrary {
play_time: Duration::from_secs(0),
last_played: None,
date_added: Some(chrono::offset::Utc::now()),
date_modified: Some(chrono::offset::Utc::now()),
tags,
album_art,
};
@ -312,9 +350,62 @@ impl MusicLibrary {
pub fn query(
&self,
query_string: &String, // The query itself
target_tags: &Vec<String>, // The tags to search
sort_by: &Vec<String>, // Tags to sort the resulting data by
) -> Option<Vec<MusicObject>> {
unimplemented!()
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();
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
}
}
}

View file

@ -8,7 +8,7 @@ use md5::{Md5, Digest};
use discord_presence::{Event};
use surf::StatusCode;
use crate::music_storage::music_db::Song;
use crate::music_storage::music_db::{Song, Tag};
#[async_trait]
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 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),
_ => return Err(TrackerError::InvalidSong)
};
@ -94,7 +94,7 @@ impl MusicTracker for LastFM {
async fn track_now(&mut self, song: Song) -> Result<(), TrackerError> {
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),
_ => return Err(TrackerError::InvalidSong)
};
@ -256,13 +256,14 @@ impl DiscordRPC {
}
}
#[async_trait]
impl MusicTracker for DiscordRPC {
async fn track_now(&mut self, song: Song) -> Result<(), TrackerError> {
let unknown = String::from("Unknown");
// 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
} else {
&unknown
@ -318,7 +319,7 @@ pub struct ListenBrainz {
#[async_trait]
impl MusicTracker for ListenBrainz {
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),
_ => return Err(TrackerError::InvalidSong)
};
@ -344,7 +345,7 @@ impl MusicTracker for ListenBrainz {
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 (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),
_ => return Err(TrackerError::InvalidSong)
};